Modbus 是一种串行通信协议,最初由 Modicon(现为施耐德电气的一部分)为其可编程逻辑控制器 (PLC) 开发。它已成为工业自动化领域事实上的标准通信协议,用于在主站设备(如监控计算机、HMI)和从站设备(如 PLC、传感器、执行器)之间交换数据。
- RTU (Remote Terminal Unit): 这是 Modbus 协议的一种传输模式。另一种常见模式是 ASCII。RTU 模式的主要特点是:
- 二进制编码: 数据以紧凑的二进制形式传输。
- 高效: 相比 ASCII 模式,RTU 模式传输相同信息量所需字节数更少,效率更高。
- 帧结构: 一个完整的 Modbus RTU 帧由以下部分组成:
- 从站地址 (1 Byte): 标识目标从站设备。
- 功能码 (1 Byte): 指定主站请求的操作类型(读、写)和数据类型(线圈、寄存器等)。
- 数据域 (n Bytes): 包含请求或响应的具体数据(地址、数量、值等)。
- CRC 校验 (2 Bytes): 循环冗余校验,用于检测传输过程中可能出现的错误。
- 时序: RTU 模式依靠字符间的时间间隔(大于 3.5 个字符时间)来界定帧的开始和结束。
核心功能: Modbus 协议的核心功能是让主站能够读取或写入从站设备中特定类型的数据存储区域。这些区域被抽象为四种主要类型:
- 线圈 (Coils): 1 位(位)输出量,可读可写。通常对应 PLC 的数字量输出点 (DO),如继电器状态、灯的控制信号。功能码 01 (读), 05 (写单个), 15 (写多个)。
- 离散量输入 (Discrete Inputs): 1 位(位)输入量,只读。通常对应 PLC 的数字量输入点 (DI),如按钮、限位开关的状态。功能码 02 (读)。
- 保持寄存器 (Holding Registers): 16 位(字)数据,可读可写。通常用于存储模拟量数据(如温度、压力)、配置参数或计算结果。功能码 03 (读), 06 (写单个), 16 (写多个)。
- 输入寄存器 (Input Registers): 16 位(字)数据,只读。通常用于存储来自模拟量输入模块 (AI) 的数据。功能码 04 (读)。
功能码解释
功能码是 Modbus 帧中紧随从站地址之后的一个字节。它定义了主站请求的操作类型(读/写)以及要操作的数据类型(线圈/离散输入/寄存器)。下表列出了最常用的功能码:
| 功能码 (十六进制) | 功能码 (十进制) | 名称 | 操作 | 数据类型 |
|---|---|---|---|---|
| 0x01 | 01 | 读线圈 | 读 | 线圈 (Coils) |
| 0x02 | 02 | 读离散量输入 | 读 | 离散输入 (DIs) |
| 0x03 | 03 | 读保持寄存器 | 读 | 保持寄存器 (HRs) |
| 0x04 | 04 | 读输入寄存器 | 读 | 输入寄存器 (IRs) |
| 0x05 | 05 | 写单个线圈 | 写 | 线圈 (Coils) |
| 0x06 | 06 | 写单个保持寄存器 | 写 | 保持寄存器 (HRs) |
| 0x0F | 15 | 写多个线圈 | 写 | 线圈 (Coils) |
| 0x10 | 16 | 写多个保持寄存器 | 写 | 保持寄存器 (HRs) |
| 0x83 | 131 | 异常响应 (功能码 + 128) | - | - |
- 异常响应: 如果从站无法执行主站的请求(例如,地址不存在、数据无效),它会返回一个异常响应帧。该帧的功能码是原始功能码值 + 0x80(或在原始功能码字节的最高位加 1)。紧随功能码之后是一个异常码(1 Byte),用于指示具体的错误原因。常见的异常码有:
- 0x01: 非法功能 (不支持此功能码)
- 0x02: 非法数据地址 (请求的地址无效或不存在)
- 0x03: 非法数据值 (写入的值超出范围或无效)
- 0x04: 从站设备故障 (设备内部错误)
功能码 01 (0x01) 详解 - 读线圈
- 功能: 读取一个或多个连续线圈 (1位输出量) 的当前状态 (ON/OFF, 1/0)。
- 请求帧结构:
[从站地址](1 Byte)[功能码 = 0x01](1 Byte)[起始地址高字节](1 Byte) - 要读取的第一个线圈的地址的高8位。[起始地址低字节](1 Byte) - 要读取的第一个线圈的地址的低8位。[线圈数量高字节](1 Byte) - 要读取的线圈总数的高8位。[线圈数量低字节](1 Byte) - 要读取的线圈总数的低8位。[CRC16 低字节](1 Byte)[CRC16 高字节](1 Byte)
- 响应帧结构:
[从站地址](1 Byte)[功能码 = 0x01](1 Byte)[字节数](1 Byte) - 后面数据域包含的字节数。计算公式:(线圈数量 + 7) / 8。[数据域](n Bytes) - 线圈状态按位打包的结果。每个字节包含8个线圈的状态(LSB 表示第一个线圈,MSB 表示该字节内的最后一个线圈)。如果读取的线圈数量不是8的倍数,最后一个字节的高位用0填充。[CRC16 低字节](1 Byte)[CRC16 高字节](1 Byte)
功能码 01 示例
- 场景: 主站 (地址 0x01) 请求读取从站 (地址 0x02) 的线圈,起始地址为 0x0000 (线圈0),读取 10 个线圈 (线圈0 - 线圈9)。假设线圈状态:线圈0=ON(1), 线圈1=OFF(0), 线圈2=ON(1), 线圈3=ON(1), 线圈4=OFF(0), 线圈5=OFF(0), 线圈6=ON(1), 线圈7=OFF(0), 线圈8=ON(1), 线圈9=OFF(0)。
-
主站请求帧 (Hex):
02 01 00 00 00 0A CRC02: 从站地址 (0x02)01: 功能码 (读线圈)00 00: 起始地址 = 0x0000 (线圈0)00 0A: 线圈数量 = 0x000A (10个)CRC: 校验码 (假设计算为C10C)
-
从站响应帧 (Hex):
02 01 02 CD 01 CRC-
02: 从站地址 (0x02) -
01: 功能码 (读线圈) -
02: 字节数 = 2 Bytes (因为 (10 + 7) / 8 = 1.25 -> 向上取整为 2) -
CD: 第一个数据字节 (0xCD 二进制1100 1101)- LSB (Bit0) =
1(线圈0 ON) - Bit1 =
0(线圈1 OFF) - Bit2 =
1(线圈2 ON) - Bit3 =
1(线圈3 ON) - Bit4 =
0(线圈4 OFF) - Bit5 =
0(线圈5 OFF) - Bit6 =
1(线圈6 ON) - MSB (Bit7) =
1(线圈7 ON? 但假设是OFF?这里有问题,后面解释)
- LSB (Bit0) =
-
01: 第二个数据字节 (0x01 二进制0000 0001)- LSB (Bit0) =
1(线圈8 ON) - Bit1 =
0(线圈9 OFF) - Bits 2-7: 未使用,填充为0 (读取10个线圈,第二个字节只用低2位)
- LSB (Bit0) =
-
注意: 第一个字节
0xCD(1100 1101) 中,MSB (Bit7) 对应线圈7。在我们的假设状态中,线圈7是 OFF(0),所以 Bit7 应该是0。但是0xCD的 Bit7 是1(因为0xCD=1100 1101)。这里存在一个错误假设。修正假设状态:线圈7=ON(1)。那么:- 线圈0=ON(1), 1=OFF(0), 2=ON(1), 3=ON(1), 4=OFF(0), 5=OFF(0), 6=ON(1), 7=ON(1) -> 第一个字节
1100 1101=0xCD(LSB 是线圈0)。 - 线圈8=ON(1), 9=OFF(0) -> 第二个字节
0000 0001=0x01(LSB 是线圈8)。
- 线圈0=ON(1), 1=OFF(0), 2=ON(1), 3=ON(1), 4=OFF(0), 5=OFF(0), 6=ON(1), 7=ON(1) -> 第一个字节
-
CRC: 校验码 (假设计算为E40B)
-
功能码 03 (0x03) 详解 - 读保持寄存器
- 功能: 读取一个或多个连续保持寄存器 (16位数据) 的当前值。
- 请求帧结构:
[从站地址](1 Byte)[功能码 = 0x03](1 Byte)[起始地址高字节](1 Byte) - 要读取的第一个寄存器的地址的高8位。[起始地址低字节](1 Byte) - 要读取的第一个寄存器的地址的低8位。[寄存器数量高字节](1 Byte) - 要读取的寄存器总数的高8位。[寄存器数量低字节](1 Byte) - 要读取的寄存器总数的低8位。[CRC16 低字节](1 Byte)[CRC16 高字节](1 Byte)
- 响应帧结构:
[从站地址](1 Byte)[功能码 = 0x03](1 Byte)[字节数](1 Byte) - 后面数据域包含的字节数。计算公式:寄存器数量 * 2(因为每个寄存器16位=2字节)。[数据域](n Bytes) - 寄存器的值,按顺序排列。每个寄存器占用2个字节,通常是大端序 (Big-Endian),即高字节在前 (MSB),低字节在后 (LSB)。例如,寄存器值 0x1234 会表示为12 34。[CRC16 低字节](1 Byte)[CRC16 高字节](1 Byte)
功能码 03 示例
- 场景: 主站 (地址 0x01) 请求读取从站 (地址 0x03) 的保持寄存器,起始地址为 0x0006 (寄存器6),读取 3 个寄存器 (寄存器6, 7, 8)。假设寄存器值:
- 寄存器6: 0x1234 (4660)
- 寄存器7: 0x5678 (22136)
- 寄存器8: 0x9ABC (39612)
-
主站请求帧 (Hex):
03 03 00 06 00 03 CRC03: 从站地址 (0x03)03: 功能码 (读保持寄存器)00 06: 起始地址 = 0x0006 (寄存器6)00 03: 寄存器数量 = 0x0003 (3个)CRC: 校验码 (假设计算为45F1)
-
从站响应帧 (Hex):
03 03 06 12 34 56 78 9A BC CRC03: 从站地址 (0x03)03: 功能码 (读保持寄存器)06: 字节数 = 6 Bytes (3 寄存器 * 2 字节/寄存器)12 34: 寄存器6 的值 (0x1234)56 78: 寄存器7 的值 (0x5678)9A BC: 寄存器8 的值 (0x9ABC)CRC: 校验码 (假设计算为05D9)
重要注意事项
- 地址偏移: Modbus 协议定义的数据区地址是从 0 开始的。但很多设备文档或软件 (如 SCADA, HMI) 可能会使用基于 1 的索引(例如,线圈1对应地址0x0000)。务必查看设备手册确认其寻址方式。
- 字节顺序 (Endianness): 对于16位寄存器,Modbus 协议规定传输时是大端序。但在将两个字节组合成一个16位整数时,或在表示更大数据类型(如32位浮点数,占用两个寄存器)时,应用层必须定义寄存器之间的组合顺序(是
[RegA Hi, RegA Lo]还是[RegA Lo, RegA Hi])以及多寄存器数据类型的字节顺序。这是应用中常见的混淆点。 - 数据类型映射: 保持寄存器中存储的原始字节数据需要映射到具体的物理意义(如温度、压力)。这通常需要知道缩放比例、偏移量和数据类型(整数、浮点数)。
- CRC 计算: CRC 校验是保证通信可靠性的重要环节。计算算法是标准的 Modbus RTU CRC16 (多项式 0x8005,初始值 0xFFFF)。通信两端必须使用相同的算法。
- 调试工具: 使用串口监视工具或专门的 Modbus 调试工具(如 Modscan, QModMaster)可以直观地查看发送和接收的原始报文,是排查通信问题的有效手段。注意查看十六进制文本。
2477

被折叠的 条评论
为什么被折叠?



