# Modbus RTU 0x14 收发 ADU 示例 ## 1. 示例条件 本示例使用功能码 `0x14` 读取文件记录,按照当前工程只处理一个子请求的格式编写。 ```text 从站地址: 1 引用类型: 0x06 文件号: 0 起始记录号: 0 读取数量: 2个寄存器 第一个寄存器值: 0x1234 第二个寄存器值: 0x5678 ``` ## 2. 请求帧 ### 2.1 完整请求 ADU 以下内容可以直接复制到十六进制串口助手发送: ```text 01 14 07 06 00 00 00 00 00 02 78 E5 ``` 请求帧长度为: ```text 12字节 ``` ### 2.2 请求帧解析 | 字节 | 含义 | 说明 | |---|---|---| | `01` | 从站地址 | 从站地址为1 | | `14` | 功能码 | 读取文件记录 | | `07` | 后续字节数 | 一个子请求固定占7字节 | | `06` | 引用类型 | Modbus文件记录规定为`0x06` | | `00 00` | 文件号 | 文件0 | | `00 00` | 起始记录号 | 从记录0开始 | | `00 02` | 记录长度 | 读取2个16位寄存器 | | `78 E5` | CRC16 | CRC低字节`78`在前,高字节`E5`在后 | 请求帧中参与CRC计算的是: ```text 01 14 07 06 00 00 00 00 00 02 ``` 计算得到: ```text CRC16 = 0xE578 ``` Modbus RTU发送CRC时低字节在前,因此帧尾为: ```text 78 E5 ``` ## 3. 正常响应帧 假设文件0的记录0和记录1分别保存: ```text 记录0 = 0x1234 记录1 = 0x5678 ``` ### 3.1 完整响应 ADU ```text 01 14 06 05 06 12 34 56 78 92 FE ``` 响应帧长度为: ```text 11字节 ``` ### 3.2 响应帧解析 | 字节 | 含义 | 说明 | |---|---|---| | `01` | 从站地址 | 响应来自从站1 | | `14` | 功能码 | 读取文件记录 | | `06` |响应数据总字节数 | 后续子响应共6字节 | | `05` | 子响应长度 | 引用类型1字节加寄存器数据4字节 | | `06` | 引用类型 | 固定为`0x06` | | `12 34` | 第一个寄存器 | 值为`0x1234` | | `56 78` | 第二个寄存器 | 值为`0x5678` | | `92 FE` | CRC16 | CRC低字节`92`在前,高字节`FE`在后 | 响应帧中参与CRC计算的是: ```text 01 14 06 05 06 12 34 56 78 ``` 计算得到: ```text CRC16 = 0xFE92 ``` 因此响应帧尾发送: ```text 92 FE ``` ## 4. 长度计算 读取数量为 `quantity` 时,单子请求的请求帧长度固定为: ```text 12字节 ``` 正常响应中的字段为: ```text 子响应长度 = 1 + quantity × 2 响应数据总字节数 = 2 + quantity × 2 完整响应ADU长度 = 7 + quantity × 2 ``` 本例读取2个寄存器: ```text 子响应长度 = 1 + 2 × 2 = 5字节 响应数据总字节数 = 2 + 2 × 2 = 6字节 完整响应ADU长度 = 7 + 2 × 2 = 11字节 ``` ## 5. 与当前代码的对应关系 请求帧中的参数由以下代码解析: ```c fileNumber = ModbusGetU16Be(&request[4]); recordNumber = ModbusGetU16Be(&request[6]); quantity = ModbusGetU16Be(&request[8]); ``` 响应帧字段对应: ```c ModbusTxFrame[0] = ModbusSlaveAddress; ModbusTxFrame[1] = 0x14U; ModbusTxFrame[2] = (uint8_t)(2U + quantity * 2U); ModbusTxFrame[3] = (uint8_t)(1U + quantity * 2U); ModbusTxFrame[4] = 0x06U; ``` 如果后续将文件号改为标准的一基编号,即文件号使用 `1~16`,测试请求中的文件号需要由: ```text 00 00 ``` 改为: ```text 00 01 ``` 同时重新计算请求帧CRC。 ## 6. 读取124个寄存器的示例 ### 6.1 请求条件 ```text 从站地址: 1 引用类型: 0x06 文件号: 0 起始记录号: 0 读取数量: 124个寄存器,即0x007C ``` ### 6.2 完整请求ADU 以下请求可以直接复制到十六进制串口助手发送: ```text 01 14 07 06 00 00 00 00 00 7C F8 C5 ``` 请求帧长度为: ```text 12字节 ``` 请求帧解析如下: | 字节 | 含义 | |---|---| | `01` | 从站地址为1 | | `14` | 读取文件记录功能码 | | `07` | 当前子请求包含7个字节 | | `06` | 引用类型 | | `00 00` | 文件号0 | | `00 00` | 从记录0开始读取 | | `00 7C` | 读取124个寄存器 | | `F8 C5` | CRC低字节、CRC高字节 | 参与CRC计算的内容为: ```text 01 14 07 06 00 00 00 00 00 7C ``` 计算结果为: ```text CRC16 = 0xC5F8 ``` 因此请求帧末尾按照低字节在前的顺序发送: ```text F8 C5 ``` ### 6.3 正常响应格式 读取124个寄存器时,寄存器数据占用: ```text 124 × 2 = 248字节 = 0xF8字节 ``` 子响应长度为: ```text 引用类型1字节 + 寄存器数据248字节 = 249字节 = 0xF9字节 ``` 响应数据总字节数为: ```text 子响应长度字段1字节 + 子响应249字节 = 250字节 = 0xFA字节 ``` 所以正常响应格式为: ```text 01 14 FA F9 06 [248字节寄存器数据] CRC低 CRC高 ``` 完整响应ADU长度为: ```text 从站地址1字节 + 功能码1字节 + 响应数据总字节数字段1字节 + 子响应长度字段1字节 + 引用类型1字节 + 寄存器数据248字节 + CRC 2字节 = 255字节 ``` 该响应可以放入当前工程的256字节发送缓冲区: ```c static uint8_t ModbusTxFrame[256]; ``` ### 6.4 所有寄存器值均为0时的响应 假设读取的124个寄存器全部为 `0x0000`,响应帧的开头为: ```text 01 14 FA F9 06 ``` 随后是248个 `00` 数据字节,帧尾CRC为: ```text 6C 45 ``` 即: ```text 01 14 FA F9 06 [248个00] 6C 45 ``` 该响应的CRC计算值为: ```text CRC16 = 0x456C ``` Modbus RTU按照低字节在前发送,因此CRC字段为: ```text 6C 45 ``` ### 6.5 对应的代码限制 发送缓冲区长度为256字节时,单个0x14子请求最多读取124个寄存器,因此数量检查应为: ```c if ((quantity == 0U) || (quantity > 124U)) { ModbusSlaveStatistics.illegalValueCount++; return ModbusBuildException( request[1], MODBUS_EX_ILLEGAL_VALUE); } ``` 如果每个 `MODBUS_HISTORY_ITEM` 为520字节,即260个16位寄存器,还必须保证本次读取不超过当前文件: ```c if (((uint32_t)recordNumber + quantity) > 260UL) { ModbusSlaveStatistics.illegalAddressCount++; return ModbusBuildException( request[1], MODBUS_EX_ILLEGAL_ADDRESS); } ```