干自动化这行的,不管是去调试一条产线,还是去现场采集设备数据,最后八成都会撞上同一个名字:Modbus。说它是最老牌的工业通信协议一点都不夸张,几十年前的东西到现在还在PLC、传感器、仪表、数控机床上遍地跑。这篇是“3-1 Modbus协议”的完整笔记,我把协议本身、报文结构、实操采集、故障排查一次讲清楚,争取你看完就能上手调试,不用再翻一堆零散教程。这篇内容更适合刚接触工业通信、或者被现场设备搞到没脾气的电气工程师、嵌入式开发者和设备运维人员,当然,想补课的学生党也能当工具书看。
我先把话撂在这:Modbus这玩意儿,看着简单,真踩起坑来一点不含糊。但只要你把“一主多从、功能码、寄存器地址、CRC校验、RS485物理层”这五件事吃透,基本就能应付工厂里八成以上的接入需求。
1. 为什么聊Modbus:它解决了什么实际问题
1.1 一主多从,一种“先来后到”的通信秩序
Modbus最核心的通信模型就是“一主多从”。一个主站(比如触摸屏、上位机、PLC、边缘网关)去问多个从站(比如传感器、变频器、智能电表、数控系统)要数据,或者让它们执行动作。从站之间不能互相通信,所有对话都必须由主站发起。
这种模型放在今天看起来有点“笨”,但放在工业现场,它反而是一种优点。你想,如果总线上每个设备都能随时开口说话,那现场几十上百个设备同时发数据,总线早就乱套了。Modbus通过“主站问,从站答”的秩序,把通信冲突从机制上就解决掉了。它不需要什么复杂的仲裁电路,也不需要交换机做冲突检测,一根双绞线拉过去,主站一个个点名,从站一个个答,干净利落。
主站的轮询方式也很灵活。你可以让主站每隔200毫秒轮流扫一遍所有从站,也可以针对某台关键设备加大采集频率。这种调度逻辑至今都是工业数据采集的主流做法,因为它的时序完全可控,实时性可以算得出来。比如20台设备、9600波特率、每次读10个寄存器,一轮下来大约1到2秒,这个数字是可以估算的,后面我会讲怎么算。
1.2 Modbus不是一种协议,而是三种形态:RTU、ASCII、TCP
很多新手会把Modbus当成一个具体的东西,实际上它是一套协议框架,跑在什么物理通道上,就变成什么形态。最常见的是这三种:
| 形态 | 物理通道 | 特点 | 典型场景 |
|---|---|---|---|
| Modbus RTU | 串口(RS485/RS232) | 数据紧凑,效率高,工业现场最常用 | 传感器、仪表、PLC、变频器采集 |
| Modbus ASCII | 串口(RS485/RS232) | 用ASCII字符传输,肉眼可读,但数据量约翻倍 | 调试、老设备、误码率较高的低速链路 |
| Modbus TCP | 以太网 | 走TCP/IP协议,端口502,可以跨交换机路由 | PLC联网、上位机通过网口采集、设备上云 |
这里要特别强调:Modbus RTU是绝对的主流。我们在现场说“走Modbus”,默认十有八九指的就是RTU。原因很简单,RTU把一帧报文压缩到最紧凑的二进制格式,同样一包数据,RTU比ASCII少传一半左右的字节,在9600波特率这种低速链路上,效率差距非常明显。所以这篇文章后面所有的报文拆解和实操,都以RTU为主线。
1.3 它只负责搬运数据,不负责理解数据
Modbus有个容易让人误解的地方:它并不规定数据到底是什么含义。寄存器地址0x0001里存的是温度、压力还是主轴转速,Modbus协议管不着,那是设备厂商自己定的。Modbus只做一件事:按地址读写数据。这就像物流公司只负责把包裹从一个地方送到另一个地方,至于包裹里装的是衣服还是食品,物流公司不关心。
这也是Modbus生命力顽强的原因之一。它足够简单、足够透明,厂商只要在手册里写清楚“保持寄存器40001是温度值,单位0.1℃”,用户拿任何支持Modbus的主站软件都能直接读。反过来,这也意味着你必须先看设备手册,搞清楚每个寄存器地址对应什么数据、单位是什么,否则读回来的就是一串没有意义的数字。
2. 报文格式与功能码:看懂Modbus的“电报语言”
2.1 RTU帧逐字节拆解
先看一个最经典的例子。我们要读地址为1的从站、起始地址0x0000的1个保持寄存器,完整的RTU请求帧是:
01 03 00 00 00 01 84 0A一个字节一个字节拆开看:
| 字节 | 内容 | 含义 |
|---|---|---|
| 01 | 从站地址 | 目标从站编号,范围1-247,0是广播地址 |
| 03 | 功能码 | 表示“读保持寄存器” |
| 00 00 | 起始地址 | 从地址0x0000开始读 |
| 00 01 | 寄存器数量 | 读1个寄存器 |
| 84 0A | CRC16校验 | 低字节在前,防止传输错误 |
从站收到之后,如果正常,会回一帧响应:
01 03 02 00 64 B8 4C跟上面对应:01是从站地址,03是功能码,02表示后面跟了2个字节的数据,00 64是寄存器内容,换算成十进制就是100。如果你读的是一个温度传感器,手册告诉你单位是0.1℃,那实际温度就是10.0℃。后面的B8 4C同样是CRC校验。
这里有个细节值得注意:数据字段是“高位字节在前”,00 64不会写成64 00。这个叫大端字节序,Modbus RTU默认采用这种方式。但有的设备厂商不走寻常路,会把高低字节调反,遇到读回来的数值明显离谱时,第一反应就应该是把高低字节交换一下试试。
2.2 常用功能码,哪些是读哪些是写
Modbus的功能码很多,现场常用的也就这几种。背下来基本够用。
| 功能码 | 名称 | 操作对象 | 用途 |
|---|---|---|---|
| 01 (0x01) | 读线圈 | 线圈 | 读开关量输出,比如继电器状态 |
| 02 (0x02) | 读离散输入 | 离散输入 | 读开关量输入,比如按钮、限位 |
| 03 (0x03) | 读保持寄存器 | 保持寄存器 | 读可读写的寄存器,最常用 |
| 04 (0x04) | 读输入寄存器 | 输入寄存器 | 读只读寄存器,常见于传感器测量值 |
| 05 (0x05) | 写单个线圈 | 线圈 | 控制一个开关量输出 |
| 06 (0x06) | 写单个寄存器 | 保持寄存器 | 写一个寄存器,比如设速度 |
| 15 (0x0F) | 写多个线圈 | 线圈 | 批量控制开关量输出 |
| 16 (0x10) | 写多个寄存器 | 保持寄存器 | 批量写多个寄存器 |
初学者最容易搞混的是03和04。简单记:传感器或仪表如果只允许你读、不允许你写,数值一般都放在输入寄存器里,用04功能码;PLC里的设定参数、可读写的工艺数据,一般放在保持寄存器里,用03功能码。但这不是绝对标准,有些传感器会把数据放在保持寄存器里,用03也能读,所以最靠谱的依据还是设备手册。
2.3 线圈、寄存器、地址编号,别被0x和4x绕晕
Modbus的数据模型分成四类:线圈、离散输入、输入寄存器、保持寄存器。老式资料里习惯用“0x、1x、3x、4x”这种前缀来区分:
- 0x:线圈,可读可写,一个bit一个点,比如00001、00002
- 1x:离散输入,只读,一个bit一个点,比如10001
- 3x:输入寄存器,只读,16bit一个寄存器,比如30001
- 4x:保持寄存器,可读可写,16bit一个寄存器,比如40001
这里藏着Modbus最容易踩的坑之一:设备手册上写的地址是40001,但在实际发送报文时,协议里的“起始地址”是多少?答案是0x0000。因为40001对应的协议地址就是“40001-40001=0”,而40002对应协议地址“1”,以此类推。
打个比方,手册上的40001是“现实中的门牌号”,协议帧里的0x0000是“数组下标”。你在写代码时要做一次“减1”的转换。很多新手登录设备后读出来的数据全是错的,不是接线问题,而是没做这个偏移。
3. 从零开始实操:把传感器数据放进上位机
3.1 物理层选择:RS485、RS232怎么接
Modbus RTU最常跑在RS485上,偶尔也会跑RS232。两者的区别必须搞明白。
RS485是差分信号,用两根线(A和B)传输数据,靠两根线之间的电压差来代表逻辑0和1,抗干扰能力比RS232强得多。而且RS485支持半双工多点通信,一条总线上最多可以挂32个标准负载,配合中继器还能更多。工业现场布线距离动辄几百米,RS485在主流的9600波特率下可以跑到1200米左右,所以它才是Modbus RTU的主力物理层。
接线时记得A接A、B接B,千万别接反,接反了信号逻辑整个颠倒,表现为“完全收不到响应”。屏蔽双绞线是RS485的标准配置,屏蔽层一般建议单端接地,接到控制柜的地排或PLC的PE端子。
RS232就是老式电脑串口那一套了,三根线:TX、RX、GND,全双工,但只能一对一连,距离也就十几米。现在直接用RS232接Modbus设备的情况不多,更多是买一个USB转RS485调试器,临时接到电脑上调试设备用。
3.2 手工发一帧:用串口助手验证通信
无论你最终是把Modbus接到柜内PLC还是边缘网关,第一步永远是“先证明这条链路和报文没问题”。我的习惯是拿一个USB转RS485头,直接插到电脑上,打开串口调试助手,手动发一帧报文给从站,确认能收到正确响应。
具体操作步骤:
- 确认从站的串口参数,常见默认值是9600、8、N、1,也就是波特率9600、数据位8、无校验、停止位1。但一定要以设备手册为准,有的老设备默认偶校验。
- 打开设备供电,把RS485的A、B线接到USB转RS485调试器上。注意调试器这边可能标的是A+、B-,对应接就行。
- 在串口助手里选择正确的COM口号,设置好波特率等参数。
- 发送读保持寄存器请求帧,比如地址1、读起始地址0x0000、数量1:
01 03 00 00 00 01 84 0A- 观察是否收到响应帧。如果收到类似“01 03 02 00 64 B8 4C”这样的数据,说明通信链路完全正常,接下来再去搞采集代码或组态画面。
绝大多数串口调试助手都内置了CRC计算,你只要填好地址、功能码、起始地址和数量,软件会自动生成完整报文,不需要自己手算CRC。刚开始调试时,千万不要硬着头皮手拼CRC,容易拼错还浪费一整天时间。
3.3 CRC16计算,手写一段代码搞定
虽然工具能帮你算,但如果你要写采集程序,CRC校验是必须自己实现的。Modbus RTU用的CRC16算法其实很短,核心逻辑就是对整帧数据做一次异或和移位。
def crc16_modbus(data: bytes) -> int: crc = 0xFFFF for byte in data: crc ^= byte for _ in range(8): if crc & 0x0001: crc = (crc >> 1) ^ 0xA001 else: crc >>= 1 return crc def build_read_frame(slave_id, function_code, start_addr, quantity): payload = bytes([slave_id, function_code, start_addr >> 8, start_addr & 0xFF, quantity >> 8, quantity & 0xFF]) crc = crc16_modbus(payload) return payload + bytes([crc & 0xFF, crc >> 8])这段代码不是我瞎写的,它就是标准Modbus CRC16的常规实现。你用刚才的例子验算一下:对“01 03 00 00 00 01”这6个字节做CRC,算出来的结果是0x0A84,发送时低字节在前,所以帧尾是“84 0A”,拼出来正好是“01 03 00 00 00 01 84 0A”。
我把CRC的道理说得生活化一点:CRC不是加密,它就是个指纹。发送方把整帧数据揉成一个16bit的数字,附在报文后面;接收方收到数据后,用同样的算法再揉一遍,如果算出来的指纹和附带的指纹一致,就认为这帧数据没有被干扰。
3.4 一主多从轮询:多台设备的调度方式
当总线上挂了不止一台从站,比如10个传感器,主站就不能只读一台了。这时候要写一个轮询循环,每台设备依次点名。
核心逻辑是这样的:
- 准备一个设备列表,每台设备有从站地址、寄存器起始地址、寄存器数量和采集周期。
- 循环中,对每台设备发送读请求帧。
- 发送后等待响应,设置超时时间,比如200毫秒。如果收到响应,解析数据;如果超时,记录这台上一次掉线,但不要卡住,直接处理下一台。
- 按固定的总周期循环执行,或者根据不同设备的采集周期做动态调度。
有两点要提醒你。第一,从站地址必须唯一,两台设备不能共用同一个地址,否则响应帧到达时主站分不清是谁回的。第二,轮询时间要留够。9600波特率下,一个字节大约1毫秒左右,一帧“读10个寄存器”的请求是8个字节,响应大概是25个字节,一次往返大约几十毫秒。20台设备一轮下来大概一两秒,实时性要求高的场景要用115200这类更高波特率,或者把每台设备的读取量压缩到最小。
4. 进阶玩法:用Modbus数据判断设备运行状态
4.1 读状态字:按位判断报警和使能
光会把数据读回来还不够,很多时候我们要根据Modbus寄存器的值判断设备“到底在干什么”。最常见的做法是读状态字寄存器。数控机床、注塑机、包装线这类设备,通常会在某个保持寄存器里放一个16bit的状态字,每一位代表一个状态。
举个例子,假设某台设备的状态寄存器地址是40001,设备手册这样描述:
- bit0:急停报警
- bit1:运行中
- bit2:故障
- bit3:回零完成
你读回来之后,不能只看整个寄存器的十进制数值,因为它可能是多个状态叠加的组合。你用“按位与”的方式去解析,比如代码里这样判断:
value = read_holding_register(1, 0, 1) # 读40001对应协议地址0的值 if value & 0x0001: print("急停报警") if value & 0x0002: print("运行中") if value & 0x0004: print("故障") if value & 0x0008: print("回零完成")如果读回来的值是0x0006,也就是二进制110,说明bit1和bit2同时为1,设备正在运行并且带故障报警。这种“多位合并”的状态字,在实际设备上非常常见,你拿十进制直接看永远看不懂。
4.2 连续采集,用数据趋势判断设备健康度
判断设备状态,除了看实时报警位,更高级一点的是看数据趋势。比如一个传感器持续回传温度数据,你可以存最近一段时间的值,做两个简单判断:
- 渐变判断:温度在一个小时内缓慢上升了5℃,可能是正常的热累积,也可能是润滑系统开始老化,这个需要结合工艺判断。
- 突变判断:前一秒温度还是80℃,下一秒直接跳到120℃,大概率是传感器故障、接线松动或者设备真的出了突发状况。
做这块的时候,不用急着上复杂的机器学习,先用modbus采集数据存下来,画一张趋势曲线,人工定几个阈值和变化率阈值,就能挡掉很多无效报警。等数据积累够了,再谈更聪明的算法。
4.3 Modbus与OPC UA、MQTT的配合关系
做现场采集时,很多人会问:现在不是流行OPC UA吗?是不是直接用OPC UA读PLC就行了,还用Modbus干嘛?
我的回答是:这俩不是替代关系,是上下游关系。Modbus依然是最贴近底层设备的“方言”,而OPC UA更像是一套“普通话”。一条典型的数据链路是这样的:边缘网关通过Modbus RTU采集变频器、传感器、数控系统的数据,然后网关内部把数据整理成语义化结构,再通过OPC UA Server往外发布,或者用MQTT上报到云端平台。
OPC UA的好处是它有完整的信息模型、安全机制和跨平台能力,但它并不直接解决“怎么从RS485总线上把报文读回来”这件事。Modbus负责解决“最后一公里”的物理通信,OPC UA负责解决“数据上去之后怎么被上层系统理解”。所以你光会Modbus不够,但不会Modbus,搞底层采集基本寸步难行。
5. 现场故障排查与避坑实录
5.1 一套排查思路,从“收发”到“解析”逐层查
遇到Modbus通信故障,最怕的就是东试一下西试一下。我会按一个固定顺序来排查,效率高很多。
第一步,确认硬件层。从站有没有上电?A、B线有没有接反?USB转RS485的驱动装没装?COM口被别的软件占了没有?先用万用表量一下A、B之间有没有一个稳定的电压差,通常空闲时有几百毫伏到一点几伏的变化区间,如果完全量不到电压,大概率是线没通。
第二步,排除硬件后,用串口助手手动发一帧,看看有没有回帧。这一步最关键,它能区分是“物理层不通”还是“协议层出错”。如果手动发帧能收到正常响应,那问题就出在你的上位机代码、组态配置或网关配置上;如果手动发帧都没反应,说明链路本身还有问题。
第三步,收到回帧但CRC校验错,说明传输过程中有数据被干扰。优先排查线材质量、布线路径、接地,以及波特率是不是开得太高。实在不行把波特率降到9600,很多疑难杂症能当场解决。
第四步,数据能读到但数值不合理,基本就是解析层面的地址偏移、字节序、功能码用错。去翻设备手册,逐项核对。
5.2 常见故障现象对照表
我把这些年遇到过的问题整理成一张表,方便你直接对照排查。
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 完全无响应 | 接线A/B反、从站未上电、从站地址不对、串口参数不一致 | 万用表检查接线,核对设备手册,逐项试参数 |
| 回帧乱码或CRC错误 | 波特率不匹配、线路干扰、屏蔽层没接好、线缆过长 | 降波特率到9600,换屏蔽双绞线,改善接地 |
| 偶发超时,其他设备正常 | 缺少终端电阻、终端电阻过多、地址冲突 | 总线两端各接一个120Ω电阻,检查从站地址唯一性 |
| 能读但数值离谱 | 起始地址未减1、高低字节反了、单位没换算 | 试地址偏移,交换高低字节,按手册换算单位 |
| 一读某台设备就卡住整个轮询 | 该设备响应慢或掉线,但超时时间设置过长 | 单独加短超时,掉线后跳过,不影响其他设备 |
| 写寄存器不生效 | 用了03功能码去读写寄存器、寄存器本身只读 | 确认对象是保持寄存器,使用06或16功能码 |
5.3 几个容易翻车的现场细节
最后说几个平时容易忽略的细节。第一个是终端电阻。总线上挂的设备多了、线长了,信号反射会让波形变差,表现为通信时好时坏。标准做法是在总线最远的两端各并一个120Ω电阻,但前提是你要知道“两端”在哪,别在中间乱并。
第二个是屏蔽层接地。RS485屏蔽层最好是单端接地,比如统一接到控制柜的接地排。如果两端都接地,地电位差会在屏蔽层上产生环流,反而引入干扰。很多现场通信不稳定,查到最后就是屏蔽层接法不对。
第三个是设备手册的坑。有些厂家的寄存器表示方法不规范,写“40001”其实对应的是输入寄存器而不是保持寄存器,或者地址表写的是十进制但是要转成十六进制再发送。调试时遇到数据对不上,别急着怀疑硬件,先把手册里的每个地址用几种常见方式都试一遍,比如40001、0x0000、0x0001、30001,很快就能找到规律。
最后一个细节,也是我自己的亲身体会:批量设备调试的时候,不要一次性把所有从站都接上总线。先单独接一台,用串口助手把这一台的寄存器表摸清楚,再接入第二台验证地址和轮询逻辑,最后全部挂上测稳定性。否则一上来就接一整条总线,出了问题根本不知道是哪台设备在捣乱。还有个经验是,读寄存器的时候不要贪多,设备支持读100个寄存器不代表你每次都要读100个,读得越多,单帧时间越长,出错的概率也越大,够用就好。