
1. 通讯不稳的排查思路先别急着换线换板子干工控这行的多多少少都遇到过 Modbus RTU 通讯时好时坏的情况。设备跑着跑着数据就不刷新了PLC 报通讯超时触摸屏上数值卡死不动重启一下又能撑一阵子。第一反应往往是线缆质量不行、485 芯片坏了、干扰太大、终端电阻没接对。于是换屏蔽线、加磁环、换收发器、折腾接地一通操作下来问题依旧。我最近就碰到这么一档子事。一条产线上三台从站设备通过 RS485 挂在 FX3U-485ADP-MB 模块下面主站轮询读取寄存器和写入设定值。现象很典型白天生产忙的时候通讯断断续续晚上停机了反而稳定。一开始我也怀疑是变频器干扰查了屏蔽层接地、检查了走线间距、甚至把通讯线单独穿管都没根治。最后用串口抓包工具把报文抓下来逐帧分析才发现根子不在硬件而是轮询逻辑和 CRC 校验处理上出了问题。这篇文章就把整个排查过程、Modbus RTU 报文结构、CRC 校验原理、寄存器读写细节、以及梯形图程序里容易踩的坑从头到尾捋一遍。不管你是刚接触 PLC 编程的新手还是已经用过几年 Modbus 的老师傅只要你的系统里涉及 RS485 组网和 Modbus RTU 通讯这里面的经验应该都能对上号。2. Modbus RTU 报文结构拆解先看懂帧再谈调试2.1 一帧报文到底长什么样很多人调通讯靠猜改个波特率、换个校验方式碰运气。其实 Modbus RTU 的帧结构非常规整看懂了帧问题就定位了一半。一帧完整的 RTU 报文由四部分组成字段长度说明从站地址1 字节0x01~0xF70 为广播地址功能码1 字节03 读保持寄存器、06 写单个寄存器、10 写多个寄存器等数据域N 字节起始地址、寄存器数量、或写入的数据CRC 校验2 字节低字节在前高字节在后以读取从站 1 的 40001 开始的 2 个保持寄存器为例主机发出的请求帧是01 03 00 00 00 02 C4 0B拆开看01是从站地址03是功能码00 00是起始地址对应 4000100 02是读取数量C4 0B是 CRC 校验值。从站正常响应会是01 03 04 XX XX XX XX CRC_L CRC_H其中04表示后面跟 4 个字节的数据也就是两个寄存器的值。注意Modbus 协议里的寄存器地址是从 0 开始编的而工程上常说的 40001 是1 基地址 4 区的表示法。写程序时填的是 0不是 40001这个换算新手特别容易搞错。2.2 功能码 03 和 06 的报文差异读和写的报文结构不一样调试时得分开看。功能码 03读保持寄存器的请求里数据域是起始地址 寄存器数量各占 2 字节。功能码 06写单个寄存器的请求里数据域是寄存器地址 写入值也是各 2 字节。功能码 10写多个寄存器则要先给起始地址、寄存器数量、字节数再跟具体数据。我在现场遇到的一个典型错误是用 06 功能码连续写多个寄存器结果只有第一个生效。原因就是 06 一次只能写一个寄存器要批量写必须用 10 功能码。这个在 FX3U 的 ADPRW 指令里体现得很明显指令参数里有个写入点数填大于 1 的时候底层会自动用 10 功能码填 1 的时候用 06。2.3 报文间隔与 3.5 字符时间RTU 模式靠时间间隔来区分帧边界规范要求帧与帧之间至少间隔 3.5 个字符时间。波特率 9600 时一个字符约 1.04ms3.5 个字符就是 3.64ms。波特率越高这个间隔越短。如果主站轮询太快上一帧还没处理完下一帧就来了从站就会丢帧或者把两帧粘在一起表现就是偶尔通讯不上。我那次排查发现程序里轮询定时器设的是 50ms 一轮但三台从站加起来响应时间加上处理时间偶尔会超过这个值导致下一轮请求发出去的时候上一轮还没收完。把轮询间隔放宽到 100ms 并加上超时重试机制之后稳定性明显改善。所以通讯不稳先看看你的轮询节奏是不是太激进了。3. CRC 校验最容易被忽视的隐形杀手3.1 CRC 校验的计算原理CRC循环冗余校验是 Modbus RTU 用来验证报文完整性的手段。它把整帧数据除 CRC 本身外当作一个多项式除以一个固定的生成多项式得到的余数就是校验值。Modbus 用的是 CRC-16生成多项式是 0xA001这是 0x8005 的反转表示。手工算太麻烦但理解它的几个特点对排查很有帮助CRC 是逐字节计算的每个字节先和当前 CRC 低字节异或然后右移 8 次每次根据最低位决定是否异或多项式。计算结果低字节在前、高字节在后和很多其他协议的顺序相反。只要报文里任何一个 bit 变了CRC 几乎必然对不上。3.2 一个字节顺序引发的血案我这次遇到的问题核心就在 CRC 的字节顺序上。当时用的是一段从网上抄来的 CRC 计算函数算出来的值是对的但发送的时候把高低字节放反了。结果就是从站收到报文CRC 校验失败直接丢弃不响应。主站等不到响应就超时重试几次偶尔碰巧成功——其实是某些特定数据下高低字节刚好相同纯属巧合。这种问题的迷惑性在于它不是每次都失败而是间歇性失败看起来特别像硬件接触不良或者干扰。判断方法很简单用串口助手抓一帧失败的报文把 CRC 两个字节对调一下再算如果对上了那就是字节顺序问题。// 正确的 Modbus CRC16 计算 uint16_t modbus_crc16(uint8_t *buf, uint16_t len) { uint16_t crc 0xFFFF; for (uint16_t i 0; i len; i) { crc ^ buf[i]; for (uint8_t j 0; j 8; j) { if (crc 0x0001) { crc (crc 1) ^ 0xA001; } else { crc 1; } } } return crc; } // 发送时先发 crc 0xFF再发 (crc 8) 0xFF提示不同厂家文档里 CRC 的写法可能不同有的直接给查表法有的给位移法。不管用哪种验证方法都一样——拿一帧已知正确的报文去算对不上就是实现有问题。3.3 CRC 校验失败的常见原因速查现象可能原因排查方法全部帧都失败CRC 算法实现错误用已知正确报文验证算法间歇性失败字节顺序反了 / 数据被截断抓包对比 CRC 字节特定数据失败缓冲区溢出 / 长度算错检查数据域长度计算高波特率下失败采样时机偏差降低波特率测试4. RS485 硬件层那些年我们误判的硬件问题4.1 自收发电路的坑RS485 是半双工收发切换需要方向控制。很多自制板子用 MOS 管搭自收发电路靠发送数据自动拉低 DE 引脚。这种电路在低波特率下没问题但波特率上到 230400 甚至更高时方向切换的延迟就可能吃掉第一个或最后一个 bit导致帧头帧尾出错。我实测过用分立元件搭的自收发电路在 115200 以上波特率时误码率明显上升。如果非要用高波特率建议用带自动方向控制的收发器芯片或者干脆用硬件流控引脚由程序精确控制 DE。别小看这个切换时间它造成的错误往往表现为CRC 偶尔错很容易被误判成干扰。4.2 终端电阻和偏置电阻RS485 总线两端各接一个 120Ω 终端电阻这是常识。但很多人忽略了偏置电阻。总线空闲时如果差分电压接近 0收发器输出会不确定从站可能误收到一堆乱码当成帧头。加一对偏置电阻通常 4.7kΩ 上拉和下拉把空闲电平拉到一个确定状态能解决不少莫名其妙收到数据的问题。还有个细节终端电阻不是越多越好。我见过一条总线上挂了五六个 120Ω 的结果总线负载太重信号幅度被拉低通讯距离反而缩短。记住只有物理总线的两个最远端各接一个中间节点不要接。4.3 接地与屏蔽屏蔽层单端接地是常规做法但接哪一端有讲究。一般接在主站侧或者控制柜的接地排上从站侧悬空避免形成地环路。如果现场变频器多、干扰大可以考虑用双绞屏蔽线并且把屏蔽层在两端通过小电容接地兼顾高频干扰泄放和地环路隔离。我那次排查时量过 A、B 线对地的共模电压发现有一百多毫伏的工频波动明显是干扰耦合进来了。后来把通讯线远离动力线、单独走金属线槽并做好接地共模干扰降下来了。但即便这样通讯还是偶尔出问题这才把注意力转回到软件层面最终定位到 CRC 字节顺序。5. PLC 梯形图程序实现以 FX3U-485ADP-MB 为例5.1 ADPRW 指令参数详解三菱 FX3U 系列用 ADPRW 指令做 Modbus 通讯这是专门为 485ADP-MB 模块设计的。指令格式是ADPRW S1 S2 S3 S4 S5 DS1从站地址S2功能码S3从站寄存器地址S4读写点数S5主站数据寄存器起始地址D完成标志位比如要读从站 1 的 40001 开始的 2 个寄存器存到 D100、D101ADPRW K1 K3 K0 K2 D100 M100这里 K0 对应 400011 基地址减 1K2 是读 2 个点。执行完成后 M100 置位D100、D101 里就是从站返回的数据。注意ADPRW 指令同一时刻只能执行一条多条指令要用完成标志位串起来做成轮询。如果同时触发多条模块会报错或者只执行一条。5.2 轮询程序的写法轮询的核心是一条执行完再执行下一条。常见写法是用完成标志位 M100 触发下一条指令同时用定时器做超时保护。如果某条指令超过设定时间还没完成就跳过它执行下一条避免一条卡死拖垮整个轮询。我一般会加一个轮询计数器每完成一条就加一到最大值归零。这样程序结构清晰加从站也方便。超时时间根据波特率和数据量估算9600 波特率下读 10 个寄存器大概需要 30~50ms超时设 200ms 比较稳妥。5.3 数据寄存器的映射从站返回的数据是按寄存器顺序排列的主站这边要对应好。比如从站 40001 是温度、40002 是压力那 D100 就是温度、D101 就是压力。如果从站数据是 32 位浮点占两个寄存器还要注意高低字顺序。不同厂家设备的高低位排列可能不同调试时先用已知值验证一下。我碰到过从站返回的 32 位数据高低字反了读出来数值完全不对。后来在程序里加了个交换处理才正常。这种问题看报文能看出来但如果不抓包光看触摸屏上的乱码数值很容易怀疑是通讯本身有问题。6. 常见问题与排查技巧实录6.1 通讯超时的排查顺序遇到通讯超时建议按这个顺序排查从软到硬避免一上来就拆线先抓包看报文有没有发出去。发不出去是程序问题发出去了没响应才是链路问题。看从站地址、功能码、寄存器地址对不对。地址错从站不会响应。看 CRC 对不对。CRC 错从站直接丢弃。看波特率、数据位、停止位、校验位是否一致。参数不匹配收不到正确数据。检查接线 A、B 有没有反。反了收不到正确电平。检查终端电阻、偏置电阻、接地。最后才怀疑芯片、线缆质量。6.2 独家避坑经验坑一轮询太快导致丢帧。主站发得太快从站处理不过来。解决办法是加间隔或者用完成标志位严格串行。坑二CRC 字节顺序。这个前面详细说了高低字节反了是间歇性失败的常见原因。坑三寄存器地址换算。40001 对应地址 0不是 1。不同厂家文档的地址表示法可能不同一定要以报文为准。坑四多主站冲突。一条总线上如果有两个主站同时轮询必然冲突。确保总线上只有一个主站。坑五从站响应延迟。有些从站处理慢主站超时设太短会误判失败。适当放宽超时时间。6.3 问题速查表问题现象优先排查方向快速验证方法完全无响应地址、接线、波特率换一台已知正常的从站测试间歇性超时轮询间隔、CRC、干扰抓包看失败帧的 CRC数据错误寄存器映射、高低字写入已知值再读回对比高波特率不稳自收发电路、线缆长度降波特率对比测试多从站部分失败地址冲突、总线负载单独接一台测试7. 写在最后的一点个人体会这次排查给我最大的教训是别让硬件问题这个先入为主的判断挡住你看报文的视线。通讯不稳的时候抓一帧报文下来把地址、功能码、数据、CRC 逐字节对一遍大部分问题都能定位。硬件当然要查但软件层面的 CRC 处理、轮询逻辑、地址换算往往是更隐蔽的坑。另外Modbus RTU 虽然简单但细节特别多。3.5 字符间隔、CRC 字节顺序、寄存器地址换算、功能码选择每一个都可能让你调半天。建议手头常备一个串口抓包工具和一个 CRC 计算器遇到问题先抓包再分析比盲目换硬件高效得多。我现在的习惯是新接一台从站设备先用串口助手手动发一帧读指令确认能正常响应了再往 PLC 程序里写。这样能把设备问题和程序问题分开省下大量来回折腾的时间。