中控工程师为什么会碰上 Modbus
你本来不想碰工业协议,但只要展厅里出现灯光继电器、窗帘电机、空调、电源时序器、环境传感器或 PLC,这些设备说的就是 Modbus。它是楼宇和工业设备的通用语,而它出现在中控工作里的位置很明确:中控系统需要碰楼宇设备的那一刻。
好消息是:Modbus 其实比多数投影机协议还简单。 它只干一件事——读写编号的寄存器。
RTU 与 TCP:同样的命令,两种外壳
这是最关键的一点:RTU 和 TCP 承载的命令载荷完全相同,区别只在外壳。
Modbus RTU : [从站ID][功能码][数据………][CRC低][CRC高]
Modbus TCP : [MBAP 头 7字节][功能码][数据………]| Modbus RTU | Modbus TCP | |
|---|---|---|
| 物理层 | RS-485(多数)、RS-232 | 以太网 |
| 寻址 | 帧内从站 ID 1–247 | IP 地址 + 单元 ID |
| 校验 | CRC-16 | 无——由 TCP 负责 |
| 端口 | 无 | 502 |
| 拓扑 | 手拉手总线,单主站 | 星型,多客户端 |
| 速率 | 9600–115200 波特 | 网络速率 |
| 距离 | RS-485 可达约 1200 米 | 网络能到的地方 |
能用 TCP 就用 TCP。 不用算 CRC、不用布总线、不用终端电阻、没有单主站限制,而且网络上任何位置都能够到设备。只有当设备只有 RS-485 口时才用 RTU——而这在便宜的继电器板和老空调设备上依然很常见。
务实的第三选项是网关。 一个 Modbus RTU 转 TCP 转换器能把 RS-485 总线搬上网络,从中控系统看过去一切都变成了 TCP。展厅里只有一条 RS-485 灯控总线、其余全是网络时,这几乎总是正确的架构——它意味着你的中控系统里只有一条协议路径,而不是两条。
你真正会用到的功能码
Modbus 的功能码列表很长。中控和楼宇场景里你只会用到四个:
| 码 | 名称 | 用途 |
|---|---|---|
0x01 | 读线圈 | 读开关量输出(继电器状态) |
0x03 | 读保持寄存器 | 读数值(温度、设定值、状态) |
0x05 | 写单个线圈 | 控制继电器通断 |
0x06 | 写单个寄存器 | 写数值(调光值、模式) |
0x0F 和 0x10 一次写多个线圈/寄存器,适合一笔事务设置整个场景。
线圈是位,寄存器是 16 位字。 继电器板暴露的是线圈,温度传感器暴露的是寄存器。读错空间会返回异常码而不是错误的值——至少它是"响亮地失败"。
CRC 陷阱:低字节在前
Modbus RTU 在帧尾追加 16 位 CRC。关于它的两个事实造成了大多数 RTU 故障:
1. 多项式是 0xA001,初值 0xFFFF。 这是专门的 CRC-16/MODBUS——不是通用库里那个 CRC-16,那个会给出完全不同的结果。
2. CRC 是低字节在前追加的。 Modbus 里其他所有多字节字段都是大端序,唯独 CRC 是例外,而这个例外一直在坑人。
实算示例——从从站 1 读 1 个保持寄存器,起始地址 0:
帧体 : 01 03 00 00 00 01
CRC-16 : 0x0A84
追加 : 84 0A ← 先低字节,后高字节
完整帧 : 01 03 00 00 00 01 84 0A最后这一行是每本教科书上都有的 Modbus 标准样例,也是个很好的自检:如果你的 CRC 例程算出的是 0A 84 而不是 84 0A,说明你把字节序搞反了,任何设备都不会回你。
更多已验算的帧,可用来对照你的实现:
| 动作 | 帧 |
|---|---|
| 读 1 个保持寄存器 @0 | 01 03 00 00 00 01 84 0A |
| 读 2 个保持寄存器 @0 | 01 03 00 00 00 02 C4 0B |
| 读 1 个线圈 @0 | 01 01 00 00 00 01 FD CA |
| 写线圈 @0 = 通 | 01 05 00 00 FF 00 8C 3A |
| 写线圈 @0 = 断 | 01 05 00 00 00 00 CD CA |
| 写寄存器 @0 = 1 | 01 06 00 00 00 01 48 0A |
注意"通"的值是 FF 00,不是 00 01。 功能码 0x05 用 FF00 表示通、0000 表示断——这是一对固定的魔数,不是布尔值。发 00 01 什么也不会发生。
那个能浪费掉一下午的差一错误
设备厂商的 Modbus 文档存在两套互不兼容的编号约定,把它们混起来是除接线外最常见的故障:
- 协议地址从 0 开始。这是写进帧里的值。
- 厂商手册里的寄存器编号常常从 1 开始,或者用 4xxxx 约定(保持寄存器 1 =
40001)。
所以手册说"温度在寄存器 40001",意思是协议地址 0。手册说"继电器 1 在线圈 1",通常意思是地址 0。
从错误的寄存器读到一个看起来合理的值时,不会有任何报错——你只是拿到了隔壁那个数据点,然后花一小时琢磨温度为什么是 47。当一个值"正好错了一位"的时候,先把地址挪一位试试,再去怀疑别的。
32 位数值与字节序
寄存器是 16 位。任何更大的东西——表示功耗的 32 位浮点、32 位计数器——都要跨两个连续寄存器存放,而这件事没有标准。
实际存在四种排列。以两个原始字 0x1234 和 0x5678 为例:
| 顺序 | 字节 | 别称 |
|---|---|---|
| ABCD | 12 34 56 78 | 大端序 |
| BADC | 34 12 78 56 | 大端字节交换 |
| CDAB | 56 78 12 34 | 字交换——非常常见 |
| DCBA | 78 56 34 12 | 小端序 |
你最常撞上的是 CDAB,因为很多设备把低位字存在前面。症状非常好认:本该是 23.5 的浮点数回来变成 1.2e-38 这样荒谬的值,或者一个整数读出来是几百万而不是几十。
修它只是改一个配置项,不用改代码——但你得先认出这个症状。如果一个 32 位值是"离谱地错"而不是"轻微地错",那是字节序问题,不是缩放问题。
缩放是另外一半:很多设备存的是 235,指望你除以 10 得到 23.5。这在每个寄存器的文档里写着,是乘法问题不是字节问题。缩放错会让你的值差一个 10 的幂;字节序错会给你一堆无意义的数。分清这两种症状能省下大量时间。
Modbus RTU 的 RS-485 接线
RS-485 是两线差分总线,它的规矩比以太网严苛得多:
- 只能手拉手。 不能星型、不能分支。设备接设备接设备。
- 极性有讲究。 A/B(有时标 D+/D−)必须全总线一致。某一台接反了,那一台就哑了——而其余设备照常工作,于是看起来像那台设备坏了。
- 必须共地。 供电分开的机柜之间长距离走线要有地参考,否则会出现看起来像干扰的间歇性数据损坏。
- 终端电阻。 120 Ω,接在总线的两个物理末端,而且只在那两处。中间多接的终端电阻会把总线拉垮。
- 单主站。 Modbus RTU 只能有一个主站。两套中控同时轮询同一条总线会碰撞,两边都会看到随机超时。
- 所有设备的波特率、校验位、停止位必须一致,而且每台要有唯一的从站 ID。两台 ID 相同的设备会同时应答、互相破坏。
从站 ID 重复这一条值得单独点出来,因为它的症状特别迷惑人:偶发的乱码回复,而不是稳定的失败。 而出厂的继电器板非常经常地默认是从站 ID 1。
为什么你的 Modbus 设备不应答
1. CRC 字节序反了。 见上。用 01 03 00 00 00 01 84 0A 自测。
2. 从站 ID 错了,或者两台设备共用了一个 ID。
3. 某一台的 A/B 接反了。
4. 终端电阻缺失或多余。
5. 地址差一位——40001 约定。
6. 功能码与数据类型不匹配——去线圈空间读寄存器。
7. 串口参数不匹配。 Modbus RTU 默认常用偶校验(8E1),这和多数中控设备不一样。设备在 8N1 下不吭声,先试 8E1。
8. TCP 场景:单元 ID 错了。 即使走 TCP,帧里仍然带一个单元标识。网关用它来选择下游总线上的哪台 RTU 设备;直连以太网的设备通常什么都收,或者要求正好是 1 或 255。
用 SoftControl 控制
SoftControl 原生支持 modbus_tcp 和 modbus_rtu 两种协议,功能码、从站 ID、地址、数量、数据类型、字节序、缩放、偏移、精度都可以按指令配置——所以 ABCD/BADC/CDAB/DCBA 是一个下拉框,而不是一次手工挪字节的练习,CRC 也由它替你算。指令测试页会显示实际发出的帧和原始响应,这正是你分辨"CRC 被拒"和"设备根本不在总线上"的手段。
这在展厅工程里的意义是:灯光继电器、窗帘电机和传感器,和投影机、显示器待在同一个界面里——协议之间怎么配合见 AV 控制协议详解,一个完整应用见工业 Modbus 看板。