Modbus协议在工业自动化领域是绝对的霸主,几乎所有PLC、变频器、仪表、传感器都支持Modbus。Modbus有几个变种:Modbus RTU走RS-485串口,Modbus TCP走以太网,Modbus ASCII走串口但用ASCII编码。其中Modbus RTU是最常用的,因为它简单、可靠、接线方便,一根RS-485总线能挂几十台设备。今天就深入聊聊Modbus RTU的协议细节和实现要点。
Modbus是主从架构,一条总线上只能有一个主机(Master),可以有多个从机(Slave),每个从机有一个唯一的地址(1到247)。主机发起请求,从机收到后响应。总线上的通信都是主机主动发起的,从机不会主动发数据。这种架构的好处是简单可靠,不会出现多台设备同时抢总线的冲突。缺点是从机不能主动上报数据,主机必须轮询各个从机。
RTU模式的帧格式由四个部分组成:从机地址(1字节)、功能码(1字节)、数据域(变长)、CRC校验(2字节,低字节在前)。常用的功能码有几个:0x01读线圈状态(数字量输出)、0x02读离散输入(数字量输入)、0x03读保持寄存器、0x04读输入寄存器、0x05写单个线圈、0x06写单个寄存器、0x0F写多个线圈、0x10写多个寄存器。掌握这几个功能码,基本就能搞定绝大多数Modbus设备。
拿功能码0x03读保持寄存器举个例子。主机发送的请求帧:[从机地址] [0x03] [起始地址高] [起始地址低] [读取数量高] [读取数量低] [CRC低] [CRC高]。从机正常响应的帧:[从机地址] [0x03] [后续字节数] [寄存器值1高] [寄存器值1低] ... [寄存器值N高] [寄存器值N低] [CRC低] [CRC高]。如果从机响应异常,会返回异常响应:[从机地址] [0x03|0x80] [异常码] [CRC低] [CRC高],功能码最高位置1表示异常。
CRC校验是Modbus RTU的核心,用的是CRC16-MODBUS算法。初始值是0xFFFF,然后对每个字节的8个bit依次处理:右移一位,如果移出的位是1就异或0xA001。所有字节处理完后得到CRC值,发送时低字节在前、高字节在后。Python里实现CRC16非常简单,几十行代码就行。校验时把整个帧(包括CRC)算一遍,如果结果是0就对了,因为正确的CRC跟数据异或完会抵消。
Modbus RTU的帧间隔很重要。RTU模式下,帧和帧之间必须有至少3.5个字符的静默时间,设备就是靠这个静默来判断一帧结束。以9600bps波特率、8N1格式为例,一个字符是10位,3.5个字符就是35位,换算成时间是35÷9600 ≈ 3.65ms。如果连续两个字符之间的间隔超过这个时间,就认为前一帧已经结束,后一个字符开始新帧。这个规则在编程实现时特别关键,超时判断的时间阈值要算准确。
实际编程实现Modbus RTU,推荐用现成的库,Python用pymodbus,C/C++可以用FreeModbus。但有时候在单片机上自己实现也不难,核心就是四个步骤:接收串口数据、等3.5字符超时后认为一帧收完、CRC校验、解析功能码和数据。发送响应帧时也是一样的流程:组装帧、算CRC、低字节在前高字节在后发出去。
常见问题排查:设备通信不上,先查波特率、数据位、停止位、校验位这些串口参数对不对;再查从机地址有没有冲突;最后用示波器看总线上有没有波形。数据错误的话重点查CRC计算对不对、字节序有没有搞反。还有RS-485总线的终端电阻(120Ω)和偏置电阻(上下各560Ω)不能省,特别是总线比较长或者挂的设备多时。
下一篇:没有了