Modbus 这东西,搞工控的几乎天天见,但真要说把地址规则讲清楚,不少人还是模棱两可。尤其是刚接触 PLC、DCS、仪表通讯的工程师,一上手就对着手册里那一堆 40001、30001、%MW100 发懵——为什么同样是保持寄存器,有的设备从 0 开始算,有的从 1 开始算,有的还要加个 30001 的偏移?今天这篇就把 Modbus 地址规则从头到尾捋一遍,顺便把线圈、寄存器、功能码、数据映射这些绕不开的坑都填上。
这个内容适合谁?刚入行做电气设计、设备调试的朋友,或者做上位机、SCADA、边缘网关开发的工程师,只要你的设备带 RS485 或者以太网口且宣称支持 Modbus,这篇就能帮你少走弯路。读完之后,你能做到:拿到任意一份 Modbus 设备手册,快速定位地址规律,正确配置通讯参数,并且能自己解释为什么通讯偶尔“抽风”。
1. 先把 Modbus 的地图摊开:线圈、寄存器到底怎么分
1.1 一张表看懂四种数据对象
Modbus 协议从本质上看,就是一张“表格”,主站(比如 PLC、上位机)和从站(比如仪表、变频器、IO 模块)之间通过读写这张表来交换数据。这张表不是一维的,而是分成了四个区域,对应的就是我们在手册里常看到的“线圈”“寄存器”这些词。
| 数据对象 | 位/字 | 读写属性 | 对应功能码 | 地址范围(常规) |
|---|---|---|---|---|
| 线圈(Coil) | 位(bit) | 可读可写 | 01、05、15 | 00001 – 09999 |
| 离散输入(Discrete Input) | 位(bit) | 只读 | 02 | 10001 – 19999 |
| 输入寄存器(Input Register) | 字(16bit) | 只读 | 04 | 30001 – 39999 |
| 保持寄存器(Holding Register) | 字(16bit) | 可读可写 | 03、06、16 | 40001 – 49999 |
这张表是 Modbus 协议的经典划分。线圈就是布尔量,比如启动、停止、阀门开关;离散输入也是布尔量,但是来自外部触点,只能被主站读取;输入寄存器是 16 位的只读数据,适合放模拟量采集值、状态字;保持寄存器最灵活,既能读又能写,放设定值、累计值、控制字都行。
1.2 为什么会有 40001 这种带偏移的地址
很多新手看到 40001 就以为物理地址是 40000,这是个大误区。所谓 40001,其实是“数据区域标识 + 偏移地址”的显示方式。4 开头代表保持寄存器区,后面 0001 才是真正的协议层地址偏移。
打个比方,这就好比一栋楼有四个单元,40001 指的是“4 单元 1 号房”,而不是说门牌号是 40001。协议报文里真正传输的是偏移量 0,主站软件为了便于人阅读,自动加上了 4 的前缀。
不过这里有个致命的分歧:有的设备手册地址写 40001,协议报文里对应偏移 0;有的设备手册地址写 40001,报文里对应偏移 1;还有的设备干脆直接写 0 开始。这个差异背后是 Modbus 协议本身没有强制规定“数据模型地址”与“协议 PDU 地址”的换算关系,于是各厂商各行其是。
遇到这种情况,只能靠一个办法:看报文。后面我会专门讲怎么用 Modbus Poll 或串口监视器抓报文来确定真实偏移。
2. 地址规则的底层逻辑:功能码、字节序与数据类型
2.1 功能码决定了你能对这个地址做什么
Modbus 功能码相当于对数据对象“动刀的许可”。读线圈用 01,读离散输入用 02,读保持寄存器用 03,读输入寄存器用 04,写单个线圈用 05,写单个寄存器用 06,写多个线圈用 15,写多个寄存器用 16。
有一个常见误解:认为“地址编号的区间”天然决定了功能码。实际上你完全可以对保持寄存器地址范围(比如 40001)用功能码 03 去读,但如果你想读的是输入寄存器(30001),就必须用功能码 04。地址编号只是给人看的分类,协议报文中的地址字段永远是 0 到 65535 的纯数字,功能码告诉从站“我要对这个数字做什么”。
从站侧的逻辑是:功能码 + 地址偏移 + 寄存器数量 → 映射到内部存储区。所以调试时如果读出来是异常码 02(非法数据地址),多半是功能码和地址区域不匹配,而不是真的地址超范围。
2.2 字节序:让无数人翻车的隐藏规则
Modbus 寄存器是 16 位的,但实际工程中的数据往往超过 16 位。比如一个 32 位浮点数(Float)或 32 位整数(Int32),就需要占用两个连续的寄存器。
这时候就有字节序的问题。常见的有四种组合:
- Big-Endian 字节序:先发高字节,再发低字节(AB CD)
- Little-Endian 字节序:先发低字节,再发高字节(CD AB)
- 字序(Word Order):有的设备先传高字,有的先传低字
很多人调试了半天,发现数值要么翻了好几万倍,要么是个天文数字,基本就是字节序没对上。举一个实际案例:某温度变送器用 Modbus RTU 返回浮点数,手册写“浮点数遵循 IEEE 754,低字节在前”。我用 Modbus Poll 读到原始字节是 42 0A 66 66,如果按大端解释就是 34.4,但按小端解释是 1.14E+09,明显不对。最后把寄存器字序交换,变成 66 66 42 0A,再按小端字节序解释,才得到正确值。
所以拿到协议手册,第一件事不是看地址,而是看“字节序”和“字序”声明。很多国产仪表手册写得很含糊,只给一个寄存器地址表,这种情况下建议先用已知的固定值(比如零点、满量程)反推字节序,而不是盲猜。
2.3 数据类型:整数、浮点、BCD 码,别搞混
同样的寄存器,存整数和存浮点完全是两种解释方式。Modbus 本身不管数据类型,它只是一辆车,车上装的是原始 0/1 字节流,至于这些字节代表什么,是设备厂商自己定义的。
常见类型有:
- 无符号整数(U16):一个寄存器直接解释,范围 0~65535
- 有符号整数(I16):一个寄存器,最高位为符号位
- 无符号长整数(U32):两个寄存器
- 有符号长整数(I32):两个寄存器
- 浮点数(Float32):两个寄存器,IEEE 754
- 32 位浮点数加状态字:三个寄存器,一般寄存器 0 是高位字,寄存器 1 是低位字,寄存器 2 是状态
还有特殊类型,比如 BCD 码——寄存器里存的是 0x1234,代表的十进制数就是 1234,温度和压力变送器很喜欢这么干。如果你把 BCD 码当作常规整数去解析,显示就会变成十六进制数或乱码。
我的建议是:先构造一张映射表,把手册里的每个地址、数据类型、缩放系数(Scale Factor)、偏移量(Offset)都写清楚。缩放系数也很关键,比如某个寄存器值是 12345,手册说分辨率是 0.01°C,那实际温度就是 123.45°C。忘了乘系数会直接导致数据偏差。
3. 实操拆解:从零配置一个 Modbus RTU 从站
3.1 物理层与协议参数的选择
Modbus 常见物理层有 RS232、RS485 和以太网 TCP。RS485 是工业现场用得最多的,半双工、两线制,支持多站挂接,理论最多 32 个标准负载。配置 RS485 时最重要的三个参数是:波特率、数据位、停止位和校验位(通常合并为串口参数)。
- 典型配置:9600, 8, N, 1(9600 波特率、8 数据位、无校验、1 停止位)
- 有些设备默认 19200,甚至 115200
- 校验位可能是 None、Even、Odd
这里有个经验:现场通讯不稳定时,优先把波特率降下来,比如 9600 改成 4800 或者 2400,往往比反复查接线更有效。为什么?因为波特率越高,对线缆质量、接地、干扰的要求越高。很多老旧厂区电缆屏蔽层没接地,跑 9600 没问题,一上 38400 就疯狂报错。
连接方式:从站的 A/B 线要对应(一般 A 对应 485+,B 对应 485-),屏蔽层单端接地,终端电阻在总线两端各并联一个 120 欧姆。别小看这几个细节,我调试过一台进口设备,和 PLC 通讯总是偶发失败,排查半天发现是终端电阻跨接了,摘掉之后立刻稳定。
3.2 地址映射实操:以西门子 S7-200 和威纶通触摸屏为例
正好结合热门词里的“西门子 PLC200”,聊聊 S7-200 做 Modbus RTU 从站的常见场景。S7-200 本体没有内置 Modbus 库,需要调用库指令(Modbus Slave 协议库),比较新一些的 firmware 支持,而老版本的 S7-200 必须外挂 EM277 或第三方模块才能做 Modbus。
在 S7-200 中做从站时,地址映射靠的是库存储区的分配。默认情况下:
- 线圈(Q 区)映射到 Modbus 线圈地址 00001 起
- 输入(I 区)映射到离散输入地址 10001 起
- 模拟量输入(AIW)映射到输入寄存器 30001 起
- 保持寄存器(V 区)映射到 40001 起
但具体偏移和长度是可以通过 MBUS_INIT 指令配置的,不是固定死的。比如你想要从站保持寄存器地址 40001 对应 VW0,40002 对应 VW2,那就在库存储器分配里把 V 区起始地址设为 VB0,然后把最大保持寄存器数量填足够。
这里最坑的地方是库存储区和用户 V 区冲突。S7-200 的程序空间和 V 区共享,库指令本身要占用一段 V 存储区作为缓冲区。如果你在程序中随意使用该区域,轻则通讯数据错乱,重则 PLC 停机。解决办法是看帮助文档中的库存储区分配表,保留首尾不用的字节,或者直接固定分配一个较大的连续区间。
威纶通触摸屏连接 S7-200 时,屏端选择“Modbus RTU Slave”设备,地址栏通常直接填 4x 地址,比如 4x 1 对应 VW0。注意触摸屏的寄存器类型如果只有 4x,不要和 3x 混淆,否则读出来的是模拟量。
3.3 用 Modbus Poll 验证地址规则
Modbus Poll 是调试 Modbus 从站的利器。它的界面长这样:左侧选功能码,中间填从站地址和起始地址,右侧显示寄存器值。当你拿到一台新设备、不确定地址规则时,可以用它来“探”:
- 设置串口参数:COM 口、波特率 9600、8N1
- 从站地址填设备手册上的默认站号,一般 1~247,常见是 1
- 功能码先选 03(读保持寄存器)
- 起始地址填 0,数量填 10,点读取
如果返回异常码 02,说明这一片地址不存在或功能码不匹配,换成 04 再试。如果返回正常,但数据全是 0 或 65535,那就把起始地址改成 1、2、3 等逐步偏移试探,直到出现非零且合理的数据。
这个过程就是实际确定“协议层偏移地址”的方法。很多手册说“保持寄存器从 40001 开始”,但你的读操作从 0 开始就能读到数据,说明厂商用的是“0 基地址”。反之,如果从 0 读报错,从 1 读正常,说明厂商用了“1 基地址”。看起来只是差 1,但在批量配置点位时差之毫厘谬以千里。
4. 常见问题与排查技巧实录(避坑宝典)
4.1 通讯超时或 CRC 校验错误
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 偶发超时 | 波特率不匹配(主从不一致) | 用串口助手抓原始报文,核对帧头帧尾 |
| CRC 错误频繁 | 数据线序反、干扰严重 | 检查 A/B 定义,确认屏蔽层接地 |
| 一请求就掉线 | 地址超出从站范围 | 用 Modbus Poll 递增地址扫描 |
| 能读不能写 | 访问权限/功能码不支持 | 尝试 06、16 功能码,确认从站写使能 |
| 多站互相干扰 | 站号重复 | 每个从站站号唯一,检查拨码和配置 |
CRC 校验是 Modbus RTU 的重中之重。协议规定用 CRC-16(多项式 0x8005,初值 0xFFFF),主站和从站都要计算。如果收到的报文 CRC 不对,从站直接丢弃,不会返回任何响应。所以当你看到“超时”而不是“异常响应”时,十有八九是物理层或报文帧格式问题,而不是业务逻辑问题。
排查技巧:先用现成的串口调试工具(如 SSCOM)看主站发出的原始十六进制报文,再比对从站的响应。正常请求帧格式大概是这样:
[从站地址] [功能码] [起始地址高] [起始地址低] [寄存器数高] [寄存器数低] [CRC低] [CRC高]比如读站号 1 的保持寄存器,从 0 开始读 2 个寄存器:
01 03 00 00 00 02 C4 0B如果从站不回,把 C4 0B 换成正确的 CRC 试试。CRC 可以用现成工具计算,也可以手算,但手算费时间,远程调试时建议用 Python:
def crc16_modbus(data): crc = 0xFFFF for b in data: crc ^= b for _ in range(8): if crc & 1: crc = (crc >> 1) ^ 0xA001 else: crc >>= 1 return crc # 示例:生成读请求帧 addr = 0x01 func = 0x03 start = 0x0000 quantity = 0x0002 frame = bytes([addr, func, start >> 8, start & 0xFF, quantity >> 8, quantity & 0xFF]) crc = crc16_modbus(frame) frame += bytes([crc & 0xFF, crc >> 8]) print(frame.hex())4.2 线圈和寄存器的读写差异:什么时候用 01,什么时候用 03
这是热词里“modbus线圈和寄存器的区别”最典型的疑问。记住一句口诀:线圈是一位一位控制,寄存器是一个一个 16 位字控制。线圈适合做 DO(数字量输出),寄存器适合做 AO(模拟量输出)或数据读取。
但实际设备中,有些厂商把数字量输出映射到保持寄存器里,比如寄存器 40001 的 bit0 代表第一路 DO。这时你用功能码 01 去读线圈是读不到的,因为从站没有实现线圈区域,只能用功能码 03 读取保持寄存器,解析出 bit。反过来,有些网关设备会把线圈区域映射到内部保持寄存器的某个位,这又增加了复杂性。
我的建议:调试任何新设备前,先通读一遍手册中的数据模型章节,确认四个区域是否有实际映射。如果没有明确说明,就用 Modbus Scan 工具扫描所有功能码,看哪个能返回有效数据。
4.3 32 位数据的对齐问题:奇偶地址的坑
在 PLC 和仪表对接时,多字节数据可能存放在奇数地址或偶数地址。比如某个保持寄存器 40010 和 40011 存一个 32 位浮点数,你能直接用“寄存器 10、11”组数,没问题。但如果说明书说“寄存器 40002 和 40004 存一个长整数”,中间空了一个寄存器,那就得先从 40002 和 40004 分别取数再组合,不能当成连续的 40002、40003。
此外,有些 PLC 的数据区(如西门子的 V 区)支持字节访问和字访问,Modbus 从站库在映射时通常按字边界对齐。你在组态时最好把数据区的起始地址选在偶数位(比如 VB100、VW100),避免跨字读取时出现奇怪的错位。
4.4 正负数和浮点数的符号位延伸
读有符号数时,要注意最高位(bit15)是符号位。如果设备回传的是 0xFFFF,按有符号整数解释是 -1,按无符号整数解释是 65535。很多上位机组态软件默认按无符号处理,导致负值显示成很大的正数。
解决办法:在数据字典中显式声明数据类型为 Int16 或 Int32,而不是笼统的“寄存器”。同时注意设备是否用补码表示负数,绝大多数符合常规的设备都是用补码,极少有原码/反码的异类,但也不是完全没有。固件日期较早的仪表可能出现反码情况,读出来正数对称,负数显示成另一个正数,比如 -10 读成 65525。这种情况只能做一步符号位扩展,或者换一个新固件。
5. 高手才懂的地址换算经验与派生产品差异
5.1 从站站号、单元 ID 与网关的关系
Modbus 报文头里有一个从站地址(8 位),范围 1~247。0 是广播地址,248~255 保留。在 Modbus TCP 模式下,这个字段变成了 Unit ID(单元标识符),尤其当 TCP 网关后面挂多个串行从站时,Unit ID 就对应串口侧的从站地址。
很多人在用 Modbus TCP 转 RS485 网关时,把 Unit ID 和 IP 地址混淆。IP 地址是网关上网络接口的地址,Unit ID 是网关下游某个从站的地址。比如网关 IP 192.168.1.10 下面挂了站号 1 和站号 2 两个电表,那上位机软件里配置连接时,IP 都填 192.168.1.10,Unit ID 分别填 1 和 2。如果只填 IP 不填 Unit ID,或者填错,大概率会超时或读错设备。
5.2 国产 DCS 和模块化 IO 的 Modbus 地址习惯
结合“国产 DCS 登顶全球第一”这种大背景下,现在很多国产控制系统在 Modbus 地址编排上,更喜欢用“偏移地址 + 起始偏移”的说明方式,而不是传统 PLC 式的 40001。比如某个国产 IO 模块手册写:
- AI 通道 0 读取地址:30001(功能码 04)
- AO 通道 0 写入地址:40001(功能码 16)
- DI 通道 0 读取地址:10001(功能码 02)
- DO 通道 0 写地址:00001(功能码 05)
看起来还是传统四区,但实际地址偏移从 0 开始。配置上位机时,有的软件(如组态王、力控)要求填“寄存器地址”时直接填 1,有的填 0。这就要看软件有没有“地址偏移”选项。组态王里通常有“偏移地址”栏,默认 0,对应协议层偏移 0;如果设备是 1 基地址,则要填 1。搞反一个,所有数据都会串位。
我自己踩过最惨的一次,是在一个储能电站 EMS 项目里,电表的 SOC 寄存器手册写 30004,我按协议层偏移 3 去读,结果读出来是上一帧的电压数据。后来抓包才发现电表实际把数据放在偏移 2 的位置,手册的 30004 本身就是“30001 + 3”的显示方式。所以真的不能只看手册页码,要用报文验证。
5.3 不同品牌 PLC 的 Modbus 地址映射差异
西门子 S7-200/300/1200/1500 各有各的 Modbus 实现方式,但地址映射的大原则一致:保持寄存器对应 DB 区或 V 区,线圈对应输出位。差异在于偏移量。
S7-1200 的 Modbus 指令(MB_COMM_LOAD 和 MB_DATA_ADDR)非常灵活,参数 DataAddr 可以直接填协议地址(比如 40001 对应的就是 40001,不需要去掉前缀)。西门子官方库在底层会做转换,但注意在 S7-1200 的“MODBUS 数据类型”里,地址范围是 0~65535,如果你填 40001 那是没用的,必须填去掉区号后的偏移,比如保持寄存器 40001 在这里就是 DataAddr = 1(或者 0,取决于库版本)。
同样是西门子,S7-200 的库指令是 MBUS_INIT 中参数 Addr 表示从站站号,而保持寄存器起始地址由库存储区决定,不是由程序填地址。所以从 S7-200 切换到 S7-1200 时,不要沿用老经验,建议直接看对应库的示例程序。
三菱 FX 系列也有类似问题,FX3U 用 RS 指令实现 Modbus 时,报文完全裸造,需要自己拼 CRC,地址规则反而一目了然,因为你可以直接控制发送 0x0000 还是 0x0001。这种情况下更容易出错,但也更容易调试。
5.4 为 Modbus Poll 设置地址扫描的实操技巧
如果想快速了解从站有哪些有效地址,可以借助 Modbus Poll 的“循环读取”功能,但对新设备做全面扫描时更推荐 Modbus Scan 这个小工具。它可以按功能码批量扫描地址范围,并高亮有响应的地址段。
扫描步骤:
- 选择串口或 TCP,填好从站地址
- 勾选要扫描的功能码(03、04、01、02 都勾上)
- 设置起始地址 0,结束地址 1000,超时时间 100ms
- 点击扫描,等待结果
扫描时注意响应速度和总线占用,如果挂载了多个从站,扫描全部地址会导致正常通讯变慢,产线上不要轻易做全地址扫描,只扫需要的区间即可。我扫描一个 200 点左右的 IO 模块,一般几百毫秒就能出来一张映射表,比翻手册补漏要快得多。
6. 关于 Modbus 地址规则的最终备忘
写到这里,关于 Modbus 地址规则的核心内容基本都覆盖了。最后再分享几个个人实操中总结的小习惯:
第一,拿到任何支持 Modbus 的设备,第一步先建一个点表 Excel,列包括:设备型号、功能码、协议层偏移地址、显示地址、数据类型、缩放系数、字节序、读写权限。这看起来繁琐,但它能让你后续配置上位机或 PLC 时少掉一半头发。
第二,现场调试前,用串口工具抓一下从站对“广播请求”的响应,很多设备在空载时会定时发心跳帧,从这些帧里能直接看出地址规则和字节序。
第三,不要迷信手册上的“寄存器地址表”,一定要结合功能码理解。很多手册把 30001 和 40001 的地址表列在同一页,但没说明哪些是 3x 区哪些是 4x 区,直接照着填必然出错。
Modbus 本身非常简单,简单到几乎每个设备的实现都不太一样。但这恰恰是它的魅力所在——兼容性极强,稍加调试就能跨品牌互通。理解了线圈、寄存器、功能码、偏移和字节序这些底层规则,再复杂的地址表也能一眼看穿。希望这篇工控小知识能帮你少走弯路,下次调试 Modbus 时能更有底气。