| @@ -24,6 +24,7 @@ | |||||
| /* USER CODE BEGIN Includes */ | /* USER CODE BEGIN Includes */ | ||||
| #include "ucos_ii.h" | #include "ucos_ii.h" | ||||
| #include "modbus_rtu_slave.h" | #include "modbus_rtu_slave.h" | ||||
| #include "stdio.h" | |||||
| /* USER CODE END Includes */ | /* USER CODE END Includes */ | ||||
| /* Private typedef -----------------------------------------------------------*/ | /* Private typedef -----------------------------------------------------------*/ | ||||
| @@ -110,6 +111,9 @@ static void AppTaskStart(void *p_arg) | |||||
| 6, | 6, | ||||
| (uint16_t)ModbusSlaveStatistics.illegalValueCount); | (uint16_t)ModbusSlaveStatistics.illegalValueCount); | ||||
| HistorySaveToRegisters(); | |||||
| @@ -122,6 +126,7 @@ static void AppTaskStart(void *p_arg) | |||||
| */ | */ | ||||
| ModbusSlavePoll(); | ModbusSlavePoll(); | ||||
| ModbusRetainedRegistersPoll(); | ModbusRetainedRegistersPoll(); | ||||
| // printf("123"); | |||||
| OSTimeDly(1U); | OSTimeDly(1U); | ||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,81 @@ | |||||
| # Modbus异常码实例 | |||||
| ## 非法功能码 | |||||
| - 请求:`01 02 00 00 00 01 B9 CA` | |||||
| - 请求帧长度:8字节 | |||||
| - 返回帧:`01 82 01 81 60` | |||||
| - 返回帧长度:5字节 | |||||
| - 解释:从站未实现功能码`0x02`,返回功能码`0x82`和异常码`0x01`,表示非法功能。 | |||||
| ## 03功能码读取非法地址 | |||||
| - 请求:`01 03 27 10 00 01 8F 7B` | |||||
| - 请求帧长度:8字节 | |||||
| - 返回帧:`01 83 02 C0 F1` | |||||
| - 返回帧长度:5字节 | |||||
| - 解释:起始地址`0x2710`等于10000,超出标准保持寄存器有效地址`0~9999`,返回异常码`0x02`,表示非法数据地址。 | |||||
| ## 03功能码读取数量为0 | |||||
| - 请求:`01 03 00 00 00 00 45 CA` | |||||
| - 请求帧长度:8字节 | |||||
| - 返回帧:`01 83 03 01 31` | |||||
| - 返回帧长度:5字节 | |||||
| - 解释:读取寄存器数量为0,不符合功能码要求,返回异常码`0x03`,表示非法数据值。 | |||||
| ## 05功能码写入非法线圈值 | |||||
| - 请求:`01 05 00 00 12 34 C0 BD` | |||||
| - 请求帧长度:8字节 | |||||
| - 返回帧:`01 85 03 02 91` | |||||
| - 返回帧长度:5字节 | |||||
| - 解释:05功能码只允许使用`FF 00`表示ON或`00 00`表示OFF,写入值`12 34`非法,返回异常码`0x03`。 | |||||
| ## 06功能码写入非法地址 | |||||
| - 请求:`01 06 27 10 00 01 43 7B` | |||||
| - 请求帧长度:8字节 | |||||
| - 返回帧:`01 86 02 C3 A1` | |||||
| - 返回帧长度:5字节 | |||||
| - 解释:写入地址`0x2710`等于10000,超出标准保持寄存器有效地址`0~9999`,返回异常码`0x02`。 | |||||
| ## 0F功能码字节数错误 | |||||
| - 请求:`01 0F 00 00 00 09 01 01 6E 95` | |||||
| - 请求帧长度:10字节 | |||||
| - 返回帧:`01 8F 03 04 31` | |||||
| - 返回帧长度:5字节 | |||||
| - 解释:请求写入9个线圈时应携带2个数据字节,但帧中的字节数字段为1,返回异常码`0x03`。 | |||||
| ## 10功能码写入数量为0 | |||||
| - 请求:`01 10 00 00 00 00 00 09 50` | |||||
| - 请求帧长度:9字节 | |||||
| - 返回帧:`01 90 03 0C 01` | |||||
| - 返回帧长度:5字节 | |||||
| - 解释:写入寄存器数量为0,不符合功能码要求,返回异常码`0x03`。 | |||||
| ## 48功能码读取非法大地址 | |||||
| - 请求:`01 48 01 11 6F 00 01 CD 69` | |||||
| - 请求帧长度:9字节 | |||||
| - 返回帧:`01 C8 02 F6 01` | |||||
| - 返回帧长度:5字节 | |||||
| - 解释:起始地址`0x01116F`等于69999,超出扩展保持寄存器有效地址`0~69998`,返回异常码`0x02`。 | |||||
| ## 48功能码读取数量为0 | |||||
| - 请求:`01 48 00 00 00 00 00 04 48` | |||||
| - 请求帧长度:9字节 | |||||
| - 返回帧:`01 C8 03 37 C1` | |||||
| - 返回帧长度:5字节 | |||||
| - 解释:读取寄存器数量为0,不符合功能码要求,返回异常码`0x03`。 | |||||
| ## 从站地址不匹配 | |||||
| - 请求:`02 03 00 00 00 01 84 39` | |||||
| - 请求帧长度:8字节 | |||||
| - 返回帧:无 | |||||
| - 返回帧长度:0字节 | |||||
| - 解释:当前从站地址为`0x01`,请求帧的目标从站地址为`0x02`,因此当前从站直接忽略该帧,不返回正常响应或异常响应。 | |||||
| @@ -71,6 +71,17 @@ typedef struct | |||||
| uint32_t uartErrorCount; ///< HAL 串口错误计数。 | uint32_t uartErrorCount; ///< HAL 串口错误计数。 | ||||
| } MODBUS_SLAVE_STATS; | } MODBUS_SLAVE_STATS; | ||||
| typedef struct | |||||
| { | |||||
| uint32_t tick; | |||||
| uint16_t requestLength; | |||||
| uint16_t responseLength; | |||||
| uint8_t request[256]; | |||||
| uint8_t response[256]; | |||||
| } MODBUS_HISTORY_ITEM; | |||||
| /** @brief Modbus 从站通信统计数据。 */ | /** @brief Modbus 从站通信统计数据。 */ | ||||
| extern volatile MODBUS_SLAVE_STATS ModbusSlaveStatistics; | extern volatile MODBUS_SLAVE_STATS ModbusSlaveStatistics; | ||||
| @@ -82,8 +93,8 @@ extern volatile MODBUS_SLAVE_STATS ModbusSlaveStatistics; | |||||
| * @retval HAL_ERROR 输入参数无效。 | * @retval HAL_ERROR 输入参数无效。 | ||||
| * @retval HAL_BUSY 串口或 DMA 当前忙。 | * @retval HAL_BUSY 串口或 DMA 当前忙。 | ||||
| */ | */ | ||||
| HAL_StatusTypeDef ModbusSlaveInit(UART_HandleTypeDef *huart, | |||||
| uint8_t slaveAddress); | |||||
| HAL_StatusTypeDef ModbusSlaveInit(UART_HandleTypeDef *huart, uint8_t slaveAddress); | |||||
| /** | /** | ||||
| * @brief 校验并处理一帧待处理请求。 | * @brief 校验并处理一帧待处理请求。 | ||||
| @@ -149,6 +160,13 @@ uint8_t ModbusSlaveGetCoil(uint16_t address, uint8_t *state); | |||||
| uint8_t ModbusSlaveIsConnected(uint32_t timeoutMs); | uint8_t ModbusSlaveIsConnected(uint32_t timeoutMs); | ||||
| void ModbusRetainedRegistersLoad(void); | void ModbusRetainedRegistersLoad(void); | ||||
| void ModbusRetainedRegistersPoll(void); | void ModbusRetainedRegistersPoll(void); | ||||
| void HistorySaveToRegisters(void); | |||||
| static void ModbusHistorySave( | |||||
| uint32_t tick, | |||||
| const uint8_t *request, | |||||
| uint16_t requestLength, | |||||
| const uint8_t *response, | |||||
| uint16_t responseLength); | |||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||
| } | } | ||||
| #endif | #endif | ||||
| @@ -1,5 +1,5 @@ | |||||
| #include "modbus_rtu_slave.h" | #include "modbus_rtu_slave.h" | ||||
| #include "ucos_ii.h" | |||||
| #include <string.h> | #include <string.h> | ||||
| #define MODBUS_RTU_ADU_SIZE_MAX (256U)//Modbus RTU 最大 ADU 长度,单位为字节 | #define MODBUS_RTU_ADU_SIZE_MAX (256U)//Modbus RTU 最大 ADU 长度,单位为字节 | ||||
| @@ -24,6 +24,8 @@ | |||||
| static UART_HandleTypeDef *ModbusUart; | static UART_HandleTypeDef *ModbusUart; | ||||
| static uint8_t ModbusSlaveAddress; | static uint8_t ModbusSlaveAddress; | ||||
| static MODBUS_HISTORY_ITEM ModbusHistory[16]; | |||||
| static uint16_t ModbusHistoryWriteIndex; | |||||
| /** | /** | ||||
| * DMA 缓冲区只由 DMA 写入;帧缓冲区在接收回调中完成一次快照, | * DMA 缓冲区只由 DMA 写入;帧缓冲区在接收回调中完成一次快照, | ||||
| * 随后由任务解析,避免 DMA 重启后覆盖尚未处理的数据 | * 随后由任务解析,避免 DMA 重启后覆盖尚未处理的数据 | ||||
| @@ -752,7 +754,12 @@ void ModbusSlavePoll(void) | |||||
| responseLength = ModbusProcessRequest(ModbusRxFrame, ModbusRxFrameLength); | responseLength = ModbusProcessRequest(ModbusRxFrame, ModbusRxFrameLength); | ||||
| ModbusHistorySave(OSTimeGet() / 1000,ModbusRxFrame, | |||||
| ModbusRxFrameLength, | |||||
| ModbusTxFrame, | |||||
| responseLength); | |||||
| if (responseLength > 0U) | if (responseLength > 0U) | ||||
| { | { | ||||
| @@ -941,3 +948,72 @@ void ModbusRetainedRegistersPoll(void) | |||||
| } | } | ||||
| static void ModbusHistorySave( | |||||
| uint32_t tick, | |||||
| const uint8_t *request, | |||||
| uint16_t requestLength, | |||||
| const uint8_t *response, | |||||
| uint16_t responseLength) | |||||
| { | |||||
| MODBUS_HISTORY_ITEM *item; | |||||
| if(requestLength > 256U) | |||||
| { | |||||
| requestLength = 256U; | |||||
| } | |||||
| if(responseLength > 256U) | |||||
| { | |||||
| responseLength = 256U; | |||||
| } | |||||
| item = &ModbusHistory[ModbusHistoryWriteIndex]; | |||||
| item->tick = tick; | |||||
| item->requestLength = requestLength; | |||||
| item->responseLength = responseLength; | |||||
| (void)memcpy( | |||||
| item->request, | |||||
| request, | |||||
| requestLength); | |||||
| if(responseLength > 0U) | |||||
| { | |||||
| (void)memcpy( | |||||
| item->response, | |||||
| response, | |||||
| responseLength); | |||||
| } | |||||
| ModbusHistoryWriteIndex++; | |||||
| if(ModbusHistoryWriteIndex >= 16U) | |||||
| { | |||||
| ModbusHistoryWriteIndex = 0U; | |||||
| } | |||||
| } | |||||
| void HistorySaveToRegisters(void) | |||||
| { | |||||
| // (void)memcpy( | |||||
| // &ModbusHoldingRegisters[1000], | |||||
| // ModbusHistory, | |||||
| // sizeof(ModbusHistory)); | |||||
| uint16_t index; | |||||
| if(ModbusHistoryWriteIndex == 0U) | |||||
| { | |||||
| index = 15U; | |||||
| } | |||||
| else | |||||
| { | |||||
| index = ModbusHistoryWriteIndex - 1U; | |||||
| } | |||||
| (void)memcpy( | |||||
| &ModbusHoldingRegisters[ | |||||
| 1000U + index * 260U], | |||||
| &ModbusHistory[index], | |||||
| sizeof(ModbusHistory[index])); | |||||
| } | |||||