目录
1. Modbus协议概述
1. 主从结构与数据模型
理解Modbus协议,首先需要掌握两个核心概念:主从通信模式和四种数据模型
-
主从通信模式 (Master-Slave): Modbus总线通常只有一台主设备(如PLC),它可以向一个或多个从设备发起请求。从设备只能被动响应,不能主动发送数据,从设备之间也不能直接通信。这种机制保证了通信的有序性。
-
四种数据模型: Modbus协议定义了四种基本的数据类型,用于操作设备内部的不同数据区域,理解它们至关重要。
| 数据类型 |
位 |
读写属性 |
物理含义 |
| 线圈 (Coils) |
1位 |
可读可写 |
控制输出,如继电器开关、指示灯 |
| 离散输入 (Discrete Inputs) |
1位 |
只读 |
采集开关状态,如限位开关、按钮 |
| 保持寄存器 (Holding Registers) |
16位 |
可读可写 |
存储设备参数,如阈值、配置、PID参数等 |
| 输入寄存器 (Input Registers) |
16位 |
只读 |
采集模拟量数据,如温度、压力、电流值 |
Modbus协议根据传输介质和方式的不同,主要分为RTU、ASCII和TCP三种,它们的报文格式有显著差异。
2. Modbus RTU协议详解
2.1 报文格式
Modbus RTU报文由以下四个部分组成:
| 字段 |
长度 |
描述 |
取值范围及说明 |
| 地址域 |
1字节 |
标识目标从设备的地址 |
1 ~ 247 (单播地址), 0 (广播地址,从机不响应) |
| 功能码 |
1字节 |
告诉从机要执行什么操作 |
1 ~ 255,128 ~ 255 用于异常响应 |
| 数据域 |
N字节 |
请求或响应的具体参数 |
长度可变,最大为 252字节 |
| CRC校验 |
2字节 |
循环冗余校验 |
对地址、功能码和数据域进行计算。传输采用小端模式 |
2.2 位操作
这类操作针对线圈和离散输入,都是1位(bit)的数据,对应物理世界的开关量。
| 功能码 |
名称 |
作用 |
请求报文数据域 |
响应报文数据域 |
| 0x01 |
读线圈 |
读取一组逻辑线圈的当前状态(ON/OFF) |
起始地址(2B) + 线圈数量(2B) |
字节数(1B) + 线圈状态(N字节,每个bit代表一个线圈) |
| 0x02 |
读离散输入 |
读取一组离散输入(开关量输入)的当前状态 |
起始地址(2B) + 输入数量(2B) |
字节数(1B) + 输入状态(N字节) |
| 0x05 |
写单个线圈 |
写入一个逻辑线圈的状态为ON或OFF |
线圈地址(2B) + 值(2B)。0xFF00表示ON,0x0000表示OFF |
与请求报文完全一致(回显),用于确认 |
| 0x0F |
写多个线圈 |
写入一串连续逻辑线圈的通断 |
起始地址(2B) + 数量(2B) + 字节数(1B) + 线圈值(N字节) |
起始地址(2B) + 写入数量(2B) |
功能码 0x01:读线圈
- 请求报文:01 01 00 00 00 0A BC 0D
- 01:从站地址
- 01:功能码(读线圈)
- 00 00:起始线圈地址(0x0000)
- 00 0A:读取10个线圈(0x000A = 10)
- BC 0D:CRC校验(低字节在前)
- 响应报文:01 01 02 CD 01 2C AC
- 01:从站地址
- 01:功能码
- 02:后续数据字节数(2字节,因为10个线圈需要2字节表示)
- CD 01:线圈状态。CD (1100 1101) 表示线圈0-7,01 (0000 0001) 表示线圈8-9(有效位只有低2位)。解析:线圈0-7依次为1,0,1,1,0,0,1,1(最低位对应起始地址),线圈8=1,线圈9=0
- 2C AC:CRC
功能码 0x02:读离散输入
- 请求报文:01 02 00 C4 00 16 B8 39
- 01:从站地址
- 02:功能码(读离散输入)
- 00 C4:起始输入地址(0x00C4 = 196)
- 00 16:读取22个输入(0x0016 = 22)
- B8 39:CRC
- 响应报文:01 02 03 AC DB 35 22 88
- 01:从站地址
- 02:功能码
- 03:数据字节数(22个输入需要3字节)
- AC DB 35:输入状态(二进制表示略)
- 22 88:CRC
功能码 0x05:写单个线圈
- 请求报文:01 05 00 AC FF 00 4C 1B
- 01:从站地址
- 05:功能码(写单个线圈)
- 00 AC:线圈地址(0x00AC = 172)
- FF 00:数据值,FF00表示ON(线圈吸合),0000表示OFF
- 4C 1B:CRC
- 响应报文:01 05 00 AC FF 00 4C 1B
- 正常响应与请求完全一致(回显),从站确认操作。
功能码 0x0F:写多个线圈
- 请求报文:01 0F 00 13 00 0A 02 CD 01 72 CB
- 01:从站地址
- 0F:功能码(写多个线圈)
- 00 13:起始线圈地址(0x0013 = 19)
- 00 0A:写入10个线圈
- 02:后续数据字节数(2字节)
- CD 01:线圈状态。CD (1100 1101) 控制前8个线圈(地址19-26),01 (0000 0001) 控制后2个线圈(地址27-28)
- 72 CB:CRC
- 响应报文:01 0F 00 13 00 0A 24 09
- 01:从站地址
- 0F:功能码
- 00 13:起始地址
- 00 0A:写入数量
- 24 09:CRC
2.3 寄存器操作
这类操作针对的是16位(双字节)的数据,对应模拟量输入、输出和参数设置。
| 功能码 |
名称 |
作用 |
请求报文数据域 |
响应报文数据域 |
| 0x03 |
读保持寄存器 |
读取一个或多个保持寄存器值 |
起始地址(2B) + 寄存器数量(2B) |
字节数(1B) + 寄存器值(N×2字节) |
| 0x04 |
读输入寄存器 |
读取一个或多个输入寄存器值 |
起始地址(2B) + 输入寄存器数量(2B) |
字节数(1B) + 输入寄存器值(N×2字节) |
| 0x06 |
写单个寄存器 |
把值写入到一个保持寄存器中 |
寄存器地址(2B) + 值(2B) |
与请求报文完全一致(回显) |
| 0x10 |
写多个寄存器 |
写一串连续的保持寄存器 |
起始地址(2B) + 数量(2B) + 字节数(1B) + 寄存器值(N×2B) |
起始地址(2B) + 写入数量(2B) |
功能码 0x03:读保持寄存器
- 请求报文:01 03 00 6B 00 03 74 17
- 01:从站地址
- 03:功能码(读保持寄存器)
- 00 6B:起始寄存器地址(0x006B = 107)
- 00 03:读取3个寄存器
- 74 17:CRC
- 响应报文:01 03 06 02 2B 00 00 00 64 05 7A
- 01:从站地址
- 03:功能码
- 06:数据字节数(3个寄存器 × 2字节 = 6字节)
- 02 2B:寄存器0x006B的值(0x022B = 555)
- 00 00:寄存器0x006C的值
- 00 64:寄存器0x006D的值(0x0064 = 100)
- 05 7A:CRC
功能码 0x04:读输入寄存器
- 请求报文:01 04 00 08 00 01 B0 08
- 01:从站地址
- 04:功能码(读输入寄存器)
- 00 08:起始输入寄存器地址(0x0008 = 8)
- 00 01:读取1个寄存器
- B0 08:CRC
- 响应报文:01 04 02 00 0A 39 37
- 01:从站地址
- 04:功能码
- 02:数据字节数
- 00 0A:输入寄存器值(0x000A = 10)
- 39 37:CRC
功能码 0x06:写单个寄存器
- 请求报文:01 06 00 01 00 03 98 0B
- 01:从站地址
- 06:功能码(写单个寄存器)
- 00 01:寄存器地址(0x0001 = 1)
- 00 03:写入值(0x0003 = 3)
- 98 0B:CRC
- 响应报文:01 06 00 01 00 03 98 0B(回显)
功能码 0x10:写多个寄存器
- 请求报文:01 10 00 01 00 02 04 00 0A 01 02 92 30
- 01:从站地址
- 10:功能码(写多个寄存器)
- 00 01:起始寄存器地址(0x0001)
- 00 02:写入2个寄存器
- 04:数据字节数(2×2 = 4字节)
- 00 0A:第一个寄存器的值(0x000A = 10)
- 01 02:第二个寄存器的值(0x0102 = 258)
- 92 30:CRC
- 响应报文:01 10 00 01 00 02 10 08
- 01:从站地址
- 10:功能码
- 00 01:起始地址
- 00 02:写入数量
- 10 08:CRC
2.4 诊断和特殊功能
这类功能码用于设备管理、诊断和高级操作,在日常数据采集中使用较少。
| 功能码 |
名称 |
作用 |
简要说明 |
| 0x07 |
读取异常状态 |
读取8个内部线圈的状态,用于快速获取从机状态 |
适用于从机状态快速巡检 |
| 0x08 |
诊断 |
用于检查通信链路,或进行其他诊断测试 |
常见子功能如0x0000返回查询数据(回路反馈),用于测试通信是否正常 |
| 0x0B |
读取事件计数 |
读取从机Modbus事务处理事件计数 |
可用于判断通信操作是否成功 |
| 0x0C |
读取通信事件记录 |
检索每台从机的Modbus通信事件记录 |
用于高级诊断和分析 |
| 0x11 |
报告从机标识 |
获取从机的类型、运行状态等标识信息 |
用于设备识别和状态查询 |
| 0x14 |
读文件记录 |
读取文件记录 |
用于访问更复杂的数据结构 |
| 0x15 |
写文件记录 |
写入文件记录 |
用于修改复杂的数据结构 |
| 0x16 |
屏蔽写寄存器 |
通过一个”与”掩码和一个“或”掩码修改寄存器的内容,而不改变未指定位 |
用于原子化地修改寄存器中的某些位 |
| 0x17 |
读/写多个寄存器 |
在一个请求中,同时执行读取和写入多个寄存器的操作 |
提高了读写交互的效率 |
- 功能码 0x07:读取异常状态
- 请求报文:01 07 41 E2
- 01:从站地址
- 07:功能码
- 无数据域
- 响应报文:01 07 02 A3 F1
- 02:异常状态字节(表示8个内部线圈的状态,具体含义由设备定义)
- A3 F1:CRC
- 功能码 0x08:诊断(子功能0x0000:返回查询数据)
- 请求报文:01 08 00 00 A5 37 55 57
- 01:从站地址
- 08:功能码
- 00 00:子功能码(回路反馈)
- A5 37:数据域(任意数据,此处为0xA537)
- 响应报文:01 08 00 00 A5 37 55 57(回显请求数据)
- 功能码 0x0B:读取事件计数
- 请求报文:01 0B 41 E7
- 01:从站地址
- 0B:功能码
- 响应报文:01 0B 00 00 00 01 65 CB
- 00 00:状态(0x0000表示成功)
- 00 01:事件计数(已发生1个Modbus事件)
- 65 CB:CRC
- 功能码 0x0C:读取通信事件记录
- 请求报文:01 0C 00 25
- 01:从站地址
- 0C:功能码
- 00 25:CRC
- 响应报文:01 0C 06 00 00 00 01 00 02 00 03 35 86
- 01:从站地址
- 0C:功能码
- 06:后续字节数(包括状态、事件计数和事件数据)
- 00 00:状态
- 00 01:事件计数(当前记录数)
- 00 02:消息计数(从站已处理的Modbus消息数)
- 00 03:事件数据(第一个事件)
- 35 86:CRC
- 功能码 0x11:报告从机标识
- 请求报文:01 11 C0 2C
- 01:从站地址
- 11:功能码
- C0 2C:CRC
- 响应报文:01 11 0C 01 02 03 48 65 6C 6C 6F 00 00 00 00 00 E1 2A
- 01:从站地址
- 11:功能码
- 0C:后续字节数
- 01:从机状态(0x01 = 正常运行)
- 02:附加数据(设备类型标识)
- 03:运行状态(0x03)
- 48 65 6C 6C 6F:ASCII字符串“Hello”
- 00 00 00 00 00:填充(根据设备定义)
- E1 2A:CRC
- 功能码 0x14:读文件记录
- 请求报文:01 14 0E 06 00 01 00 02 00 03 00 04 00 05 00 06 00 07 96 2F
- 此报文包含子请求结构,较为复杂。简化示例:请求读取一个文件记录,子请求长度为0E(14> - 字节),子参考类型06(请求记录),文件号00 01,记录号00 02,记录长度00 03(3个寄> - 存器),然后重复另一个子请求(文件号00 04...)
- 完整解析需参考Modbus规范,此处仅示意。
- 响应报文:01 14 0C 07 06 00 01 00 02 00 03 00 04 00 05 00 06 45 23(略)
- 功能码 0x15:写文件记录
- 请求报文:01 15 12 06 00 01 00 02 00 02 00 03 12 34 56 78 06 00 03 00 04 00 01 9A BC
- 包含写入的数据,格式类似读文件记录。
- 响应报文:01 15 0E 06 00 01 00 02 00 02 06 00 03 00 04 00 01 C3 D4(回显成功写入的文件号和记录号)
- 功能码 0x16:屏蔽写寄存器
- 请求报文:01 16 00 02 00 F0 0F 0F CA 01
- 01:从站地址
- 16:功能码
- 00 02:寄存器地址
- 00 F0:与掩码(AND_MASK)
- 0F 0F:或掩码(OR_MASK)
- CA 01:CRC
- 操作:新值 = (原值 & AND_MASK) | (OR_MASK & (~AND_MASK))
- 响应报文:01 16 00 02 00 F0 0F 0F CA 01(回显请求)
- 功能码 0x17:读/写多个寄存器
- 请求报文:01 17 00 01 00 02 00 03 00 02 04 11 22 33 44 CB 63
- 01:从站地址
- 17:功能码
- 00 01:读起始地址
- 00 02:读取2个寄存器
- 00 03:写起始地址
- 00 02:写入2个寄存器
- 04:写入数据字节数(2×2)
- 11 22:写入第一个寄存器的值
- 33 44:写入第二个寄存器的值
- CB 63:CRC
- 响应报文:01 17 04 AA BB CC DD 3D 83
- 04:读取数据字节数
- AA BB:第一个读取寄存器的值
- CC DD:第二个读取寄存器的值
- 3D 83:CRC
2.5.异常响应
当从机无法执行主机请求时(如不支持的功能码、地址非法、数据值错误等),它会返回一个异常响应帧,异常功能码:等于请求功能码 + 0x80例如,请求功能码为0x03,异常响应功能码则为0x83,常见的异常码含义如下:
| 异常码 |
名称 |
含义 |
| 0x01 |
非法功能码 |
从机不支持请求中的功能码。 |
| 0x02 |
非法数据地址 |
请求中的数据地址超出从机的有效地址范围。 |
| 0x03 |
非法数据值 |
请求中包含的数据值(如数量、写入值)不在允许范围内。 |
| 0x04 |
从机设备故障 |
从机在尝试执行请求操作时发生了不可恢复的错误。 |
| 0x05 |
确认 |
从机已接受请求并正在处理,但需要较长时间,以防止主机超时。 |
| 0x06 |
从机设备忙 |
从机正在处理一个长时间命令,无法接受新的请求,主机稍后可以重发。 |
- 异常响应示例(以功能码0x03为例)
- 请求报文:01 03 00 6B 00 03 74 17(读寄存器,但地址超出范围)
- 异常响应:01 83 02 C0 F1
- 01:从站地址
- 83:异常功能码(请求码0x03 + 0x80)
- 02:异常码(0x02 = 非法数据地址)
- C0 F1:CRC
2.6.CRC校验
C语言版本示例方式一:
- 初始值:0xFFFF(Modbus标准初始值)
- 多项式:0xA001(即0x8005的反转形式)
- 输出:直接返回计算得到的CRC值
/*crc modbus*/
uint16_t crc_modbus(const uint8_t* data, uint16_t len)
{
uint16_t crc_value = 0xFFFF;
while (len--) {
crc_value ^= *data++;
for (int i = 0; i < 8; i++) {
crc_value = (crc_value & 1) ? (crc_value >> 1) ^ 0xA001 : (crc_value >> 1);
}
}
// 直接返回,无需交换字节
// 低字节在前:crc_value & 0xFF 先发送
// 高字节在后:(crc_value >> 8) 后发送
return crc_value;
}
C语言版本示例方式二:
//预计算的 CRC-16 Modbus 查找表 (多项式 0x8005,反转后为 0xA001)
static const uint16_t crc_modbus_table[256] = {
0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40,
0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841,
0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40,
0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41,
0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641,
0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040,
0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240,
0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441,
0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41,
0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840,
0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41,
0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40,
0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640,
0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041,
0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240,
0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441,
0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41,
0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840,
0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41,
0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40,
0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640,
0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041,
0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241,
0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40,
0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,
0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40,
0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040
};
/**
* @brief 计算 Modbus CRC-16 (查表法)
* @param data 数据指针
* @param len 数据长度
* @return CRC-16 值
*/
uint16_t crc_modbus(const uint8_t* data, uint16_t len)
{
uint16_t crc_value = 0xFFFF;
while (len--) {
crc_value = (crc_value >> 8) ^ crc_modbus_table[(crc_value ^ *data++) & 0xFF];
}
return crc_value;
}
3. Modbus TCP协议详解
3.1 报文格式
Modbus TCP报文 = MBAP Header(7字节) + PDU(功能码+数据)
MBAP Header详解
| 字段 |
长度 |
描述 |
示例 |
说明 |
| 事务标识符 |
2字节 |
请求编号 |
00 01 |
匹配请求与响应 |
| 协议标识符 |
2字节 |
固定为0 |
00 00 |
0代表Modbus/TCP |
| 长度 |
2字节 |
后续字节数 |
00 06 |
长度表示从单元标识符开始的字节数 |
| 单元标识符 |
1字节 |
从站地址 |
01 |
用于网关场景 |
大端传输:所有多字节数据高位字节在前(如0x1234传输为0x12 0x34)。
功能码说明
| 类别 |
功能码 |
名称 |
最大数据量 |
| 位操作 |
0x01 |
读线圈 |
2000位 |
|
0x02 |
读离散输入 |
2000位 |
|
0x05 |
写单个线圈 |
1位 |
|
0x0F |
写多个线圈 |
2000位 |
| 寄存器操作 |
0x03 |
读保持寄存器 |
125字节 |
|
0x04 |
读输入寄存器 |
125字节 |
|
0x06 |
写单个寄存器 |
1字节 |
|
0x10 |
写多个寄存器 |
123字节 |
| 高级功能 |
0x16 |
屏蔽写寄存器 |
1字节 |
|
0x17 |
读/写多个寄存器 |
读125字/写121字节 |
| 诊断 |
0x07 |
读取异常状态 |
1字节 |
|
0x08 |
诊断 |
可变 |
|
0x0B |
读取事件计数 |
2字节 |
|
0x0C |
读取通信事件记录 |
可变 |
|
0x11 |
报告从机标识 |
可变 |
3.2 位操作
所有示例中:事务标识00 01,协议标识00 00,从站地址01。
功能码 0x01:读线圈
| 项目 |
报文内容 |
| 请求报文 |
00 01 00 00 00 06 01 01 00 09 00 05 |
| 解析 |
MBAP头 | 单元标识01 | 功能码01 | 起始地址00 09 | 数量00 05 |
| 响应报文 |
00 01 00 00 00 04 01 01 01 14 |
| 解析 |
MBAP头 | 功能码01 | 字节数01 | 数据14(线圈状态) |
功能码 0x02:读离散输入
| 项目 |
报文内容 |
| 请求报文 |
00 01 00 00 00 06 01 02 00 00 00 12 |
| 响应报文 |
00 01 00 00 00 06 01 02 03 01 04 00 |
| 解析 |
字节数03 | 数据01 04 00(输入状态) |
功能码 0x05:写单个线圈
| 项目 |
报文内容 |
| 请求报文 |
00 01 00 00 00 06 01 05 00 02 FF 00 |
| 响应报文 |
00 01 00 00 00 06 01 05 00 02 FF 00(回显) |
功能码 0x0F:写多个线圈
| 项目 |
报文内容 |
| 请求报文 |
00 01 00 00 00 09 01 0F 00 13 00 0A 02 CD 00 |
| 响应报文 |
00 01 00 00 00 06 01 0F 00 13 00 0A |
3.3 寄存器操作
功能码 0x03:读保持寄存器
| 项目 |
报文内容 |
| 请求报文 |
00 01 00 00 00 06 01 03 00 03 00 02 |
| 响应报文 |
00 01 00 00 00 07 01 03 04 00 05 00 06 |
| 解析 |
字节数04 | 寄存器值00 05(5)| 00 06(6) |
功能码 0x04:读输入寄存器
| 项目 |
报文内容 |
| 请求报文 |
00 01 00 00 00 06 01 04 00 03 00 02 |
| 响应报文 |
00 01 00 00 00 07 01 04 04 00 0E 00 13 |
| 解析 |
字节数04 | 值00 0E(14)| 00 13(19) |
功能码 0x06:写单个寄存器
| 项目 |
报文内容 |
| 请求报文 |
00 01 00 00 00 06 01 06 00 20 00 06 |
| 响应报文 |
00 01 00 00 00 06 01 06 00 20 00 06(回显) |
功能码 0x10:写多个寄存器
| 项目 |
报文内容 |
| 请求报文 |
00 01 00 00 00 0B 01 10 00 03 00 02 04 04 D2 0D 80 |
| 解析 |
数据04 D2(1234)| 0D 80(3456) |
| 响应报文 |
00 01 00 00 00 06 01 10 00 03 00 02 |
3.4 高级功能
功能码 0x16:屏蔽写寄存器
| 项目 |
报文内容 |
| 请求报文 |
00 04 00 00 00 08 01 16 00 00 00 0F 0F 00 |
| 解析 |
AND_MASK=0x000F,OR_MASK=0x0F00 |
| 响应报文 |
00 04 00 00 00 08 01 16 00 00 00 0F 0F 00(回显) |
功能码 0x17:读/写多个寄存器
| 项目 |
报文内容 |
| 请求报文 |
00 01 00 00 00 0F 01 17 00 03 00 02 00 20 00 02 04 00 06 00 04 |
| 响应报文 |
00 01 00 00 00 07 01 17 04 04 D1 0D 7F |
3.5 诊断功能
功能码 0x07:读取异常状态
| 项目 |
报文内容 |
| 请求报文 |
00 01 00 00 00 03 01 07 |
| 响应报文 |
00 01 00 00 00 04 01 07 01 02 |
功能码 0x08:诊断
| 项目 |
报文内容 |
| 请求报文 |
00 01 00 00 00 06 01 08 00 00 12 34 |
| 响应报文 |
00 01 00 00 00 06 01 08 00 00 12 34(回显) |
功能码 0x11:报告从机标识
| 项目 |
报文内容 |
| 请求报文 |
00 01 00 00 00 02 01 11 |
| 响应报文 |
00 01 00 00 00 0A 01 11 07 FF 44 65 76 69 63 65 |
| 解析 |
长度00 0A(10字节)\| 单元标识01 \| 功能码11 \| 字节数07(7字节)\| 状态FF \| 标识44 65 76 69 63 65("Device",6字节) |
3.6 异常处理
异常响应格式
当服务器无法执行请求时,返回异常响应帧:
- TCP格式:MBAP头 +
功能码+0x80 + 异常码
异常码说明
| 异常码 |
名称 |
含义 |
| 01 (0x01) |
非法功能码 |
服务器不支持请求中的功能码 |
| 02 (0x02) |
非法数据地址 |
请求中的数据地址超出服务器的有效地址范围 |
| 03 (0x03) |
非法数据值 |
请求中包含的数据值不在允许范围内 |
| 04 (0x04) |
服务器设备故障 |
服务器执行操作时发生不可恢复的错误 |
| 05 (0x05) |
确认 |
服务器已接受请求并正在处理,需要较长时间 |
| 06 (0x06) |
服务器设备忙 |
服务器正在处理长时间命令,无法接受新请求 |
异常示例(TCP)
| 项目 |
报文内容 |
| 请求报文 |
00 01 00 00 00 06 01 03 00 6B 00 03 |
| 异常响应 |
00 01 00 00 00 03 01 83 02 |
| 解析 |
异常功能码83(03+80)| 异常码02(非法数据地址) |
4. Modbus ASCII协议详解
Modbus ASCII将所有数据转换为ASCII字符传输,报文文本可读,便于调试。
4.1 报文帧结构
| 字段 |
长度 |
描述 |
| 起始符 |
1字符 |
冒号 : (0x3A) |
| 地址域 |
2字符 |
从站地址的ASCII十六进制表示 |
| 功能码 |
2字符 |
功能码的ASCII十六进制表示 |
| 数据域 |
N×2字符 |
数据的ASCII十六进制表示 |
| LRC校验 |
2字符 |
纵向冗余校验的ASCII十六进制表示 |
| 结束符 |
2字符 |
回车 \r (0x0D) + 换行 \n (0x0A) |
4.2 示例
读取保持寄存器(与RTU示例相同功能):
| 项目 |
报文内容 |
| 请求报文 |
:010300000003F9\r\n |
| 解析 |
:起始符 | 01地址 | 03功能码 | 0000起始地址 | 0003数量 | F9LRC | \r\n结束 |
4.3 LRC计算
LRC(Longitudinal Redundancy Check,纵向冗余校验)是对报文中地址、功能码和数据域的字节值进行累加后取补码。
uint8_t lrc(uint8_t *data, uint16_t len) {
uint8_t sum = 0;
for (uint16_t i = 0; i < len; i++) {
sum += data[i];
}
return ((~sum) + 1) & 0xFF; // 或者直接 return -sum
}
本文章使用limfx的vscode插件快速发布