Modbus-RTU 是一种基于串行通信(如 RS-232、RS-485)的 Modbus 协议变体,采用二进制编码,具有高数据密度和通信效率,广泛应用于工业自动化领域。
核心原理与帧结构Modbus-RTU 采用主从(Master-Slave)架构,通信由主设备发起,从设备响应。一个完整的数据帧由以下部分组成:
| 组成部分 | 长度(字节) | 说明 |
|---|---|---|
| 从站地址 | 1 | 标识目标从设备的地址(1-247)。 |
| 功能码 | 1 | 定义请求的操作类型(如读、写)。 |
| 数据域 | N | 包含请求或响应的具体数据,长度可变。 |
| CRC校验 | 2 | 循环冗余校验,用于检测传输错误。 |
帧与帧之间需保持至少 3.5个字符 的静默时间作为间隔。
常用功能码下表列出了部分核心功能码:
| 功能码(十进制) | 名称 | 作用对象 |
|---|---|---|
| 01 (0x01) | 读线圈状态 | 离散量输出(线圈) |
| 02 (0x02) | 读离散输入状态 | 离散量输入 |
| 03 (0x03) | 读保持寄存器 | 保持寄存器 |
| 04 (0x04) | 读输入寄存器 | 输入寄存器 |
| 05 (0x05) | 写单个线圈 | 单个线圈 |
| 06 (0x06) | 写单个寄存器 | 单个保持寄存器 |
| 15 (0x0F) | 写多个线圈 | 多个线圈 |
| 16 (0x10) | 写多个寄存器 | 多个保持寄存器 |
CRC-16校验计算
Modbus-RTU 使用 CRC-16(多项式为 0x8005,初始值为 0xFFFF)进行校验。以下是 C 语言实现示例:
c
/**
* @brief 计算 Modbus RTU帧的 CRC16 校验值
* @param pData 指向待校验数据缓冲区的指针
* @param length 数据长度(字节数)
* @return计算得到的 CRC16 校验值(16位无符号整数)
*/
uint16_t Modbus_CRC16(uint8_t *pData, uint16_t length) {
uint16_t crc = 0xFFFF; // CRC初始值 uint16_t i, j;
for (i = 0; i < length; i++) {
crc ^= pData[i]; // 与数据字节进行异或
for (j = 0; j < 8; j++) {
if (crc & 0x0001) { // 检查最低位是否为1
crc >>= 1; // 右移一位
crc ^= 0xA001; // 与多项式 0xA001 (0x8005 的位反转) 异或 } else {
crc >>= 1; // 右移一位 }
}
}
return crc;
}
通信示例:读取保持寄存器
假设主设备(地址 0x01)请求读取从设备地址为 0x01 的 2 个保持寄存器,起始地址为 0x0000。
1. 主设备请求帧:
[从站地址] [功能码] [起始地址高] [起始地址低] [寄存器数量高] [寄存器数量低] [CRC低] [CRC高]
0x01 0x03 0x00 0x00 0x00 0x02 0xC4 0x0B
*数据域:起始地址 0x0000,寄存器数量 0x0002。
- CRC 计算:对
01 03 00 00 00 02计算得到0x0BC4,低字节在前,故为C4 0B。
2. 从设备正常响应帧:
[从站地址] [功能码] [字节计数] [寄存器1值高] [寄存器1值低] [寄存器2值高] [寄存器2值低] [CRC低] [CRC高]
0x01 0x03 0x04 0x00 0x0A0x00 0x02 0xF8 0x48
- 数据域:字节计数
0x04(4字节数据),两个寄存器的值分别为0x000A(十进制10)和0x0002(十进制2)。
代码实现示例(C# 使用 NModbus库)
以下 C# 代码演示了如何使用 NModbus 库通过串口实现 Modbus RTU 主站读取保持寄存器:
csharp
using Modbus.Device;
using System.IO.Ports;
// 1. 配置串口
SerialPort serialPort = new SerialPort("COM1", 9600, Parity.None, 8, StopBits.One);
serialPort.Open();
// 2. 创建 Modbus RTU 主站实例
IModbusSerialMaster master = ModbusSerialMaster.CreateRtu(serialPort);
// 3. 读取保持寄存器
byte slaveId = 1; // 从站地址
ushort startAddress = 0; // 起始寄存器地址
ushort numRegisters = 2; // 读取的寄存器数量
ushort[] registers = master.ReadHoldingRegisters(slaveId, startAddress, numRegisters);
// 4. 输出结果
Console.WriteLine($"Register 0: {registers[0]}");
Console.WriteLine($"Register 1: {registers[1]}");
// 5. 关闭连接
serialPort.Close();
调试与工具
- 模拟器:可使用 Modbus Slave/Modbus Poll、ModScan32 等软件模拟从设备或主设备进行协议测试。
- 接线:RS485 网络通常采用双绞线,需正确连接 A(+)、B(-) 信号线,并在总线两端安装终端电阻(通常为 120Ω)以消除信号反射。