2. Modbus协议基础
Modbus协议自1979年由Modicon(现施耐德电气)推出以来,凭借其开放、简单、可靠的特点,已成为工业自动化领域应用最广泛的通信协议之一。在嵌入式开发中,理解Modbus的三种传输模式(RTU、ASCII、TCP)及其报文结构、功能码语义,是完成协议栈移植与调试的前提。本章将从协议分层模型出发,系统梳理三种模式的核心差异、帧格式细节、功能码分类逻辑,并结合典型工程场景给出选型建议。
2.1 三种传输模式的核心原理与选型依据
Modbus协议在OSI模型中的应用层定义了统一的数据模型和功能码,但传输层根据物理介质和编码方式分化为三种模式。RTU(Remote Terminal Unit)模式采用二进制编码,每个8位字节直接映射为一个数据帧字节,效率最高;ASCII模式将每个字节拆分为两个ASCII字符(十六进制表示),传输效率降低一半但便于人工阅读和调试;TCP模式则完全基于以太网和TCP/IP协议栈,通过MBAP(Modbus Application Protocol)头替代了RTU/ASCII中的地址校验和CRC/LRC,实现了跨网段通信。
从帧格式看,RTU帧以至少3.5个字符时间的静默间隔作为帧起始和结束标志,帧内字节间间隔不得超过1.5个字符时间,否则接收方判定帧错误。ASCII帧则以冒号(0x3A)起始,以CRLF(0x0D 0x0A)结束,帧内字符间隔允许长达1秒,因此对时钟精度要求较低,适合低速或干扰较大的链路。TCP模式将RTU中的从站地址替换为单元标识符(Unit ID),并增加了事务处理标识符(Transaction ID)和协议标识符(Protocol ID),使得同一TCP连接上可以复用多个请求-响应事务。
| 对比维度 | Modbus RTU | Modbus ASCII | Modbus TCP |
|---|---|---|---|
| 编码方式 | 二进制(0x00-0xFF) | ASCII十六进制字符 | 二进制(以太网帧) |
| 帧开销 | 最小(地址+功能码+数据+CRC16) | 较大(每个字节翻倍+首尾符) | 中等(MBAP头7字节) |
| 校验方式 | CRC16(多项式0xA001) | LRC(纵向冗余校验) | TCP/IP校验(无应用层校验) |
| 最大从站数 | 247(地址1-247) | 247 | 理论无限制(IP寻址) |
| 典型波特率 | 9600-115200 | 9600-19200 | 10/100/1000Mbps |
| 适用场景 | 工业现场总线、PLC、仪表 | 调试、老旧设备、无线透传 | SCADA、跨网段集成、上位机 |
2.2 报文帧格式详解与字节序规则
RTU请求帧结构为:从站地址(1字节) + 功能码(1字节) + 数据段(N字节) + CRC16低字节 + CRC16高字节。其中CRC16的计算范围覆盖地址、功能码和数据段,采用多项式0x8005(初始值0xFFFF),先发送低字节后发送高字节。例如读取从站1的保持寄存器起始地址0x0000、数量0x0001,请求帧为:01 03 00 00 00 01 84 0A,其中84 0A为CRC16校验值。
ASCII帧格式为:起始符:(0x3A) + 地址(2字符) + 功能码(2字符) + 数据(2N字符) + LRC(2字符) + 结束符\r\n。LRC的计算方法为:将地址、功能码和数据的所有字节累加,取补码(即0x100 - 累加和)。例如上述RTU请求对应的ASCII帧为::010300000001F8\r\n,其中F8为LRC值。
TCP帧结构为:事务处理标识符(2字节) + 协议标识符(2字节,固定0x0000) + 长度(2字节,表示后续字节数) + 单元标识符(1字节) + 功能码(1字节) + 数据段。长度字段从单元标识符开始计算,因此最小长度为2(单元标识符+功能码)。TCP模式无需CRC/LRC,因为TCP/IP协议栈已保证数据完整性。
// RTU请求帧示例(读取保持寄存器)
uint8_t rtu_frame[] = {0x01, 0x03, 0x00, 0x00, 0x00, 0x01, 0x84, 0x0A};
// 解析:从站1,功能码03,起始地址0x0000,数量1,CRC16=0x0A84
// TCP请求帧示例(相同请求)
uint8_t tcp_frame[] = {0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, 0x01};
// 解析:事务ID=1,协议ID=0,长度=6,单元ID=1,功能码03,起始地址0x0000,数量1
12 34,而非34 12。移植协议栈时需特别注意此点,否则会出现数据错乱。2.3 功能码分类与数据模型映射
Modbus功能码按操作对象分为位操作和字操作两大类,每类又分为读和写。位操作对象包括线圈(Coil,可读写)和离散输入(Discrete Input,只读);字操作对象包括输入寄存器(Input Register,只读)和保持寄存器(Holding Register,可读写)。这四种数据模型构成了Modbus的地址空间:线圈地址0x0000-0xFFFF,离散输入0x0000-0xFFFF,输入寄存器0x0000-0xFFFF,保持寄存器0x0000-0xFFFF。
常用功能码包括:01H(读线圈)、02H(读离散输入)、03H(读保持寄存器)、04H(读输入寄存器)、05H(写单线圈)、06H(写单寄存器)、0FH(写多线圈)、10H(写多寄存器)。此外还有17H(读设备标识)等辅助功能码。在嵌入式移植中,通常只需实现03H、06H、10H、01H、05H、0FH这六个核心功能码即可覆盖90%以上的应用场景。
| 功能码 | 名称 | 操作对象 | 请求数据 | 响应数据 |
|---|---|---|---|---|
| 01H | 读线圈 | 线圈(位) | 起始地址(2B)+数量(2B) | 字节数(1B)+位打包数据 |
| 02H | 读离散输入 | 离散输入(位) | 起始地址(2B)+数量(2B) | 字节数(1B)+位打包数据 |
| 03H | 读保持寄存器 | 保持寄存器(字) | 起始地址(2B)+数量(2B) | 字节数(1B)+寄存器值(2B/个) |
| 04H | 读输入寄存器 | 输入寄存器(字) | 起始地址(2B)+数量(2B) | 字节数(1B)+寄存器值(2B/个) |
| 05H | 写单线圈 | 线圈(位) | 输出地址(2B)+输出值(2B) | 请求帧原样返回 |
| 06H | 写单寄存器 | 保持寄存器(字) | 寄存器地址(2B)+寄存器值(2B) | 请求帧原样返回 |
| 0FH | 写多线圈 | 线圈(位) | 起始地址(2B)+数量(2B)+字节数(1B)+位数据 | 起始地址(2B)+数量(2B) |
| 10H | 写多寄存器 | 保持寄存器(字) | 起始地址(2B)+数量(2B)+字节数(1B)+寄存器值 | 起始地址(2B)+数量(2B) |
位打包规则:线圈或离散输入在响应中按位排列,第一个线圈对应数据字节的最低位(LSB)。例如读取8个线圈,若返回字节为0x05(二进制00000101),则线圈0和线圈2为ON,其余为OFF。寄存器数据始终为16位无符号整数,范围0x0000-0xFFFF,若需表示负数或浮点数,需在应用层定义映射规则。
2.4 典型应用场景与案例拆解
场景一:工业现场PLC与变频器通信(RTU模式)
某产线使用西门子S7-1200 PLC通过RS485总线控制5台台达变频器,通信参数为9600bps、8数据位、无校验、1停止位。PLC作为Modbus主站,变频器作为从站,地址分别设为1-5。PLC需要实时读取变频器的运行频率(保持寄存器地址0x2000)和输出电流(保持寄存器地址0x2001),并控制启停(线圈地址0x0000)。
实现过程中发现,变频器对帧间隔敏感,若PLC扫描周期过短导致连续请求间隔小于3.5字符时间,变频器会丢弃帧。解决方案是在PLC程序中增加定时发送逻辑,确保相邻请求间隔不小于20ms(9600bps下3.5字符时间约4ms,预留余量)。同时,变频器的CRC校验采用标准Modbus算法,但部分国产变频器存在CRC字节序颠倒问题,需在协议栈中增加字节序配置选项。
场景二:楼宇自控系统数据采集(TCP模式)
某智能楼宇项目需要采集200个电表数据,电表支持Modbus TCP,通过以太网交换机接入监控中心。由于电表数量多且分布在不同楼层,采用多线程TCP客户端并发采集,每个线程维护一个独立的TCP连接。实际运行中发现,部分电表在长时间无通信后会自动断开连接,导致采集线程异常退出。
解决方案是在协议栈中增加TCP保活机制(Keep-Alive),每30秒发送一次心跳请求(读取设备标识功能码17H),并在检测到连接断开后自动重连。同时,由于电表响应时间不一致(50-200ms),采用异步I/O模型而非阻塞式请求,避免单个电表响应慢影响整体采集周期。最终系统实现了5秒内完成200个电表的全量数据刷新。
2.5 常见误区与工程注意事项
- 误区一:认为RTU和ASCII可以混用。同一RS485总线上所有设备必须使用相同的传输模式,否则无法通信。若需切换模式,必须重新配置所有从站。
- 误区二:忽略广播地址0。Modbus规定地址0为广播地址,从站接收广播帧但不返回响应。在移植协议栈时,需单独处理广播帧,避免从站对广播请求发送响应导致总线冲突。
- 误区三:寄存器地址偏移。Modbus协议中寄存器地址从0开始,但许多设备手册使用PLC地址(如40001)表示保持寄存器。40001对应Modbus地址0x0000,40002对应0x0001,依此类推。移植时需明确地址映射关系。
- 误区四:异常响应处理不完整。当从站收到非法功能码、非法数据地址或非法数据值时,应返回异常响应帧:功能码最高位置1(如0x83表示03H功能码异常),后跟异常码(01H非法功能、02H非法地址、03H非法数据、04H从站故障)。主站必须解析异常码并给出相应提示。