| @@ -0,0 +1,141 @@ | |||||
| # Modbus RTU最大长度ADU测试 | |||||
| 串口助手请选择HEX发送。下列报文已经完整展开,可以直接复制。 | |||||
| 测试条件: | |||||
| - 从站地址为`0x01` | |||||
| - 先执行0x0F写线圈,再执行0x01读线圈 | |||||
| - 先执行0x10写保持寄存器,再执行0x03读保持寄存器 | |||||
| - 0x10和0x03使用D6000起始地址,避免覆盖掉电保持区和通信历史区 | |||||
| - 文档中的全零读取响应以相关线圈和保持寄存器初始值均为0为前提 | |||||
| - 0x0F一次最多写1968个线圈,不能单次清零全部2000个待读线圈;测试0x01前还应确认线圈1968~1999为0 | |||||
| - 0x10一次最多写123个寄存器,不能单次清零全部125个待读寄存器;测试0x03前还应确认D6123~D6124为0 | |||||
| ## 0x0F写入1968个线圈 | |||||
| 含义: | |||||
| - `01`:从站地址 | |||||
| - `0F`:写多个线圈功能码 | |||||
| - `00 00`:起始线圈地址0 | |||||
| - `07 B0`:写入数量1968 | |||||
| - `F6`:后续线圈数据长度246字节 | |||||
| - 连续246个`00`:将线圈0~1967全部写为OFF | |||||
| - `A6 FE`:CRC低字节、CRC高字节 | |||||
| - 测试目的:用255字节合法请求帧检查DMA接收缓冲区边界和最大线圈写入数量 | |||||
| 请求帧长度:255字节 | |||||
| 请求帧: | |||||
| ```text | |||||
| 01 0F 00 00 07 B0 F6 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 A6 FE | |||||
| ``` | |||||
| 预期响应帧长度:8字节 | |||||
| 预期响应帧: | |||||
| ```text | |||||
| 01 0F 00 00 07 B0 56 4F | |||||
| ``` | |||||
| ## 0x01读取2000个线圈 | |||||
| 含义: | |||||
| - `01`:从站地址 | |||||
| - `01`:读线圈功能码 | |||||
| - `00 00`:起始线圈地址0 | |||||
| - `07 D0`:读取数量2000 | |||||
| - `3F A6`:CRC低字节、CRC高字节 | |||||
| - 响应中的`FA`表示后续包含250字节线圈状态,因为`2000 ÷ 8 = 250` | |||||
| - 响应中的250个`00`表示线圈0~1999全部为OFF | |||||
| - `F5 AF`:全零响应对应的CRC低字节、CRC高字节 | |||||
| - 测试目的:检查最大线圈读取数量以及255字节响应帧的DMA发送 | |||||
| 请求帧长度:8字节 | |||||
| 请求帧: | |||||
| ```text | |||||
| 01 01 00 00 07 D0 3F A6 | |||||
| ``` | |||||
| 预期响应帧长度:255字节 | |||||
| 预期响应帧: | |||||
| ```text | |||||
| 01 01 FA 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 F5 AF | |||||
| ``` | |||||
| ## 0x10写入123个保持寄存器 | |||||
| 含义: | |||||
| - `01`:从站地址 | |||||
| - `10`:写多个保持寄存器功能码 | |||||
| - `17 70`:起始地址6000,即D6000 | |||||
| - `00 7B`:写入数量123 | |||||
| - `F6`:后续寄存器数据长度246字节 | |||||
| - 连续246个`00`:将D6000~D6122全部写为0 | |||||
| - `76 06`:CRC低字节、CRC高字节 | |||||
| - 正常响应回显起始地址`17 70`和写入数量`00 7B` | |||||
| - 测试目的:用255字节合法请求帧检查DMA接收缓冲区边界和最大寄存器写入数量 | |||||
| 请求帧长度:255字节 | |||||
| 请求帧: | |||||
| ```text | |||||
| 01 10 17 70 00 7B F6 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 76 06 | |||||
| ``` | |||||
| 预期响应帧长度:8字节 | |||||
| 预期响应帧: | |||||
| ```text | |||||
| 01 10 17 70 00 7B 84 45 | |||||
| ``` | |||||
| ## 0x03读取125个保持寄存器 | |||||
| 含义: | |||||
| - `01`:从站地址 | |||||
| - `03`:读保持寄存器功能码 | |||||
| - `17 70`:起始地址6000,即D6000 | |||||
| - `00 7D`:读取数量125 | |||||
| - `81 84`:CRC低字节、CRC高字节 | |||||
| - 响应中的`FA`表示后续包含250字节寄存器数据,因为`125 × 2 = 250` | |||||
| - 响应中的250个`00`表示D6000~D6124全部为0 | |||||
| - `08 E8`:全零响应对应的CRC低字节、CRC高字节 | |||||
| - 测试目的:检查最大保持寄存器读取数量以及255字节响应帧的DMA发送 | |||||
| 请求帧长度:8字节 | |||||
| 请求帧: | |||||
| ```text | |||||
| 01 03 17 70 00 7D 81 84 | |||||
| ``` | |||||
| 预期响应帧长度:255字节 | |||||
| 预期响应帧: | |||||
| ```text | |||||
| 01 03 FA 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 08 E8 | |||||
| ``` | |||||
| ## 检查项 | |||||
| - 0x0F和0x10请求的`ModbusRxFrameLength`应为255 | |||||
| - 0x01和0x03正常响应的`responseLength`应为255 | |||||
| - `droppedFrameCount`不应增加 | |||||
| - `uartErrorCount`不应增加 | |||||
| - 不应发生HardFault、DMA越界或接收停止 | |||||
| - 完成255字节帧后,下一帧仍应正常通信 | |||||
| @@ -9,10 +9,10 @@ extern "C" | |||||
| #endif | #endif | ||||
| /** | /** | ||||
| * @brief Modbus 数据模型的最大地址。 | |||||
| * @brief Modbus 数据模型的最大地址 | |||||
| * | * | ||||
| * 任务要求规定线圈和保持寄存器地址范围均为 0~0x270F, | * 任务要求规定线圈和保持寄存器地址范围均为 0~0x270F, | ||||
| * 因此每种数据共有 10000 项。 | |||||
| * 因此每种数据共有 10000 项 | |||||
| */ | */ | ||||
| #define MODBUS_MAP_LAST_ADDRESS (0x270FU)//保持寄存器或线圈地址 | #define MODBUS_MAP_LAST_ADDRESS (0x270FU)//保持寄存器或线圈地址 | ||||
| #define MODBUS_MAP_ITEM_COUNT (0x2710U)//保持寄存器或线圈数量 | #define MODBUS_MAP_ITEM_COUNT (0x2710U)//保持寄存器或线圈数量 | ||||
| @@ -24,9 +24,9 @@ extern "C" | |||||
| #define MODBUS_CONNECTION_TIMEOUT_MS (1000U) | #define MODBUS_CONNECTION_TIMEOUT_MS (1000U) | ||||
| /** | /** | ||||
| * @brief 与 TouchWin 触摸屏联调使用的演示地址。 | |||||
| * @brief 与 TouchWin 触摸屏联调使用的演示地址 | |||||
| * | * | ||||
| * 可以直接访问以下地址。 | |||||
| * 可以直接访问以下地址 | |||||
| */ | */ | ||||
| #define HMI_REG_DEVICE_ID (0U) | #define HMI_REG_DEVICE_ID (0U) | ||||
| #define HMI_REG_UPTIME_SECONDS (1U) | #define HMI_REG_UPTIME_SECONDS (1U) | ||||
| @@ -55,20 +55,20 @@ static volatile MODBUS_BACKUP_DATA *backupData =(volatile MODBUS_BACKUP_DATA *)B | |||||
| /** | /** | ||||
| * @brief Modbus 从站通信统计信息。 | |||||
| * @brief Modbus 从站通信统计信息 | |||||
| */ | */ | ||||
| typedef struct | typedef struct | ||||
| { | { | ||||
| uint32_t rxEventCount; ///< 串口接收事件总数。 | |||||
| uint32_t validFrameCount; ///< CRC 和从站地址均有效的帧数。 | |||||
| uint32_t txFrameCount; ///< 成功启动 DMA 发送的帧数。 | |||||
| uint32_t crcErrorCount; ///< CRC 校验错误帧数。 | |||||
| uint32_t ignoredAddressCount; ///< 因从站地址不匹配而忽略的帧数。 | |||||
| uint32_t illegalFunctionCount; ///< 非法功能码计数。 | |||||
| uint32_t illegalAddressCount; ///< 非法数据地址计数。 | |||||
| uint32_t illegalValueCount; ///< 非法数据值计数。 | |||||
| uint32_t droppedFrameCount; ///< 接收槽占用或长度错误导致的丢帧数。 | |||||
| uint32_t uartErrorCount; ///< HAL 串口错误计数。 | |||||
| uint32_t rxEventCount; ///< 串口接收事件总数 | |||||
| uint32_t validFrameCount; ///< CRC 和从站地址均有效的帧数 | |||||
| uint32_t txFrameCount; ///< 成功启动 DMA 发送的帧数 | |||||
| uint32_t crcErrorCount; ///< CRC 校验错误帧数 | |||||
| uint32_t ignoredAddressCount; ///< 因从站地址不匹配而忽略的帧数 | |||||
| uint32_t illegalFunctionCount; ///< 非法功能码计数 | |||||
| uint32_t illegalAddressCount; ///< 非法数据地址计数 | |||||
| uint32_t illegalValueCount; ///< 非法数据值计数 | |||||
| uint32_t droppedFrameCount; ///< 接收槽占用或长度错误导致的丢帧数 | |||||
| uint32_t uartErrorCount; ///< HAL 串口错误计数 | |||||
| } MODBUS_SLAVE_STATS; | } MODBUS_SLAVE_STATS; | ||||
| typedef struct | typedef struct | ||||
| @@ -82,80 +82,80 @@ typedef struct | |||||
| uint8_t response[256]; | uint8_t response[256]; | ||||
| } MODBUS_HISTORY_ITEM; | } MODBUS_HISTORY_ITEM; | ||||
| /** @brief Modbus 从站通信统计数据。 */ | |||||
| /** @brief Modbus 从站通信统计数据 */ | |||||
| extern volatile MODBUS_SLAVE_STATS ModbusSlaveStatistics; | extern volatile MODBUS_SLAVE_STATS ModbusSlaveStatistics; | ||||
| /** | /** | ||||
| * @brief 初始化 Modbus 从站并启动 USART DMA 空闲接收。 | |||||
| * @param[in] huart 已由 HAL 初始化完成的串口句柄。 | |||||
| * @param[in] slaveAddress 从站地址,有效范围为 1~247。 | |||||
| * @retval HAL_OK 初始化成功且 DMA 接收已启动。 | |||||
| * @retval HAL_ERROR 输入参数无效。 | |||||
| * @retval HAL_BUSY 串口或 DMA 当前忙。 | |||||
| * @brief 初始化 Modbus 从站并启动 USART DMA 空闲接收 | |||||
| * @param[in] huart 已由 HAL 初始化完成的串口句柄 | |||||
| * @param[in] slaveAddress 从站地址,有效范围为 1~247 | |||||
| * @retval HAL_OK 初始化成功且 DMA 接收已启动 | |||||
| * @retval HAL_ERROR 输入参数无效 | |||||
| * @retval HAL_BUSY 串口或 DMA 当前忙 | |||||
| */ | */ | ||||
| HAL_StatusTypeDef ModbusSlaveInit(UART_HandleTypeDef *huart, uint8_t slaveAddress); | HAL_StatusTypeDef ModbusSlaveInit(UART_HandleTypeDef *huart, uint8_t slaveAddress); | ||||
| /** | /** | ||||
| * @brief 校验并处理一帧待处理请求。 | |||||
| * @brief 校验并处理一帧待处理请求 | |||||
| * | * | ||||
| * 本函数应在 uC/OS-II 任务中周期调用。中断只负责接收数据, | |||||
| * CRC 校验、协议解析和响应发送均在任务上下文中执行。 | |||||
| * 本函数应在 uC/OS-II 任务中周期调用中断只负责接收数据, | |||||
| * CRC 校验、协议解析和响应发送均在任务上下文中执行 | |||||
| */ | */ | ||||
| void ModbusSlavePoll(void); | void ModbusSlavePoll(void); | ||||
| /** | /** | ||||
| * @brief 处理 HAL 串口 DMA 接收事件。 | |||||
| * @param[in] huart 产生接收事件的串口句柄。 | |||||
| * @param[in] size 本次接收到的字节数。 | |||||
| * @brief 处理 HAL 串口 DMA 接收事件 | |||||
| * @param[in] huart 产生接收事件的串口句柄 | |||||
| * @param[in] size 本次接收到的字节数 | |||||
| */ | */ | ||||
| void ModbusSlaveOnRxEvent(UART_HandleTypeDef *huart, uint16_t size); | void ModbusSlaveOnRxEvent(UART_HandleTypeDef *huart, uint16_t size); | ||||
| /** | /** | ||||
| * @brief 处理 HAL 串口 DMA 发送完成事件。 | |||||
| * @param[in] huart 完成发送的串口句柄。 | |||||
| * @brief 处理 HAL 串口 DMA 发送完成事件 | |||||
| * @param[in] huart 完成发送的串口句柄 | |||||
| */ | */ | ||||
| void ModbusSlaveOnTxComplete(UART_HandleTypeDef *huart); | void ModbusSlaveOnTxComplete(UART_HandleTypeDef *huart); | ||||
| /** | /** | ||||
| * @brief 处理 HAL 串口错误事件并恢复接收。 | |||||
| * @param[in] huart 发生错误的串口句柄。 | |||||
| * @brief 处理 HAL 串口错误事件并恢复接收 | |||||
| * @param[in] huart 发生错误的串口句柄 | |||||
| */ | */ | ||||
| void ModbusSlaveOnUartError(UART_HandleTypeDef *huart); | void ModbusSlaveOnUartError(UART_HandleTypeDef *huart); | ||||
| /** | /** | ||||
| * @brief 写入一个保持寄存器。 | |||||
| * @param[in] address 保持寄存器地址。 | |||||
| * @param[in] value 待写入的 16 位数据。 | |||||
| * @brief 写入一个保持寄存器 | |||||
| * @param[in] address 保持寄存器地址 | |||||
| * @param[in] value 待写入的 16 位数据 | |||||
| */ | */ | ||||
| uint8_t ModbusSlaveSetHoldingRegister(uint16_t address, uint16_t value); | uint8_t ModbusSlaveSetHoldingRegister(uint16_t address, uint16_t value); | ||||
| /** | /** | ||||
| * @brief 读取一个保持寄存器。 | |||||
| * @param[in] address 保持寄存器地址。 | |||||
| * @param[out] value 用于保存读取结果的指针。 | |||||
| * @brief 读取一个保持寄存器 | |||||
| * @param[in] address 保持寄存器地址 | |||||
| * @param[out] value 用于保存读取结果的指针 | |||||
| */ | */ | ||||
| uint8_t ModbusSlaveGetHoldingRegister(uint16_t address, uint16_t *value); | uint8_t ModbusSlaveGetHoldingRegister(uint16_t address, uint16_t *value); | ||||
| /** | /** | ||||
| * @brief 设置一个线圈的状态。 | |||||
| * @param[in] address 线圈地址。 | |||||
| * @param[in] state 0 表示复位,非 0 表示置位。 | |||||
| * @brief 设置一个线圈的状态 | |||||
| * @param[in] address 线圈地址 | |||||
| * @param[in] state 0 表示复位,非 0 表示置位 | |||||
| */ | */ | ||||
| uint8_t ModbusSlaveSetCoil(uint16_t address, uint8_t state); | uint8_t ModbusSlaveSetCoil(uint16_t address, uint8_t state); | ||||
| /** | /** | ||||
| * @brief 读取一个线圈的状态。 | |||||
| * @param[in] address 线圈地址。 | |||||
| * @param[out] state 用于保存线圈状态的指针,结果为 0 或 1。 | |||||
| * @brief 读取一个线圈的状态 | |||||
| * @param[in] address 线圈地址 | |||||
| * @param[out] state 用于保存线圈状态的指针,结果为 0 或 1 | |||||
| */ | */ | ||||
| uint8_t ModbusSlaveGetCoil(uint16_t address, uint8_t *state); | uint8_t ModbusSlaveGetCoil(uint16_t address, uint8_t *state); | ||||
| /** | /** | ||||
| * @brief 判断指定时间内是否收到过有效请求。 | |||||
| * @param[in] timeoutMs 连接超时时间,单位为毫秒。 | |||||
| * @retval 1 从站在指定时间内收到过有效请求。 | |||||
| * @retval 0 尚未收到有效请求或连接已经超时。 | |||||
| * @brief 判断指定时间内是否收到过有效请求 | |||||
| * @param[in] timeoutMs 连接超时时间,单位为毫秒 | |||||
| * @retval 1 从站在指定时间内收到过有效请求 | |||||
| * @retval 0 尚未收到有效请求或连接已经超时 | |||||
| */ | */ | ||||
| uint8_t ModbusSlaveIsConnected(uint32_t timeoutMs); | uint8_t ModbusSlaveIsConnected(uint32_t timeoutMs); | ||||
| void ModbusRetainedRegistersLoad(void); | void ModbusRetainedRegistersLoad(void); | ||||
| @@ -110,9 +110,9 @@ static uint32_t ModbusGetU24Be(const uint8_t *data) | |||||
| } | } | ||||
| /** | /** | ||||
| * @brief 检查连续数据地址范围是否合法。 | |||||
| * @param[in] start 起始地址。 | |||||
| * @param[in] quantity 数据项数量。 | |||||
| * @brief 检查连续数据地址范围是否合法 | |||||
| * @param[in] start 起始地址 | |||||
| * @param[in] quantity 数据项数量 | |||||
| */ | */ | ||||
| static uint8_t ModbusAddressRangeIsValid(uint16_t start, uint16_t quantity) | static uint8_t ModbusAddressRangeIsValid(uint16_t start, uint16_t quantity) | ||||
| { | { | ||||
| @@ -126,9 +126,9 @@ static uint8_t ModbusAddressRangeIsValid(uint16_t start, uint16_t quantity) | |||||
| } | } | ||||
| /** | /** | ||||
| * @brief 读取已经确认地址合法的线圈。 | |||||
| * @param[in] address 线圈地址。 | |||||
| * @return 线圈状态,取值为 0 或 1。 | |||||
| * @brief 读取已经确认地址合法的线圈 | |||||
| * @param[in] address 线圈地址 | |||||
| * @return 线圈状态,取值为 0 或 1 | |||||
| */ | */ | ||||
| static uint8_t ModbusCoilGetUnchecked(uint16_t address) | static uint8_t ModbusCoilGetUnchecked(uint16_t address) | ||||
| { | { | ||||
| @@ -138,9 +138,9 @@ static uint8_t ModbusCoilGetUnchecked(uint16_t address) | |||||
| } | } | ||||
| /** | /** | ||||
| * @brief 设置已经确认地址合法的线圈。 | |||||
| * @param[in] address 线圈地址。 | |||||
| * @param[in] state 0 表示复位,非 0 表示置位。 | |||||
| * @brief 设置已经确认地址合法的线圈 | |||||
| * @param[in] address 线圈地址 | |||||
| * @param[in] state 0 表示复位,非 0 表示置位 | |||||
| */ | */ | ||||
| static void ModbusCoilSetUnchecked(uint16_t address, uint8_t state) | static void ModbusCoilSetUnchecked(uint16_t address, uint8_t state) | ||||
| { | { | ||||
| @@ -157,10 +157,10 @@ static void ModbusCoilSetUnchecked(uint16_t address, uint8_t state) | |||||
| } | } | ||||
| /** | /** | ||||
| * @brief 启动 USART DMA 空闲接收。 | |||||
| * @retval HAL_OK DMA 接收启动成功。 | |||||
| * @retval HAL_BUSY 串口未配置或发送尚未结束。 | |||||
| * @return 其他 HAL 状态表示 DMA 接收启动失败。 | |||||
| * @brief 启动 USART DMA 空闲接收 | |||||
| * @retval HAL_OK DMA 接收启动成功 | |||||
| * @retval HAL_BUSY 串口未配置或发送尚未结束 | |||||
| * @return 其他 HAL 状态表示 DMA 接收启动失败 | |||||
| */ | */ | ||||
| static HAL_StatusTypeDef ModbusStartReceive(void) | static HAL_StatusTypeDef ModbusStartReceive(void) | ||||
| { | { | ||||
| @@ -178,8 +178,8 @@ static HAL_StatusTypeDef ModbusStartReceive(void) | |||||
| if ((status == HAL_OK) && (ModbusUart->hdmarx != NULL)) | if ((status == HAL_OK) && (ModbusUart->hdmarx != NULL)) | ||||
| { | { | ||||
| /* | /* | ||||
| * 普通 Modbus 帧只应在 IDLE 或缓冲区满时交给应用。 | |||||
| * 关闭 DMA 半传输中断,避免长帧在一半位置被误认为完整帧。 | |||||
| * 普通 Modbus 帧只应在 IDLE 或缓冲区满时交给应用 | |||||
| * 关闭 DMA 半传输中断,避免长帧在一半位置被误认为完整帧 | |||||
| */ | */ | ||||
| __HAL_DMA_DISABLE_IT(ModbusUart->hdmarx, DMA_IT_HT); | __HAL_DMA_DISABLE_IT(ModbusUart->hdmarx, DMA_IT_HT); | ||||
| } | } | ||||
| @@ -190,24 +190,24 @@ static HAL_StatusTypeDef ModbusStartReceive(void) | |||||
| /** | /** | ||||
| * @brief 在 RTU 帧末尾追加 CRC 低字节和高字节。 | |||||
| * @param[in,out] frame 待追加 CRC 的帧缓冲区。 | |||||
| * @param[in] payloadLength 不包含 CRC 的有效载荷长度。 | |||||
| * @brief 在 RTU 帧末尾追加 CRC 低字节和高字节 | |||||
| * @param[in,out] frame 待追加 CRC 的帧缓冲区 | |||||
| * @param[in] payloadLength 不包含 CRC 的有效载荷长度 | |||||
| */ | */ | ||||
| static void ModbusAppendCrc(uint8_t *frame, uint16_t payloadLength) | static void ModbusAppendCrc(uint8_t *frame, uint16_t payloadLength) | ||||
| { | { | ||||
| uint16_t crc = ModbusCrc16(frame, payloadLength); | uint16_t crc = ModbusCrc16(frame, payloadLength); | ||||
| /* Modbus RTU 在线路上传输 CRC 低字节在前、高字节在后。 */ | |||||
| /* Modbus RTU 在线路上传输 CRC 低字节在前、高字节在后 */ | |||||
| frame[payloadLength] = (uint8_t)(crc & 0x00FFU); | frame[payloadLength] = (uint8_t)(crc & 0x00FFU); | ||||
| frame[payloadLength + 1U] = (uint8_t)(crc >> 8U); | frame[payloadLength + 1U] = (uint8_t)(crc >> 8U); | ||||
| } | } | ||||
| /** | /** | ||||
| * @brief 构造 Modbus 异常响应。 | |||||
| * @param[in] function 请求功能码。 | |||||
| * @param[in] exception 异常码。 | |||||
| * @return 异常响应 ADU 长度。 | |||||
| * @brief 构造 Modbus 异常响应 | |||||
| * @param[in] function 请求功能码 | |||||
| * @param[in] exception 异常码 | |||||
| * @return 异常响应 ADU 长度 | |||||
| */ | */ | ||||
| static uint16_t ModbusBuildException(uint8_t function, uint8_t exception) | static uint16_t ModbusBuildException(uint8_t function, uint8_t exception) | ||||
| { | { | ||||
| @@ -338,11 +338,11 @@ static uint16_t ModbusProcessReadHolding(const uint8_t *request,uint16_t request | |||||
| } | } | ||||
| /** | /** | ||||
| * @brief 处理写单个保持寄存器功能码 0x06。 | |||||
| * @param[in] request RTU 请求帧。 | |||||
| * @param[in] requestLength 请求帧长度。 | |||||
| * @param[in] isBroadcast 非 0 表示当前请求为广播。 | |||||
| * @return 单播响应长度;广播或无法响应时返回 0。 | |||||
| * @brief 处理写单个保持寄存器功能码 0x06 | |||||
| * @param[in] request RTU 请求帧 | |||||
| * @param[in] requestLength 请求帧长度 | |||||
| * @param[in] isBroadcast 非 0 表示当前请求为广播 | |||||
| * @return 单播响应长度;广播或无法响应时返回 0 | |||||
| */ | */ | ||||
| static uint16_t ModbusProcessWriteSingleRegister( | static uint16_t ModbusProcessWriteSingleRegister( | ||||
| const uint8_t *request, | const uint8_t *request, | ||||
| @@ -376,18 +376,18 @@ static uint16_t ModbusProcessWriteSingleRegister( | |||||
| return 0U; | return 0U; | ||||
| } | } | ||||
| /* 0x06 的正常响应必须原样回显请求的前 6 个字节。 */ | |||||
| /* 0x06 的正常响应必须原样回显请求的前 6 个字节 */ | |||||
| (void)memcpy(ModbusTxFrame, request, 6U); | (void)memcpy(ModbusTxFrame, request, 6U); | ||||
| ModbusAppendCrc(ModbusTxFrame, 6U); | ModbusAppendCrc(ModbusTxFrame, 6U); | ||||
| return 8U; | return 8U; | ||||
| } | } | ||||
| /** | /** | ||||
| * @brief 处理写单个线圈功能码 0x05。 | |||||
| * @param[in] request RTU 请求帧。 | |||||
| * @param[in] requestLength 请求帧长度。 | |||||
| * @param[in] isBroadcast 非 0 表示当前请求为广播。 | |||||
| * @return 单播响应长度;广播或无法响应时返回 0。 | |||||
| * @brief 处理写单个线圈功能码 0x05 | |||||
| * @param[in] request RTU 请求帧 | |||||
| * @param[in] requestLength 请求帧长度 | |||||
| * @param[in] isBroadcast 非 0 表示当前请求为广播 | |||||
| * @return 单播响应长度;广播或无法响应时返回 0 | |||||
| */ | */ | ||||
| static uint16_t ModbusProcessWriteSingleCoil( | static uint16_t ModbusProcessWriteSingleCoil( | ||||
| const uint8_t *request, | const uint8_t *request, | ||||
| @@ -409,7 +409,7 @@ static uint16_t ModbusProcessWriteSingleCoil( | |||||
| /* | /* | ||||
| * 0x05 只接受 0xFF00(线圈置位)和 0x0000(线圈复位); | * 0x05 只接受 0xFF00(线圈置位)和 0x0000(线圈复位); | ||||
| * 其他数值属于非法数据值。 | |||||
| * 其他数值属于非法数据值 | |||||
| */ | */ | ||||
| if ((value != MODBUS_COIL_VALUE_ON) | if ((value != MODBUS_COIL_VALUE_ON) | ||||
| && (value != MODBUS_COIL_VALUE_OFF)) | && (value != MODBUS_COIL_VALUE_OFF)) | ||||
| @@ -435,18 +435,18 @@ static uint16_t ModbusProcessWriteSingleCoil( | |||||
| return 0U; | return 0U; | ||||
| } | } | ||||
| /* 0x05 正常应答原样回显请求的前 6 个字节,再追加 CRC。 */ | |||||
| /* 0x05 正常应答原样回显请求的前 6 个字节,再追加 CRC */ | |||||
| (void)memcpy(ModbusTxFrame, request, 6U); | (void)memcpy(ModbusTxFrame, request, 6U); | ||||
| ModbusAppendCrc(ModbusTxFrame, 6U); | ModbusAppendCrc(ModbusTxFrame, 6U); | ||||
| return 8U; | return 8U; | ||||
| } | } | ||||
| /** | /** | ||||
| * @brief 处理写多个线圈功能码 0x0F。 | |||||
| * @param[in] request RTU 请求帧。 | |||||
| * @param[in] requestLength 请求帧长度。 | |||||
| * @param[in] isBroadcast 非 0 表示当前请求为广播。 | |||||
| * @return 单播响应长度;广播或无法响应时返回 0。 | |||||
| * @brief 处理写多个线圈功能码 0x0F | |||||
| * @param[in] request RTU 请求帧 | |||||
| * @param[in] requestLength 请求帧长度 | |||||
| * @param[in] isBroadcast 非 0 表示当前请求为广播 | |||||
| * @return 单播响应长度;广播或无法响应时返回 0 | |||||
| */ | */ | ||||
| static uint16_t ModbusProcessWriteMultipleCoils( | static uint16_t ModbusProcessWriteMultipleCoils( | ||||
| const uint8_t *request, | const uint8_t *request, | ||||
| @@ -511,11 +511,11 @@ static uint16_t ModbusProcessWriteMultipleCoils( | |||||
| } | } | ||||
| /** | /** | ||||
| * @brief 处理写多个保持寄存器功能码 0x10。 | |||||
| * @param[in] request RTU 请求帧。 | |||||
| * @param[in] requestLength 请求帧长度。 | |||||
| * @param[in] isBroadcast 非 0 表示当前请求为广播。 | |||||
| * @return 单播响应长度;广播或无法响应时返回 0。 | |||||
| * @brief 处理写多个保持寄存器功能码 0x10 | |||||
| * @param[in] request RTU 请求帧 | |||||
| * @param[in] requestLength 请求帧长度 | |||||
| * @param[in] isBroadcast 非 0 表示当前请求为广播 | |||||
| * @return 单播响应长度;广播或无法响应时返回 0 | |||||
| */ | */ | ||||
| static uint16_t ModbusProcessWriteMultipleRegisters( | static uint16_t ModbusProcessWriteMultipleRegisters( | ||||
| const uint8_t *request, | const uint8_t *request, | ||||
| @@ -590,7 +590,7 @@ static uint16_t ModbusProcessReadBigHolding( | |||||
| uint16_t index; | uint16_t index; | ||||
| uint16_t value; | uint16_t value; | ||||
| /* 请求帧:站号1 + 功能码1 + 地址3 + 数量2 + CRC2。 */ | |||||
| /* 请求帧:站号1 + 功能码1 + 地址3 + 数量2 + CRC2 */ | |||||
| if (requestLength != 9U) | if (requestLength != 9U) | ||||
| { | { | ||||
| ModbusSlaveStatistics.illegalValueCount++; | ModbusSlaveStatistics.illegalValueCount++; | ||||
| @@ -600,11 +600,11 @@ static uint16_t ModbusProcessReadBigHolding( | |||||
| MODBUS_EX_ILLEGAL_VALUE); | MODBUS_EX_ILLEGAL_VALUE); | ||||
| } | } | ||||
| /* 提取24位起始地址和16位寄存器数量。 */ | |||||
| /* 提取24位起始地址和16位寄存器数量 */ | |||||
| start = ModbusGetU24Be(&request[2]); | start = ModbusGetU24Be(&request[2]); | ||||
| quantity = ModbusGetU16Be(&request[5]); | quantity = ModbusGetU16Be(&request[5]); | ||||
| /* 一次最多读取125个寄存器。 */ | |||||
| /* 一次最多读取125个寄存器 */ | |||||
| if ((quantity == 0U) || | if ((quantity == 0U) || | ||||
| (quantity > MODBUS_READ_REGS_MAX)) | (quantity > MODBUS_READ_REGS_MAX)) | ||||
| { | { | ||||
| @@ -617,7 +617,7 @@ static uint16_t ModbusProcessReadBigHolding( | |||||
| /* | /* | ||||
| * 普通SRAM有40000个寄存器,CCMRAM有29999个寄存器, | * 普通SRAM有40000个寄存器,CCMRAM有29999个寄存器, | ||||
| * 总地址范围为0~69998。 | |||||
| * 总地址范围为0~69998 | |||||
| */ | */ | ||||
| if ((start >= 69999UL) || ((uint32_t)quantity > (69999UL - start))) | if ((start >= 69999UL) || ((uint32_t)quantity > (69999UL - start))) | ||||
| @@ -629,7 +629,7 @@ static uint16_t ModbusProcessReadBigHolding( | |||||
| } | } | ||||
| /* 组成正常响应帧头。 */ | |||||
| /* 组成正常响应帧头 */ | |||||
| ModbusTxFrame[0] = ModbusSlaveAddress; | ModbusTxFrame[0] = ModbusSlaveAddress; | ||||
| ModbusTxFrame[1] = 0x48U; | ModbusTxFrame[1] = 0x48U; | ||||
| ModbusTxFrame[2] = (uint8_t)(quantity * 2U); | ModbusTxFrame[2] = (uint8_t)(quantity * 2U); | ||||
| @@ -640,7 +640,7 @@ static uint16_t ModbusProcessReadBigHolding( | |||||
| /* | /* | ||||
| * 地址0~39999位于普通SRAM; | * 地址0~39999位于普通SRAM; | ||||
| * 地址40000~69998位于CCMRAM。 | |||||
| * 地址40000~69998位于CCMRAM | |||||
| */ | */ | ||||
| if (currentAddress < 40000UL) | if (currentAddress < 40000UL) | ||||
| { | { | ||||
| @@ -652,22 +652,22 @@ static uint16_t ModbusProcessReadBigHolding( | |||||
| } | } | ||||
| /* 每个寄存器按照高字节、低字节装入响应帧。 */ | |||||
| /* 每个寄存器按照高字节、低字节装入响应帧 */ | |||||
| ModbusTxFrame[3U + index * 2U] = (uint8_t)(value >> 8U); | ModbusTxFrame[3U + index * 2U] = (uint8_t)(value >> 8U); | ||||
| ModbusTxFrame[4U + index * 2U] = (uint8_t)(value & 0x00FFU); | ModbusTxFrame[4U + index * 2U] = (uint8_t)(value & 0x00FFU); | ||||
| } | } | ||||
| /* 添加CRC。 */ | |||||
| /* 添加CRC */ | |||||
| ModbusAppendCrc( ModbusTxFrame, (uint16_t)(3U + quantity * 2U)); | ModbusAppendCrc( ModbusTxFrame, (uint16_t)(3U + quantity * 2U)); | ||||
| return (uint16_t)(5U + quantity * 2U); | return (uint16_t)(5U + quantity * 2U); | ||||
| } | } | ||||
| /** | /** | ||||
| * @brief 校验并分发一帧 Modbus RTU 请求。 | |||||
| * @param[in] request RTU 请求帧。 | |||||
| * @param[in] requestLength 请求帧长度。 | |||||
| * @return 待发送响应长度;无需响应时返回 0。 | |||||
| * @brief 校验并分发一帧 Modbus RTU 请求 | |||||
| * @param[in] request RTU 请求帧 | |||||
| * @param[in] requestLength 请求帧长度 | |||||
| * @return 待发送响应长度;无需响应时返回 0 | |||||
| */ | */ | ||||
| static uint16_t ModbusProcessRequest(const uint8_t *request,uint16_t requestLength) | static uint16_t ModbusProcessRequest(const uint8_t *request,uint16_t requestLength) | ||||
| { | { | ||||
| @@ -706,7 +706,7 @@ static uint16_t ModbusProcessRequest(const uint8_t *request,uint16_t requestLeng | |||||
| switch (request[1]) | switch (request[1]) | ||||
| { | { | ||||
| case 0X01U://读线圈 | case 0X01U://读线圈 | ||||
| // 广播请求不允许读取,从站不作响应。 | |||||
| // 广播请求不允许读取,从站不作响应 | |||||
| return (isBroadcast != 0U) ? 0U : | return (isBroadcast != 0U) ? 0U : | ||||
| ModbusProcessReadCoils(request, requestLength); | ModbusProcessReadCoils(request, requestLength); | ||||
| @@ -798,7 +798,7 @@ void ModbusSlavePoll(void) | |||||
| } | } | ||||
| /* | /* | ||||
| * 当前帧;处理完成后再释放帧槽。 | |||||
| * 当前帧;处理完成后再释放帧槽 | |||||
| */ | */ | ||||
| ModbusRxFrameReady = 0U; | ModbusRxFrameReady = 0U; | ||||
| } | } | ||||
| @@ -936,7 +936,7 @@ void ModbusRetainedRegistersLoad(void) | |||||
| /* | /* | ||||
| * 数据初始化完成后最后写magic,避免初始化中途断电 | * 数据初始化完成后最后写magic,避免初始化中途断电 | ||||
| * 却把不完整数据标记成有效。 | |||||
| * 却把不完整数据标记成有效 | |||||
| */ | */ | ||||
| backupData->magic = MODBUS_BACKUP_MAGIC; | backupData->magic = MODBUS_BACKUP_MAGIC; | ||||
| } | } | ||||