ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Modbus RTU协议详解:从报文结构、CRC校验到RS485实战排查

Modbus RTU协议详解:从报文结构、CRC校验到RS485实战排查 做自动化项目的人迟早要和Modbus协议打交道。课程目录里3-1这个章节只写了四个字“Modbus协议”可它背后串起来的真实内容比想象中多得多一主多从的通讯模型、RTU电报格式、CRC校验、RS485电气接线每一项都足够让人在现场挠头。我第一次被拉去处理一台变频器没法被上位机控制时老师傅只丢过来一句话“把Modbus RTU报文看懂再来找我。”当时心里很不服气可真正啃下来之后才明白这套协议就是工业串口通信的地基。下面不是抄手册而是把我在现场踩过的坑、反复验证过的报文和可以直接抄作业的代码整理出来适合刚接触工控通信的电气工程师、自动化专业学生以及想把仪表数据读回来的嵌入式开发者。1. 为什么工控现场都在用Modbus协议1.1 一个协议解决设备通信的“普通话”问题和客户聊需求时经常听到一句话“我们这个设备通讯协议可以定制。”但到了现场你会发现真正默认的通用语言还是Modbus。变频器、温控仪表、电表、PLC、HMI哪怕来自不同厂家基本都留了Modbus接口。它把不同设备统一到一种上报和下发数据的规则里大家按照同一套报文说话就像一群方言不同的人突然统一说普通话设备联调一下子简单很多。Modbus能普及核心原因就是足够简单。它不像一些私有协议那样藏着掖着报文格式公开功能码固定报文逻辑非常直白。工业现场维护人员不需要懂太多复杂的网络知识拿一个串口助手就能手工拼出一帧数据这在其他总线协议里很难想象。它不是一个追求性能极限的协议但在可靠性、易用性、兼容性之间找到了一条很务实的路。1.2 RTU、ASCII、TCP三兄弟怎么选不少人一看到Modbus有三个变体就发懵。其实不用慌它们只是传输外壳不同核心的数据读写逻辑一致。形态物理层编码方式典型场景备注Modbus RTURS232/RS485二进制大部分串口仪表、PLC效率高最常用Modbus ASCIIRS232/RS485每个字节转两个ASCII字符早期设备、透明性要求高的场景效率低适合调试Modbus TCP以太网TCP/IP封装工业以太网、上位机通信端口502无CRC实际项目里串口设备九成走Modbus RTU所以后面展开也以RTU为主。ASCII虽然报文可读性好但同样的数据量长度翻倍在低波特率下轮询很吃亏。TCP则把CRC校验交给TCP/IP协议栈处理报文里不再需要计算CRC这点后面看Modbus TCP报文时会特别明显。1.3 “一主多从”到底是什么意思Modbus RTU最经典的通讯模型就是“一主多从”。总线上只能有一个主站通常是PLC、上位机或者触摸屏其余都是从站比如电表、温控器、IO模块。主站负责发起所有请求从站只能被动响应绝对不允许从站自己主动往总线上发数据。这个规则非常关键就像课堂上一个老师点名提问学生只能听到自己被点名后才站起来回答没人提问就说话会被罚站。主站按从站地址一个一个轮流访问例如先问地址1的设备再问地址2的设备周而复始。每个从站的地址必须唯一从站地址范围一般是1到2470是广播地址发到地址0的命令所有从站都会收到但从站不会应答。轮询周期等于“从站数量 × 单帧通信时间”如果一帧加等待要20毫秒挂了20个从站一圈下来就要400毫秒对响应速度要求高的场景要提前算清楚。2. RS485与RS232协议详解及Modbus通信指南2.1 RS232和RS485的核心差异很多人把Modbus和RS485混为一谈其实它们是两层东西Modbus是应用层协议定义报文怎么拼RS232/RS485是物理层电气标准定义信号怎么在线路上传输。RS232和RS485虽然都叫串口脾气差别很大。项目RS232RS485信号方式单端信号靠电压对地判断差分信号靠A/B两线电压差判断通信距离一般15到20米低波特率下可达1200米节点数量只能点对点一条总线上可挂多台设备工作模式全双工半双工抗干扰能力一般较好差分结构抵消共模干扰常用接口DB9接线端子或RJ45RS485最吸引人的点就是多节点。一个主站带十几个从站总线结构非常方便。RS232更像一条专门连接两台设备的短网线距离一长、环境一吵就出问题。2.2 Modbus RTU和RS485的组合拳Modbus RTU之所以偏爱RS485是因为它俩特性完美匹配。Modbus要求一主多从RS485天然支持多节点总线Modbus是半双工问答模式RS485两线制恰好就是半双工。协议和物理层一拍即合几乎没有更顺手的搭配。接线时最常遇见两件事。第一是A/B线接反。RS485设备上一般标着A、B或者D、D-不同厂家标法可能相反接反之后主站完全收不到响应这是最典型的“模块没问题但就是不通”的故障。第二是通讯距离和分支限制。RS485总线应该“手拉手”菊花链式连接不要从中间扯一堆很长的小分支分支太长会形成反射信号数据一多就乱。总线两端各接一个120欧终端电阻中间节点不加可以吸收反射波。短距离通信比如10米以内终端电阻经常省掉也没事但超过几十米最好按规范来。另外RS485虽然叫“两线制”却不是说只靠两根信号线就能走得远。建议用双绞屏蔽线屏蔽层在信号源一端单点接地把所有从站的参考地连起来。很多偶发乱码都是地电位不一致导致的共地之后立竿见影。2.3 串口参数必须对齐Modbus RTU最常见的串口参数是9600波特率、8位数据位、无校验、1位停止位简写为9600 8N1。但这不是绝对的有些设备默认9600有些默认19200校验位有的用偶校验。主站和从站必须完全一致否则抓到的全是乱码或者干脆没响应。改设备参数后一般要断电重启才生效很多工程师改了波特率发现还是不通其实是没重新上电。3. Modbus RTU协议详解一主多从、电报格式与数据解析实战3.1 报文帧结构拆解Modbus RTU一帧报文由四部分组成字段长度说明从站地址1字节目标从站地址功能码1字节要执行的操作类型数据区N字节寄存器地址、数量、具体数据CRC校验2字节对前面所有字节做CRC16校验以最常用的“读保持寄存器”功能码03为例读从站1、起始地址0x0000、数量2个寄存器的请求帧是01 03 00 00 00 02 C4 0B其中01是从站地址03是功能码00 00是起始寄存器地址00 02是读取数量C4 0B是CRC校验。注意CRC发送时低字节在前代码里算出0x0BC4后线上先发C4再发0B这个顺序几乎每本手册都会坑一次人。如果从站两个寄存器里的值分别是10和20正常响应帧大致是01 03 04 00 0A 00 14 [CRC]01仍然是从站地址03是功能码04是后续数据字节数00 0A是寄存器1的值1000 14是寄存器2的值20。从站地址和功能码必须和请求帧完全一致这是判断“响应到底是不是给我的”第一道关口。3.2 常用功能码速查Modbus的功能码有很多但工程上常用就那么几个功能码操作对象说明01线圈读单个/多个线圈状态02离散输入读开关量输入03保持寄存器读16位寄存器04输入寄存器读只读寄存器05线圈写单个线圈06保持寄存器写单个寄存器0F线圈写多个线圈10保持寄存器写多个寄存器实际项目里03和06用得最多。设备说明书里常见的“40001”地址对应Modbus协议里的保持寄存器也就是用03读、用06写而“30001”通常是输入寄存器用04读。功能码带0x80的返回表示异常例如从站返回0x83说明03这个操作被拒绝后面紧接着的异常码会告诉你具体原因01是非法功能02是非法数据地址03是非法数据值04是从站设备故障。3.3 数据解析寄存器、字节序和量程换算Modbus寄存器都是16位一个寄存器能表示0到65535的无符号数。想读温度、流量、频率这些超过65535的量就用两个连续寄存器拼成32位或者把浮点数拆成4个字节放在两个寄存器里。看起来简单但字节序问题会让数据解析变成一个深坑。Modbus协议默认多字节数据是“大端”排列高位在前。收到00 0A 00 14时两个寄存器分别是0x000A等于100x0014等于20。但很多设备的内部习惯是“小端”低字在前同样两个寄存器组在设备里可能表达成0x0014000A解析出来就是一个莫名其妙的大数。 遇到具体设备不要只看说明书上的点位表还要确认它的寄存器字序和字节序是否可配置。曾有一颗温度传感器出厂默认低字在前上位机软件却按高字前解析显示出来的温度一会儿正常一会儿炸裂查到最后是两个字序不一致。量程换算也不能忽略。不少仪表寄存器里存的是带系数的原始值比如PT100温度模块返回250实际温度可能是25.0摄氏度。不清楚系数就直接读数据看着有变化但永远对不上。3.4 CRC校验手写实现CRC校验是Modbus RTU防错的关键发送方把地址、功能码、数据全部算一遍接收方收到后做同样计算对不上就丢弃这一帧。手写CRC并不复杂网上很多Modbus RTU协议源码下载里都是一张512字节的查表但先用循环方式跑通逻辑更踏实。POLY 0xA001 # 反射多项式 def crc16_modbus(data: bytes) - int: crc 0xFFFF for b in data: crc ^ b for _ in range(8): if crc 0x0001: crc (crc 1) ^ POLY else: crc 1 return crc把01 03 00 00 00 02喂进去算出来是0x0BC4放到帧尾时低字节在前就是C4 0B。自己写完CRC后再拿几个设备说明书上的标准报文对一遍如果全部吻合说明算法没问题后面排查通讯故障时就可以大胆排除CRC环节。4. 从零做一个Modbus RTU主站4.1 工具准备与最小硬件清单动手之前先备齐工具不需要多贵一套就能跑通USB转RS485转换器一个最好带隔离避免现场地电位把电脑串口烧掉一台支持Modbus RTU的设备或者用Modbus Slave软件仿真从站Modbus Poll软件作为参照主站串口助手用来手工发报文测试Python环境安装pyserial库建议拿到设备后先用串口助手发一帧01 03 00 00 00 02 C4 0B如果设备正常运行应该能收到一帧以01 03 04开头的响应。这一步能快速判断线接对没有、从站地址对不对、串口参数对不对。很多新手一上来就写程序反而分不清是程序问题还是接线问题。4.2 手写报文读保持寄存器不再依赖现成库有些人习惯直接调现成Modbus库这当然快但我建议至少手写一次主站帧。自己拼帧之后你才会真正记住“地址、功能码、数据、CRC”这个结构。import serial import struct POLY 0xA001 def crc16_modbus(data: bytes) - int: crc 0xFFFF for b in data: crc ^ b for _ in range(8): if crc 0x0001: crc (crc 1) ^ POLY else: crc 1 return crc def build_read_holding_frame(slave_id: int, start_addr: int, quantity: int) - bytes: body struct.pack(BBHH, slave_id, 0x03, start_addr, quantity) crc crc16_modbus(body) return body struct.pack(H, crc) def parse_read_holding_response(resp: bytes, slave_id: int): if len(resp) 5: raise ValueError(响应帧太短) resp_addr, func, byte_count resp[0], resp[1], resp[2] if resp_addr ! slave_id: raise ValueError(从站地址不匹配) if func 0x80: raise RuntimeError(f从站返回异常码: {resp[2]}) if len(resp) ! 3 byte_count 2: raise ValueError(响应长度不对) crc struct.unpack(H, resp[-2:])[0] if crc ! crc16_modbus(resp[:-2]): raise ValueError(CRC校验失败) data resp[3:3 byte_count] return [struct.unpack(H, data[i:i2])[0] for i in range(0, len(data), 2)] def hex_str(data: bytes) - str: return .join(f{b:02X} for b in data) if __name__ __main__: ser serial.Serial( portCOM3, # Linux下通常是 /dev/ttyUSB0 baudrate9600, bytesize8, parityserial.PARITY_NONE, stopbits1, timeout0.5 ) frame build_read_holding_frame(0x01, 0x0000, 2) print(发送:, hex_str(frame)) ser.write(frame) resp ser.read(100) print(接收:, hex_str(resp)) values parse_read_holding_response(resp, 0x01) print(寄存器值:, values) ser.close()struct.pack(BBHH, ...)里的B是单字节整数H是16位无符号整数表示大端。请求帧拼完后CRC要按小端追加这样才能和协议要求一致。接收端把响应数据按每两个字节一组的顺序解析这里同样按大端解回寄存器值。4.3 没有设备也能联调Modbus Slave加虚拟串口手头没有真实仪表时Modbus Slave软件配虚拟串口也能搭一套闭环。在Windows下用虚拟串口工具把COM5和COM6做成一对Modbus Slave打开COM5作为从站设置从站地址为1功能码03起始寄存器0x0000寄存器1和2填入两个测试值。脚本把串口改成COM6运行后应该能读到一样的数据。如果只有一台串口设备更简单的方法是先把Modbus Poll连上设备确认能正常读数再关闭Modbus Poll立刻运行自己的脚本。这样至少能排除软件端口占用导致的假故障。Virtual Serial Port工具不能和别的程序同时占用同一个物理串口这一点经常被忽视。4.4 模块选型与通讯参数注意事项USB转RS485转换器建议选带隔离的尤其是连接电柜里的设备时地电位差容易通过串口烧电脑主板。转换器的A、B端和设备的A、B端一定要同名相连不要凭颜色判断最好拿万用表确认一下标识。Modbus RTU单帧能读的数据数量也有限制。功能码03一次最多读125个寄存器功能码10一次最多写123个寄存器超过限制从站会返回非法数据值异常。读大块连续数据时要么分段读要么改寄存器区规划这个限制在写采集程序时要提前设计好。5. 常见问题与排查技巧实录5.1 典型故障现象速查表故障现象可能原因优先排查方向完全无响应A/B接反、从站地址错、波特率不匹配、设备没有上电供电、A/B电压、串口参数数据乱码波特率不一致、校验位停止位不一致、用了非屏蔽线且距离过长核对串口参数、缩短距离读到全0或全F信号线断路、终端电阻短路、总线电平异常万用表测A/B差分电压偶尔能读到但总CRC报错屏蔽层没接地、地电位差、线路干扰共地、屏蔽层接地、降低波特率总线上出现两个设备同时响应从站地址冲突逐个断开从站定位重复地址读取大量寄存器失败单帧数量超限、寄存器地址不存在拆分读取核对点位表5.2 从物理层往上查三步定位问题排查串口通讯我的习惯是从下往上推而不是一上来就改程序。第一步看物理层。用万用表测RS485的A/B端电压正常情况下空闲时A相对B有一个正电压通常0.2伏以上。如果A/B电压接近0说明总线没有被驱动可能是转换器没工作、设备没供电或者长度太长导致信号衰减。第二步看协议层。用串口助手手工发送一帧固定报文看返回什么。能收到正确响应说明物理层和设备参数没问题接下去才怀疑自己的脚本收不到响应就先别查程序把线和参数问题解决再说。抓到的报文用hex格式看不要看ASCII否则二进制帧很容易被显示成乱码干扰判断。第三步看应用层。响应正常但数值对不上通常是寄存器地址偏移、字节序或者量程换算的问题。PLC的“40001”在Modbus报文里实际起始地址是0x0000有些仪表说明书直接写“地址0”有些写“40001”换算错一个数数据就全偏。5.3 我踩过的坑第一个坑是A/B接反。那次现场排查变频器通讯换了三个转换器都不行最后把A/B对调后一秒恢复。从那以后我包里永远带一个小标签写着“线标可能反了”。第二个坑是地址冲突。同一个串口下面挂了两台电表厂家都设了地址1结果主站一读请求两个从站同时抢答总线数据全是乱的。逐个断开从站后才发现重复地址改完立即恢复。第三个坑是CRC低字节在前。自己第一次手写CRC时按习惯把高字节放前面结果所有报文CRC全错设备端直接丢弃。后来把标准报文喂给CRC函数对拍了一下才发现问题。第四个坑是终端电阻。短距离不接终端电阻没事但有一趟总线走到80米不接电阻时偶发乱码接上120欧终端电阻后马上稳定。从此超过30米的线我都会在设备侧预留终端电阻位置。6. 从RTU走出去Modbus TCP和后续扩展6.1 熟悉RTU后再看Modbus TCPRTU吃透之后Modbus TCP几乎不用重新学。TCP帧只是把RTU的CRC去掉换成MBAP报头包含事务标识符、协议标识符、报文长度和单元标识符端口固定502。原来RTU里的从站地址变成了单元标识符功能码和数据区完全一样。有一次给客户做数据采集设备本体是串口但现场只能走网线最终用一个串口转以太网网关协议从RTU转成TCP。因为两边都是Modbus网关配置一下地址映射就行几乎没碰报文格式。这就是Modbus生态的好处串口和网口之间过渡非常平滑。6.2 真正值钱的是排查思路学了这么多报文和接线最后你会发现最值钱的不是背下CRC算法而是那一套排查思路。遇到通讯问题先看线再看参数再看报文最后看代码。很多人习惯一出问题就在代码里翻来覆去其实现场故障七成都在物理层和参数配置上。我自己最喜欢的调试习惯是在工程机上留一个串口助手快捷键遇到问题第一时间手工发帧而不是先改程序重新编译。这一步省下来的时间比多买几个转换器都值。最后想说的是Modbus看起来简单但把它每条规则都讲明白、每帧报文都亲手验证过后面学再复杂的工业协议都有底气。
返回列表