| @@ -124,4 +124,18 @@ debug | |||||
| *.py[cod] | *.py[cod] | ||||
| *$py.class | *$py.class | ||||
| deps/ | deps/ | ||||
| .deps_cache/ | |||||
| .deps_cache/ | |||||
| # ---> IAR EWARM generated build artifacts | |||||
| /EWARM/*.dep | |||||
| /EWARM/*/Obj/ | |||||
| /EWARM/*/List/*.map | |||||
| /EWARM/*/Exe/*.out | |||||
| /EWARM/*/Exe/*.sim | |||||
| *.stackdump | |||||
| # 项目主机测试二进制文件不纳入固件交付物。 | |||||
| /test/bin/ | |||||
| # Keep the deployable firmware image available in the project tree. | |||||
| !EWARM/Modbus/Exe/Modbus.hex | |||||
| @@ -1,11 +1,11 @@ | |||||
| /** | /** | ||||
| * @file main.h | * @file main.h | ||||
| * @brief 主程序公共定义和 PLC 输出引脚映射。 | |||||
| * @brief 主程序公共定义、PLC 输出和连接指示灯引脚映射。 | |||||
| * @version 2.0 | * @version 2.0 | ||||
| * @author zengbingjie | * @author zengbingjie | ||||
| * @date 2026-07-29 | * @date 2026-07-29 | ||||
| * @copyright 版权所有 (c) 2026 Xinje Electric。 | |||||
| * @note 修改记录:2.0 将线圈 0 至 7 映射到 Q0 至 Q7。 | |||||
| * @copyright 版权所有信捷科技股份有限公司 | |||||
| * @note 修改记录:2.0 将 PF6 用作 Modbus 连接指示灯。 | |||||
| */ | */ | ||||
| #ifndef MAIN_H | #ifndef MAIN_H | ||||
| @@ -18,11 +18,50 @@ extern "C" { | |||||
| #include "stm32f4xx_hal.h" | #include "stm32f4xx_hal.h" | ||||
| #include "app_cfg.h" | #include "app_cfg.h" | ||||
| #include "modbus.h" | |||||
| #include "os_cfg.h" | #include "os_cfg.h" | ||||
| #include "ucos_ii.h" | #include "ucos_ii.h" | ||||
| /** @brief USART1 的 HAL 句柄,仅由初始化和中断回调路径共享。 */ | |||||
| extern UART_HandleTypeDef Uart1Handle; | extern UART_HandleTypeDef Uart1Handle; | ||||
| /** | |||||
| * @brief 处理 TIM5 更新中断中的 RTU 静默计时状态。 | |||||
| * @note 本函数由 TIM5_IRQHandler 在 OSIntEnter/OSIntExit 之间调用, | |||||
| * 不执行 Modbus 帧解析或阻塞操作。 | |||||
| */ | |||||
| void AppModbusRtuTimerIrqHandler(void); | |||||
| /** | |||||
| * @brief 普通保持寄存器数据区,可在 IAR Live Watch 中直接查看或调试写入。 | |||||
| * @note 地址范围为 0x0000 至 0x270F;该数组不参与掉电保持。 | |||||
| */ | |||||
| extern volatile uint16_t holdingRegisters[MODBUS_DATA_POINT_COUNT]; | |||||
| /** | |||||
| * @brief 按位压缩的普通线圈数据区,可在 IAR Live Watch 中直接查看。 | |||||
| * @note 地址范围为 0x0000 至 0x270F;线圈 1 至 7 在有效写入后同步至 Q1 至 Q7。 | |||||
| */ | |||||
| extern volatile uint8_t coilStorage[MODBUS_COIL_STORAGE_SIZE]; | |||||
| /** | |||||
| * @brief 私有地址 0x00010000 对应的唯一扩展保持寄存器。 | |||||
| * @note 仅此变量由 0x42 成功写入后的 RTC 备份流程保存和恢复。 | |||||
| */ | |||||
| extern volatile uint16_t extendedHoldingRegister; | |||||
| /** @brief USART1 恢复诊断量,供 IAR Live Watch 定位断线重连问题。 */ | |||||
| extern volatile uint32_t ModbusUartLastErrorCode; | |||||
| extern volatile uint32_t ModbusUartLastRxState; | |||||
| extern volatile uint32_t ModbusUartRecoveryCount; | |||||
| extern volatile uint32_t ModbusUartRecoveryFailureCount; | |||||
| extern volatile uint32_t ModbusUartRxArmCount; | |||||
| extern volatile uint32_t ModbusUartRxArmFailureCount; | |||||
| extern volatile uint8_t ModbusUartLastRxByte; | |||||
| extern volatile uint16_t ModbusLastCompleteFrameLength; | |||||
| /* Q0 保留给低电平有效的通信/连接指示灯。Modbus 用户线圈从线圈 1 开始, | |||||
| * 因此 Q1..Q7 作为 7 个可控输出。 */ | |||||
| #define PLC_Q0_PORT (GPIOF) | #define PLC_Q0_PORT (GPIOF) | ||||
| #define PLC_Q0_PIN (GPIO_PIN_6) | #define PLC_Q0_PIN (GPIO_PIN_6) | ||||
| #define PLC_Q1_PORT (GPIOF) | #define PLC_Q1_PORT (GPIOF) | ||||
| @@ -40,6 +79,10 @@ extern UART_HandleTypeDef Uart1Handle; | |||||
| #define PLC_Q7_PORT (GPIOE) | #define PLC_Q7_PORT (GPIOE) | ||||
| #define PLC_Q7_PIN (GPIO_PIN_4) | #define PLC_Q7_PIN (GPIO_PIN_4) | ||||
| #define PLC_LINK_LED_PORT (PLC_Q0_PORT) | |||||
| #define PLC_LINK_LED_PIN (PLC_Q0_PIN) | |||||
| /** @brief 关闭中断并停留在不可恢复错误状态。 */ | |||||
| void Error_Handler(void); | void Error_Handler(void); | ||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||
| @@ -56,6 +56,11 @@ void SVC_Handler(void); | |||||
| void DebugMon_Handler(void); | void DebugMon_Handler(void); | ||||
| void PendSV_Handler(void); | void PendSV_Handler(void); | ||||
| void SysTick_Handler(void); | void SysTick_Handler(void); | ||||
| /** @brief 将 TIM5 更新中断转交给 Modbus RTU 静默计时状态机。 */ | |||||
| void TIM5_IRQHandler(void); | |||||
| /** @brief 将 USART1 收发事件转交给 HAL 回调和 Modbus 接收路径。 */ | |||||
| void USART1_IRQHandler(void); | void USART1_IRQHandler(void); | ||||
| /* USER CODE BEGIN EFP */ | /* USER CODE BEGIN EFP */ | ||||
| @@ -4,12 +4,14 @@ | |||||
| * @version 2.0 | * @version 2.0 | ||||
| * @author zengbingjie | * @author zengbingjie | ||||
| * @date 2026-07-29 | * @date 2026-07-29 | ||||
| * @copyright 版权所有 (c) 2026 Xinje Electric。 | |||||
| * @copyright 版权所有信捷科技股份有限公司 | |||||
| * @note 修改记录:2.0 增加严格 RTU 校验和广播处理。 | * @note 修改记录:2.0 增加严格 RTU 校验和广播处理。 | ||||
| */ | */ | ||||
| #include "modbus.h" | #include "modbus.h" | ||||
| /* RTU ADU 布局为:地址和功能码在前,后面是 PDU 和两字节 CRC。 | |||||
| * 以下偏移量由所有请求和响应处理路径共用。 */ | |||||
| #define MODBUS_RTU_ADDRESS_INDEX (0U) | #define MODBUS_RTU_ADDRESS_INDEX (0U) | ||||
| #define MODBUS_RTU_FUNCTION_INDEX (1U) | #define MODBUS_RTU_FUNCTION_INDEX (1U) | ||||
| #define MODBUS_RTU_DATA_INDEX (2U) | #define MODBUS_RTU_DATA_INDEX (2U) | ||||
| @@ -24,7 +26,10 @@ | |||||
| typedef enum { | typedef enum { | ||||
| MODBUS_REQUEST_OK = 0, | MODBUS_REQUEST_OK = 0, | ||||
| MODBUS_REQUEST_ILLEGAL_ADDRESS = MODBUS_EXCEPTION_ILLEGAL_DATA_ADDR, | MODBUS_REQUEST_ILLEGAL_ADDRESS = MODBUS_EXCEPTION_ILLEGAL_DATA_ADDR, | ||||
| MODBUS_REQUEST_ILLEGAL_VALUE = MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE | |||||
| MODBUS_REQUEST_ILLEGAL_VALUE = MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, | |||||
| /* 0xFF 仅作为内部哨兵值,绝不会作为 Modbus 异常码发送。 | |||||
| * 调用者会将上面的两类协议错误转换为异常响应 0x02 和 0x03。 */ | |||||
| MODBUS_REQUEST_RESPONSE_ERROR = 0xFFU | |||||
| } MODBUS_REQUEST_RESULT; | } MODBUS_REQUEST_RESULT; | ||||
| /** | /** | ||||
| @@ -51,6 +56,8 @@ static uint8_t ModbusIsUnicastAddress(uint8_t slaveAddress) | |||||
| static uint8_t ModbusIsDataRangeValid(uint16_t startAddress, | static uint8_t ModbusIsDataRangeValid(uint16_t startAddress, | ||||
| uint16_t dataCount) | uint16_t dataCount) | ||||
| { | { | ||||
| /* 使用减法而不是 startAddress + dataCount,避免 16 位溢出后错误地将越界 | |||||
| * 请求判断为有效。 */ | |||||
| if ((dataCount == 0U) || (startAddress > MODBUS_DATA_ADDRESS_MAX)) { | if ((dataCount == 0U) || (startAddress > MODBUS_DATA_ADDRESS_MAX)) { | ||||
| return 0U; | return 0U; | ||||
| } | } | ||||
| @@ -62,6 +69,23 @@ static uint8_t ModbusIsDataRangeValid(uint16_t startAddress, | |||||
| return 1U; | return 1U; | ||||
| } | } | ||||
| /** | |||||
| * @brief 校验私有 32 位地址是否唯一指向扩展保持寄存器。 | |||||
| * @param[in] slave 从站数据对象。 | |||||
| * @param[in] address 私有功能码帧内的 32 位地址。 | |||||
| * @return 地址为 0x00010000 且扩展存储已绑定时返回 1,否则返回 0。 | |||||
| */ | |||||
| static uint8_t ModbusIsExtendedRegisterAddressValid( | |||||
| const MODBUS_SLAVE *slave, uint32_t address) | |||||
| { | |||||
| if ((slave == NULL) || (slave->extendedHoldingRegister == NULL) | |||||
| || (address != MODBUS_EXTENDED_HOLDING_ADDRESS)) { | |||||
| return 0U; | |||||
| } | |||||
| return 1U; | |||||
| } | |||||
| /** | /** | ||||
| * @brief 从 Modbus ADU 中读取一个大端序 16 位数值。 | * @brief 从 Modbus ADU 中读取一个大端序 16 位数值。 | ||||
| * @param[in] buffer 数据缓冲区。 | * @param[in] buffer 数据缓冲区。 | ||||
| @@ -71,11 +95,42 @@ static uint16_t ModbusReadU16(const uint8_t *buffer) | |||||
| { | { | ||||
| uint16_t value; | uint16_t value; | ||||
| /* Modbus 字段采用大端序(高字节在前),但 CRC 在总线上按低字节在前发送。 */ | |||||
| value = ((uint16_t)buffer[0] << 8U) | (uint16_t)buffer[1]; | value = ((uint16_t)buffer[0] << 8U) | (uint16_t)buffer[1]; | ||||
| return value; | return value; | ||||
| } | } | ||||
| /** | |||||
| * @brief 从私有功能码 PDU 读取大端序 32 位地址。 | |||||
| * @param[in] buffer 至少包含 4 个字节的 PDU 字段。 | |||||
| * @return 解析后的 32 位地址。 | |||||
| */ | |||||
| static uint32_t ModbusReadU32(const uint8_t *buffer) | |||||
| { | |||||
| uint32_t value; | |||||
| value = ((uint32_t)buffer[0] << 24U) | |||||
| | ((uint32_t)buffer[1] << 16U) | |||||
| | ((uint32_t)buffer[2] << 8U) | |||||
| | (uint32_t)buffer[3]; | |||||
| return value; | |||||
| } | |||||
| /** | |||||
| * @brief 向私有功能码 PDU 写入大端序 32 位地址。 | |||||
| * @param[out] buffer 至少可容纳 4 个字节的 PDU 字段。 | |||||
| * @param[in] value 待写入的 32 位地址。 | |||||
| */ | |||||
| static void ModbusWriteU32(uint8_t *buffer, uint32_t value) | |||||
| { | |||||
| buffer[0] = (uint8_t)(value >> 24U); | |||||
| buffer[1] = (uint8_t)(value >> 16U); | |||||
| buffer[2] = (uint8_t)(value >> 8U); | |||||
| buffer[3] = (uint8_t)value; | |||||
| } | |||||
| /** | /** | ||||
| * @brief 向 Modbus ADU 中写入一个大端序 16 位数值。 | * @brief 向 Modbus ADU 中写入一个大端序 16 位数值。 | ||||
| * @param[out] buffer 数据缓冲区。 | * @param[out] buffer 数据缓冲区。 | ||||
| @@ -108,6 +163,7 @@ static MODBUS_STATUS ModbusAppendCrc(uint8_t *buffer, uint16_t capacity, | |||||
| return MODBUS_STATUS_FRAME_ERROR; | return MODBUS_STATUS_FRAME_ERROR; | ||||
| } | } | ||||
| /* 按 Modbus RTU 要求,以小端序追加 CRC 值。 */ | |||||
| crcValue = ModbusCrc16(buffer, *length); | crcValue = ModbusCrc16(buffer, *length); | ||||
| buffer[*length] = (uint8_t)(crcValue & 0x00FFU); | buffer[*length] = (uint8_t)(crcValue & 0x00FFU); | ||||
| buffer[*length + 1U] = (uint8_t)(crcValue >> 8U); | buffer[*length + 1U] = (uint8_t)(crcValue >> 8U); | ||||
| @@ -135,6 +191,7 @@ static MODBUS_STATUS ModbusValidateCrc(const uint8_t *buffer, uint16_t length) | |||||
| return MODBUS_STATUS_FRAME_ERROR; | return MODBUS_STATUS_FRAME_ERROR; | ||||
| } | } | ||||
| /* 对去掉末尾两个字节的 ADU 重新计算 CRC,再与收到的低字节/高字节表示进行比较。 */ | |||||
| expectedCrc = ModbusCrc16(buffer, length - MODBUS_RTU_CRC_LENGTH); | expectedCrc = ModbusCrc16(buffer, length - MODBUS_RTU_CRC_LENGTH); | ||||
| actualCrc = (uint16_t)buffer[length - MODBUS_RTU_CRC_LENGTH] | actualCrc = (uint16_t)buffer[length - MODBUS_RTU_CRC_LENGTH] | ||||
| | ((uint16_t)buffer[length - 1U] << 8U); | | ((uint16_t)buffer[length - 1U] << 8U); | ||||
| @@ -168,10 +225,15 @@ static MODBUS_STATUS ModbusBuildException(uint8_t slaveAddress, | |||||
| return MODBUS_STATUS_INVALID_ARGUMENT; | return MODBUS_STATUS_INVALID_ARGUMENT; | ||||
| } | } | ||||
| if ((functionCode & MODBUS_RTU_EXCEPTION_MASK) != 0U) { | |||||
| return MODBUS_STATUS_FRAME_ERROR; | |||||
| } | |||||
| if (txCapacity < MODBUS_RTU_EXCEPTION_LENGTH) { | if (txCapacity < MODBUS_RTU_EXCEPTION_LENGTH) { | ||||
| return MODBUS_STATUS_FRAME_ERROR; | return MODBUS_STATUS_FRAME_ERROR; | ||||
| } | } | ||||
| /* 标准异常响应格式为:原功能码 | 0x80,后跟一个字节的异常码和重新计算的 CRC。 */ | |||||
| txBuffer[MODBUS_RTU_ADDRESS_INDEX] = slaveAddress; | txBuffer[MODBUS_RTU_ADDRESS_INDEX] = slaveAddress; | ||||
| txBuffer[MODBUS_RTU_FUNCTION_INDEX] = functionCode | txBuffer[MODBUS_RTU_FUNCTION_INDEX] = functionCode | ||||
| | MODBUS_RTU_EXCEPTION_MASK; | | MODBUS_RTU_EXCEPTION_MASK; | ||||
| @@ -197,6 +259,8 @@ static uint8_t ModbusReadCoilBit(const MODBUS_SLAVE *slave, uint16_t address) | |||||
| uint8_t bitIndex; | uint8_t bitIndex; | ||||
| uint8_t value; | uint8_t value; | ||||
| /* 线圈按低位在前压缩:线圈 0 是字节 0 的 bit 0,线圈 8 是字节 1 的 bit 0。 | |||||
| * 构造 0x01/0x0F 帧时使用相同的映射关系。 */ | |||||
| byteIndex = address / MODBUS_BITS_PER_BYTE; | byteIndex = address / MODBUS_BITS_PER_BYTE; | ||||
| bitIndex = (uint8_t)(address % MODBUS_BITS_PER_BYTE); | bitIndex = (uint8_t)(address % MODBUS_BITS_PER_BYTE); | ||||
| value = (uint8_t)((slave->coilStorage[byteIndex] >> bitIndex) & 0x01U); | value = (uint8_t)((slave->coilStorage[byteIndex] >> bitIndex) & 0x01U); | ||||
| @@ -217,6 +281,7 @@ static void ModbusWriteCoilBit(MODBUS_SLAVE *slave, uint16_t address, | |||||
| uint8_t bitIndex; | uint8_t bitIndex; | ||||
| uint8_t bitMask; | uint8_t bitMask; | ||||
| /* 只更新选中的位,保持同一压缩字节中相邻线圈的值不变。 */ | |||||
| byteIndex = address / MODBUS_BITS_PER_BYTE; | byteIndex = address / MODBUS_BITS_PER_BYTE; | ||||
| bitIndex = (uint8_t)(address % MODBUS_BITS_PER_BYTE); | bitIndex = (uint8_t)(address % MODBUS_BITS_PER_BYTE); | ||||
| bitMask = (uint8_t)(1U << bitIndex); | bitMask = (uint8_t)(1U << bitIndex); | ||||
| @@ -257,7 +322,7 @@ static MODBUS_REQUEST_RESULT ModbusBuildReadCoilsResponse( | |||||
| byteCount = (coilCount + 7U) / MODBUS_BITS_PER_BYTE; | byteCount = (coilCount + 7U) / MODBUS_BITS_PER_BYTE; | ||||
| if (txCapacity < (3U + byteCount + MODBUS_RTU_CRC_LENGTH)) { | if (txCapacity < (3U + byteCount + MODBUS_RTU_CRC_LENGTH)) { | ||||
| return MODBUS_REQUEST_ILLEGAL_VALUE; | |||||
| return MODBUS_REQUEST_RESPONSE_ERROR; | |||||
| } | } | ||||
| txBuffer[MODBUS_RTU_ADDRESS_INDEX] = slave->slaveAddress; | txBuffer[MODBUS_RTU_ADDRESS_INDEX] = slave->slaveAddress; | ||||
| @@ -267,6 +332,7 @@ static MODBUS_REQUEST_RESULT ModbusBuildReadCoilsResponse( | |||||
| txBuffer[3U + byteIndex] = 0U; | txBuffer[3U + byteIndex] = 0U; | ||||
| } | } | ||||
| /* 响应将第一个请求线圈放入 bit 0,并保持最后一个数据字节中未使用的高位为 0。 */ | |||||
| for (coilOffset = 0U; coilOffset < coilCount; coilOffset++) { | for (coilOffset = 0U; coilOffset < coilCount; coilOffset++) { | ||||
| if (ModbusReadCoilBit(slave, startAddress + coilOffset) != 0U) { | if (ModbusReadCoilBit(slave, startAddress + coilOffset) != 0U) { | ||||
| byteIndex = coilOffset / MODBUS_BITS_PER_BYTE; | byteIndex = coilOffset / MODBUS_BITS_PER_BYTE; | ||||
| @@ -278,7 +344,7 @@ static MODBUS_REQUEST_RESULT ModbusBuildReadCoilsResponse( | |||||
| *txLength = 3U + byteCount; | *txLength = 3U + byteCount; | ||||
| status = ModbusAppendCrc(txBuffer, txCapacity, txLength); | status = ModbusAppendCrc(txBuffer, txCapacity, txLength); | ||||
| if (status != MODBUS_STATUS_OK) { | if (status != MODBUS_STATUS_OK) { | ||||
| return MODBUS_REQUEST_ILLEGAL_VALUE; | |||||
| return MODBUS_REQUEST_RESPONSE_ERROR; | |||||
| } | } | ||||
| return MODBUS_REQUEST_OK; | return MODBUS_REQUEST_OK; | ||||
| @@ -314,7 +380,7 @@ static MODBUS_REQUEST_RESULT ModbusBuildReadRegistersResponse( | |||||
| byteCount = registerCount * 2U; | byteCount = registerCount * 2U; | ||||
| if (txCapacity < (3U + byteCount + MODBUS_RTU_CRC_LENGTH)) { | if (txCapacity < (3U + byteCount + MODBUS_RTU_CRC_LENGTH)) { | ||||
| return MODBUS_REQUEST_ILLEGAL_VALUE; | |||||
| return MODBUS_REQUEST_RESPONSE_ERROR; | |||||
| } | } | ||||
| txBuffer[MODBUS_RTU_ADDRESS_INDEX] = slave->slaveAddress; | txBuffer[MODBUS_RTU_ADDRESS_INDEX] = slave->slaveAddress; | ||||
| @@ -329,7 +395,7 @@ static MODBUS_REQUEST_RESULT ModbusBuildReadRegistersResponse( | |||||
| *txLength = 3U + byteCount; | *txLength = 3U + byteCount; | ||||
| status = ModbusAppendCrc(txBuffer, txCapacity, txLength); | status = ModbusAppendCrc(txBuffer, txCapacity, txLength); | ||||
| if (status != MODBUS_STATUS_OK) { | if (status != MODBUS_STATUS_OK) { | ||||
| return MODBUS_REQUEST_ILLEGAL_VALUE; | |||||
| return MODBUS_REQUEST_RESPONSE_ERROR; | |||||
| } | } | ||||
| return MODBUS_REQUEST_OK; | return MODBUS_REQUEST_OK; | ||||
| @@ -355,6 +421,8 @@ static MODBUS_REQUEST_RESULT ModbusProcessWriteSingleCoil( | |||||
| uint16_t coilValue; | uint16_t coilValue; | ||||
| uint16_t index; | uint16_t index; | ||||
| /* 功能码 0x05 的 ADU 固定为 8 字节,必须检查两种合法线圈值;接受任意非零值 | |||||
| * 会违反 Modbus 规范。 */ | |||||
| if (rxLength != MODBUS_RTU_READ_REQUEST_LENGTH) { | if (rxLength != MODBUS_RTU_READ_REQUEST_LENGTH) { | ||||
| return MODBUS_REQUEST_ILLEGAL_VALUE; | return MODBUS_REQUEST_ILLEGAL_VALUE; | ||||
| } | } | ||||
| @@ -370,16 +438,18 @@ static MODBUS_REQUEST_RESULT ModbusProcessWriteSingleCoil( | |||||
| return MODBUS_REQUEST_ILLEGAL_VALUE; | return MODBUS_REQUEST_ILLEGAL_VALUE; | ||||
| } | } | ||||
| if ((isBroadcast == 0U) | |||||
| && (txCapacity < MODBUS_RTU_WRITE_RESPONSE_LENGTH)) { | |||||
| return MODBUS_REQUEST_RESPONSE_ERROR; | |||||
| } | |||||
| ModbusWriteCoilBit(slave, address, (uint8_t)(coilValue != 0U)); | ModbusWriteCoilBit(slave, address, (uint8_t)(coilValue != 0U)); | ||||
| if (isBroadcast != 0U) { | if (isBroadcast != 0U) { | ||||
| *txLength = 0U; | *txLength = 0U; | ||||
| return MODBUS_REQUEST_OK; | return MODBUS_REQUEST_OK; | ||||
| } | } | ||||
| if (txCapacity < MODBUS_RTU_WRITE_RESPONSE_LENGTH) { | |||||
| return MODBUS_REQUEST_ILLEGAL_VALUE; | |||||
| } | |||||
| /* 单播写响应必须完整回显请求(包括原 CRC)。广播写入会执行,但按规范不发送响应。 */ | |||||
| for (index = 0U; index < rxLength; index++) { | for (index = 0U; index < rxLength; index++) { | ||||
| txBuffer[index] = rxBuffer[index]; | txBuffer[index] = rxBuffer[index]; | ||||
| } | } | ||||
| @@ -416,16 +486,17 @@ static MODBUS_REQUEST_RESULT ModbusProcessWriteSingleRegister( | |||||
| return MODBUS_REQUEST_ILLEGAL_ADDRESS; | return MODBUS_REQUEST_ILLEGAL_ADDRESS; | ||||
| } | } | ||||
| if ((isBroadcast == 0U) | |||||
| && (txCapacity < MODBUS_RTU_WRITE_RESPONSE_LENGTH)) { | |||||
| return MODBUS_REQUEST_RESPONSE_ERROR; | |||||
| } | |||||
| slave->holdingRegisters[address] = ModbusReadU16(&rxBuffer[4U]); | slave->holdingRegisters[address] = ModbusReadU16(&rxBuffer[4U]); | ||||
| if (isBroadcast != 0U) { | if (isBroadcast != 0U) { | ||||
| *txLength = 0U; | *txLength = 0U; | ||||
| return MODBUS_REQUEST_OK; | return MODBUS_REQUEST_OK; | ||||
| } | } | ||||
| if (txCapacity < MODBUS_RTU_WRITE_RESPONSE_LENGTH) { | |||||
| return MODBUS_REQUEST_ILLEGAL_VALUE; | |||||
| } | |||||
| for (index = 0U; index < rxLength; index++) { | for (index = 0U; index < rxLength; index++) { | ||||
| txBuffer[index] = rxBuffer[index]; | txBuffer[index] = rxBuffer[index]; | ||||
| } | } | ||||
| @@ -456,6 +527,8 @@ static MODBUS_REQUEST_RESULT ModbusProcessWriteMultipleCoils( | |||||
| uint16_t coilOffset; | uint16_t coilOffset; | ||||
| uint8_t coilValue; | uint8_t coilValue; | ||||
| /* 读取数据或修改线圈前,先校验数量、字节数和总长度,防止畸形帧只修改请求范围 | |||||
| * 的前一部分。 */ | |||||
| if (rxLength < MODBUS_RTU_WRITE_REQUEST_BASE_LENGTH) { | if (rxLength < MODBUS_RTU_WRITE_REQUEST_BASE_LENGTH) { | ||||
| return MODBUS_REQUEST_ILLEGAL_VALUE; | return MODBUS_REQUEST_ILLEGAL_VALUE; | ||||
| } | } | ||||
| @@ -479,6 +552,12 @@ static MODBUS_REQUEST_RESULT ModbusProcessWriteMultipleCoils( | |||||
| return MODBUS_REQUEST_ILLEGAL_ADDRESS; | return MODBUS_REQUEST_ILLEGAL_ADDRESS; | ||||
| } | } | ||||
| if ((isBroadcast == 0U) | |||||
| && (txCapacity < MODBUS_RTU_WRITE_RESPONSE_LENGTH)) { | |||||
| return MODBUS_REQUEST_RESPONSE_ERROR; | |||||
| } | |||||
| /* 请求数据区中的位采用与 0x01 相同的低位在前压缩方式。 */ | |||||
| for (coilOffset = 0U; coilOffset < coilCount; coilOffset++) { | for (coilOffset = 0U; coilOffset < coilCount; coilOffset++) { | ||||
| coilValue = (uint8_t)((rxBuffer[MODBUS_RTU_WRITE_DATA_INDEX | coilValue = (uint8_t)((rxBuffer[MODBUS_RTU_WRITE_DATA_INDEX | ||||
| + (coilOffset / MODBUS_BITS_PER_BYTE)] | + (coilOffset / MODBUS_BITS_PER_BYTE)] | ||||
| @@ -491,17 +570,13 @@ static MODBUS_REQUEST_RESULT ModbusProcessWriteMultipleCoils( | |||||
| return MODBUS_REQUEST_OK; | return MODBUS_REQUEST_OK; | ||||
| } | } | ||||
| if (txCapacity < MODBUS_RTU_WRITE_RESPONSE_LENGTH) { | |||||
| return MODBUS_REQUEST_ILLEGAL_VALUE; | |||||
| } | |||||
| txBuffer[MODBUS_RTU_ADDRESS_INDEX] = slave->slaveAddress; | txBuffer[MODBUS_RTU_ADDRESS_INDEX] = slave->slaveAddress; | ||||
| txBuffer[MODBUS_RTU_FUNCTION_INDEX] = MODBUS_FC_WRITE_MULTIPLE_COILS; | txBuffer[MODBUS_RTU_FUNCTION_INDEX] = MODBUS_FC_WRITE_MULTIPLE_COILS; | ||||
| ModbusWriteU16(&txBuffer[2U], address); | ModbusWriteU16(&txBuffer[2U], address); | ||||
| ModbusWriteU16(&txBuffer[4U], coilCount); | ModbusWriteU16(&txBuffer[4U], coilCount); | ||||
| *txLength = 6U; | *txLength = 6U; | ||||
| if (ModbusAppendCrc(txBuffer, txCapacity, txLength) != MODBUS_STATUS_OK) { | if (ModbusAppendCrc(txBuffer, txCapacity, txLength) != MODBUS_STATUS_OK) { | ||||
| return MODBUS_REQUEST_ILLEGAL_VALUE; | |||||
| return MODBUS_REQUEST_RESPONSE_ERROR; | |||||
| } | } | ||||
| return MODBUS_REQUEST_OK; | return MODBUS_REQUEST_OK; | ||||
| @@ -528,6 +603,8 @@ static MODBUS_REQUEST_RESULT ModbusProcessWriteMultipleRegisters( | |||||
| uint16_t byteCount; | uint16_t byteCount; | ||||
| uint16_t registerOffset; | uint16_t registerOffset; | ||||
| /* 功能码 0x10 为每个寄存器携带一个大端序 16 位值。所有帧结构检查都在写入循环 | |||||
| * 之前完成,避免部分写入。 */ | |||||
| if (rxLength < MODBUS_RTU_WRITE_REQUEST_BASE_LENGTH) { | if (rxLength < MODBUS_RTU_WRITE_REQUEST_BASE_LENGTH) { | ||||
| return MODBUS_REQUEST_ILLEGAL_VALUE; | return MODBUS_REQUEST_ILLEGAL_VALUE; | ||||
| } | } | ||||
| @@ -552,6 +629,11 @@ static MODBUS_REQUEST_RESULT ModbusProcessWriteMultipleRegisters( | |||||
| return MODBUS_REQUEST_ILLEGAL_ADDRESS; | return MODBUS_REQUEST_ILLEGAL_ADDRESS; | ||||
| } | } | ||||
| if ((isBroadcast == 0U) | |||||
| && (txCapacity < MODBUS_RTU_WRITE_RESPONSE_LENGTH)) { | |||||
| return MODBUS_REQUEST_RESPONSE_ERROR; | |||||
| } | |||||
| for (registerOffset = 0U; registerOffset < registerCount; | for (registerOffset = 0U; registerOffset < registerCount; | ||||
| registerOffset++) { | registerOffset++) { | ||||
| slave->holdingRegisters[address + registerOffset] = ModbusReadU16( | slave->holdingRegisters[address + registerOffset] = ModbusReadU16( | ||||
| @@ -563,19 +645,140 @@ static MODBUS_REQUEST_RESULT ModbusProcessWriteMultipleRegisters( | |||||
| return MODBUS_REQUEST_OK; | return MODBUS_REQUEST_OK; | ||||
| } | } | ||||
| if (txCapacity < MODBUS_RTU_WRITE_RESPONSE_LENGTH) { | |||||
| return MODBUS_REQUEST_ILLEGAL_VALUE; | |||||
| } | |||||
| txBuffer[MODBUS_RTU_ADDRESS_INDEX] = slave->slaveAddress; | txBuffer[MODBUS_RTU_ADDRESS_INDEX] = slave->slaveAddress; | ||||
| txBuffer[MODBUS_RTU_FUNCTION_INDEX] = MODBUS_FC_WRITE_MULTIPLE_REGS; | txBuffer[MODBUS_RTU_FUNCTION_INDEX] = MODBUS_FC_WRITE_MULTIPLE_REGS; | ||||
| ModbusWriteU16(&txBuffer[2U], address); | ModbusWriteU16(&txBuffer[2U], address); | ||||
| ModbusWriteU16(&txBuffer[4U], registerCount); | ModbusWriteU16(&txBuffer[4U], registerCount); | ||||
| *txLength = 6U; | *txLength = 6U; | ||||
| if (ModbusAppendCrc(txBuffer, txCapacity, txLength) != MODBUS_STATUS_OK) { | if (ModbusAppendCrc(txBuffer, txCapacity, txLength) != MODBUS_STATUS_OK) { | ||||
| return MODBUS_REQUEST_RESPONSE_ERROR; | |||||
| } | |||||
| return MODBUS_REQUEST_OK; | |||||
| } | |||||
| /** | |||||
| * @brief 构建私有功能码 0x41 的扩展寄存器读取响应。 | |||||
| * @note 该接口只接受地址 0x00010000 和数量 1,响应数据为一个 16 位值。 | |||||
| */ | |||||
| static MODBUS_REQUEST_RESULT ModbusBuildReadExtendedRegistersResponse( | |||||
| const MODBUS_SLAVE *slave, uint32_t startAddress, | |||||
| uint16_t registerCount, uint8_t *txBuffer, uint16_t txCapacity, | |||||
| uint16_t *txLength) | |||||
| { | |||||
| MODBUS_STATUS status; | |||||
| if (registerCount != 1U) { | |||||
| return MODBUS_REQUEST_ILLEGAL_VALUE; | return MODBUS_REQUEST_ILLEGAL_VALUE; | ||||
| } | } | ||||
| if (ModbusIsExtendedRegisterAddressValid(slave, startAddress) == 0U) { | |||||
| return MODBUS_REQUEST_ILLEGAL_ADDRESS; | |||||
| } | |||||
| if (txCapacity < 7U) { | |||||
| return MODBUS_REQUEST_RESPONSE_ERROR; | |||||
| } | |||||
| txBuffer[MODBUS_RTU_ADDRESS_INDEX] = slave->slaveAddress; | |||||
| txBuffer[MODBUS_RTU_FUNCTION_INDEX] = MODBUS_FC_READ_EXTENDED_HOLDING_REGS; | |||||
| txBuffer[MODBUS_RTU_DATA_INDEX] = 2U; | |||||
| ModbusWriteU16(&txBuffer[3U], *slave->extendedHoldingRegister); | |||||
| *txLength = 5U; | |||||
| status = ModbusAppendCrc(txBuffer, txCapacity, txLength); | |||||
| if (status != MODBUS_STATUS_OK) { | |||||
| return MODBUS_REQUEST_RESPONSE_ERROR; | |||||
| } | |||||
| return MODBUS_REQUEST_OK; | |||||
| } | |||||
| /** | |||||
| * @brief 执行私有功能码 0x42 的扩展寄存器写入并回显地址和值。 | |||||
| * @note 本函数只更新 RAM 中的扩展值;RTC 持久化由应用层在成功处理后触发。 | |||||
| */ | |||||
| static MODBUS_REQUEST_RESULT ModbusBuildWriteExtendedRegisterResponse( | |||||
| MODBUS_SLAVE *slave, uint32_t startAddress, uint16_t registerValue, | |||||
| uint8_t *txBuffer, uint16_t txCapacity, uint16_t *txLength) | |||||
| { | |||||
| MODBUS_STATUS status; | |||||
| if (ModbusIsExtendedRegisterAddressValid(slave, startAddress) == 0U) { | |||||
| return MODBUS_REQUEST_ILLEGAL_ADDRESS; | |||||
| } | |||||
| if (txCapacity < 10U) { | |||||
| return MODBUS_REQUEST_RESPONSE_ERROR; | |||||
| } | |||||
| /* 私有扩展使用 32 位地址字段表示 0x00010000,不改变标准功能码的 16 位地址模型。 */ | |||||
| *slave->extendedHoldingRegister = registerValue; | |||||
| txBuffer[MODBUS_RTU_ADDRESS_INDEX] = slave->slaveAddress; | |||||
| txBuffer[MODBUS_RTU_FUNCTION_INDEX] = MODBUS_FC_WRITE_EXTENDED_HOLDING_REGS; | |||||
| ModbusWriteU32(&txBuffer[2U], startAddress); | |||||
| ModbusWriteU16(&txBuffer[6U], registerValue); | |||||
| *txLength = 8U; | |||||
| status = ModbusAppendCrc(txBuffer, txCapacity, txLength); | |||||
| if (status != MODBUS_STATUS_OK) { | |||||
| return MODBUS_REQUEST_RESPONSE_ERROR; | |||||
| } | |||||
| return MODBUS_REQUEST_OK; | |||||
| } | |||||
| /** | |||||
| * @brief 构建私有功能码 0x43 的连续奇数地址读取响应。 | |||||
| * @note 数量必须为 1 至 123 的奇数。偶数起始基准地址自动上调到下一奇数, | |||||
| * 例如起始地址 0 依次返回地址 1、3、5 等的寄存器值。 | |||||
| */ | |||||
| static MODBUS_REQUEST_RESULT ModbusBuildReadOddAddressRegistersResponse( | |||||
| const MODBUS_SLAVE *slave, uint16_t startAddress, | |||||
| uint16_t registerCount, uint8_t *txBuffer, uint16_t txCapacity, | |||||
| uint16_t *txLength) | |||||
| { | |||||
| MODBUS_STATUS status; | |||||
| uint16_t byteCount; | |||||
| uint16_t firstOddAddress; | |||||
| uint16_t readAddress; | |||||
| uint16_t registerOffset; | |||||
| if ((registerCount == 0U) | |||||
| || (registerCount > MODBUS_MAX_ODD_COUNT_REGISTER_COUNT) | |||||
| || ((registerCount & 0x0001U) == 0U)) { | |||||
| return MODBUS_REQUEST_ILLEGAL_VALUE; | |||||
| } | |||||
| /* 请求数量表示返回的奇数地址寄存器个数,而不是连续地址跨度。从偶数地址开始时 | |||||
| * 会跳过该地址,从下一个奇数地址开始;后续每个地址递增 2。 */ | |||||
| firstOddAddress = (uint16_t)(startAddress | 0x0001U); | |||||
| if ((firstOddAddress > MODBUS_DATA_ADDRESS_MAX) | |||||
| || (registerCount | |||||
| > (((MODBUS_DATA_ADDRESS_MAX - firstOddAddress) / 2U) + 1U))) { | |||||
| return MODBUS_REQUEST_ILLEGAL_ADDRESS; | |||||
| } | |||||
| byteCount = registerCount * 2U; | |||||
| if (txCapacity < (3U + byteCount + MODBUS_RTU_CRC_LENGTH)) { | |||||
| return MODBUS_REQUEST_RESPONSE_ERROR; | |||||
| } | |||||
| txBuffer[MODBUS_RTU_ADDRESS_INDEX] = slave->slaveAddress; | |||||
| txBuffer[MODBUS_RTU_FUNCTION_INDEX] = MODBUS_FC_READ_ODD_COUNT_REGS; | |||||
| txBuffer[MODBUS_RTU_DATA_INDEX] = (uint8_t)byteCount; | |||||
| for (registerOffset = 0U; registerOffset < registerCount; | |||||
| registerOffset++) { | |||||
| readAddress = (uint16_t)(firstOddAddress + (registerOffset * 2U)); | |||||
| ModbusWriteU16(&txBuffer[3U + (registerOffset * 2U)], | |||||
| slave->holdingRegisters[readAddress]); | |||||
| } | |||||
| *txLength = 3U + byteCount; | |||||
| status = ModbusAppendCrc(txBuffer, txCapacity, txLength); | |||||
| if (status != MODBUS_STATUS_OK) { | |||||
| return MODBUS_REQUEST_RESPONSE_ERROR; | |||||
| } | |||||
| return MODBUS_REQUEST_OK; | return MODBUS_REQUEST_OK; | ||||
| } | } | ||||
| @@ -595,6 +798,8 @@ uint16_t ModbusCrc16(const uint8_t *buffer, uint16_t length) | |||||
| return 0U; | return 0U; | ||||
| } | } | ||||
| /* Modbus CRC-16 使用初值 0xFFFF 和反射多项式 0xA001。按最低有效位优先处理, | |||||
| * 与 RTU 总线字节序一致,也不依赖 MCU 本身的字节序。 */ | |||||
| crcValue = MODBUS_RTU_CRC_INIT; | crcValue = MODBUS_RTU_CRC_INIT; | ||||
| for (byteIndex = 0U; byteIndex < length; byteIndex++) { | for (byteIndex = 0U; byteIndex < length; byteIndex++) { | ||||
| crcValue ^= (uint16_t)buffer[byteIndex]; | crcValue ^= (uint16_t)buffer[byteIndex]; | ||||
| @@ -617,13 +822,18 @@ uint16_t ModbusCrc16(const uint8_t *buffer, uint16_t length) | |||||
| * @param[in] slaveAddress 范围为 1 至 247 的单播从站地址。 | * @param[in] slaveAddress 范围为 1 至 247 的单播从站地址。 | ||||
| * @return 成功返回 MODBUS_STATUS_OK,否则返回错误状态。 | * @return 成功返回 MODBUS_STATUS_OK,否则返回错误状态。 | ||||
| */ | */ | ||||
| MODBUS_STATUS ModbusSlaveInit(MODBUS_SLAVE *slave, uint8_t slaveAddress) | |||||
| MODBUS_STATUS ModbusSlaveInit( | |||||
| MODBUS_SLAVE *slave, | |||||
| uint8_t slaveAddress, | |||||
| volatile uint16_t *holdingRegisterStorage, | |||||
| volatile uint8_t *coilBitStorage) | |||||
| { | { | ||||
| uint16_t registerIndex; | uint16_t registerIndex; | ||||
| uint16_t coilByteIndex; | uint16_t coilByteIndex; | ||||
| uint16_t coilAddress; | uint16_t coilAddress; | ||||
| if (slave == NULL) { | |||||
| if ((slave == NULL) || (holdingRegisterStorage == NULL) | |||||
| || (coilBitStorage == NULL)) { | |||||
| return MODBUS_STATUS_INVALID_ARGUMENT; | return MODBUS_STATUS_INVALID_ARGUMENT; | ||||
| } | } | ||||
| @@ -632,6 +842,11 @@ MODBUS_STATUS ModbusSlaveInit(MODBUS_SLAVE *slave, uint8_t slaveAddress) | |||||
| } | } | ||||
| slave->slaveAddress = slaveAddress; | slave->slaveAddress = slaveAddress; | ||||
| slave->holdingRegisters = holdingRegisterStorage; | |||||
| slave->coilStorage = coilBitStorage; | |||||
| slave->extendedHoldingRegister = NULL; | |||||
| /* 启动时清空完整的普通数据模型。扩展值单独初始化,因为随后可能从 RTC 备份 | |||||
| * 寄存器恢复。 */ | |||||
| for (registerIndex = 0U; registerIndex < MODBUS_DATA_POINT_COUNT; | for (registerIndex = 0U; registerIndex < MODBUS_DATA_POINT_COUNT; | ||||
| registerIndex++) { | registerIndex++) { | ||||
| slave->holdingRegisters[registerIndex] = 0U; | slave->holdingRegisters[registerIndex] = 0U; | ||||
| @@ -658,6 +873,21 @@ MODBUS_STATUS ModbusSlaveInit(MODBUS_SLAVE *slave, uint8_t slaveAddress) | |||||
| return MODBUS_STATUS_OK; | return MODBUS_STATUS_OK; | ||||
| } | } | ||||
| MODBUS_STATUS ModbusConfigureExtendedHoldingRegister( | |||||
| MODBUS_SLAVE *slave, volatile uint16_t *storage) | |||||
| { | |||||
| if ((slave == NULL) || (storage == NULL)) { | |||||
| return MODBUS_STATUS_INVALID_ARGUMENT; | |||||
| } | |||||
| slave->extendedHoldingRegister = storage; | |||||
| /* 首次启动的默认值;AppTaskModbus 随后可能用 RTC 备份值覆盖它。 */ | |||||
| /* 只有不存在有效 RTC 备份时才使用这个默认值。 */ | |||||
| *slave->extendedHoldingRegister = 0x1000U; | |||||
| return MODBUS_STATUS_OK; | |||||
| } | |||||
| /** | /** | ||||
| * @brief 从从站数据模型中读取线圈值。 | * @brief 从从站数据模型中读取线圈值。 | ||||
| * @param[in] slave 从站数据对象。 | * @param[in] slave 从站数据对象。 | ||||
| @@ -759,6 +989,8 @@ MODBUS_STATUS ModbusSetHoldingRegister(MODBUS_SLAVE *slave, | |||||
| * @param[in] txCapacity 响应 ADU 缓冲区容量,单位为字节。 | * @param[in] txCapacity 响应 ADU 缓冲区容量,单位为字节。 | ||||
| * @param[out] txLength 生成的响应长度,单位为字节。 | * @param[out] txLength 生成的响应长度,单位为字节。 | ||||
| * @return 请求处理状态;广播写入请求不生成响应。 | * @return 请求处理状态;广播写入请求不生成响应。 | ||||
| * @note 处理顺序固定为参数、帧长度、CRC、站号、功能码和字段校验, | |||||
| * 因而 CRC 错误或非本机帧绝不会写入数据区。 | |||||
| */ | */ | ||||
| MODBUS_STATUS ModbusProcessFrame(MODBUS_SLAVE *slave, | MODBUS_STATUS ModbusProcessFrame(MODBUS_SLAVE *slave, | ||||
| const uint8_t *rxBuffer, uint16_t rxLength, | const uint8_t *rxBuffer, uint16_t rxLength, | ||||
| @@ -772,13 +1004,22 @@ MODBUS_STATUS ModbusProcessFrame(MODBUS_SLAVE *slave, | |||||
| uint8_t isBroadcast; | uint8_t isBroadcast; | ||||
| uint16_t startAddress; | uint16_t startAddress; | ||||
| uint16_t dataCount; | uint16_t dataCount; | ||||
| uint16_t registerValue; | |||||
| uint32_t extendedAddress; | |||||
| if ((slave == NULL) || (rxBuffer == NULL) || (txBuffer == NULL) | if ((slave == NULL) || (rxBuffer == NULL) || (txBuffer == NULL) | ||||
| || (txLength == NULL)) { | || (txLength == NULL)) { | ||||
| return MODBUS_STATUS_INVALID_ARGUMENT; | return MODBUS_STATUS_INVALID_ARGUMENT; | ||||
| } | } | ||||
| /* 始终从空响应开始。被忽略的帧、CRC 错误帧和广播写入都必须保持 txLength 为 0。 */ | |||||
| *txLength = 0U; | *txLength = 0U; | ||||
| if (rxLength > MODBUS_RTU_ADU_MAX_LENGTH) { | |||||
| return MODBUS_STATUS_FRAME_ERROR; | |||||
| } | |||||
| /* 校验顺序是有意设计的:先拒绝格式错误或 CRC 错误的帧,再检查地址,最后才访问 | |||||
| * 数据模型。 */ | |||||
| status = ModbusValidateCrc(rxBuffer, rxLength); | status = ModbusValidateCrc(rxBuffer, rxLength); | ||||
| if (status != MODBUS_STATUS_OK) { | if (status != MODBUS_STATUS_OK) { | ||||
| return status; | return status; | ||||
| @@ -791,8 +1032,15 @@ MODBUS_STATUS ModbusProcessFrame(MODBUS_SLAVE *slave, | |||||
| } | } | ||||
| functionCode = rxBuffer[MODBUS_RTU_FUNCTION_INDEX]; | functionCode = rxBuffer[MODBUS_RTU_FUNCTION_INDEX]; | ||||
| if ((functionCode & MODBUS_RTU_EXCEPTION_MASK) != 0U) { | |||||
| return MODBUS_STATUS_FRAME_ERROR; | |||||
| } | |||||
| /* Modbus RTU 的广播地址 0 只允许写入。广播读取和私有读取扩展会被忽略且不响应。 */ | |||||
| isBroadcast = (uint8_t)(requestAddress == MODBUS_BROADCAST_ADDRESS); | isBroadcast = (uint8_t)(requestAddress == MODBUS_BROADCAST_ADDRESS); | ||||
| requestResult = MODBUS_REQUEST_OK; | requestResult = MODBUS_REQUEST_OK; | ||||
| /* 每个处理函数负责校验自身的固定/可变帧格式和数据边界,然后返回内部结果, | |||||
| * 由下面的逻辑映射为标准异常响应。 */ | |||||
| switch (functionCode) { | switch (functionCode) { | ||||
| case MODBUS_FC_READ_COILS: | case MODBUS_FC_READ_COILS: | ||||
| if (isBroadcast != 0U) { | if (isBroadcast != 0U) { | ||||
| @@ -822,6 +1070,50 @@ MODBUS_STATUS ModbusProcessFrame(MODBUS_SLAVE *slave, | |||||
| slave, startAddress, dataCount, txBuffer, txCapacity, txLength); | slave, startAddress, dataCount, txBuffer, txCapacity, txLength); | ||||
| break; | break; | ||||
| case MODBUS_FC_READ_EXTENDED_HOLDING_REGS: | |||||
| if (isBroadcast != 0U) { | |||||
| return MODBUS_STATUS_IGNORED; | |||||
| } | |||||
| if (rxLength != 10U) { | |||||
| requestResult = MODBUS_REQUEST_ILLEGAL_VALUE; | |||||
| break; | |||||
| } | |||||
| extendedAddress = ModbusReadU32(&rxBuffer[2U]); | |||||
| dataCount = ModbusReadU16(&rxBuffer[6U]); | |||||
| requestResult = ModbusBuildReadExtendedRegistersResponse( | |||||
| slave, extendedAddress, dataCount, txBuffer, txCapacity, | |||||
| txLength); | |||||
| break; | |||||
| case MODBUS_FC_WRITE_EXTENDED_HOLDING_REGS: | |||||
| if (isBroadcast != 0U) { | |||||
| return MODBUS_STATUS_IGNORED; | |||||
| } | |||||
| if (rxLength != 10U) { | |||||
| requestResult = MODBUS_REQUEST_ILLEGAL_VALUE; | |||||
| break; | |||||
| } | |||||
| extendedAddress = ModbusReadU32(&rxBuffer[2U]); | |||||
| registerValue = ModbusReadU16(&rxBuffer[6U]); | |||||
| requestResult = ModbusBuildWriteExtendedRegisterResponse( | |||||
| slave, extendedAddress, registerValue, txBuffer, txCapacity, | |||||
| txLength); | |||||
| break; | |||||
| case MODBUS_FC_READ_ODD_COUNT_REGS: | |||||
| if (isBroadcast != 0U) { | |||||
| return MODBUS_STATUS_IGNORED; | |||||
| } | |||||
| if (rxLength != MODBUS_RTU_READ_REQUEST_LENGTH) { | |||||
| requestResult = MODBUS_REQUEST_ILLEGAL_VALUE; | |||||
| break; | |||||
| } | |||||
| startAddress = ModbusReadU16(&rxBuffer[2U]); | |||||
| dataCount = ModbusReadU16(&rxBuffer[4U]); | |||||
| requestResult = ModbusBuildReadOddAddressRegistersResponse( | |||||
| slave, startAddress, dataCount, txBuffer, txCapacity, txLength); | |||||
| break; | |||||
| case MODBUS_FC_WRITE_SINGLE_COIL: | case MODBUS_FC_WRITE_SINGLE_COIL: | ||||
| requestResult = ModbusProcessWriteSingleCoil( | requestResult = ModbusProcessWriteSingleCoil( | ||||
| slave, rxBuffer, rxLength, isBroadcast, txBuffer, txCapacity, | slave, rxBuffer, rxLength, isBroadcast, txBuffer, txCapacity, | ||||
| @@ -855,6 +1147,12 @@ MODBUS_STATUS ModbusProcessFrame(MODBUS_SLAVE *slave, | |||||
| txBuffer, txCapacity, txLength); | txBuffer, txCapacity, txLength); | ||||
| } | } | ||||
| /* 响应缓冲区或内部处理失败不是协议异常,应该报告给应用层,而不能伪造一帧 | |||||
| * 发送到总线上的异常响应。 */ | |||||
| if (requestResult == MODBUS_REQUEST_RESPONSE_ERROR) { | |||||
| return MODBUS_STATUS_FRAME_ERROR; | |||||
| } | |||||
| if (requestResult != MODBUS_REQUEST_OK) { | if (requestResult != MODBUS_REQUEST_OK) { | ||||
| if (isBroadcast != 0U) { | if (isBroadcast != 0U) { | ||||
| return MODBUS_STATUS_IGNORED; | return MODBUS_STATUS_IGNORED; | ||||
| @@ -4,7 +4,7 @@ | |||||
| * @version 2.0 | * @version 2.0 | ||||
| * @author zengbingjie | * @author zengbingjie | ||||
| * @date 2026-07-29 | * @date 2026-07-29 | ||||
| * @copyright 版权所有 (c) 2026 Xinje Electric。 | |||||
| * @copyright 版权所有信捷科技股份有限公司 | |||||
| * @note 修改记录:2.0 重构帧校验和从站接口。 | * @note 修改记录:2.0 重构帧校验和从站接口。 | ||||
| */ | */ | ||||
| @@ -22,7 +22,8 @@ | |||||
| #define MODBUS_RTU_WRITE_RESPONSE_LENGTH (8U) | #define MODBUS_RTU_WRITE_RESPONSE_LENGTH (8U) | ||||
| #define MODBUS_RTU_EXCEPTION_LENGTH (5U) | #define MODBUS_RTU_EXCEPTION_LENGTH (5U) | ||||
| #define MODBUS_RX_BUF_SIZE (MODBUS_RTU_ADU_MAX_LENGTH) | |||||
| /* 保留一个额外接收字节,用于在覆盖有效 ADU 前检测超长帧。 */ | |||||
| #define MODBUS_RX_BUF_SIZE (MODBUS_RTU_ADU_MAX_LENGTH + 1U) | |||||
| #define MODBUS_TX_BUF_SIZE (MODBUS_RTU_ADU_MAX_LENGTH) | #define MODBUS_TX_BUF_SIZE (MODBUS_RTU_ADU_MAX_LENGTH) | ||||
| #define MODBUS_SLAVE_ADDRESS_MIN (1U) | #define MODBUS_SLAVE_ADDRESS_MIN (1U) | ||||
| @@ -30,8 +31,10 @@ | |||||
| #define MODBUS_BROADCAST_ADDRESS (0U) | #define MODBUS_BROADCAST_ADDRESS (0U) | ||||
| #define MODBUS_SLAVE_ADDR_DEFAULT (1U) | #define MODBUS_SLAVE_ADDR_DEFAULT (1U) | ||||
| /* 普通线圈和保持寄存器共用从 0 开始的逻辑地址范围 0x0000..0x270F。 | |||||
| * 两端都包含在内,所以数据点数量为最大地址加 1。 */ | |||||
| #define MODBUS_DATA_ADDRESS_MIN (0U) | #define MODBUS_DATA_ADDRESS_MIN (0U) | ||||
| #define MODBUS_DATA_ADDRESS_MAX (0x270) | |||||
| #define MODBUS_DATA_ADDRESS_MAX (0x270FU) | |||||
| #define MODBUS_DATA_POINT_COUNT (MODBUS_DATA_ADDRESS_MAX + 1) | #define MODBUS_DATA_POINT_COUNT (MODBUS_DATA_ADDRESS_MAX + 1) | ||||
| #define MODBUS_COIL_STORAGE_SIZE \ | #define MODBUS_COIL_STORAGE_SIZE \ | ||||
| ((MODBUS_DATA_POINT_COUNT + 7U) / 8U) | ((MODBUS_DATA_POINT_COUNT + 7U) / 8U) | ||||
| @@ -43,12 +46,20 @@ | |||||
| #define MODBUS_FC_WRITE_MULTIPLE_COILS (0x0FU) | #define MODBUS_FC_WRITE_MULTIPLE_COILS (0x0FU) | ||||
| #define MODBUS_FC_WRITE_MULTIPLE_REGS (0x10U) | #define MODBUS_FC_WRITE_MULTIPLE_REGS (0x10U) | ||||
| /* 项目私有扩展与标准功能码分开定义。0x41/0x42 使用 32 位地址表示 0x00010000; | |||||
| * 0x43 只返回奇数地址寄存器,最多 123 个值。 */ | |||||
| #define MODBUS_FC_READ_EXTENDED_HOLDING_REGS (0x41U) | |||||
| #define MODBUS_FC_WRITE_EXTENDED_HOLDING_REGS (0x42U) | |||||
| #define MODBUS_FC_READ_ODD_COUNT_REGS (0x43U) | |||||
| #define MODBUS_EXTENDED_HOLDING_ADDRESS (0x00010000UL) | |||||
| #define MODBUS_EXCEPTION_ILLEGAL_FUNC (0x01U) | #define MODBUS_EXCEPTION_ILLEGAL_FUNC (0x01U) | ||||
| #define MODBUS_EXCEPTION_ILLEGAL_DATA_ADDR (0x02U) | #define MODBUS_EXCEPTION_ILLEGAL_DATA_ADDR (0x02U) | ||||
| #define MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE (0x03U) | #define MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE (0x03U) | ||||
| #define MODBUS_MAX_READ_COIL_COUNT (2000U) | #define MODBUS_MAX_READ_COIL_COUNT (2000U) | ||||
| #define MODBUS_MAX_READ_REGISTER_COUNT (125U) | #define MODBUS_MAX_READ_REGISTER_COUNT (125U) | ||||
| #define MODBUS_MAX_ODD_COUNT_REGISTER_COUNT (123U) | |||||
| #define MODBUS_MAX_WRITE_COIL_COUNT (0x07B0U) | #define MODBUS_MAX_WRITE_COIL_COUNT (0x07B0U) | ||||
| #define MODBUS_MAX_WRITE_REGISTER_COUNT (123U) | #define MODBUS_MAX_WRITE_REGISTER_COUNT (123U) | ||||
| @@ -56,19 +67,22 @@ | |||||
| #define MODBUS_COIL_OFF_VALUE (0x0000U) | #define MODBUS_COIL_OFF_VALUE (0x0000U) | ||||
| typedef enum { | typedef enum { | ||||
| MODBUS_STATUS_OK = 0, | |||||
| MODBUS_STATUS_BROADCAST = 1, | |||||
| MODBUS_STATUS_EXCEPTION = 2, | |||||
| MODBUS_STATUS_IGNORED = 3, | |||||
| MODBUS_STATUS_INVALID_ARGUMENT = -1, | |||||
| MODBUS_STATUS_FRAME_ERROR = -2, | |||||
| MODBUS_STATUS_CRC_ERROR = -3 | |||||
| MODBUS_STATUS_OK = 0, /**< 已处理普通单播请求。 */ | |||||
| MODBUS_STATUS_BROADCAST = 1, /**< 已处理广播写请求,无响应帧。 */ | |||||
| MODBUS_STATUS_EXCEPTION = 2, /**< 已生成标准 Modbus 异常响应。 */ | |||||
| MODBUS_STATUS_IGNORED = 3, /**< 非本机站号或不支持的广播读请求。 */ | |||||
| MODBUS_STATUS_INVALID_ARGUMENT = -1, /**< 调用方提供了空指针或无效参数。 */ | |||||
| MODBUS_STATUS_FRAME_ERROR = -2, /**< 帧长度、地址、容量或字段格式错误。 */ | |||||
| MODBUS_STATUS_CRC_ERROR = -3 /**< RTU CRC 校验不匹配。 */ | |||||
| } MODBUS_STATUS; | } MODBUS_STATUS; | ||||
| typedef struct { | |||||
| struct MODBUS_SLAVE; | |||||
| typedef struct MODBUS_SLAVE { | |||||
| uint8_t slaveAddress; ///< Modbus 从站站号。 | uint8_t slaveAddress; ///< Modbus 从站站号。 | ||||
| uint16_t holdingRegisters[MODBUS_DATA_POINT_COUNT]; | |||||
| uint8_t coilStorage[MODBUS_COIL_STORAGE_SIZE]; | |||||
| volatile uint16_t *holdingRegisters; /**< 普通 16 位保持寄存器数据区。 */ | |||||
| volatile uint8_t *coilStorage; /**< 按位压缩的普通线圈数据区。 */ | |||||
| volatile uint16_t *extendedHoldingRegister; /**< 0x00010000 的唯一扩展值。 */ | |||||
| } MODBUS_SLAVE; | } MODBUS_SLAVE; | ||||
| /** | /** | ||||
| @@ -85,7 +99,21 @@ uint16_t ModbusCrc16(const uint8_t *buffer, uint16_t length); | |||||
| * @param[in] slaveAddress 范围为 1 至 247 的单播从站地址。 | * @param[in] slaveAddress 范围为 1 至 247 的单播从站地址。 | ||||
| * @return 成功返回 MODBUS_STATUS_OK;失败返回错误状态。 | * @return 成功返回 MODBUS_STATUS_OK;失败返回错误状态。 | ||||
| */ | */ | ||||
| MODBUS_STATUS ModbusSlaveInit(MODBUS_SLAVE *slave, uint8_t slaveAddress); | |||||
| MODBUS_STATUS ModbusSlaveInit( | |||||
| MODBUS_SLAVE *slave, | |||||
| uint8_t slaveAddress, | |||||
| volatile uint16_t *holdingRegisterStorage, | |||||
| volatile uint8_t *coilBitStorage); | |||||
| /** | |||||
| * @brief 绑定项目私有地址 0x00010000 的唯一扩展保持寄存器。 | |||||
| * @param[in,out] slave 已初始化的从站数据对象。 | |||||
| * @param[in] storage 扩展寄存器的有效存储地址。 | |||||
| * @return 成功返回 MODBUS_STATUS_OK;空参数返回 MODBUS_STATUS_INVALID_ARGUMENT。 | |||||
| * @note 初始化值仅用于首次启动;后续可由 RTC 备份恢复流程覆盖。 | |||||
| */ | |||||
| MODBUS_STATUS ModbusConfigureExtendedHoldingRegister( | |||||
| MODBUS_SLAVE *slave, volatile uint16_t *storage); | |||||
| /** | /** | ||||
| * @brief 从从站数据模型读取线圈值。 | * @brief 从从站数据模型读取线圈值。 | ||||
| @@ -0,0 +1,232 @@ | |||||
| /** | |||||
| * @file modbus_backup.c | |||||
| * @brief 私有扩展保持寄存器的 RTC 备份与恢复实现。 | |||||
| * @note 仅保存地址 0x00010000 对应的 16 位数据。普通保持寄存器、线圈和 | |||||
| * 触摸屏显示值均不经过本模块保存。 | |||||
| */ | |||||
| #include "main.h" | |||||
| #include "modbus_backup.h" | |||||
| /* 魔数最后写入,作为一次备份事务完成的提交标记。 */ | |||||
| #define MODBUS_BACKUP_MAGIC (0x4D424B50UL) | |||||
| #define MODBUS_BACKUP_VERSION (0x0002U) | |||||
| #define MODBUS_BACKUP_RTC_READY_TIMEOUT_MS (100U) | |||||
| #define MODBUS_BACKUP_REG_MAGIC (0U) | |||||
| #define MODBUS_BACKUP_REG_VERSION_CRC (1U) | |||||
| #define MODBUS_BACKUP_REG_EXTENDED (2U) | |||||
| /** | |||||
| * @brief 用一个字节更新 RTC 备份元数据的 Modbus CRC16。 | |||||
| * @param[in] crc 当前 CRC 累积值。 | |||||
| * @param[in] value 待纳入校验的字节。 | |||||
| * @return 更新后的 CRC 值。 | |||||
| */ | |||||
| static uint16_t ModbusBackupCrcUpdate(uint16_t crc, uint8_t value) | |||||
| { | |||||
| uint8_t bitIndex; | |||||
| crc ^= value; | |||||
| for (bitIndex = 0U; bitIndex < 8U; bitIndex++) | |||||
| { | |||||
| if ((crc & 0x0001U) != 0U) | |||||
| { | |||||
| crc = (crc >> 1U) ^ 0xA001U; | |||||
| } | |||||
| else | |||||
| { | |||||
| crc >>= 1U; | |||||
| } | |||||
| } | |||||
| return crc; | |||||
| } | |||||
| /** | |||||
| * @brief 以高字节在前的顺序将 16 位扩展值纳入备份 CRC。 | |||||
| * @param[in] crc 当前 CRC 累积值。 | |||||
| * @param[in] value 扩展保持寄存器值。 | |||||
| * @return 更新后的 CRC 值。 | |||||
| */ | |||||
| static uint16_t ModbusBackupCrcUpdateU16(uint16_t crc, uint16_t value) | |||||
| { | |||||
| crc = ModbusBackupCrcUpdate(crc, (uint8_t)(value >> 8U)); | |||||
| crc = ModbusBackupCrcUpdate(crc, (uint8_t)value); | |||||
| return crc; | |||||
| } | |||||
| /** | |||||
| * @brief 获取项目占用的 RTC 备份寄存器地址。 | |||||
| * @param[in] index 本模块定义的备份寄存器索引。 | |||||
| * @return 对应的 32 位 RTC 备份寄存器指针。 | |||||
| */ | |||||
| static volatile uint32_t *ModbusBackupRegister(uint8_t index) | |||||
| { | |||||
| /* RTC 备份寄存器在备份域供电时可跨越普通 MCU 复位和断电保持内容。 | |||||
| * 将布局限制在本模块内,避免影响普通 Modbus 地址。 */ | |||||
| return &RTC->BKP0R + index; | |||||
| } | |||||
| /** @brief 读取一个项目占用的 RTC 备份寄存器。 */ | |||||
| static uint32_t ModbusBackupReadRegister(uint8_t index) | |||||
| { | |||||
| return *ModbusBackupRegister(index); | |||||
| } | |||||
| /** @brief 写入一个项目占用的 RTC 备份寄存器。 */ | |||||
| static void ModbusBackupWriteRegister(uint8_t index, uint32_t value) | |||||
| { | |||||
| *ModbusBackupRegister(index) = value; | |||||
| } | |||||
| /** | |||||
| * @brief 打开备份域访问并确保 RTC 时钟可用。 | |||||
| * @return RTC 就绪返回 MODBUS_STATUS_OK;LSI 等待超时返回 MODBUS_STATUS_FRAME_ERROR。 | |||||
| * @note 此步骤不修改扩展寄存器数据,只建立访问 RTC 备份寄存器的前置条件。 | |||||
| */ | |||||
| static MODBUS_STATUS ModbusBackupEnableRtcAccess(void) | |||||
| { | |||||
| uint32_t startTick; | |||||
| /* STM32 的备份域写入受到保护,必须先开启访问权限;只有没有配置 RTC 时钟源时, | |||||
| * 才提供 LSI 时钟作为备用时钟。 */ | |||||
| __HAL_RCC_PWR_CLK_ENABLE(); | |||||
| HAL_PWR_EnableBkUpAccess(); | |||||
| if (__HAL_RCC_GET_RTC_SOURCE() == RCC_RTCCLKSOURCE_NO_CLK) | |||||
| { | |||||
| __HAL_RCC_LSI_ENABLE(); | |||||
| startTick = HAL_GetTick(); | |||||
| while (__HAL_RCC_GET_FLAG(RCC_FLAG_LSIRDY) == RESET) | |||||
| { | |||||
| if ((HAL_GetTick() - startTick) | |||||
| >= MODBUS_BACKUP_RTC_READY_TIMEOUT_MS) | |||||
| { | |||||
| return MODBUS_STATUS_FRAME_ERROR; | |||||
| } | |||||
| } | |||||
| __HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_LSI); | |||||
| } | |||||
| __HAL_RCC_RTC_ENABLE(); | |||||
| return MODBUS_STATUS_OK; | |||||
| } | |||||
| /** | |||||
| * @brief 验证从站对象并读取其绑定的扩展保持寄存器。 | |||||
| * @param[in] slave 从站数据对象。 | |||||
| * @param[out] extendedValue 接收当前扩展值的输出地址。 | |||||
| * @return 参数有效返回 MODBUS_STATUS_OK,否则返回 MODBUS_STATUS_INVALID_ARGUMENT。 | |||||
| */ | |||||
| static MODBUS_STATUS ModbusBackupReadExtendedValue( | |||||
| const MODBUS_SLAVE *slave, uint16_t *extendedValue) | |||||
| { | |||||
| if ((slave == NULL) || (extendedValue == NULL) | |||||
| || (slave->extendedHoldingRegister == NULL)) | |||||
| { | |||||
| return MODBUS_STATUS_INVALID_ARGUMENT; | |||||
| } | |||||
| *extendedValue = *slave->extendedHoldingRegister; | |||||
| return MODBUS_STATUS_OK; | |||||
| } | |||||
| /** | |||||
| * @brief 计算扩展保持寄存器值的备份 CRC。 | |||||
| * @param[in] extendedValue 待保护的 16 位寄存器值。 | |||||
| * @return 使用初始值 0xFFFF 计算的 CRC16。 | |||||
| */ | |||||
| static uint16_t ModbusBackupCalculateCrc(uint16_t extendedValue) | |||||
| { | |||||
| uint16_t crc; | |||||
| crc = 0xFFFFU; | |||||
| crc = ModbusBackupCrcUpdateU16(crc, extendedValue); | |||||
| return crc; | |||||
| } | |||||
| /** | |||||
| * @brief 保存扩展保持寄存器及其版本、CRC 元数据。 | |||||
| * @param[in] slave 已绑定扩展保持寄存器的从站对象。 | |||||
| * @return 成功返回 MODBUS_STATUS_OK;RTC 或参数失败时返回对应状态。 | |||||
| * @note 魔数会在数据和 CRC 写完后最后提交,掉电时不会误恢复半写入数据。 | |||||
| */ | |||||
| MODBUS_STATUS ModbusBackupSave(const MODBUS_SLAVE *slave) | |||||
| { | |||||
| uint16_t extendedValue; | |||||
| uint16_t crc; | |||||
| MODBUS_STATUS status; | |||||
| status = ModbusBackupEnableRtcAccess(); | |||||
| if (status != MODBUS_STATUS_OK) | |||||
| { | |||||
| return status; | |||||
| } | |||||
| status = ModbusBackupReadExtendedValue(slave, &extendedValue); | |||||
| if (status != MODBUS_STATUS_OK) | |||||
| { | |||||
| return status; | |||||
| } | |||||
| /* 写入数据前先使提交标志失效。如果中间任一步骤断电,启动恢复时会忽略这条不完整记录。 */ | |||||
| crc = ModbusBackupCalculateCrc(extendedValue); | |||||
| ModbusBackupWriteRegister(MODBUS_BACKUP_REG_MAGIC, 0U); | |||||
| ModbusBackupWriteRegister(MODBUS_BACKUP_REG_EXTENDED, extendedValue); | |||||
| ModbusBackupWriteRegister(MODBUS_BACKUP_REG_VERSION_CRC, | |||||
| ((uint32_t)MODBUS_BACKUP_VERSION << 16U) | crc); | |||||
| /* 最后才写入提交标志;因此读到该标志时,说明数值、版本和 CRC 已由同一次保存操作写完。 */ | |||||
| ModbusBackupWriteRegister(MODBUS_BACKUP_REG_MAGIC, MODBUS_BACKUP_MAGIC); | |||||
| return MODBUS_STATUS_OK; | |||||
| } | |||||
| /** | |||||
| * @brief 在启动阶段恢复通过魔数、版本和 CRC 校验的扩展寄存器数据。 | |||||
| * @param[in,out] slave 已绑定扩展保持寄存器的从站对象。 | |||||
| * @return RTC 访问失败时返回错误状态;无效或缺失备份按首次启动处理并返回成功。 | |||||
| */ | |||||
| MODBUS_STATUS ModbusBackupRestore(MODBUS_SLAVE *slave) | |||||
| { | |||||
| uint16_t extendedValue; | |||||
| uint16_t expectedCrc; | |||||
| uint32_t packedValue; | |||||
| MODBUS_STATUS status; | |||||
| status = ModbusBackupEnableRtcAccess(); | |||||
| if (status != MODBUS_STATUS_OK) | |||||
| { | |||||
| return status; | |||||
| } | |||||
| /* 缺少提交标志、版本不匹配或 CRC 不匹配都视为“没有有效备份”。调用者会保留 | |||||
| * RAM 中的默认值,而不会恢复可能被断电撕裂的数据。 */ | |||||
| if ((slave == NULL) || (slave->extendedHoldingRegister == NULL) | |||||
| || (ModbusBackupReadRegister(MODBUS_BACKUP_REG_MAGIC) | |||||
| != MODBUS_BACKUP_MAGIC)) | |||||
| { | |||||
| return MODBUS_STATUS_OK; | |||||
| } | |||||
| packedValue = ModbusBackupReadRegister(MODBUS_BACKUP_REG_VERSION_CRC); | |||||
| if ((uint16_t)(packedValue >> 16U) != MODBUS_BACKUP_VERSION) | |||||
| { | |||||
| return MODBUS_STATUS_OK; | |||||
| } | |||||
| expectedCrc = (uint16_t)packedValue; | |||||
| extendedValue = (uint16_t)ModbusBackupReadRegister( | |||||
| MODBUS_BACKUP_REG_EXTENDED); | |||||
| if (ModbusBackupCalculateCrc(extendedValue) != expectedCrc) | |||||
| { | |||||
| return MODBUS_STATUS_OK; | |||||
| } | |||||
| *slave->extendedHoldingRegister = extendedValue; | |||||
| return MODBUS_STATUS_OK; | |||||
| } | |||||
| @@ -0,0 +1,27 @@ | |||||
| /** | |||||
| * @file modbus_backup.h | |||||
| * @brief 私有扩展保持寄存器的 RTC 备份接口。 | |||||
| */ | |||||
| #ifndef MODBUS_BACKUP_H | |||||
| #define MODBUS_BACKUP_H | |||||
| #include "modbus.h" | |||||
| /** | |||||
| * @brief 从 RTC 备份寄存器恢复私有地址 0x00010000 的值。 | |||||
| * @param[in,out] slave 已绑定扩展寄存器的从站对象。 | |||||
| * @return RTC 访问失败时返回错误状态;无有效备份时返回 MODBUS_STATUS_OK 且不修改值。 | |||||
| * @note 仅恢复魔数、版本和 CRC 均有效的数据;需要 VBAT 为 RTC 备份域供电。 | |||||
| */ | |||||
| MODBUS_STATUS ModbusBackupRestore(MODBUS_SLAVE *slave); | |||||
| /** | |||||
| * @brief 将私有地址 0x00010000 的当前值写入 RTC 备份寄存器。 | |||||
| * @param[in] slave 已绑定扩展寄存器的从站对象。 | |||||
| * @return 成功返回 MODBUS_STATUS_OK;RTC 或参数错误返回对应状态。 | |||||
| * @note 写入时先清除魔数,完成值与 CRC 写入后最后提交魔数,避免掉电产生有效假象。 | |||||
| */ | |||||
| MODBUS_STATUS ModbusBackupSave(const MODBUS_SLAVE *slave); | |||||
| #endif | |||||
| @@ -1,256 +0,0 @@ | |||||
| /** | |||||
| * @file modbus_test.c | |||||
| * @brief Modbus RTU 从站协议层单元测试。 | |||||
| * @version 2.0 | |||||
| * @author zengbingjie | |||||
| * @date 2026-07-30 | |||||
| * @copyright 版权所有 (c) 2026 Xinje Electric。 | |||||
| * @note 修改记录:2.0 改为直接构造主站请求帧。 | |||||
| */ | |||||
| #include <assert.h> | |||||
| #include <stdint.h> | |||||
| #include "modbus.h" | |||||
| #define TEST_SLAVE_ADDRESS (1U) | |||||
| #define TEST_COIL_COUNT (8U) | |||||
| #define TEST_COIL_WRITE_BYTE_COUNT (1U) | |||||
| #define TEST_COIL_WRITE_PAYLOAD_LENGTH (9U) | |||||
| #define TEST_COIL_WRITE_FRAME_LENGTH (11U) | |||||
| #define TEST_COIL_READ_PAYLOAD_LENGTH (6U) | |||||
| #define TEST_COIL_READ_FRAME_LENGTH (8U) | |||||
| #define TEST_COIL_READ_RESPONSE_LENGTH (6U) | |||||
| #define TEST_COIL_WRITE_VALUE (0x4DU) | |||||
| #define TEST_REGISTER_START_ADDRESS (0x0100U) | |||||
| #define TEST_REGISTER_COUNT (3U) | |||||
| #define TEST_REGISTER_WRITE_BYTE_COUNT (6U) | |||||
| #define TEST_REGISTER_WRITE_PAYLOAD_LENGTH (13U) | |||||
| #define TEST_REGISTER_WRITE_FRAME_LENGTH (15U) | |||||
| #define TEST_REGISTER_READ_PAYLOAD_LENGTH (6U) | |||||
| #define TEST_REGISTER_READ_FRAME_LENGTH (8U) | |||||
| #define TEST_REGISTER_READ_RESPONSE_LENGTH (11U) | |||||
| #define TEST_REGISTER_VALUE_0 (0x1234U) | |||||
| #define TEST_REGISTER_VALUE_1 (0xABCDU) | |||||
| #define TEST_REGISTER_VALUE_2 (0x0001U) | |||||
| #define TEST_BROADCAST_WRITE_PAYLOAD_LENGTH (9U) | |||||
| #define TEST_BROADCAST_WRITE_FRAME_LENGTH (11U) | |||||
| #define TEST_BROADCAST_WRITE_VALUE (0xBEEFU) | |||||
| /** | |||||
| * @brief 向测试帧末尾追加 Modbus RTU CRC。 | |||||
| * @param[in,out] frame 测试帧缓冲区。 | |||||
| * @param[in] payloadLength 未含 CRC 的帧长度。 | |||||
| */ | |||||
| static void TestAppendCrc(uint8_t *frame, uint16_t payloadLength) | |||||
| { | |||||
| uint16_t crcValue; | |||||
| assert(frame != NULL); | |||||
| crcValue = ModbusCrc16(frame, payloadLength); | |||||
| frame[payloadLength] = (uint8_t)(crcValue & 0x00FFU); | |||||
| frame[payloadLength + 1U] = (uint8_t)(crcValue >> 8U); | |||||
| } | |||||
| /** | |||||
| * @brief 校验 Modbus RTU 响应帧的 CRC。 | |||||
| * @param[in] frame 响应帧缓冲区。 | |||||
| * @param[in] frameLength 含 CRC 的完整响应帧长度。 | |||||
| */ | |||||
| static void TestAssertFrameCrc(const uint8_t *frame, uint16_t frameLength) | |||||
| { | |||||
| uint16_t expectedCrc; | |||||
| uint16_t actualCrc; | |||||
| assert(frame != NULL); | |||||
| assert(frameLength >= MODBUS_RTU_ADU_MIN_LENGTH); | |||||
| expectedCrc = ModbusCrc16(frame, frameLength - MODBUS_RTU_CRC_LENGTH); | |||||
| actualCrc = (uint16_t)frame[frameLength - MODBUS_RTU_CRC_LENGTH] | |||||
| | ((uint16_t)frame[frameLength - 1U] << 8U); | |||||
| assert(actualCrc == expectedCrc); | |||||
| } | |||||
| /** | |||||
| * @brief 验证标准 Modbus RTU CRC 字节序。 | |||||
| */ | |||||
| static void TestCrcByteOrder(void) | |||||
| { | |||||
| static const uint8_t requestPayload[] = { | |||||
| TEST_SLAVE_ADDRESS, MODBUS_FC_READ_HOLDING_REGS, 0x00U, 0x00U, | |||||
| 0x00U, 0x01U | |||||
| }; | |||||
| assert(ModbusCrc16(requestPayload, sizeof(requestPayload)) == 0x0A84U); | |||||
| } | |||||
| /** | |||||
| * @brief 验证从站处理 0x0F 写线圈和 0x01 读线圈请求。 | |||||
| */ | |||||
| static void TestCoilReadWrite(void) | |||||
| { | |||||
| static MODBUS_SLAVE slave; | |||||
| uint8_t writeRequest[TEST_COIL_WRITE_FRAME_LENGTH] = { | |||||
| TEST_SLAVE_ADDRESS, MODBUS_FC_WRITE_MULTIPLE_COILS, | |||||
| 0x00U, 0x00U, 0x00U, TEST_COIL_COUNT, | |||||
| TEST_COIL_WRITE_BYTE_COUNT, TEST_COIL_WRITE_VALUE, | |||||
| 0x00U, 0x00U | |||||
| }; | |||||
| uint8_t readRequest[TEST_COIL_READ_FRAME_LENGTH] = { | |||||
| TEST_SLAVE_ADDRESS, MODBUS_FC_READ_COILS, | |||||
| 0x00U, 0x00U, 0x00U, TEST_COIL_COUNT, | |||||
| 0x00U, 0x00U | |||||
| }; | |||||
| uint8_t responseFrame[MODBUS_RTU_ADU_MAX_LENGTH]; | |||||
| uint16_t responseLength; | |||||
| assert(ModbusSlaveInit(&slave, TEST_SLAVE_ADDRESS) | |||||
| == MODBUS_STATUS_OK); | |||||
| TestAppendCrc(writeRequest, TEST_COIL_WRITE_PAYLOAD_LENGTH); | |||||
| assert(ModbusProcessFrame(&slave, writeRequest, sizeof(writeRequest), | |||||
| responseFrame, sizeof(responseFrame), | |||||
| &responseLength) == MODBUS_STATUS_OK); | |||||
| assert(responseLength == MODBUS_RTU_WRITE_RESPONSE_LENGTH); | |||||
| assert(responseFrame[0U] == TEST_SLAVE_ADDRESS); | |||||
| assert(responseFrame[1U] == MODBUS_FC_WRITE_MULTIPLE_COILS); | |||||
| assert(responseFrame[2U] == 0x00U); | |||||
| assert(responseFrame[3U] == 0x00U); | |||||
| assert(responseFrame[4U] == 0x00U); | |||||
| assert(responseFrame[5U] == TEST_COIL_COUNT); | |||||
| TestAssertFrameCrc(responseFrame, responseLength); | |||||
| TestAppendCrc(readRequest, TEST_COIL_READ_PAYLOAD_LENGTH); | |||||
| assert(ModbusProcessFrame(&slave, readRequest, sizeof(readRequest), | |||||
| responseFrame, sizeof(responseFrame), | |||||
| &responseLength) == MODBUS_STATUS_OK); | |||||
| assert(responseLength == TEST_COIL_READ_RESPONSE_LENGTH); | |||||
| assert(responseFrame[0U] == TEST_SLAVE_ADDRESS); | |||||
| assert(responseFrame[1U] == MODBUS_FC_READ_COILS); | |||||
| assert(responseFrame[2U] == TEST_COIL_WRITE_BYTE_COUNT); | |||||
| assert(responseFrame[3U] == TEST_COIL_WRITE_VALUE); | |||||
| TestAssertFrameCrc(responseFrame, responseLength); | |||||
| } | |||||
| /** | |||||
| * @brief 验证从站处理 0x10 写寄存器和 0x03 读寄存器请求。 | |||||
| */ | |||||
| static void TestHoldingRegisterReadWrite(void) | |||||
| { | |||||
| static MODBUS_SLAVE slave; | |||||
| uint8_t writeRequest[TEST_REGISTER_WRITE_FRAME_LENGTH] = { | |||||
| TEST_SLAVE_ADDRESS, MODBUS_FC_WRITE_MULTIPLE_REGS, | |||||
| 0x01U, 0x00U, 0x00U, TEST_REGISTER_COUNT, | |||||
| TEST_REGISTER_WRITE_BYTE_COUNT, | |||||
| 0x12U, 0x34U, 0xABU, 0xCDU, 0x00U, 0x01U, | |||||
| 0x00U, 0x00U | |||||
| }; | |||||
| uint8_t readRequest[TEST_REGISTER_READ_FRAME_LENGTH] = { | |||||
| TEST_SLAVE_ADDRESS, MODBUS_FC_READ_HOLDING_REGS, | |||||
| 0x01U, 0x00U, 0x00U, TEST_REGISTER_COUNT, | |||||
| 0x00U, 0x00U | |||||
| }; | |||||
| uint8_t responseFrame[MODBUS_RTU_ADU_MAX_LENGTH]; | |||||
| uint16_t responseLength; | |||||
| assert(ModbusSlaveInit(&slave, TEST_SLAVE_ADDRESS) | |||||
| == MODBUS_STATUS_OK); | |||||
| TestAppendCrc(writeRequest, TEST_REGISTER_WRITE_PAYLOAD_LENGTH); | |||||
| assert(ModbusProcessFrame(&slave, writeRequest, sizeof(writeRequest), | |||||
| responseFrame, sizeof(responseFrame), | |||||
| &responseLength) == MODBUS_STATUS_OK); | |||||
| assert(responseLength == MODBUS_RTU_WRITE_RESPONSE_LENGTH); | |||||
| assert(responseFrame[0U] == TEST_SLAVE_ADDRESS); | |||||
| assert(responseFrame[1U] == MODBUS_FC_WRITE_MULTIPLE_REGS); | |||||
| assert(responseFrame[2U] == 0x01U); | |||||
| assert(responseFrame[3U] == 0x00U); | |||||
| assert(responseFrame[4U] == 0x00U); | |||||
| assert(responseFrame[5U] == TEST_REGISTER_COUNT); | |||||
| TestAssertFrameCrc(responseFrame, responseLength); | |||||
| TestAppendCrc(readRequest, TEST_REGISTER_READ_PAYLOAD_LENGTH); | |||||
| assert(ModbusProcessFrame(&slave, readRequest, sizeof(readRequest), | |||||
| responseFrame, sizeof(responseFrame), | |||||
| &responseLength) == MODBUS_STATUS_OK); | |||||
| assert(responseLength == TEST_REGISTER_READ_RESPONSE_LENGTH); | |||||
| assert(responseFrame[0U] == TEST_SLAVE_ADDRESS); | |||||
| assert(responseFrame[1U] == MODBUS_FC_READ_HOLDING_REGS); | |||||
| assert(responseFrame[2U] == TEST_REGISTER_WRITE_BYTE_COUNT); | |||||
| assert(responseFrame[3U] == 0x12U); | |||||
| assert(responseFrame[4U] == 0x34U); | |||||
| assert(responseFrame[5U] == 0xABU); | |||||
| assert(responseFrame[6U] == 0xCDU); | |||||
| assert(responseFrame[7U] == 0x00U); | |||||
| assert(responseFrame[8U] == 0x01U); | |||||
| TestAssertFrameCrc(responseFrame, responseLength); | |||||
| } | |||||
| /** | |||||
| * @brief 验证从站对非法数据地址返回 0x02 异常响应。 | |||||
| */ | |||||
| static void TestIllegalAddressException(void) | |||||
| { | |||||
| static MODBUS_SLAVE slave; | |||||
| uint8_t requestFrame[MODBUS_RTU_READ_REQUEST_LENGTH] = { | |||||
| TEST_SLAVE_ADDRESS, MODBUS_FC_READ_HOLDING_REGS, | |||||
| 0x27U, 0x0FU, 0x00U, 0x02U, | |||||
| 0x00U, 0x00U | |||||
| }; | |||||
| uint8_t responseFrame[MODBUS_RTU_ADU_MAX_LENGTH]; | |||||
| uint16_t responseLength; | |||||
| assert(ModbusSlaveInit(&slave, TEST_SLAVE_ADDRESS) | |||||
| == MODBUS_STATUS_OK); | |||||
| TestAppendCrc(requestFrame, TEST_COIL_READ_PAYLOAD_LENGTH); | |||||
| assert(ModbusProcessFrame(&slave, requestFrame, sizeof(requestFrame), | |||||
| responseFrame, sizeof(responseFrame), | |||||
| &responseLength) == MODBUS_STATUS_EXCEPTION); | |||||
| assert(responseLength == MODBUS_RTU_EXCEPTION_LENGTH); | |||||
| assert(responseFrame[0U] == TEST_SLAVE_ADDRESS); | |||||
| assert(responseFrame[1U] | |||||
| == (MODBUS_FC_READ_HOLDING_REGS | 0x80U)); | |||||
| assert(responseFrame[2U] == MODBUS_EXCEPTION_ILLEGAL_DATA_ADDR); | |||||
| TestAssertFrameCrc(responseFrame, responseLength); | |||||
| } | |||||
| /** | |||||
| * @brief 验证广播写寄存器生效且从站不返回响应。 | |||||
| */ | |||||
| static void TestBroadcastWrite(void) | |||||
| { | |||||
| static MODBUS_SLAVE slave; | |||||
| uint8_t requestFrame[TEST_BROADCAST_WRITE_FRAME_LENGTH] = { | |||||
| MODBUS_BROADCAST_ADDRESS, MODBUS_FC_WRITE_MULTIPLE_REGS, | |||||
| 0x01U, 0x00U, 0x00U, 0x01U, 0x02U, | |||||
| 0xBEU, 0xEFU, 0x00U, 0x00U | |||||
| }; | |||||
| uint8_t responseFrame[MODBUS_RTU_ADU_MAX_LENGTH]; | |||||
| uint16_t responseLength; | |||||
| uint16_t registerValue; | |||||
| assert(ModbusSlaveInit(&slave, TEST_SLAVE_ADDRESS) | |||||
| == MODBUS_STATUS_OK); | |||||
| TestAppendCrc(requestFrame, TEST_BROADCAST_WRITE_PAYLOAD_LENGTH); | |||||
| assert(ModbusProcessFrame(&slave, requestFrame, sizeof(requestFrame), | |||||
| responseFrame, sizeof(responseFrame), | |||||
| &responseLength) == MODBUS_STATUS_BROADCAST); | |||||
| assert(responseLength == 0U); | |||||
| assert(ModbusGetHoldingRegister(&slave, TEST_REGISTER_START_ADDRESS, | |||||
| ®isterValue) == MODBUS_STATUS_OK); | |||||
| assert(registerValue == TEST_BROADCAST_WRITE_VALUE); | |||||
| } | |||||
| /** | |||||
| * @brief 执行 Modbus RTU 从站协议单元测试。 | |||||
| * @return 0 表示全部测试通过。 | |||||
| */ | |||||
| int main(void) | |||||
| { | |||||
| TestCrcByteOrder(); | |||||
| TestCoilReadWrite(); | |||||
| TestHoldingRegisterReadWrite(); | |||||
| TestIllegalAddressException(); | |||||
| TestBroadcastWrite(); | |||||
| return 0; | |||||
| } | |||||
| @@ -4,7 +4,7 @@ | |||||
| * @version 2.0 | * @version 2.0 | ||||
| * @author zengbingjie | * @author zengbingjie | ||||
| * @date 2026-07-29 | * @date 2026-07-29 | ||||
| * @copyright 版权所有 (c) 2026 Xinje Electric。 | |||||
| * @copyright 版权所有信捷科技股份有限公司 | |||||
| * @note 修改记录:2.0 增加 Modbus RTU 从站任务配置。 | * @note 修改记录:2.0 增加 Modbus RTU 从站任务配置。 | ||||
| */ | */ | ||||
| @@ -18,31 +18,49 @@ | |||||
| #define APP_TASK_START_PRIO (5U) | #define APP_TASK_START_PRIO (5U) | ||||
| #define APP_TASK_T35_PRIO (6U) | #define APP_TASK_T35_PRIO (6U) | ||||
| // uC/OS-II 定时器管理任务使用低优先级保留任务槽。 | |||||
| /* uC/OS-II 定时器管理任务使用低优先级保留任务槽。 */ | |||||
| #define OS_TASK_TMR_PRIO (OS_LOWEST_PRIO - 2U) | #define OS_TASK_TMR_PRIO (OS_LOWEST_PRIO - 2U) | ||||
| #define APP_TASK_START_STK_SIZE (128U) | #define APP_TASK_START_STK_SIZE (128U) | ||||
| #define APP_TASK_MODBUS_STK_SIZE (384U) | #define APP_TASK_MODBUS_STK_SIZE (384U) | ||||
| #define APP_TASK_T35_STK_SIZE (128U) | #define APP_TASK_T35_STK_SIZE (128U) | ||||
| /* USART1 使用 115200 bit/s、8E1:1 个起始位、8 个数据位、偶校验和 1 个停止位。 */ | |||||
| #define APP_MODBUS_BAUD_RATE (115200U) | #define APP_MODBUS_BAUD_RATE (115200U) | ||||
| #define APP_MODBUS_T35_TICKS (2U) | |||||
| #define APP_MODBUS_T35_CHECK_TICKS (1U) | |||||
| #define APP_MODBUS_BITS_PER_CHARACTER (10U) | |||||
| /* 向上取整的单字符时间,确保计时不会短于实际串行字符。 */ | |||||
| #define APP_MODBUS_RTU_CHARACTER_TIME_US \ | |||||
| (((APP_MODBUS_BITS_PER_CHARACTER * 1000000U) \ | |||||
| + APP_MODBUS_BAUD_RATE - 1U) / APP_MODBUS_BAUD_RATE) | |||||
| /* 高于 19200 bit/s 时遵循 Modbus RTU 的固定 750 us / 1750 us 时序。 */ | |||||
| #define APP_MODBUS_RTU_T15_US \ | |||||
| ((APP_MODBUS_BAUD_RATE > 19200U) ? 750U : \ | |||||
| ((3U * APP_MODBUS_RTU_CHARACTER_TIME_US + 1U) / 2U)) | |||||
| #define APP_MODBUS_RTU_T35_US \ | |||||
| ((APP_MODBUS_BAUD_RATE > 19200U) ? 1750U : \ | |||||
| ((7U * APP_MODBUS_RTU_CHARACTER_TIME_US + 1U) / 2U)) | |||||
| #define APP_MODBUS_RTU_T15_RX_TIMEOUT_US \ | |||||
| (APP_MODBUS_RTU_T15_US + APP_MODBUS_RTU_CHARACTER_TIME_US) | |||||
| #define APP_MODBUS_RTU_T35_AFTER_T15_US \ | |||||
| (APP_MODBUS_RTU_T35_US - APP_MODBUS_RTU_T15_RX_TIMEOUT_US) | |||||
| /* TIM5 使用 1 MHz 计数基准,将 RTU 静默时间直接表示为微秒。 */ | |||||
| #define APP_MODBUS_RTU_TIMER_CLOCK_HZ (84000000U) | |||||
| #define APP_MODBUS_RTU_TIMER_TICK_HZ (1000000U) | |||||
| #define APP_MODBUS_STATUS_CHECK_TICKS (1U) | |||||
| #define APP_MODBUS_STATUS_UPDATE_TICKS (100U) | #define APP_MODBUS_STATUS_UPDATE_TICKS (100U) | ||||
| #define APP_MODBUS_LINK_TIMEOUT_TICKS (5000U) | |||||
| #define APP_MODBUS_LINK_TIMEOUT_TICKS (3000U) | |||||
| #define APP_MODBUS_TX_TIMEOUT_TICKS (100U) | #define APP_MODBUS_TX_TIMEOUT_TICKS (100U) | ||||
| /* 线圈 0 是链路状态;线圈 1 至 7 映射为实际 Q1 至 Q7 输出。 */ | |||||
| #define APP_MODBUS_OUTPUT_COIL_COUNT (8U) | #define APP_MODBUS_OUTPUT_COIL_COUNT (8U) | ||||
| #define APP_MODBUS_FIRST_USER_OUTPUT_COIL (1U) | |||||
| #define APP_MODBUS_USER_OUTPUT_COIL_COUNT \ | |||||
| (APP_MODBUS_OUTPUT_COIL_COUNT - APP_MODBUS_FIRST_USER_OUTPUT_COIL) | |||||
| #define APP_MODBUS_LINK_STATE_COIL (100U) | |||||
| #define APP_MODBUS_REG_LINK_STATE (0U) | |||||
| #define APP_MODBUS_REG_OPERATION_RX_COUNT (1U) | |||||
| #define APP_MODBUS_REG_OPERATION_TX_COUNT (2U) | |||||
| #define APP_MODBUS_REG_ERROR_COUNT (3U) | |||||
| #define APP_MODBUS_REG_SYSTEM_TICK_LOW (4U) | |||||
| #define APP_MODBUS_REG_SYSTEM_TICK_HIGH (5U) | |||||
| #define APP_MODBUS_REG_LAST_STATUS (6U) | |||||
| #define APP_MODBUS_REG_LAST_FRAME_LENGTH (7U) | |||||
| /* 链路状态由最近一次已寻址的有效 Modbus 请求决定。 */ | |||||
| #define APP_MODBUS_LINK_DISCONNECTED (0U) | #define APP_MODBUS_LINK_DISCONNECTED (0U) | ||||
| #define APP_MODBUS_LINK_CONNECTED (1U) | #define APP_MODBUS_LINK_CONNECTED (1U) | ||||
| @@ -4,16 +4,18 @@ | |||||
| * @version 2.0 | * @version 2.0 | ||||
| * @author zengbingjie | * @author zengbingjie | ||||
| * @date 2026-07-29 | * @date 2026-07-29 | ||||
| * @copyright 版权所有 (c) 2026 Xinje Electric。 | |||||
| * @copyright 版权所有信捷科技股份有限公司 | |||||
| * @note 修改记录:2.0 增加帧隔离、连接状态和可靠收发处理。 | * @note 修改记录:2.0 增加帧隔离、连接状态和可靠收发处理。 | ||||
| */ | */ | ||||
| #include "main.h" | #include "main.h" | ||||
| #include "modbus.h" | #include "modbus.h" | ||||
| #include "modbus_backup.h" | |||||
| #define APP_MODBUS_STATUS_REGISTER_COUNT (8U) | |||||
| #define APP_MODBUS_FUNCTION_INDEX (1U) | #define APP_MODBUS_FUNCTION_INDEX (1U) | ||||
| #define APP_MODBUS_RTU_TIMER_PHASE_T15 (0U) | |||||
| #define APP_MODBUS_RTU_TIMER_PHASE_T35 (1U) | |||||
| static OS_STK AppTaskStartStk[APP_TASK_START_STK_SIZE]; | static OS_STK AppTaskStartStk[APP_TASK_START_STK_SIZE]; | ||||
| static OS_STK AppTaskModbusStk[APP_TASK_MODBUS_STK_SIZE]; | static OS_STK AppTaskModbusStk[APP_TASK_MODBUS_STK_SIZE]; | ||||
| @@ -21,27 +23,44 @@ static OS_STK AppTaskT35Stk[APP_TASK_T35_STK_SIZE]; | |||||
| UART_HandleTypeDef Uart1Handle; | UART_HandleTypeDef Uart1Handle; | ||||
| /* 保留真实数据区供 IAR Live Watch 调试;不维护请求、应答或状态镜像副本。 */ | |||||
| volatile uint16_t holdingRegisters[MODBUS_DATA_POINT_COUNT]; | |||||
| volatile uint8_t coilStorage[MODBUS_COIL_STORAGE_SIZE]; | |||||
| volatile uint16_t extendedHoldingRegister; | |||||
| /* UART 诊断变量保留为全局可见,便于在 IAR Live Watch 中观察断线和重连测试。 */ | |||||
| volatile uint32_t ModbusUartLastErrorCode; | |||||
| volatile uint32_t ModbusUartLastRxState; | |||||
| volatile uint32_t ModbusUartRecoveryCount; | |||||
| volatile uint32_t ModbusUartRecoveryFailureCount; | |||||
| volatile uint32_t ModbusUartRxArmCount; | |||||
| volatile uint32_t ModbusUartRxArmFailureCount; | |||||
| volatile uint8_t ModbusUartLastRxByte; | |||||
| volatile uint16_t ModbusLastCompleteFrameLength; | |||||
| static MODBUS_SLAVE ModbusSlave; | static MODBUS_SLAVE ModbusSlave; | ||||
| static OS_EVENT *ModbusFrameSem; | static OS_EVENT *ModbusFrameSem; | ||||
| static OS_EVENT *ModbusTxSem; | static OS_EVENT *ModbusTxSem; | ||||
| /* 接收缓冲区由 UART 中断逐字节填充。只有 RTU 定时器置位 | |||||
| * ModbusFrameReady 后,Modbus 任务才读取它,因此解析器不会读到仍在接收中的帧。 */ | |||||
| static uint8_t ModbusRxBuffer[MODBUS_RX_BUF_SIZE]; | static uint8_t ModbusRxBuffer[MODBUS_RX_BUF_SIZE]; | ||||
| static uint8_t ModbusTxBuffer[MODBUS_TX_BUF_SIZE]; | static uint8_t ModbusTxBuffer[MODBUS_TX_BUF_SIZE]; | ||||
| /* 以下状态由中断和任务共享;更新应保持简短,读取完整帧快照时使用已有的临界区保护。 */ | |||||
| static volatile uint16_t ModbusRxLength; | static volatile uint16_t ModbusRxLength; | ||||
| static volatile uint16_t ModbusFrameLength; | static volatile uint16_t ModbusFrameLength; | ||||
| static volatile uint32_t ModbusLastRxTick; | |||||
| static volatile uint8_t ModbusFrameReady; | static volatile uint8_t ModbusFrameReady; | ||||
| static volatile uint8_t ModbusRxOverflow; | static volatile uint8_t ModbusRxOverflow; | ||||
| static volatile uint32_t ModbusReceiveErrorCount; | |||||
| static volatile uint32_t ModbusSemaphoreErrorCount; | |||||
| static volatile uint8_t ModbusRtuTimerPhase; | |||||
| static volatile uint8_t ModbusInterCharacterTimeout; | |||||
| static volatile uint8_t ModbusRxDiscard; | |||||
| static volatile uint8_t ModbusReceptionNeedsRecovery; | |||||
| static uint32_t ModbusLastActivityTick; | static uint32_t ModbusLastActivityTick; | ||||
| static uint32_t ModbusOperationRxCount; | |||||
| static uint32_t ModbusOperationTxCount; | |||||
| static uint32_t ModbusProtocolErrorCount; | |||||
| static uint8_t ModbusHasActivity; | static uint8_t ModbusHasActivity; | ||||
| static MODBUS_STATUS ModbusLastStatus; | static MODBUS_STATUS ModbusLastStatus; | ||||
| static GPIO_TypeDef *const AppOutputPorts[APP_MODBUS_OUTPUT_COIL_COUNT] = { | |||||
| PLC_Q0_PORT, | |||||
| static GPIO_TypeDef *const | |||||
| AppUserOutputPorts[APP_MODBUS_USER_OUTPUT_COIL_COUNT] = | |||||
| { | |||||
| PLC_Q1_PORT, | PLC_Q1_PORT, | ||||
| PLC_Q2_PORT, | PLC_Q2_PORT, | ||||
| PLC_Q3_PORT, | PLC_Q3_PORT, | ||||
| @@ -51,8 +70,9 @@ static GPIO_TypeDef *const AppOutputPorts[APP_MODBUS_OUTPUT_COIL_COUNT] = { | |||||
| PLC_Q7_PORT | PLC_Q7_PORT | ||||
| }; | }; | ||||
| static const uint16_t AppOutputPins[APP_MODBUS_OUTPUT_COIL_COUNT] = { | |||||
| PLC_Q0_PIN, | |||||
| static const uint16_t | |||||
| AppUserOutputPins[APP_MODBUS_USER_OUTPUT_COIL_COUNT] = | |||||
| { | |||||
| PLC_Q1_PIN, | PLC_Q1_PIN, | ||||
| PLC_Q2_PIN, | PLC_Q2_PIN, | ||||
| PLC_Q3_PIN, | PLC_Q3_PIN, | ||||
| @@ -67,20 +87,27 @@ void SystemClock_Config(void); | |||||
| static void AppTaskStart(void *argument); | static void AppTaskStart(void *argument); | ||||
| static void AppTaskModbus(void *argument); | static void AppTaskModbus(void *argument); | ||||
| static void AppTaskT35(void *argument); | static void AppTaskT35(void *argument); | ||||
| static void AppInitSystemTick(void); | |||||
| static void AppInitGpio(void); | static void AppInitGpio(void); | ||||
| static void AppInitUart1(void); | static void AppInitUart1(void); | ||||
| static void AppInitModbusRtuTimer(void); | |||||
| static INT8U AppCreateTasks(void); | static INT8U AppCreateTasks(void); | ||||
| static HAL_StatusTypeDef AppStartUartReception(void); | static HAL_StatusTypeDef AppStartUartReception(void); | ||||
| static HAL_StatusTypeDef AppRecoverUartReception(void); | |||||
| static HAL_StatusTypeDef AppRecoverPendingUartReception(void); | |||||
| static HAL_StatusTypeDef AppEnsureUartReception(void); | |||||
| static HAL_StatusTypeDef AppTransmitResponse(uint16_t txLength); | static HAL_StatusTypeDef AppTransmitResponse(uint16_t txLength); | ||||
| static uint8_t AppSealReceivedFrame(void); | |||||
| static void AppStartModbusRtuTimer(void); | |||||
| static void AppStopModbusRtuTimer(void); | |||||
| static void AppSetModbusRtuTimer(uint16_t timeoutUs); | |||||
| static void AppHandleModbusRtuTimer(void); | |||||
| static uint16_t AppGetReadyFrameLength(void); | static uint16_t AppGetReadyFrameLength(void); | ||||
| static INT8U AppPostModbusFrameSem(void); | static INT8U AppPostModbusFrameSem(void); | ||||
| static uint8_t AppIsModbusStatusAccepted(MODBUS_STATUS status); | static uint8_t AppIsModbusStatusAccepted(MODBUS_STATUS status); | ||||
| static uint8_t AppIsModbusWriteFunction(uint8_t functionCode); | |||||
| static MODBUS_STATUS AppUpdateStatusRegisters(void); | |||||
| static uint8_t AppIsModbusRetentionWriteFunction(uint8_t functionCode); | |||||
| static MODBUS_STATUS AppUpdateLinkStatus(void); | |||||
| static MODBUS_STATUS AppSyncCoilsToGpio(void); | static MODBUS_STATUS AppSyncCoilsToGpio(void); | ||||
| static void AppProcessFrame(uint16_t rxLength); | static void AppProcessFrame(uint16_t rxLength); | ||||
| static void AppRecordIsrError(void); | |||||
| /** | /** | ||||
| * @brief 应用程序入口。 | * @brief 应用程序入口。 | ||||
| @@ -90,11 +117,17 @@ int main(void) | |||||
| { | { | ||||
| INT8U osError; | INT8U osError; | ||||
| /* 启动调度器前必须先完成硬件初始化。尤其要先初始化 USART1 和 TIM5, | |||||
| * 这样任务才能安全地启动接收和使用 RTU 帧间隔定时器。 */ | |||||
| HAL_Init(); | HAL_Init(); | ||||
| AppInitSystemTick(); | |||||
| SystemClock_Config(); | SystemClock_Config(); | ||||
| AppInitGpio(); | AppInitGpio(); | ||||
| AppInitUart1(); | AppInitUart1(); | ||||
| AppInitModbusRtuTimer(); | |||||
| /* OSStart() 之后由 uC/OS-II 负责任务调度;启动任务会创建下面的 | |||||
| * Modbus 工作任务和定时监测任务。 */ | |||||
| OSInit(); | OSInit(); | ||||
| osError = OSTaskCreateExt( | osError = OSTaskCreateExt( | ||||
| AppTaskStart, NULL, | AppTaskStart, NULL, | ||||
| @@ -148,7 +181,15 @@ void SystemClock_Config(void) | |||||
| } | } | ||||
| /** | /** | ||||
| * @brief 初始化 XDM-60T4-E 的 Q0 至 Q7 输出引脚。 | |||||
| * @brief 将 SysTick 配置为可调用 uC/OS-II 的内核感知中断优先级。 | |||||
| */ | |||||
| static void AppInitSystemTick(void) | |||||
| { | |||||
| HAL_NVIC_SetPriority(SysTick_IRQn, CPU_CFG_KA_IPL_BOUNDARY, 0U); | |||||
| } | |||||
| /** | |||||
| * @brief 初始化 XDM-60T4-E 的连接指示灯和 Q1 至 Q7 输出引脚。 | |||||
| * @note 板卡输出为低电平有效,初始状态全部关闭。 | * @note 板卡输出为低电平有效,初始状态全部关闭。 | ||||
| */ | */ | ||||
| static void AppInitGpio(void) | static void AppInitGpio(void) | ||||
| @@ -186,9 +227,11 @@ static void AppInitUart1(void) | |||||
| { | { | ||||
| Uart1Handle.Instance = USART1; | Uart1Handle.Instance = USART1; | ||||
| Uart1Handle.Init.BaudRate = APP_MODBUS_BAUD_RATE; | Uart1Handle.Init.BaudRate = APP_MODBUS_BAUD_RATE; | ||||
| Uart1Handle.Init.WordLength = UART_WORDLENGTH_8B; | |||||
| /* 串口参数必须与触摸屏/PLC 主站完全一致。当前工程使用 9 位字长和偶校验 | |||||
| * (HAL 对“8 位数据 + 1 位校验”的配置方式);不能只修改从站而不修改主站。 */ | |||||
| Uart1Handle.Init.WordLength = UART_WORDLENGTH_9B; | |||||
| Uart1Handle.Init.StopBits = UART_STOPBITS_1; | Uart1Handle.Init.StopBits = UART_STOPBITS_1; | ||||
| Uart1Handle.Init.Parity = UART_PARITY_NONE; | |||||
| Uart1Handle.Init.Parity = UART_PARITY_EVEN; | |||||
| Uart1Handle.Init.Mode = UART_MODE_TX_RX; | Uart1Handle.Init.Mode = UART_MODE_TX_RX; | ||||
| Uart1Handle.Init.HwFlowCtl = UART_HWCONTROL_NONE; | Uart1Handle.Init.HwFlowCtl = UART_HWCONTROL_NONE; | ||||
| Uart1Handle.Init.OverSampling = UART_OVERSAMPLING_16; | Uart1Handle.Init.OverSampling = UART_OVERSAMPLING_16; | ||||
| @@ -197,6 +240,26 @@ static void AppInitUart1(void) | |||||
| } | } | ||||
| } | } | ||||
| /** | |||||
| * @brief 将 TIM5 配置为 Modbus RTU 帧间隔单次定时器。 | |||||
| */ | |||||
| static void AppInitModbusRtuTimer(void) | |||||
| { | |||||
| __HAL_RCC_TIM5_CLK_ENABLE(); | |||||
| TIM5->CR1 = 0U; | |||||
| TIM5->PSC = (APP_MODBUS_RTU_TIMER_CLOCK_HZ | |||||
| / APP_MODBUS_RTU_TIMER_TICK_HZ) - 1U; | |||||
| TIM5->ARR = APP_MODBUS_RTU_T35_US - 1U; | |||||
| TIM5->CNT = 0U; | |||||
| TIM5->EGR = TIM_EGR_UG; | |||||
| TIM5->SR = 0U; | |||||
| TIM5->DIER = TIM_DIER_UIE; | |||||
| HAL_NVIC_SetPriority(TIM5_IRQn, 5U, 0U); | |||||
| HAL_NVIC_EnableIRQ(TIM5_IRQn); | |||||
| } | |||||
| /** | /** | ||||
| * @brief 创建 Modbus 工作任务与帧间隔检测任务。 | * @brief 创建 Modbus 工作任务与帧间隔检测任务。 | ||||
| * @return uC/OS-II 错误码。 | * @return uC/OS-II 错误码。 | ||||
| @@ -258,19 +321,108 @@ static HAL_StatusTypeDef AppStartUartReception(void) | |||||
| OS_CPU_SR cpu_sr = 0U; | OS_CPU_SR cpu_sr = 0U; | ||||
| HAL_StatusTypeDef halStatus; | HAL_StatusTypeDef halStatus; | ||||
| /* 新帧必须从干净的软件状态开始。这里使用临界区,避免定时器或 UART 中断 | |||||
| * 看到只清除了一半的状态字段。 */ | |||||
| OS_ENTER_CRITICAL(); | OS_ENTER_CRITICAL(); | ||||
| AppStopModbusRtuTimer(); | |||||
| ModbusRxLength = 0U; | ModbusRxLength = 0U; | ||||
| ModbusFrameLength = 0U; | ModbusFrameLength = 0U; | ||||
| ModbusFrameReady = 0U; | ModbusFrameReady = 0U; | ||||
| ModbusRxOverflow = 0U; | ModbusRxOverflow = 0U; | ||||
| ModbusLastRxTick = OSTimeGet(); | |||||
| ModbusRtuTimerPhase = APP_MODBUS_RTU_TIMER_PHASE_T15; | |||||
| ModbusInterCharacterTimeout = 0U; | |||||
| ModbusRxDiscard = 0U; | |||||
| OS_EXIT_CRITICAL(); | OS_EXIT_CRITICAL(); | ||||
| /* 每次只挂接一个字节的接收。接收完成回调负责推进缓冲区并重新挂接下一个 | |||||
| * 字节,使组帧完全由中断驱动且不会阻塞 Modbus 任务。 */ | |||||
| halStatus = HAL_UART_Receive_IT(&Uart1Handle, &ModbusRxBuffer[0], 1U); | halStatus = HAL_UART_Receive_IT(&Uart1Handle, &ModbusRxBuffer[0], 1U); | ||||
| ModbusUartRxArmCount++; | |||||
| if (halStatus != HAL_OK) { | |||||
| ModbusUartRxArmFailureCount++; | |||||
| } | |||||
| ModbusUartLastRxState = (uint32_t)Uart1Handle.RxState; | |||||
| return halStatus; | |||||
| } | |||||
| /** | |||||
| * @brief 在 Modbus 任务上下文中恢复 USART1 单字节中断接收。 | |||||
| * @return HAL 状态码。 | |||||
| */ | |||||
| static HAL_StatusTypeDef AppRecoverUartReception(void) | |||||
| { | |||||
| HAL_StatusTypeDef halStatus; | |||||
| HAL_StatusTypeDef abortStatus; | |||||
| ModbusUartRecoveryCount++; | |||||
| /* 出错的接收仍由 HAL 持有时,HAL_UART_Receive_IT() 会返回 BUSY。 | |||||
| * 先终止旧接收并清除外设状态,下一次挂接才能从 READY 状态开始。 */ | |||||
| abortStatus = HAL_UART_AbortReceive_IT(&Uart1Handle); | |||||
| /* STM32 HAL 宏会按外设要求读取 SR 和 DR,以清除奇偶校验、帧格式和溢出错误。 | |||||
| * 该操作必须在中断之外执行。 */ | |||||
| __HAL_UART_CLEAR_PEFLAG(&Uart1Handle); | |||||
| halStatus = AppStartUartReception(); | |||||
| if ((abortStatus != HAL_OK) || (halStatus != HAL_OK)) { | |||||
| (void)HAL_UART_AbortReceive_IT(&Uart1Handle); | |||||
| __HAL_UART_CLEAR_PEFLAG(&Uart1Handle); | |||||
| halStatus = AppStartUartReception(); | |||||
| } | |||||
| if (halStatus != HAL_OK) { | |||||
| ModbusUartRecoveryFailureCount++; | |||||
| } | |||||
| ModbusUartLastRxState = (uint32_t)Uart1Handle.RxState; | |||||
| return halStatus; | return halStatus; | ||||
| } | } | ||||
| /** | |||||
| * @brief 处理一个待执行的 UART 接收恢复请求。 | |||||
| * @return 本次恢复操作返回的 HAL 状态。 | |||||
| * @note 接收已经挂接时 UART 中断仍可能设置待恢复标志。只能清除本次尝试前 | |||||
| * 已取出的请求,不能清除恢复过程中并发产生的新请求。 | |||||
| */ | |||||
| static HAL_StatusTypeDef AppRecoverPendingUartReception(void) | |||||
| { | |||||
| HAL_StatusTypeDef halStatus; | |||||
| halStatus = HAL_OK; | |||||
| if (ModbusReceptionNeedsRecovery != 0U) { | |||||
| ModbusReceptionNeedsRecovery = 0U; | |||||
| halStatus = AppRecoverUartReception(); | |||||
| if (halStatus != HAL_OK) { | |||||
| ModbusReceptionNeedsRecovery = 1U; | |||||
| } | |||||
| } | |||||
| return halStatus; | |||||
| } | |||||
| /** | |||||
| * @brief 即使 HAL 状态丢失,也保持 UART 接收中断处于挂接状态。 | |||||
| * @return 恢复操作的 HAL 状态;接收已经挂接时返回 HAL_OK。 | |||||
| * @note RS-485 断线时可能完全没有 UART 中断,因此这里的检查可以补充错误回调, | |||||
| * 同时处理接收被打断后 HAL 停留在 READY/BUSY 状态的情况。 | |||||
| */ | |||||
| static HAL_StatusTypeDef AppEnsureUartReception(void) | |||||
| { | |||||
| if (ModbusFrameReady != 0U) { | |||||
| return HAL_OK; | |||||
| } | |||||
| /* 线缆断开可能完全不产生 UART 中断。任务每次唤醒时检查 HAL 状态,可以补充 | |||||
| * ErrorCallback 的处理,即使硬件没有上报错误标志也能重新挂接接收。 */ | |||||
| if ((ModbusReceptionNeedsRecovery != 0U) | |||||
| || (Uart1Handle.RxState != HAL_UART_STATE_BUSY_RX)) { | |||||
| ModbusReceptionNeedsRecovery = 1U; | |||||
| return AppRecoverPendingUartReception(); | |||||
| } | |||||
| return HAL_OK; | |||||
| } | |||||
| /** | /** | ||||
| * @brief 通过 USART1 发送 Modbus 从站响应帧。 | * @brief 通过 USART1 发送 Modbus 从站响应帧。 | ||||
| * @param[in] txLength 响应帧长度。 | * @param[in] txLength 响应帧长度。 | ||||
| @@ -285,6 +437,8 @@ static HAL_StatusTypeDef AppTransmitResponse(uint16_t txLength) | |||||
| return HAL_ERROR; | return HAL_ERROR; | ||||
| } | } | ||||
| /* 发送是异步进行的。任务等待 ModbusTxSem,确保 USART1 发出最后一个字节前 | |||||
| * 不会复用同一个发送缓冲区。 */ | |||||
| halStatus = HAL_UART_Transmit_IT(&Uart1Handle, ModbusTxBuffer, txLength); | halStatus = HAL_UART_Transmit_IT(&Uart1Handle, ModbusTxBuffer, txLength); | ||||
| if (halStatus != HAL_OK) { | if (halStatus != HAL_OK) { | ||||
| return halStatus; | return halStatus; | ||||
| @@ -300,43 +454,74 @@ static HAL_StatusTypeDef AppTransmitResponse(uint16_t txLength) | |||||
| } | } | ||||
| /** | /** | ||||
| * @brief 根据 T3.5 静默时间将接收缓冲区封存为完整帧。 | |||||
| * @return 1 表示已封存完整帧;0 表示未满足封帧条件。 | |||||
| * @brief 设置正在运行的 RTU 定时器单次超时时间,单位为微秒。 | |||||
| * @param[in] timeoutUs 单次超时时间,单位为微秒。 | |||||
| */ | */ | ||||
| static uint8_t AppSealReceivedFrame(void) | |||||
| static void AppSetModbusRtuTimer(uint16_t timeoutUs) | |||||
| { | { | ||||
| OS_CPU_SR cpu_sr = 0U; | |||||
| HAL_StatusTypeDef halStatus; | |||||
| uint32_t currentTick; | |||||
| uint32_t elapsedTicks; | |||||
| uint8_t frameSealed; | |||||
| /* TIM5 作为单次定时器使用。装载新间隔前先停止、清零并确认状态, | |||||
| * 否则残留的更新标志可能立即产生错误的帧边界。 */ | |||||
| TIM5->CR1 &= (uint32_t)(~TIM_CR1_CEN); | |||||
| TIM5->CNT = 0U; | |||||
| TIM5->ARR = (uint32_t)timeoutUs - 1U; | |||||
| TIM5->SR = 0U; | |||||
| TIM5->CR1 |= TIM_CR1_CEN; | |||||
| } | |||||
| frameSealed = 0U; | |||||
| currentTick = OSTimeGet(); | |||||
| OS_ENTER_CRITICAL(); | |||||
| elapsedTicks = currentTick - ModbusLastRxTick; | |||||
| if ((ModbusRxLength > 0U) && (ModbusFrameReady == 0U) | |||||
| && (elapsedTicks >= APP_MODBUS_T35_TICKS)) { | |||||
| ModbusFrameLength = ModbusRxLength; | |||||
| ModbusFrameReady = 1U; | |||||
| frameSealed = 1U; | |||||
| } | |||||
| OS_EXIT_CRITICAL(); | |||||
| /** | |||||
| * @brief 启动 Modbus RTU T1.5/T3.5 定时器的第一阶段。 | |||||
| */ | |||||
| static void AppStartModbusRtuTimer(void) | |||||
| { | |||||
| /* 每收到一个字节就重新开始 T1.5。如果线路静默超过 T1.5,当前帧会被标记为 | |||||
| * 无效,随后第二阶段继续等待 T3.5,再决定丢弃帧还是交给任务处理。 */ | |||||
| ModbusRtuTimerPhase = APP_MODBUS_RTU_TIMER_PHASE_T15; | |||||
| ModbusInterCharacterTimeout = 0U; | |||||
| AppSetModbusRtuTimer(APP_MODBUS_RTU_T15_RX_TIMEOUT_US); | |||||
| } | |||||
| /** | |||||
| * @brief 停止并确认 Modbus RTU 帧间隔定时器。 | |||||
| */ | |||||
| static void AppStopModbusRtuTimer(void) | |||||
| { | |||||
| TIM5->CR1 &= (uint32_t)(~TIM_CR1_CEN); | |||||
| TIM5->SR = 0U; | |||||
| } | |||||
| if (frameSealed == 0U) { | |||||
| return 0U; | |||||
| /** | |||||
| * @brief 在中断上下文中处理 T1.5 和 T3.5 单次定时到期事件。 | |||||
| */ | |||||
| static void AppHandleModbusRtuTimer(void) | |||||
| { | |||||
| if (ModbusRtuTimerPhase == APP_MODBUS_RTU_TIMER_PHASE_T15) { | |||||
| /* T1.5 表示帧内出现了字符间隔,此时不能立即解析;还要等待后续 T3.5, | |||||
| * 用它把这条不完整帧与下一条合法 RTU 帧分隔开。 */ | |||||
| ModbusInterCharacterTimeout = 1U; | |||||
| ModbusRtuTimerPhase = APP_MODBUS_RTU_TIMER_PHASE_T35; | |||||
| AppSetModbusRtuTimer(APP_MODBUS_RTU_T35_AFTER_T15_US); | |||||
| return; | |||||
| } | } | ||||
| halStatus = HAL_UART_AbortReceive_IT(&Uart1Handle); | |||||
| if (halStatus != HAL_OK) { | |||||
| AppRecordIsrError(); | |||||
| AppStopModbusRtuTimer(); | |||||
| ModbusInterCharacterTimeout = 0U; | |||||
| if (ModbusRxDiscard != 0U) { | |||||
| ModbusRxDiscard = 0U; | |||||
| ModbusRxLength = 0U; | |||||
| return; | |||||
| } | } | ||||
| if (AppPostModbusFrameSem() != OS_ERR_NONE) { | |||||
| AppRecordIsrError(); | |||||
| if ((ModbusRxLength == 0U) || (ModbusFrameReady != 0U)) { | |||||
| return; | |||||
| } | } | ||||
| return 1U; | |||||
| /* T3.5 表示帧结束。通知任务前先保存帧长度,任务随后解析稳定的缓冲区, | |||||
| * 完成后再重置接收状态。 */ | |||||
| ModbusFrameLength = ModbusRxLength; | |||||
| ModbusLastCompleteFrameLength = ModbusFrameLength; | |||||
| ModbusFrameReady = 1U; | |||||
| (void)HAL_UART_AbortReceive_IT(&Uart1Handle); | |||||
| (void)AppPostModbusFrameSem(); | |||||
| } | } | ||||
| /** | /** | ||||
| @@ -348,6 +533,8 @@ static uint16_t AppGetReadyFrameLength(void) | |||||
| OS_CPU_SR cpu_sr = 0U; | OS_CPU_SR cpu_sr = 0U; | ||||
| uint16_t frameLength; | uint16_t frameLength; | ||||
| /* 定时器中断将 ModbusFrameLength 和 ModbusFrameReady 作为一个逻辑事件发布。 | |||||
| * 这里原子地读取快照,避免任务拿到属于另一种缓冲区状态的长度。 */ | |||||
| frameLength = 0U; | frameLength = 0U; | ||||
| OS_ENTER_CRITICAL(); | OS_ENTER_CRITICAL(); | ||||
| if (ModbusFrameReady != 0U) { | if (ModbusFrameReady != 0U) { | ||||
| @@ -366,6 +553,8 @@ static INT8U AppPostModbusFrameSem(void) | |||||
| { | { | ||||
| INT8U osError; | INT8U osError; | ||||
| /* RTU 定时器、UART 错误路径和周期链路检查等多个来源共用一个信号量。 | |||||
| * 任务会再次检查帧长度,因此没有完整帧的唤醒也是安全的,并能刷新链路指示。 */ | |||||
| osError = OSSemPost(ModbusFrameSem); | osError = OSSemPost(ModbusFrameSem); | ||||
| return osError; | return osError; | ||||
| @@ -387,17 +576,15 @@ static uint8_t AppIsModbusStatusAccepted(MODBUS_STATUS status) | |||||
| } | } | ||||
| /** | /** | ||||
| * @brief 判断功能码是否为触摸屏的写操作。 | |||||
| * @brief 判断功能码是否为需要保存到 RTC 的写操作。 | |||||
| * @param[in] functionCode Modbus 功能码。 | * @param[in] functionCode Modbus 功能码。 | ||||
| * @return 写操作返回 1,否则返回 0。 | * @return 写操作返回 1,否则返回 0。 | ||||
| * @note 仅 0x42 修改 0x00010000;标准寄存器和线圈写入不具备掉电保持承诺。 | |||||
| */ | */ | ||||
| static uint8_t AppIsModbusWriteFunction(uint8_t functionCode) | |||||
| static uint8_t AppIsModbusRetentionWriteFunction(uint8_t functionCode) | |||||
| { | { | ||||
| switch (functionCode) { | switch (functionCode) { | ||||
| case MODBUS_FC_WRITE_SINGLE_COIL: | |||||
| case MODBUS_FC_WRITE_SINGLE_REG: | |||||
| case MODBUS_FC_WRITE_MULTIPLE_COILS: | |||||
| case MODBUS_FC_WRITE_MULTIPLE_REGS: | |||||
| case MODBUS_FC_WRITE_EXTENDED_HOLDING_REGS: | |||||
| return 1U; | return 1U; | ||||
| default: | default: | ||||
| @@ -406,104 +593,75 @@ static uint8_t AppIsModbusWriteFunction(uint8_t functionCode) | |||||
| } | } | ||||
| /** | /** | ||||
| * @brief 更新供触摸屏读取的通信状态寄存器。 | |||||
| * @brief 更新连接线圈和 PLC 连接指示灯。 | |||||
| * @return 成功返回 MODBUS_STATUS_OK;失败返回错误状态。 | * @return 成功返回 MODBUS_STATUS_OK;失败返回错误状态。 | ||||
| * @note 仅已寻址的正常响应、广播写或异常响应刷新活动时间;CRC 错误和 | |||||
| * 非本机帧不会误报链路在线。 | |||||
| */ | */ | ||||
| static MODBUS_STATUS AppUpdateStatusRegisters(void) | |||||
| static MODBUS_STATUS AppUpdateLinkStatus(void) | |||||
| { | { | ||||
| MODBUS_STATUS status; | MODBUS_STATUS status; | ||||
| uint32_t currentTick; | uint32_t currentTick; | ||||
| uint8_t linkState; | uint8_t linkState; | ||||
| uint16_t registerValue; | |||||
| GPIO_PinState pinState; | |||||
| currentTick = OSTimeGet(); | currentTick = OSTimeGet(); | ||||
| linkState = APP_MODBUS_LINK_DISCONNECTED; | linkState = APP_MODBUS_LINK_DISCONNECTED; | ||||
| if ((ModbusHasActivity != 0U) | |||||
| && ((currentTick - ModbusLastActivityTick) | |||||
| < APP_MODBUS_LINK_TIMEOUT_TICKS)) { | |||||
| if ((ModbusHasActivity != 0U) && | |||||
| ((currentTick - ModbusLastActivityTick) | |||||
| < APP_MODBUS_LINK_TIMEOUT_TICKS)) | |||||
| { | |||||
| linkState = APP_MODBUS_LINK_CONNECTED; | linkState = APP_MODBUS_LINK_CONNECTED; | ||||
| } | } | ||||
| status = ModbusSetHoldingRegister(&ModbusSlave, | |||||
| APP_MODBUS_REG_LINK_STATE, linkState); | |||||
| if (status != MODBUS_STATUS_OK) { | |||||
| return status; | |||||
| } | |||||
| registerValue = (uint16_t)(ModbusOperationRxCount & 0xFFFFU); | |||||
| status = ModbusSetHoldingRegister(&ModbusSlave, | |||||
| APP_MODBUS_REG_OPERATION_RX_COUNT, | |||||
| registerValue); | |||||
| if (status != MODBUS_STATUS_OK) { | |||||
| status = ModbusSetCoil(&ModbusSlave, APP_MODBUS_LINK_STATE_COIL, | |||||
| linkState); | |||||
| if (status != MODBUS_STATUS_OK) | |||||
| { | |||||
| return status; | return status; | ||||
| } | } | ||||
| registerValue = (uint16_t)(ModbusOperationTxCount & 0xFFFFU); | |||||
| status = ModbusSetHoldingRegister(&ModbusSlave, | |||||
| APP_MODBUS_REG_OPERATION_TX_COUNT, | |||||
| registerValue); | |||||
| if (status != MODBUS_STATUS_OK) { | |||||
| return status; | |||||
| if (linkState == APP_MODBUS_LINK_CONNECTED) | |||||
| { | |||||
| pinState = GPIO_PIN_RESET; | |||||
| } | } | ||||
| registerValue = (uint16_t)((ModbusProtocolErrorCount | |||||
| + ModbusReceiveErrorCount + ModbusSemaphoreErrorCount) & 0xFFFFU); | |||||
| status = ModbusSetHoldingRegister(&ModbusSlave, | |||||
| APP_MODBUS_REG_ERROR_COUNT, | |||||
| registerValue); | |||||
| if (status != MODBUS_STATUS_OK) { | |||||
| return status; | |||||
| else | |||||
| { | |||||
| pinState = GPIO_PIN_SET; | |||||
| } | } | ||||
| status = ModbusSetHoldingRegister(&ModbusSlave, | |||||
| APP_MODBUS_REG_SYSTEM_TICK_LOW, | |||||
| (uint16_t)(currentTick & 0xFFFFU)); | |||||
| if (status != MODBUS_STATUS_OK) { | |||||
| return status; | |||||
| } | |||||
| HAL_GPIO_WritePin(PLC_LINK_LED_PORT, PLC_LINK_LED_PIN, pinState); | |||||
| status = ModbusSetHoldingRegister(&ModbusSlave, | |||||
| APP_MODBUS_REG_SYSTEM_TICK_HIGH, | |||||
| (uint16_t)(currentTick >> 16U)); | |||||
| if (status != MODBUS_STATUS_OK) { | |||||
| return status; | |||||
| } | |||||
| status = ModbusSetHoldingRegister(&ModbusSlave, | |||||
| APP_MODBUS_REG_LAST_STATUS, | |||||
| (uint16_t)ModbusLastStatus); | |||||
| if (status != MODBUS_STATUS_OK) { | |||||
| return status; | |||||
| } | |||||
| status = ModbusSetHoldingRegister(&ModbusSlave, | |||||
| APP_MODBUS_REG_LAST_FRAME_LENGTH, | |||||
| ModbusFrameLength); | |||||
| return status; | |||||
| return MODBUS_STATUS_OK; | |||||
| } | } | ||||
| /** | /** | ||||
| * @brief 将线圈 0 至 7 的状态同步到 PLC Q0 至 Q7 输出。 | |||||
| * @brief 将线圈 1 至 7 的状态同步到 PLC Q1 至 Q7 输出。 | |||||
| * @return 成功返回 MODBUS_STATUS_OK;失败返回错误状态。 | * @return 成功返回 MODBUS_STATUS_OK;失败返回错误状态。 | ||||
| * @note 板级输出为低有效,因此逻辑线圈置 1 时向 GPIO 写入 GPIO_PIN_RESET。 | |||||
| */ | */ | ||||
| static MODBUS_STATUS AppSyncCoilsToGpio(void) | static MODBUS_STATUS AppSyncCoilsToGpio(void) | ||||
| { | { | ||||
| MODBUS_STATUS status; | MODBUS_STATUS status; | ||||
| uint16_t coilOffset; | |||||
| uint16_t coilAddress; | uint16_t coilAddress; | ||||
| uint8_t coilValue; | uint8_t coilValue; | ||||
| GPIO_PinState pinState; | GPIO_PinState pinState; | ||||
| for (coilAddress = 0U; coilAddress < APP_MODBUS_OUTPUT_COIL_COUNT; | |||||
| coilAddress++) { | |||||
| for (coilOffset = 0U; | |||||
| coilOffset < APP_MODBUS_USER_OUTPUT_COIL_COUNT; | |||||
| coilOffset++) | |||||
| { | |||||
| coilAddress = coilOffset + APP_MODBUS_FIRST_USER_OUTPUT_COIL; | |||||
| status = ModbusGetCoil(&ModbusSlave, coilAddress, &coilValue); | status = ModbusGetCoil(&ModbusSlave, coilAddress, &coilValue); | ||||
| if (status != MODBUS_STATUS_OK) { | |||||
| if (status != MODBUS_STATUS_OK) | |||||
| { | |||||
| return status; | return status; | ||||
| } | } | ||||
| pinState = (coilValue != 0U) ? GPIO_PIN_RESET : GPIO_PIN_SET; | pinState = (coilValue != 0U) ? GPIO_PIN_RESET : GPIO_PIN_SET; | ||||
| HAL_GPIO_WritePin(AppOutputPorts[coilAddress], | |||||
| AppOutputPins[coilAddress], pinState); | |||||
| HAL_GPIO_WritePin(AppUserOutputPorts[coilOffset], | |||||
| AppUserOutputPins[coilOffset], pinState); | |||||
| } | } | ||||
| return MODBUS_STATUS_OK; | return MODBUS_STATUS_OK; | ||||
| @@ -512,76 +670,72 @@ static MODBUS_STATUS AppSyncCoilsToGpio(void) | |||||
| /** | /** | ||||
| * @brief 处理已封存的 Modbus RTU 请求帧。 | * @brief 处理已封存的 Modbus RTU 请求帧。 | ||||
| * @param[in] rxLength 请求帧长度。 | * @param[in] rxLength 请求帧长度。 | ||||
| * @note 此函数在 Modbus 任务上下文执行。它负责协议分发、链路活动刷新、 | |||||
| * 线圈 GPIO 同步、0x42 成功后的 RTC 保存和异步 UART 应答启动。 | |||||
| */ | */ | ||||
| static void AppProcessFrame(uint16_t rxLength) | static void AppProcessFrame(uint16_t rxLength) | ||||
| { | { | ||||
| HAL_StatusTypeDef halStatus; | |||||
| MODBUS_STATUS status; | MODBUS_STATUS status; | ||||
| uint16_t txLength; | uint16_t txLength; | ||||
| uint8_t functionCode; | |||||
| uint8_t isWriteOperation; | uint8_t isWriteOperation; | ||||
| txLength = 0U; | |||||
| functionCode = 0U; | |||||
| isWriteOperation = 0U; | isWriteOperation = 0U; | ||||
| if (rxLength > APP_MODBUS_FUNCTION_INDEX) { | |||||
| functionCode = ModbusRxBuffer[APP_MODBUS_FUNCTION_INDEX]; | |||||
| } | |||||
| if ((rxLength == 0U) || (rxLength > MODBUS_RX_BUF_SIZE)) { | |||||
| if ((rxLength == 0U) || (rxLength > MODBUS_RX_BUF_SIZE)) | |||||
| { | |||||
| ModbusLastStatus = MODBUS_STATUS_FRAME_ERROR; | ModbusLastStatus = MODBUS_STATUS_FRAME_ERROR; | ||||
| ModbusProtocolErrorCount++; | |||||
| return; | return; | ||||
| } | } | ||||
| if (ModbusRxOverflow != 0U) { | |||||
| if (ModbusRxOverflow != 0U) | |||||
| { | |||||
| ModbusLastStatus = MODBUS_STATUS_FRAME_ERROR; | ModbusLastStatus = MODBUS_STATUS_FRAME_ERROR; | ||||
| ModbusProtocolErrorCount++; | |||||
| return; | return; | ||||
| } | } | ||||
| /* 协议解析固定放在任务上下文中执行。这样解析器可以更新线圈/寄存器并构造 | |||||
| * 响应,而不必在 UART 或定时器中断中执行较长的处理。 */ | |||||
| status = ModbusProcessFrame(&ModbusSlave, ModbusRxBuffer, rxLength, | status = ModbusProcessFrame(&ModbusSlave, ModbusRxBuffer, rxLength, | ||||
| ModbusTxBuffer, MODBUS_TX_BUF_SIZE, | ModbusTxBuffer, MODBUS_TX_BUF_SIZE, | ||||
| &txLength); | &txLength); | ||||
| ModbusLastStatus = status; | ModbusLastStatus = status; | ||||
| if (AppIsModbusStatusAccepted(status) != 0U) { | |||||
| /* 只有 CRC 正确且寻址到本站的帧(包括合法广播写入或已生成的异常响应)才算 | |||||
| * 有效链路活动,线路上的随机干扰不能错误点亮连接指示灯。 */ | |||||
| if (AppIsModbusStatusAccepted(status) != 0U) | |||||
| { | |||||
| ModbusLastActivityTick = OSTimeGet(); | ModbusLastActivityTick = OSTimeGet(); | ||||
| ModbusHasActivity = 1U; | ModbusHasActivity = 1U; | ||||
| if (AppIsModbusWriteFunction( | |||||
| ModbusRxBuffer[APP_MODBUS_FUNCTION_INDEX]) != 0U) { | |||||
| ModbusOperationRxCount++; | |||||
| isWriteOperation = 1U; | |||||
| isWriteOperation = AppIsModbusRetentionWriteFunction(functionCode); | |||||
| if (((ModbusLastStatus == MODBUS_STATUS_OK) | |||||
| || (ModbusLastStatus == MODBUS_STATUS_BROADCAST)) | |||||
| && (isWriteOperation != 0U)) | |||||
| { | |||||
| (void)ModbusBackupSave(&ModbusSlave); | |||||
| } | } | ||||
| } | } | ||||
| else if (status != MODBUS_STATUS_IGNORED) { | |||||
| ModbusProtocolErrorCount++; | |||||
| } | |||||
| if ((status == MODBUS_STATUS_OK) || (status == MODBUS_STATUS_BROADCAST)) { | |||||
| status = AppSyncCoilsToGpio(); | |||||
| if (status != MODBUS_STATUS_OK) { | |||||
| ModbusProtocolErrorCount++; | |||||
| } | |||||
| /* 线圈值是协议数据模型;这里是唯一的同步桥,将用户线圈 1..7 映射到板卡 | |||||
| * 低电平有效的 Q1..Q7 输出。 */ | |||||
| if ((status == MODBUS_STATUS_OK) | |||||
| || (status == MODBUS_STATUS_BROADCAST)) | |||||
| { | |||||
| (void)AppSyncCoilsToGpio(); | |||||
| } | } | ||||
| status = AppUpdateStatusRegisters(); | |||||
| if (status != MODBUS_STATUS_OK) { | |||||
| ModbusProtocolErrorCount++; | |||||
| } | |||||
| (void)AppUpdateLinkStatus(); | |||||
| if (txLength > 0U) { | |||||
| halStatus = AppTransmitResponse(txLength); | |||||
| if (halStatus != HAL_OK) { | |||||
| ModbusProtocolErrorCount++; | |||||
| } | |||||
| else if (isWriteOperation != 0U) { | |||||
| ModbusOperationTxCount++; | |||||
| } | |||||
| if (txLength > 0U) | |||||
| { | |||||
| (void)AppTransmitResponse(txLength); | |||||
| } | } | ||||
| } | } | ||||
| /** | |||||
| * @brief 记录中断上下文中无法立即恢复的通信错误。 | |||||
| */ | |||||
| static void AppRecordIsrError(void) | |||||
| { | |||||
| ModbusReceiveErrorCount++; | |||||
| } | |||||
| /** | /** | ||||
| * @brief Modbus 协议处理任务。 | * @brief Modbus 协议处理任务。 | ||||
| * @param[in] argument 未使用。 | * @param[in] argument 未使用。 | ||||
| @@ -593,28 +747,48 @@ static void AppTaskModbus(void *argument) | |||||
| uint16_t rxLength; | uint16_t rxLength; | ||||
| (void)argument; | (void)argument; | ||||
| if (ModbusSlaveInit(&ModbusSlave, MODBUS_SLAVE_ADDR_DEFAULT) | |||||
| /* 先将协议模型绑定到 RAM 数据区,再恢复由 RTC 备份的扩展寄存器,确保第一条 | |||||
| * 请求到达前数据已经准备好。 */ | |||||
| if (ModbusSlaveInit(&ModbusSlave, MODBUS_SLAVE_ADDR_DEFAULT, | |||||
| holdingRegisters, coilStorage) | |||||
| != MODBUS_STATUS_OK) { | != MODBUS_STATUS_OK) { | ||||
| Error_Handler(); | Error_Handler(); | ||||
| } | } | ||||
| if (ModbusConfigureExtendedHoldingRegister(&ModbusSlave, | |||||
| &extendedHoldingRegister) | |||||
| != MODBUS_STATUS_OK) { | |||||
| Error_Handler(); | |||||
| } | |||||
| if (ModbusBackupRestore(&ModbusSlave) != MODBUS_STATUS_OK) { | |||||
| Error_Handler(); | |||||
| } | |||||
| if (AppSyncCoilsToGpio() != MODBUS_STATUS_OK) { | if (AppSyncCoilsToGpio() != MODBUS_STATUS_OK) { | ||||
| Error_Handler(); | Error_Handler(); | ||||
| } | } | ||||
| if (AppUpdateStatusRegisters() != MODBUS_STATUS_OK) { | |||||
| if (AppUpdateLinkStatus() != MODBUS_STATUS_OK) { | |||||
| Error_Handler(); | Error_Handler(); | ||||
| } | } | ||||
| halStatus = AppStartUartReception(); | |||||
| halStatus = AppRecoverUartReception(); | |||||
| if (halStatus != HAL_OK) { | if (halStatus != HAL_OK) { | ||||
| Error_Handler(); | |||||
| ModbusReceptionNeedsRecovery = 1U; | |||||
| } | } | ||||
| while (1) { | while (1) { | ||||
| /* 完整帧解析和 UART 恢复由本任务负责。中断回调只收集字节、标记帧边界或 | |||||
| * 错误,并投递此信号量。 */ | |||||
| halStatus = AppEnsureUartReception(); | |||||
| if (halStatus != HAL_OK) { | |||||
| OSTimeDly(1U); | |||||
| continue; | |||||
| } | |||||
| OSSemPend(ModbusFrameSem, 0U, &osError); | OSSemPend(ModbusFrameSem, 0U, &osError); | ||||
| if (osError != OS_ERR_NONE) { | if (osError != OS_ERR_NONE) { | ||||
| ModbusSemaphoreErrorCount++; | |||||
| continue; | continue; | ||||
| } | } | ||||
| @@ -622,13 +796,15 @@ static void AppTaskModbus(void *argument) | |||||
| if (rxLength > 0U) { | if (rxLength > 0U) { | ||||
| AppProcessFrame(rxLength); | AppProcessFrame(rxLength); | ||||
| /* 帧定时器已经终止了本次完整帧的接收。正常挂接下一帧时不能再次清除 | |||||
| * SR/DR,因为主站可能在 T3.5 后已经开始发送下一条请求。 */ | |||||
| halStatus = AppStartUartReception(); | halStatus = AppStartUartReception(); | ||||
| if (halStatus != HAL_OK) { | if (halStatus != HAL_OK) { | ||||
| ModbusReceiveErrorCount++; | |||||
| ModbusReceptionNeedsRecovery = 1U; | |||||
| } | } | ||||
| } | } | ||||
| else if (AppUpdateStatusRegisters() != MODBUS_STATUS_OK) { | |||||
| ModbusProtocolErrorCount++; | |||||
| else { | |||||
| (void)AppUpdateLinkStatus(); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -639,23 +815,20 @@ static void AppTaskModbus(void *argument) | |||||
| */ | */ | ||||
| static void AppTaskT35(void *argument) | static void AppTaskT35(void *argument) | ||||
| { | { | ||||
| INT8U osError; | |||||
| uint16_t updateElapsedTicks; | uint16_t updateElapsedTicks; | ||||
| (void)argument; | (void)argument; | ||||
| updateElapsedTicks = 0U; | updateElapsedTicks = 0U; | ||||
| while (1) { | while (1) { | ||||
| OSTimeDly(APP_MODBUS_T35_CHECK_TICKS); | |||||
| (void)AppSealReceivedFrame(); | |||||
| /* 这个低频任务不解析帧,只按周期唤醒 Modbus 任务,使空闲线路在达到配置 | |||||
| * 的超时时间后将连接指示灯恢复为断开状态。 */ | |||||
| OSTimeDly(APP_MODBUS_STATUS_CHECK_TICKS); | |||||
| updateElapsedTicks += APP_MODBUS_T35_CHECK_TICKS; | |||||
| updateElapsedTicks += APP_MODBUS_STATUS_CHECK_TICKS; | |||||
| if (updateElapsedTicks >= APP_MODBUS_STATUS_UPDATE_TICKS) { | if (updateElapsedTicks >= APP_MODBUS_STATUS_UPDATE_TICKS) { | ||||
| updateElapsedTicks = 0U; | updateElapsedTicks = 0U; | ||||
| osError = AppPostModbusFrameSem(); | |||||
| if (osError != OS_ERR_NONE) { | |||||
| ModbusSemaphoreErrorCount++; | |||||
| } | |||||
| (void)AppPostModbusFrameSem(); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -672,17 +845,37 @@ void HAL_UART_RxCpltCallback(UART_HandleTypeDef *uartHandle) | |||||
| return; | return; | ||||
| } | } | ||||
| /* 此回调每次只处理一个字节,不解析功能码,也不写入数据模型;这些操作由 | |||||
| * AppTaskModbus 任务完成。 */ | |||||
| if (ModbusFrameReady != 0U) { | if (ModbusFrameReady != 0U) { | ||||
| return; | return; | ||||
| } | } | ||||
| /* T1.5 间隔会使当前不完整帧失效。继续接收并丢弃字节直到后续 T3.5 边界, | |||||
| * 防止迟到的字节被误认为新帧的首字节。 */ | |||||
| if ((ModbusRxDiscard != 0U) | |||||
| || (ModbusInterCharacterTimeout != 0U)) { | |||||
| ModbusRxDiscard = 1U; | |||||
| ModbusRxLength = 0U; | |||||
| AppStartModbusRtuTimer(); | |||||
| halStatus = HAL_UART_Receive_IT(&Uart1Handle, &ModbusRxBuffer[0], | |||||
| 1U); | |||||
| if (halStatus != HAL_OK) { | |||||
| ModbusReceptionNeedsRecovery = 1U; | |||||
| (void)AppPostModbusFrameSem(); | |||||
| } | |||||
| return; | |||||
| } | |||||
| if (ModbusRxLength >= MODBUS_RX_BUF_SIZE) { | if (ModbusRxLength >= MODBUS_RX_BUF_SIZE) { | ||||
| ModbusRxOverflow = 1U; | ModbusRxOverflow = 1U; | ||||
| return; | return; | ||||
| } | } | ||||
| /* HAL 已经将当前字节放入 ModbusRxBuffer[ModbusRxLength]。 */ | |||||
| ModbusRxLength++; | ModbusRxLength++; | ||||
| ModbusLastRxTick = OSTimeGet(); | |||||
| ModbusUartLastRxByte = ModbusRxBuffer[ModbusRxLength - 1U]; | |||||
| AppStartModbusRtuTimer(); | |||||
| if (ModbusRxLength >= MODBUS_RX_BUF_SIZE) { | if (ModbusRxLength >= MODBUS_RX_BUF_SIZE) { | ||||
| ModbusRxOverflow = 1U; | ModbusRxOverflow = 1U; | ||||
| return; | return; | ||||
| @@ -691,23 +884,47 @@ void HAL_UART_RxCpltCallback(UART_HandleTypeDef *uartHandle) | |||||
| halStatus = HAL_UART_Receive_IT(&Uart1Handle, | halStatus = HAL_UART_Receive_IT(&Uart1Handle, | ||||
| &ModbusRxBuffer[ModbusRxLength], 1U); | &ModbusRxBuffer[ModbusRxLength], 1U); | ||||
| if (halStatus != HAL_OK) { | if (halStatus != HAL_OK) { | ||||
| AppRecordIsrError(); | |||||
| ModbusReceptionNeedsRecovery = 1U; | |||||
| (void)AppPostModbusFrameSem(); | |||||
| } | } | ||||
| } | } | ||||
| /** | /** | ||||
| * @brief USART1 通信错误回调。 | * @brief USART1 通信错误回调。 | ||||
| * @param[in] uartHandle 触发回调的串口句柄。 | * @param[in] uartHandle 触发回调的串口句柄。 | ||||
| * @note 中断仅停止计时、标记当前帧无效并通知任务;接收重挂接由 | |||||
| * AppRecoverPendingUartReception 在任务上下文执行。 | |||||
| */ | */ | ||||
| void HAL_UART_ErrorCallback(UART_HandleTypeDef *uartHandle) | void HAL_UART_ErrorCallback(UART_HandleTypeDef *uartHandle) | ||||
| { | { | ||||
| if ((uartHandle == NULL) || (uartHandle->Instance != USART1)) { | |||||
| if ((uartHandle == NULL) || (uartHandle->Instance != USART1)) | |||||
| { | |||||
| return; | return; | ||||
| } | } | ||||
| /* UART 错误会使当前整帧失效。中断只记录错误并唤醒任务,不在这里终止或重新 | |||||
| * 进入 HAL 接收流程,从而避免 HAL 重入,并让恢复操作集中在一个上下文中。 */ | |||||
| AppStopModbusRtuTimer(); | |||||
| ModbusUartLastErrorCode = uartHandle->ErrorCode; | |||||
| ModbusUartLastRxState = (uint32_t)uartHandle->RxState; | |||||
| __HAL_UART_CLEAR_PEFLAG(uartHandle); | |||||
| ModbusRxOverflow = 1U; | ModbusRxOverflow = 1U; | ||||
| ModbusLastRxTick = OSTimeGet(); | |||||
| AppRecordIsrError(); | |||||
| ModbusReceptionNeedsRecovery = 1U; | |||||
| (void)AppPostModbusFrameSem(); | |||||
| } | |||||
| /** | |||||
| * @brief 处理 TIM5 的 Modbus RTU T1.5/T3.5 单次定时到期事件。 | |||||
| * @note 本函数只更新接收状态和投递信号量,不解析帧内容。 | |||||
| */ | |||||
| void AppModbusRtuTimerIrqHandler(void) | |||||
| { | |||||
| if ((TIM5->SR & TIM_SR_UIF) == 0U) { | |||||
| return; | |||||
| } | |||||
| TIM5->SR &= (uint32_t)(~TIM_SR_UIF); | |||||
| AppHandleModbusRtuTimer(); | |||||
| } | } | ||||
| /** | /** | ||||
| @@ -716,16 +933,12 @@ void HAL_UART_ErrorCallback(UART_HandleTypeDef *uartHandle) | |||||
| */ | */ | ||||
| void HAL_UART_TxCpltCallback(UART_HandleTypeDef *uartHandle) | void HAL_UART_TxCpltCallback(UART_HandleTypeDef *uartHandle) | ||||
| { | { | ||||
| INT8U osError; | |||||
| if ((uartHandle == NULL) || (uartHandle->Instance != USART1)) { | if ((uartHandle == NULL) || (uartHandle->Instance != USART1)) { | ||||
| return; | return; | ||||
| } | } | ||||
| osError = OSSemPost(ModbusTxSem); | |||||
| if (osError != OS_ERR_NONE) { | |||||
| ModbusSemaphoreErrorCount++; | |||||
| } | |||||
| /* 只有最后一个字节已经移出串口后,才唤醒发送任务。 */ | |||||
| (void)OSSemPost(ModbusTxSem); | |||||
| } | } | ||||
| /** | /** | ||||
| @@ -79,23 +79,42 @@ void PendSV_Handler(void) | |||||
| /** | /** | ||||
| * @brief 处理 1 ms 系统节拍中断。 | * @brief 处理 1 ms 系统节拍中断。 | ||||
| * @note OS_CPU_SysTickHandler 封装了 uC/OS-II 的中断进入、节拍和退出顺序; | |||||
| * 不得在此处改为手工调用 OSIntEnter、OSTimeTick 和 OSIntExit。 | |||||
| */ | */ | ||||
| void SysTick_Handler(void) | void SysTick_Handler(void) | ||||
| { | { | ||||
| if (OSRunning == OS_TRUE) { | |||||
| OSIntEnter(); | |||||
| OSTimeTick(); | |||||
| OSIntExit(); | |||||
| /* HAL 维护毫秒级时间基准,uC/OS-II 维护任务延时和超时。 | |||||
| * OS 移植层封装了这个共享节拍所需的中断嵌套和调度记录。 */ | |||||
| HAL_IncTick(); | |||||
| if (OSRunning == OS_TRUE) | |||||
| { | |||||
| OS_CPU_SysTickHandler(); | |||||
| } | } | ||||
| } | |||||
| HAL_IncTick(); | |||||
| /** | |||||
| * @brief 处理 Modbus RTU T1.5/T3.5 单次静默计时中断。 | |||||
| * @note 中断仅推进帧边界状态机;完整帧通过信号量交给 Modbus 任务处理。 | |||||
| */ | |||||
| void TIM5_IRQHandler(void) | |||||
| { | |||||
| /* TIM5 中断只推进 RTU T1.5/T3.5 帧边界状态机。在这里解析帧或访问应用数据, | |||||
| * 会使中断执行时间不可控。 */ | |||||
| OSIntEnter(); | |||||
| AppModbusRtuTimerIrqHandler(); | |||||
| OSIntExit(); | |||||
| } | } | ||||
| /** | /** | ||||
| * @brief 处理 USART1 的 Modbus 收发中断。 | * @brief 处理 USART1 的 Modbus 收发中断。 | ||||
| * @note HAL 回调只接收字节或登记恢复请求,不在中断上下文解析协议或等待发送。 | |||||
| */ | */ | ||||
| void USART1_IRQHandler(void) | void USART1_IRQHandler(void) | ||||
| { | { | ||||
| /* HAL 会分发接收完成、发送完成和 UART 错误回调。回调只记录状态并通知任务, | |||||
| * 不阻塞,也不解析 ADU。 */ | |||||
| OSIntEnter(); | OSIntEnter(); | ||||
| HAL_UART_IRQHandler(&Uart1Handle); | HAL_UART_IRQHandler(&Uart1Handle); | ||||
| OSIntExit(); | OSIntExit(); | ||||
| @@ -1136,42 +1136,18 @@ | |||||
| <file> | <file> | ||||
| <name>$PROJ_DIR$\..\Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal_dma.c</name> | <name>$PROJ_DIR$\..\Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal_dma.c</name> | ||||
| </file> | </file> | ||||
| <file> | |||||
| <name>$PROJ_DIR$\..\Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal_dma_ex.c</name> | |||||
| </file> | |||||
| <file> | |||||
| <name>$PROJ_DIR$\..\Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal_exti.c</name> | |||||
| </file> | |||||
| <file> | |||||
| <name>$PROJ_DIR$\..\Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal_flash.c</name> | |||||
| </file> | |||||
| <file> | |||||
| <name>$PROJ_DIR$\..\Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal_flash_ex.c</name> | |||||
| </file> | |||||
| <file> | |||||
| <name>$PROJ_DIR$\..\Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal_flash_ramfunc.c</name> | |||||
| </file> | |||||
| <file> | <file> | ||||
| <name>$PROJ_DIR$\..\Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal_gpio.c</name> | <name>$PROJ_DIR$\..\Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal_gpio.c</name> | ||||
| </file> | </file> | ||||
| <file> | <file> | ||||
| <name>$PROJ_DIR$\..\Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal_pwr.c</name> | <name>$PROJ_DIR$\..\Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal_pwr.c</name> | ||||
| </file> | </file> | ||||
| <file> | |||||
| <name>$PROJ_DIR$\..\Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal_pwr_ex.c</name> | |||||
| </file> | |||||
| <file> | <file> | ||||
| <name>$PROJ_DIR$\..\Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal_rcc.c</name> | <name>$PROJ_DIR$\..\Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal_rcc.c</name> | ||||
| </file> | </file> | ||||
| <file> | <file> | ||||
| <name>$PROJ_DIR$\..\Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal_rcc_ex.c</name> | <name>$PROJ_DIR$\..\Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal_rcc_ex.c</name> | ||||
| </file> | </file> | ||||
| <file> | |||||
| <name>$PROJ_DIR$\..\Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal_tim.c</name> | |||||
| </file> | |||||
| <file> | |||||
| <name>$PROJ_DIR$\..\Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal_tim_ex.c</name> | |||||
| </file> | |||||
| <file> | <file> | ||||
| <name>$PROJ_DIR$\..\Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal_uart.c</name> | <name>$PROJ_DIR$\..\Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal_uart.c</name> | ||||
| </file> | </file> | ||||
| @@ -1182,5 +1158,8 @@ | |||||
| <file> | <file> | ||||
| <name>$PROJ_DIR$\..\Core\Modbus\modbus.c</name> | <name>$PROJ_DIR$\..\Core\Modbus\modbus.c</name> | ||||
| </file> | </file> | ||||
| <file> | |||||
| <name>$PROJ_DIR$\..\Core\Modbus\modbus_backup.c</name> | |||||
| </file> | |||||
| </group> | </group> | ||||
| </project> | </project> | ||||
| @@ -1,72 +1,103 @@ | |||||
| # ninja log v5 | # ninja log v5 | ||||
| 34 390 8070828551388656 ucos_ii.pbi 8d2ac5b9d103140d | |||||
| 26 114 8070928010006759 modbus.pbi 619525046922d8c | |||||
| 2528 3389 8070828581354535 stm32f4xx_hal_exti.pbi 3e347d0bb1d3798c | |||||
| 1574 2528 8070828571037431 main.pbi 35262febfa1deec1 | |||||
| 114 599 8070928014884916 Modbus_part4.pbi a5f1633af49df1f3 | |||||
| 46 799 8070828555492769 stm32f4xx_hal.pbi 2c3a496029d4643a | |||||
| 805 1829 8070828565666790 stm32f4xx_hal_msp.pbi 329860e7b1b76f81 | |||||
| 1641 2539 8070828571567703 stm32f4xx_it.pbi 5f6e49a9db86e0a1 | |||||
| 799 1574 8070828563182084 stm32f4xx_hal_pwr_ex.pbi eb5356e76493648c | |||||
| 390 605 8070828553320206 os_cpu_c.pbi ad0f935dbd645057 | |||||
| 28 804 8070828555535079 stm32f4xx_hal_gpio.pbi 5aaf28c36969a435 | |||||
| 40 812 8070828555597583 system_stm32f4xx.pbi b28508562e5c4dda | |||||
| 52 821 8070828555672830 stm32f4xx_hal_dma_ex.pbi 9eeeced39efb067c | |||||
| 20 866 8070828556157495 stm32f4xx_hal_uart.pbi 959c493f73ca383c | |||||
| 3320 3899 8070828586515222 Modbus_part1.pbi feefd27adb9ca10b | |||||
| 1657 2521 8070828570931305 stm32f4xx_hal_flash_ex.pbi 664405238146573 | |||||
| 606 1330 8070828560606807 stm32f4xx_hal_pwr.pbi c2df820d73bcc158 | |||||
| 2545 2980 8070828577317413 Modbus_part0.pbi 52bb3bf37a100d07 | |||||
| 822 1640 8070828563857208 stm32f4xx_hal_rcc_ex.pbi 11298246e7dc88f6 | |||||
| 813 1647 8070828563917091 stm32f4xx_hal_dma.pbi 17eef16aa98bd8d4 | |||||
| 867 1656 8070828563999593 stm32f4xx_hal_tim.pbi c827d2cd726b5c6 | |||||
| 1333 2035 8070828567858312 stm32f4xx_hal_rcc.pbi f933dfa22ce90c5e | |||||
| 2036 2248 8070828569833988 os_dbg.pbi ad48542575ebad16 | |||||
| 1648 2305 8070828570266813 stm32f4xx_hal_tim_ex.pbi 17c406494728d79b | |||||
| 1830 2534 8070828571521766 stm32f4xx_hal_flash_ramfunc.pbi 12cb5cfb79057af7 | |||||
| 2250 2544 8070828571794464 app_hooks.pbi f504fdb9b23706b6 | |||||
| 2522 3319 8070828580686838 stm32f4xx_hal_cortex.pbi 4a5dfd605852426e | |||||
| 2540 3379 8070828581284231 stm32f4xx_hal_flash.pbi 2cdfaeb0d9e8300a | |||||
| 2534 3620 8070828583726305 Modbus_part3.pbi 9333d2da01108b1d | |||||
| 3389 3917 8070828586707585 Modbus_part2.pbi b2e7cb118606ffa | |||||
| 600 1229 8070928021175748 Modbus.pbd 6b0161af8161b195 | |||||
| 1230 3309 8070928041616374 Modbus.pbw f93412b0d27fc6cf | |||||
| 26 133 8070928067459812 modbus.pbi 619525046922d8c | |||||
| 133 772 8070928073867407 Modbus_part4.pbi a5f1633af49df1f3 | |||||
| 773 1334 8070928079497459 Modbus.pbd 6b0161af8161b195 | |||||
| 1335 3288 8070928098662891 Modbus.pbw f93412b0d27fc6cf | |||||
| 31 127 8070928124535926 modbus.pbi 619525046922d8c | |||||
| 127 649 8070928129798325 Modbus_part4.pbi a5f1633af49df1f3 | |||||
| 650 1228 8070928135561146 Modbus.pbd 6b0161af8161b195 | |||||
| 1230 3804 8070928160556861 Modbus.pbw f93412b0d27fc6cf | |||||
| 37 163 8070928187757842 modbus.pbi 619525046922d8c | |||||
| 164 768 8070928193853367 Modbus_part4.pbi a5f1633af49df1f3 | |||||
| 768 1486 8070928201015536 Modbus.pbd 6b0161af8161b195 | |||||
| 1487 4296 8070928228582323 Modbus.pbw f93412b0d27fc6cf | |||||
| 21 92 8070928253882646 modbus.pbi 619525046922d8c | |||||
| 92 507 8070928258057244 Modbus_part4.pbi a5f1633af49df1f3 | |||||
| 507 880 8070928261751609 Modbus.pbd 6b0161af8161b195 | |||||
| 881 2972 8070928282387578 Modbus.pbw f93412b0d27fc6cf | |||||
| 24 227 8070937252171649 modbus.pbi 619525046922d8c | |||||
| 227 591 8070937255849142 Modbus_part4.pbi a5f1633af49df1f3 | |||||
| 592 931 8070937259239481 Modbus.pbd 6b0161af8161b195 | |||||
| 931 2379 8070937273436792 Modbus.pbw f93412b0d27fc6cf | |||||
| 21 109 8070940375680027 modbus.pbi 619525046922d8c | |||||
| 109 471 8070940379321817 Modbus_part4.pbi a5f1633af49df1f3 | |||||
| 472 946 8070940384070652 Modbus.pbd 6b0161af8161b195 | |||||
| 946 4186 8070940415717068 Modbus.pbw f93412b0d27fc6cf | |||||
| 47 183 8070940443415816 modbus.pbi 619525046922d8c | |||||
| 184 954 8070940451166866 Modbus_part4.pbi a5f1633af49df1f3 | |||||
| 955 1678 8070940458418291 Modbus.pbd 6b0161af8161b195 | |||||
| 1679 4364 8070940484596141 Modbus.pbw f93412b0d27fc6cf | |||||
| 23 114 8070940551196197 modbus.pbi 619525046922d8c | |||||
| 114 549 8070940555570699 Modbus_part4.pbi a5f1633af49df1f3 | |||||
| 550 946 8070940559562149 Modbus.pbd 6b0161af8161b195 | |||||
| 947 3418 8070940583803335 Modbus.pbw f93412b0d27fc6cf | |||||
| 17 177 8070942659756723 modbus.pbi 619525046922d8c | |||||
| 178 466 8070942662663781 Modbus_part4.pbi a5f1633af49df1f3 | |||||
| 157 580 8070955668635620 os_dbg.pbi ad48542575ebad16 | |||||
| 140 581 8070955668655595 modbus.pbi 619525046922d8c | |||||
| 236 1923 8070955681916975 stm32f4xx_hal_flash_ramfunc.pbi 12cb5cfb79057af7 | |||||
| 220 1929 8070955682030796 stm32f4xx_hal_flash_ex.pbi 664405238146573 | |||||
| 110 2053 8070955683374841 stm32f4xx_hal_uart.pbi 959c493f73ca383c | |||||
| 32 212 8074502355678817 modbus.pbi 619525046922d8c | |||||
| 37 414 8074502357703612 modbus_backup.pbi 123e4703660258f1 | |||||
| 42 436 8074502357928329 main.pbi 35262febfa1deec1 | |||||
| 48 1091 8073755826259841 stm32f4xx_hal_exti.pbi 3e347d0bb1d3798c | |||||
| 59 1101 8073755826249831 stm32f4xx_hal_flash.pbi 2cdfaeb0d9e8300a | |||||
| 33 612 8074489180587909 stm32f4xx_hal_uart.pbi 959c493f73ca383c | |||||
| 26 542 8074489179881410 stm32f4xx_hal_rcc.pbi f933dfa22ce90c5e | |||||
| 229 1568 8074482060783229 stm32f4xx_hal.pbi 2c3a496029d4643a | |||||
| 415 534 8074502358932342 Modbus_part4.pbi 3eb554587643adc3 | |||||
| 77 260 8074482047832492 os_dbg.pbi ad48542575ebad16 | |||||
| 1080 1446 8073755829909061 Modbus_part5.pbi a654eec37f5b129c | |||||
| 542 633 8074489180806267 app_hooks.pbi f504fdb9b23706b6 | |||||
| 1127 2030 8073755835700893 stm32f4xx_hal_pwr_ex.pbi eb5356e76493648c | |||||
| 54 228 8074482047480349 os_cpu_c.pbi ad0f935dbd645057 | |||||
| 47 272 8074489177181738 ucos_ii.pbi 8d2ac5b9d103140d | |||||
| 633 688 8074489181391279 Modbus_part0.pbi aeb228c86547226c | |||||
| 261 1563 8074482060853453 stm32f4xx_hal_rcc_ex.pbi 11298246e7dc88f6 | |||||
| 63 1459 8074482059780887 stm32f4xx_hal_cortex.pbi 4a5dfd605852426e | |||||
| 1232 2188 8073755837289972 stm32f4xx_hal_tim.pbi c827d2cd726b5c6 | |||||
| 1885 2834 8073755843634689 stm32f4xx_hal_dma_ex.pbi 9eeeced39efb067c | |||||
| 2050 2950 8073755844919672 stm32f4xx_hal_flash_ramfunc.pbi 12cb5cfb79057af7 | |||||
| 436 748 8074502361068038 Modbus_part1.pbi ddb2d0c7a212e1f7 | |||||
| 272 699 8074489181451303 stm32f4xx_hal_pwr.pbi c2df820d73bcc158 | |||||
| 2031 3022 8073755845615244 stm32f4xx_hal_flash_ex.pbi 664405238146573 | |||||
| 25 382 8074502357363743 stm32f4xx_hal_msp.pbi 329860e7b1b76f81 | |||||
| 16 403 8074502357588863 stm32f4xx_it.pbi 5f6e49a9db86e0a1 | |||||
| 88 1319 8074482058418971 stm32f4xx_hal_dma.pbi 17eef16aa98bd8d4 | |||||
| 59 558 8074489180020666 stm32f4xx_hal_gpio.pbi 5aaf28c36969a435 | |||||
| 17 527 8074489179731170 system_stm32f4xx.pbi b28508562e5c4dda | |||||
| 3078 3994 8073755855353542 stm32f4xx_hal_tim_ex.pbi 17c406494728d79b | |||||
| 559 937 8074489183865675 Modbus_part2.pbi fbca1e612a9f24ac | |||||
| 699 1047 8074489184966527 Modbus_part3.pbi c380dcd3336f25d2 | |||||
| 748 1016 8074502363749022 Modbus.pbd 6b0161af8161b195 | |||||
| 1016 2469 8074502378065434 Modbus.pbw f93412b0d27fc6cf | |||||
| 34 502 8074502468996889 main.pbi 35262febfa1deec1 | |||||
| 503 1178 8074502475774119 Modbus_part1.pbi ddb2d0c7a212e1f7 | |||||
| 1178 1849 8074502482481135 Modbus.pbd 6b0161af8161b195 | |||||
| 1849 4679 8074502510379520 Modbus.pbw f93412b0d27fc6cf | |||||
| 14 780 8074503194135192 main.pbi 35262febfa1deec1 | |||||
| 781 1500 8074503201409890 Modbus_part1.pbi ddb2d0c7a212e1f7 | |||||
| 1501 2185 8074503208246680 Modbus.pbd 6b0161af8161b195 | |||||
| 2185 5560 8074503241357568 Modbus.pbw f93412b0d27fc6cf | |||||
| 17 459 8074507473220841 main.pbi 35262febfa1deec1 | |||||
| 460 782 8074507476470558 Modbus_part1.pbi ddb2d0c7a212e1f7 | |||||
| 782 1071 8074507479354879 Modbus.pbd 6b0161af8161b195 | |||||
| 1071 3584 8074507504106085 Modbus.pbw f93412b0d27fc6cf | |||||
| 28 212 8074507978663912 modbus.pbi 619525046922d8c | |||||
| 213 385 8074507980416262 Modbus_part4.pbi 3eb554587643adc3 | |||||
| 385 735 8074507983905221 Modbus.pbd 6b0161af8161b195 | |||||
| 735 3343 8074508009558632 Modbus.pbw f93412b0d27fc6cf | |||||
| 17 197 8074508260830677 modbus.pbi 619525046922d8c | |||||
| 39 426 8074508263090619 stm32f4xx_hal_msp.pbi 329860e7b1b76f81 | |||||
| 34 443 8074508263236502 main.pbi 35262febfa1deec1 | |||||
| 44 444 8074508263236502 stm32f4xx_it.pbi 5f6e49a9db86e0a1 | |||||
| 27 515 8074508264005574 modbus_backup.pbi 123e4703660258f1 | |||||
| 515 654 8074508265413927 Modbus_part4.pbi 3eb554587643adc3 | |||||
| 444 773 8074508266607698 Modbus_part1.pbi ddb2d0c7a212e1f7 | |||||
| 773 1100 8074508269873381 Modbus.pbd 6b0161af8161b195 | |||||
| 1101 2462 8074508283197878 Modbus.pbw f93412b0d27fc6cf | |||||
| 81 376 8074516456313523 modbus.pbi 619525046922d8c | |||||
| 377 790 8074516460568228 Modbus_part4.pbi 3eb554587643adc3 | |||||
| 791 1838 8074516471051303 Modbus.pbd 6b0161af8161b195 | |||||
| 1839 6486 8074516516755621 Modbus.pbw f93412b0d27fc6cf | |||||
| 91 1592 8074516729543765 main.pbi 35262febfa1deec1 | |||||
| 1593 2907 8074516742819311 Modbus_part1.pbi ddb2d0c7a212e1f7 | |||||
| 2908 4130 8074516755009356 Modbus.pbd 6b0161af8161b195 | |||||
| 4131 10953 8074516822428244 Modbus.pbw f93412b0d27fc6cf | |||||
| 36 190 8074517401469028 os_dbg.pbi ad48542575ebad16 | |||||
| 47 199 8074517401479066 os_cpu_c.pbi ad0f935dbd645057 | |||||
| 190 449 8074517404085301 app_hooks.pbi f504fdb9b23706b6 | |||||
| 66 612 8074517405706245 ucos_ii.pbi 8d2ac5b9d103140d | |||||
| 612 821 8074517407737225 Modbus_part0.pbi aeb228c86547226c | |||||
| 59 1270 8074517412230290 stm32f4xx_it.pbi 5f6e49a9db86e0a1 | |||||
| 82 1368 8074517413203243 modbus_backup.pbi 123e4703660258f1 | |||||
| 27 1461 8074517414153987 main.pbi 35262febfa1deec1 | |||||
| 199 1519 8074517414731834 stm32f4xx_hal_msp.pbi 329860e7b1b76f81 | |||||
| 1369 1806 8074517417613113 Modbus_part4.pbi 3eb554587643adc3 | |||||
| 1520 2626 8074517425878496 Modbus_part1.pbi ddb2d0c7a212e1f7 | |||||
| 2627 3697 8074517436566989 Modbus.pbd 6b0161af8161b195 | |||||
| 3698 8529 8074517483962162 Modbus.pbw f93412b0d27fc6cf | |||||
| 41 221 8074529120713179 modbus.pbi 619525046922d8c | |||||
| 18 536 8074529123854861 stm32f4xx_it.pbi 5f6e49a9db86e0a1 | |||||
| 28 563 8074529124122196 main.pbi 35262febfa1deec1 | |||||
| 35 570 8074529124197355 stm32f4xx_hal_msp.pbi 329860e7b1b76f81 | |||||
| 50 579 8074529124231645 modbus_backup.pbi 123e4703660258f1 | |||||
| 579 731 8074529125848910 Modbus_part4.pbi 3eb554587643adc3 | |||||
| 571 919 8074529127730504 Modbus_part1.pbi ddb2d0c7a212e1f7 | |||||
| 919 1258 8074529131114879 Modbus.pbd 6b0161af8161b195 | |||||
| 1258 2925 8074529147442958 Modbus.pbw f93412b0d27fc6cf | |||||
| 31 106 8074529272163016 app_hooks.pbi f504fdb9b23706b6 | |||||
| 24 112 8074529272163016 os_cpu_c.pbi ad0f935dbd645057 | |||||
| 15 210 8074529273221302 os_dbg.pbi ad48542575ebad16 | |||||
| 107 301 8074529274102657 ucos_ii.pbi 8d2ac5b9d103140d | |||||
| 301 367 8074529274814198 Modbus_part0.pbi aeb228c86547226c | |||||
| 44 509 8074529276203566 modbus_backup.pbi 123e4703660258f1 | |||||
| 38 529 8074529276394234 stm32f4xx_it.pbi 5f6e49a9db86e0a1 | |||||
| 50 540 8074529276499069 main.pbi 35262febfa1deec1 | |||||
| 112 582 8074529276937420 stm32f4xx_hal_msp.pbi 329860e7b1b76f81 | |||||
| 510 679 8074529277942639 Modbus_part4.pbi 3eb554587643adc3 | |||||
| 582 980 8074529280941178 Modbus_part1.pbi ddb2d0c7a212e1f7 | |||||
| 980 1396 8074529285110341 Modbus.pbd 6b0161af8161b195 | |||||
| 1396 3164 8074529302191447 Modbus.pbw f93412b0d27fc6cf | |||||
| @@ -1,4 +1,4 @@ | |||||
| "d:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\Core\OS\Cfg\app_hooks.c" | |||||
| "D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\Core\OS\Cfg\app_hooks.c" | |||||
| -std=c11 | -std=c11 | ||||
| -ferror-limit=0 | -ferror-limit=0 | ||||
| -fbracket-depth=512 | -fbracket-depth=512 | ||||
| @@ -8,7 +8,7 @@ D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\Modbus\Obj\app_h | |||||
| -o | -o | ||||
| app_hooks.pbi | app_hooks.pbi | ||||
| -I | -I | ||||
| d:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\Core\OS\Cfg | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\Core\OS\Cfg | |||||
| -I | -I | ||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/Inc | D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/Inc | ||||
| -I | -I | ||||
| @@ -31,25 +31,18 @@ build system_stm32f4xx.pbi : index system_stm32f4xx.xcl | |||||
| build stm32f4xx_hal.pbi : index stm32f4xx_hal.xcl | build stm32f4xx_hal.pbi : index stm32f4xx_hal.xcl | ||||
| build stm32f4xx_hal_cortex.pbi : index stm32f4xx_hal_cortex.xcl | build stm32f4xx_hal_cortex.pbi : index stm32f4xx_hal_cortex.xcl | ||||
| build stm32f4xx_hal_dma.pbi : index stm32f4xx_hal_dma.xcl | build stm32f4xx_hal_dma.pbi : index stm32f4xx_hal_dma.xcl | ||||
| build stm32f4xx_hal_dma_ex.pbi : index stm32f4xx_hal_dma_ex.xcl | |||||
| build stm32f4xx_hal_exti.pbi : index stm32f4xx_hal_exti.xcl | |||||
| build stm32f4xx_hal_flash.pbi : index stm32f4xx_hal_flash.xcl | |||||
| build stm32f4xx_hal_flash_ex.pbi : index stm32f4xx_hal_flash_ex.xcl | |||||
| build stm32f4xx_hal_flash_ramfunc.pbi : index stm32f4xx_hal_flash_ramfunc.xcl | |||||
| build stm32f4xx_hal_gpio.pbi : index stm32f4xx_hal_gpio.xcl | build stm32f4xx_hal_gpio.pbi : index stm32f4xx_hal_gpio.xcl | ||||
| build stm32f4xx_hal_pwr.pbi : index stm32f4xx_hal_pwr.xcl | build stm32f4xx_hal_pwr.pbi : index stm32f4xx_hal_pwr.xcl | ||||
| build stm32f4xx_hal_pwr_ex.pbi : index stm32f4xx_hal_pwr_ex.xcl | |||||
| build stm32f4xx_hal_rcc.pbi : index stm32f4xx_hal_rcc.xcl | build stm32f4xx_hal_rcc.pbi : index stm32f4xx_hal_rcc.xcl | ||||
| build stm32f4xx_hal_rcc_ex.pbi : index stm32f4xx_hal_rcc_ex.xcl | build stm32f4xx_hal_rcc_ex.pbi : index stm32f4xx_hal_rcc_ex.xcl | ||||
| build stm32f4xx_hal_tim.pbi : index stm32f4xx_hal_tim.xcl | |||||
| build stm32f4xx_hal_tim_ex.pbi : index stm32f4xx_hal_tim_ex.xcl | |||||
| build stm32f4xx_hal_uart.pbi : index stm32f4xx_hal_uart.xcl | build stm32f4xx_hal_uart.pbi : index stm32f4xx_hal_uart.xcl | ||||
| build modbus.pbi : index modbus.xcl | build modbus.pbi : index modbus.xcl | ||||
| build Modbus_part0.pbi : link app_hooks.pbi os_cpu_c.pbi os_dbg.pbi ucos_ii.pbi main.pbi | |||||
| build Modbus_part1.pbi : link stm32f4xx_hal_msp.pbi stm32f4xx_it.pbi system_stm32f4xx.pbi stm32f4xx_hal.pbi stm32f4xx_hal_cortex.pbi | |||||
| build Modbus_part2.pbi : link stm32f4xx_hal_dma.pbi stm32f4xx_hal_dma_ex.pbi stm32f4xx_hal_exti.pbi stm32f4xx_hal_flash.pbi stm32f4xx_hal_flash_ex.pbi | |||||
| build Modbus_part3.pbi : link stm32f4xx_hal_flash_ramfunc.pbi stm32f4xx_hal_gpio.pbi stm32f4xx_hal_pwr.pbi stm32f4xx_hal_pwr_ex.pbi stm32f4xx_hal_rcc.pbi | |||||
| build Modbus_part4.pbi : link stm32f4xx_hal_rcc_ex.pbi stm32f4xx_hal_tim.pbi stm32f4xx_hal_tim_ex.pbi stm32f4xx_hal_uart.pbi modbus.pbi | |||||
| build modbus_backup.pbi : index modbus_backup.xcl | |||||
| build Modbus_part0.pbi : link app_hooks.pbi os_cpu_c.pbi os_dbg.pbi ucos_ii.pbi | |||||
| build Modbus_part1.pbi : link main.pbi stm32f4xx_hal_msp.pbi stm32f4xx_it.pbi system_stm32f4xx.pbi | |||||
| build Modbus_part2.pbi : link stm32f4xx_hal.pbi stm32f4xx_hal_cortex.pbi stm32f4xx_hal_dma.pbi stm32f4xx_hal_gpio.pbi | |||||
| build Modbus_part3.pbi : link stm32f4xx_hal_pwr.pbi stm32f4xx_hal_rcc.pbi stm32f4xx_hal_rcc_ex.pbi stm32f4xx_hal_uart.pbi | |||||
| build Modbus_part4.pbi : link modbus.pbi modbus_backup.pbi | |||||
| build Modbus.pbd : link Modbus_part0.pbi Modbus_part1.pbi Modbus_part2.pbi Modbus_part3.pbi Modbus_part4.pbi | build Modbus.pbd : link Modbus_part0.pbi Modbus_part1.pbi Modbus_part2.pbi Modbus_part3.pbi Modbus_part4.pbi | ||||
| build Modbus.pbw : browsedata Modbus.pbd | build Modbus.pbw : browsedata Modbus.pbd | ||||
| @@ -1,41 +1,41 @@ | |||||
| main.pbi: \ | main.pbi: \ | ||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\Core\Src\main.c \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/Inc\main.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/STM32F4xx_HAL_Driver/Inc\stm32f4xx_hal.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/Inc\stm32f4xx_hal_conf.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/STM32F4xx_HAL_Driver/Inc\stm32f4xx_hal_rcc.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_def.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/CMSIS/Device/ST/STM32F4xx/Include\stm32f4xx.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/CMSIS/Device/ST/STM32F4xx/Include/stm32f407xx.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/CMSIS/Include\core_cm4.h \ | |||||
| E:\IAR\arm\inc\c\stdint.h E:\IAR\arm\inc\c\ycheck.h \ | |||||
| E:\IAR\arm\inc\c\yvals.h E:\IAR\arm\inc\c\DLib_Defaults.h \ | |||||
| E:\\IAR\\arm\\inc\\c\\DLib_Config_Full.h \ | |||||
| E:\IAR\arm\inc\c\DLib_Product.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/CMSIS/Include/cmsis_version.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/CMSIS/Include/cmsis_compiler.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/CMSIS/Include\cmsis_iccarm.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\..\Core\Modbus\modbus_backup.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\..\Core\OS\Source\os_trace.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\..\Core\OS\Ports\os_cpu.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\..\Core\OS\Source\ucos_ii.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\..\Core\OS\Cfg\os_cfg.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\..\Core\Modbus\modbus.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\..\Core\OS\Cfg\app_cfg.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\..\Drivers\STM32F4xx_HAL_Driver\Inc\stm32f4xx_hal_uart.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\..\Drivers\STM32F4xx_HAL_Driver\Inc\stm32f4xx_hal_pwr_ex.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\..\Drivers\STM32F4xx_HAL_Driver\Inc\stm32f4xx_hal_pwr.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\..\Drivers\STM32F4xx_HAL_Driver\Inc\stm32f4xx_hal_flash_ramfunc.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\..\Drivers\STM32F4xx_HAL_Driver\Inc\stm32f4xx_hal_flash_ex.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\..\Drivers\STM32F4xx_HAL_Driver\Inc\stm32f4xx_hal_flash.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\..\Drivers\STM32F4xx_HAL_Driver\Inc\stm32f4xx_hal_cortex.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\..\Drivers\STM32F4xx_HAL_Driver\Inc\stm32f4xx_hal_dma_ex.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\..\Drivers\STM32F4xx_HAL_Driver\Inc\stm32f4xx_hal_dma.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\..\Drivers\STM32F4xx_HAL_Driver\Inc\stm32f4xx_hal_exti.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\..\Drivers\STM32F4xx_HAL_Driver\Inc\stm32f4xx_hal_gpio_ex.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\..\Drivers\STM32F4xx_HAL_Driver\Inc\stm32f4xx_hal_gpio.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\..\Drivers\STM32F4xx_HAL_Driver\Inc\stm32f4xx_hal_rcc_ex.h \ | |||||
| E:\IAR\arm\inc\c\ycheck.h E:\IAR\arm\inc\c\ysizet.h \ | |||||
| E:\IAR\arm\inc\c\stddef.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\..\Drivers\STM32F4xx_HAL_Driver\Inc\Legacy\stm32_hal_legacy.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\..\Drivers\STM32F4xx_HAL_Driver\Inc\stm32f4xx_hal.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\..\Drivers\CMSIS\Device\ST\STM32F4xx\Include\system_stm32f4xx.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\..\Drivers\CMSIS\Include\mpu_armv7.h \ | |||||
| E:\IAR\arm\inc\c\iccarm_builtin.h \ | E:\IAR\arm\inc\c\iccarm_builtin.h \ | ||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/CMSIS/Include/mpu_armv7.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/CMSIS/Device/ST/STM32F4xx/Include/system_stm32f4xx.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/STM32F4xx_HAL_Driver/Inc/Legacy/stm32_hal_legacy.h \ | |||||
| E:\IAR\arm\inc\c\stddef.h E:\IAR\arm\inc\c\ysizet.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_rcc_ex.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/STM32F4xx_HAL_Driver/Inc\stm32f4xx_hal_gpio.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_gpio_ex.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/STM32F4xx_HAL_Driver/Inc\stm32f4xx_hal_exti.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/STM32F4xx_HAL_Driver/Inc\stm32f4xx_hal_dma.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_dma_ex.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/STM32F4xx_HAL_Driver/Inc\stm32f4xx_hal_cortex.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/STM32F4xx_HAL_Driver/Inc\stm32f4xx_hal_flash.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_flash_ex.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_flash_ramfunc.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/STM32F4xx_HAL_Driver/Inc\stm32f4xx_hal_pwr.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_pwr_ex.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/STM32F4xx_HAL_Driver/Inc\stm32f4xx_hal_uart.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/OS/Cfg\app_cfg.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/OS/Cfg\os_cfg.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/OS/Source\ucos_ii.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/OS/Ports\os_cpu.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/OS/Source/os_trace.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/Modbus\modbus.h | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\..\Drivers\CMSIS\Include\cmsis_iccarm.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\..\Drivers\CMSIS\Include\cmsis_compiler.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\..\Drivers\CMSIS\Include\cmsis_version.h \ | |||||
| E:\IAR\arm\inc\c\DLib_Product.h E:\IAR\arm\inc\c\DLib_Defaults.h \ | |||||
| E:\IAR\arm\inc\c\yvals.h E:\IAR\arm\inc\c\stdint.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\..\Drivers\CMSIS\Include\core_cm4.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\..\Drivers\CMSIS\Device\ST\STM32F4xx\Include\stm32f407xx.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\..\Drivers\CMSIS\Device\ST\STM32F4xx\Include\stm32f4xx.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\..\Drivers\STM32F4xx_HAL_Driver\Inc\stm32f4xx_hal_def.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\..\Drivers\STM32F4xx_HAL_Driver\Inc\stm32f4xx_hal_rcc.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\..\Core\Inc\stm32f4xx_hal_conf.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\..\Core\Inc\main.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\Core\Src\main.c | |||||
| @@ -1,8 +1,6 @@ | |||||
| modbus.pbi: \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\Core\Modbus\modbus.c \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\Core\Modbus/modbus.h \ | |||||
| E:\IAR\arm\inc\c\stddef.h E:\IAR\arm\inc\c\ycheck.h \ | |||||
| E:\IAR\arm\inc\c\yvals.h E:\IAR\arm\inc\c\DLib_Defaults.h \ | |||||
| E:\\IAR\\arm\\inc\\c\\DLib_Config_Full.h \ | |||||
| E:\IAR\arm\inc\c\DLib_Product.h E:\IAR\arm\inc\c\ysizet.h \ | |||||
| E:\IAR\arm\inc\c\stdint.h | |||||
| modbus.pbi: E:\IAR\arm\inc\c\ycheck.h E:\IAR\arm\inc\c\stdint.h \ | |||||
| E:\IAR\arm\inc\c\ysizet.h E:\IAR\arm\inc\c\DLib_Product.h \ | |||||
| E:\IAR\arm\inc\c\DLib_Defaults.h E:\IAR\arm\inc\c\yvals.h \ | |||||
| E:\IAR\arm\inc\c\stddef.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\Core\Modbus\modbus.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\Core\Modbus\modbus.c | |||||
| @@ -1,4 +1,4 @@ | |||||
| "d:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\Core\OS\Ports\os_cpu_c.c" | |||||
| "D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\Core\OS\Ports\os_cpu_c.c" | |||||
| -std=c11 | -std=c11 | ||||
| -ferror-limit=0 | -ferror-limit=0 | ||||
| -fbracket-depth=512 | -fbracket-depth=512 | ||||
| @@ -8,7 +8,7 @@ D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\Modbus\Obj\os_cp | |||||
| -o | -o | ||||
| os_cpu_c.pbi | os_cpu_c.pbi | ||||
| -I | -I | ||||
| d:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\Core\OS\Ports | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\Core\OS\Ports | |||||
| -I | -I | ||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/Inc | D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/Inc | ||||
| -I | -I | ||||
| @@ -1,384 +0,0 @@ | |||||
| "D:\xinjiedianqi\学习文档\TrainCamp_zengbingjie_Modbus\Modbus\Core\OS\Source\os_dbg_r.c" | |||||
| -std=c11 | |||||
| -ferror-limit=0 | |||||
| -fbracket-depth=512 | |||||
| -MD | |||||
| -MF | |||||
| D:\xinjiedianqi\学习文档\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\Modbus\Obj\os_dbg_r.pbi.dep | |||||
| -o | |||||
| os_dbg_r.pbi | |||||
| -I | |||||
| D:\xinjiedianqi\学习文档\TrainCamp_zengbingjie_Modbus\Modbus\Core\OS\Source | |||||
| -I | |||||
| D:\xinjiedianqi\学习文档\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/Inc | |||||
| -I | |||||
| D:\xinjiedianqi\学习文档\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/STM32F4xx_HAL_Driver/Inc | |||||
| -I | |||||
| D:\xinjiedianqi\学习文档\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/STM32F4xx_HAL_Driver/Inc/Legacy | |||||
| -I | |||||
| D:\xinjiedianqi\学习文档\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/CMSIS/Device/ST/STM32F4xx/Include | |||||
| -I | |||||
| D:\xinjiedianqi\学习文档\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/CMSIS/Include | |||||
| -I | |||||
| D:\xinjiedianqi\学习文档\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/OS/Source | |||||
| -I | |||||
| D:\xinjiedianqi\学习文档\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/OS/Ports | |||||
| -I | |||||
| D:\xinjiedianqi\学习文档\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/OS/Cfg | |||||
| -I | |||||
| E:\IAR\arm\inc | |||||
| -I | |||||
| E:\IAR\arm\inc\c | |||||
| -I | |||||
| D:\xinjiedianqi\学习文档\TrainCamp_zengbingjie_Modbus\Modbus\Core\Inc | |||||
| -I | |||||
| D:\xinjiedianqi\学习文档\TrainCamp_zengbingjie_Modbus\Modbus\Drivers\STM32F4xx_HAL_Driver\Inc | |||||
| -I | |||||
| D:\xinjiedianqi\学习文档\TrainCamp_zengbingjie_Modbus\Modbus\Drivers\STM32F4xx_HAL_Driver\Inc\Legacy | |||||
| -I | |||||
| D:\xinjiedianqi\学习文档\TrainCamp_zengbingjie_Modbus\Modbus\Drivers\CMSIS\Device\ST\STM32F4xx\Include | |||||
| -I | |||||
| D:\xinjiedianqi\学习文档\TrainCamp_zengbingjie_Modbus\Modbus\Drivers\CMSIS\Include | |||||
| -I | |||||
| D:\xinjiedianqi\学习文档\TrainCamp_zengbingjie_Modbus\Modbus\Core\OS\Source | |||||
| -I | |||||
| D:\xinjiedianqi\学习文档\TrainCamp_zengbingjie_Modbus\Modbus\Core\OS\Ports | |||||
| -I | |||||
| D:\xinjiedianqi\学习文档\TrainCamp_zengbingjie_Modbus\Modbus\Core\OS\Cfg | |||||
| -D__CHAR_BITS__=8 | |||||
| -D__CHAR_MAX__=0xff | |||||
| -D__CHAR_MIN__=0 | |||||
| -D__CHAR_SIZE__=1 | |||||
| -D__UNSIGNED_CHAR_MAX__=0xff | |||||
| -D__SIGNED_CHAR_MAX__=127 | |||||
| -D__SIGNED_CHAR_MIN__=(-__SIGNED_CHAR_MAX__-1) | |||||
| -D__CHAR_ALIGN__=1 | |||||
| -D__SHORT_SIZE__=2 | |||||
| -D__UNSIGNED_SHORT_MAX__=0xffff | |||||
| -D__SIGNED_SHORT_MAX__=32767 | |||||
| -D__SIGNED_SHORT_MIN__=(-__SIGNED_SHORT_MAX__-1) | |||||
| -D__SHORT_ALIGN__=2 | |||||
| -D__INT_SIZE__=4 | |||||
| -D__UNSIGNED_INT_MAX__=0xffffffffU | |||||
| -D__SIGNED_INT_MAX__=2147483647 | |||||
| -D__SIGNED_INT_MIN__=(-__SIGNED_INT_MAX__-1) | |||||
| -D__INT_ALIGN__=4 | |||||
| -D__LONG_SIZE__=4 | |||||
| -D__UNSIGNED_LONG_MAX__=0xffffffffUL | |||||
| -D__SIGNED_LONG_MAX__=2147483647L | |||||
| -D__SIGNED_LONG_MIN__=(-__SIGNED_LONG_MAX__-1) | |||||
| -D__LONG_ALIGN__=4 | |||||
| -D__LONG_LONG_SIZE__=8 | |||||
| -D__UNSIGNED_LONG_LONG_MAX__=0xffffffffffffffffULL | |||||
| -D__SIGNED_LONG_LONG_MAX__=9223372036854775807LL | |||||
| -D__SIGNED_LONG_LONG_MIN__=(-__SIGNED_LONG_LONG_MAX__-1) | |||||
| -D__LONG_LONG_ALIGN__=8 | |||||
| -D__INT8_T_TYPE__=signed char | |||||
| -D__INT8_T_MAX__=127 | |||||
| -D__INT8_T_MIN__=(-__INT8_T_MAX__-1) | |||||
| -D__UINT8_T_TYPE__=unsigned char | |||||
| -D__UINT8_T_MAX__=0xff | |||||
| -D__INT8_SIZE_PREFIX__="hh" | |||||
| -D__INT16_T_TYPE__=signed short int | |||||
| -D__INT16_T_MAX__=32767 | |||||
| -D__INT16_T_MIN__=(-__INT16_T_MAX__-1) | |||||
| -D__UINT16_T_TYPE__=unsigned short int | |||||
| -D__UINT16_T_MAX__=0xffff | |||||
| -D__INT16_SIZE_PREFIX__="h" | |||||
| -D__INT32_T_TYPE__=signed int | |||||
| -D__INT32_T_MAX__=2147483647 | |||||
| -D__INT32_T_MIN__=(-__INT32_T_MAX__-1) | |||||
| -D__UINT32_T_TYPE__=unsigned int | |||||
| -D__UINT32_T_MAX__=0xffffffffU | |||||
| -D__INT32_SIZE_PREFIX__="" | |||||
| -D__INT64_T_TYPE__=signed long long int | |||||
| -D__INT64_T_MAX__=9223372036854775807LL | |||||
| -D__INT64_T_MIN__=(-__INT64_T_MAX__-1) | |||||
| -D__UINT64_T_TYPE__=unsigned long long int | |||||
| -D__UINT64_T_MAX__=0xffffffffffffffffULL | |||||
| -D__INT64_SIZE_PREFIX__="ll" | |||||
| -D__INT_LEAST8_T_TYPE__=signed char | |||||
| -D__INT_LEAST8_T_MAX__=127 | |||||
| -D__INT_LEAST8_T_MIN__=(-__INT_LEAST8_T_MAX__-1) | |||||
| -D__UINT_LEAST8_T_TYPE__=unsigned char | |||||
| -D__UINT_LEAST8_T_MAX__=0xff | |||||
| -D__INT8_C_SUFFIX__= | |||||
| -D__UINT8_C_SUFFIX__= | |||||
| -D__INT_LEAST8_SIZE_PREFIX__="hh" | |||||
| -D__INT_LEAST16_T_TYPE__=signed short int | |||||
| -D__INT_LEAST16_T_MAX__=32767 | |||||
| -D__INT_LEAST16_T_MIN__=(-__INT_LEAST16_T_MAX__-1) | |||||
| -D__UINT_LEAST16_T_TYPE__=unsigned short int | |||||
| -D__UINT_LEAST16_T_MAX__=0xffff | |||||
| -D__INT16_C_SUFFIX__= | |||||
| -D__UINT16_C_SUFFIX__= | |||||
| -D__INT_LEAST16_SIZE_PREFIX__="h" | |||||
| -D__INT_LEAST32_T_TYPE__=signed int | |||||
| -D__INT_LEAST32_T_MAX__=2147483647 | |||||
| -D__INT_LEAST32_T_MIN__=(-__INT_LEAST32_T_MAX__-1) | |||||
| -D__UINT_LEAST32_T_TYPE__=unsigned int | |||||
| -D__UINT_LEAST32_T_MAX__=0xffffffffU | |||||
| -D__INT32_C_SUFFIX__= | |||||
| -D__UINT32_C_SUFFIX__=U | |||||
| -D__INT_LEAST32_SIZE_PREFIX__="" | |||||
| -D__INT_LEAST64_T_TYPE__=signed long long int | |||||
| -D__INT_LEAST64_T_MAX__=9223372036854775807LL | |||||
| -D__INT_LEAST64_T_MIN__=(-__INT_LEAST64_T_MAX__-1) | |||||
| -D__UINT_LEAST64_T_TYPE__=unsigned long long int | |||||
| -D__UINT_LEAST64_T_MAX__=0xffffffffffffffffULL | |||||
| -D__INT64_C_SUFFIX__=LL | |||||
| -D__UINT64_C_SUFFIX__=ULL | |||||
| -D__INT_LEAST64_SIZE_PREFIX__="ll" | |||||
| -D__INT_FAST8_T_TYPE__=signed int | |||||
| -D__INT_FAST8_T_MAX__=2147483647 | |||||
| -D__INT_FAST8_T_MIN__=(-__INT_FAST8_T_MAX__-1) | |||||
| -D__UINT_FAST8_T_TYPE__=unsigned int | |||||
| -D__UINT_FAST8_T_MAX__=0xffffffffU | |||||
| -D__INT_FAST8_SIZE_PREFIX__="" | |||||
| -D__INT_FAST16_T_TYPE__=signed int | |||||
| -D__INT_FAST16_T_MAX__=2147483647 | |||||
| -D__INT_FAST16_T_MIN__=(-__INT_FAST16_T_MAX__-1) | |||||
| -D__UINT_FAST16_T_TYPE__=unsigned int | |||||
| -D__UINT_FAST16_T_MAX__=0xffffffffU | |||||
| -D__INT_FAST16_SIZE_PREFIX__="" | |||||
| -D__INT_FAST32_T_TYPE__=signed int | |||||
| -D__INT_FAST32_T_MAX__=2147483647 | |||||
| -D__INT_FAST32_T_MIN__=(-__INT_FAST32_T_MAX__-1) | |||||
| -D__UINT_FAST32_T_TYPE__=unsigned int | |||||
| -D__UINT_FAST32_T_MAX__=0xffffffffU | |||||
| -D__INT_FAST32_SIZE_PREFIX__="" | |||||
| -D__INT_FAST64_T_TYPE__=signed long long int | |||||
| -D__INT_FAST64_T_MAX__=9223372036854775807LL | |||||
| -D__INT_FAST64_T_MIN__=(-__INT_FAST64_T_MAX__-1) | |||||
| -D__UINT_FAST64_T_TYPE__=unsigned long long int | |||||
| -D__UINT_FAST64_T_MAX__=0xffffffffffffffffULL | |||||
| -D__INT_FAST64_SIZE_PREFIX__="ll" | |||||
| -D__INTMAX_T_TYPE__=signed long long int | |||||
| -D__INTMAX_T_MAX__=9223372036854775807LL | |||||
| -D__INTMAX_T_MIN__=(-__INTMAX_T_MAX__-1) | |||||
| -D__UINTMAX_T_TYPE__=unsigned long long int | |||||
| -D__UINTMAX_T_MAX__=0xffffffffffffffffULL | |||||
| -D__INTMAX_C_SUFFIX__=LL | |||||
| -D__UINTMAX_C_SUFFIX__=ULL | |||||
| -D__INTMAX_SIZE_PREFIX__="ll" | |||||
| -D__ATOMIC_BOOL_LOCK_FREE=2 | |||||
| -D__ATOMIC_CHAR_LOCK_FREE=2 | |||||
| -D__ATOMIC_CHAR16_T_LOCK_FREE=2 | |||||
| -D__ATOMIC_CHAR32_T_LOCK_FREE=2 | |||||
| -D__ATOMIC_WCHAR_T_LOCK_FREE=2 | |||||
| -D__ATOMIC_SHORT_LOCK_FREE=2 | |||||
| -D__ATOMIC_INT_LOCK_FREE=2 | |||||
| -D__ATOMIC_LONG_LOCK_FREE=2 | |||||
| -D__ATOMIC_LLONG_LOCK_FREE=0 | |||||
| -D__ATOMIC_POINTER_LOCK_FREE=2 | |||||
| -D__FLOAT_SIZE__=4 | |||||
| -D__FLOAT_ALIGN__=4 | |||||
| -D__DOUBLE_SIZE__=8 | |||||
| -D__DOUBLE_ALIGN__=8 | |||||
| -D__LONG_DOUBLE_SIZE__=8 | |||||
| -D__LONG_DOUBLE_ALIGN__=8 | |||||
| -D____FP16_SIZE__=2 | |||||
| -D____FP16_ALIGN__=2 | |||||
| -D__NAN_HAS_HIGH_MANTISSA_BIT_SET__=0 | |||||
| -D__SUBNORMAL_FLOATING_POINTS__=1 | |||||
| -D__SIZE_T_TYPE__=unsigned int | |||||
| -D__SIZE_T_MAX__=0xffffffffU | |||||
| -D__PTRDIFF_T_TYPE__=signed int | |||||
| -D__PTRDIFF_T_MAX__=2147483647 | |||||
| -D__PTRDIFF_T_MIN__=(-__PTRDIFF_T_MAX__-1) | |||||
| -D__INTPTR_T_TYPE__=signed int | |||||
| -D__INTPTR_T_MAX__=2147483647 | |||||
| -D__INTPTR_T_MIN__=(-__INTPTR_T_MAX__-1) | |||||
| -D__UINTPTR_T_TYPE__=unsigned int | |||||
| -D__UINTPTR_T_MAX__=0xffffffffU | |||||
| -D__INTPTR_SIZE_PREFIX__="" | |||||
| -D__JMP_BUF_ELEMENT_TYPE__=unsigned long long int | |||||
| -D__JMP_BUF_NUM_ELEMENTS__=16 | |||||
| -D__TID__=0xcf60 | |||||
| -D__VER__=8040002 | |||||
| -D__BUILD_NUMBER__=214 | |||||
| -D__IAR_SYSTEMS_ICC__=9 | |||||
| -D_MAX_ALIGNMENT=8 | |||||
| -D__LITTLE_ENDIAN__=1 | |||||
| -D__BOOL_TYPE__=unsigned char | |||||
| -D__BOOL_SIZE__=1 | |||||
| -D__WCHAR_T_TYPE__=unsigned int | |||||
| -D__WCHAR_T_SIZE__=4 | |||||
| -D__WCHAR_T_MAX__=0xffffffffU | |||||
| -D__DEF_PTR_MEM__=__data | |||||
| -D__DEF_PTR_SIZE__=4 | |||||
| -D__DATA_MEM0__=__data | |||||
| -D__DATA_MEM0_POINTER_OK__=1 | |||||
| -D__DATA_MEM0_UNIQUE_POINTER__=1 | |||||
| -D__DATA_MEM0_VAR_OK__=1 | |||||
| -D__DATA_MEM0_INTPTR_TYPE__=int | |||||
| -D__DATA_MEM0_UINTPTR_TYPE__=unsigned int | |||||
| -D__DATA_MEM0_INTPTR_SIZE_PREFIX__="" | |||||
| -D__DATA_MEM0_MAX_SIZE__=0x7fffffffU | |||||
| -D_RSIZE_MAX=0x7fffffffU | |||||
| -D__DATA_MEM0_HEAP_SEGMENT__="HEAP" | |||||
| -D__DATA_MEM0_PAGE_SIZE__=0 | |||||
| -D__DATA_MEM0_HEAP__=0 | |||||
| -D__CODE_MEM0__=__code | |||||
| -D__CODE_MEM0_POINTER_OK__=1 | |||||
| -D__CODE_MEM0_UNIQUE_POINTER__=1 | |||||
| -D__HEAP_MEM0__=0 | |||||
| -D__HEAP_DEFAULT_MEM__=0 | |||||
| -D__HEAPND_MEMORY_LIST1__()= | |||||
| -D__MULTIPLE_HEAPS__=0 | |||||
| -D__DEF_HEAP_MEM__=__data | |||||
| -D__DEF_STACK_MEM_INDEX__=0 | |||||
| -D__PRAGMA_PACK_ON__=1 | |||||
| -D__MULTIPLE_INHERITANCE__=1 | |||||
| -D__AAPCS_VFP__=1 | |||||
| -D__ARM4TM__=4 | |||||
| -D__ARM5TM__=5 | |||||
| -D__ARM5T__=5 | |||||
| -D__ARM5__=5 | |||||
| -D__ARM6MEDIA__=6 | |||||
| -D__ARM6M__=11 | |||||
| -D__ARM6SM__=12 | |||||
| -D__ARM6T2__=6 | |||||
| -D__ARM6__=6 | |||||
| -D__ARM7EM__=13 | |||||
| -D__ARM7M__=7 | |||||
| -D__ARM7__=7 | |||||
| -D__ARMVFPV1__=1 | |||||
| -D__ARMVFPV2__=2 | |||||
| -D__ARMVFPV3_D16__=1 | |||||
| -D__ARMVFPV3_FP16__=1 | |||||
| -D__ARMVFPV3__=3 | |||||
| -D__ARMVFPV4__=4 | |||||
| -D__ARMVFP_D16__=1 | |||||
| -D__ARMVFP_FP16__=1 | |||||
| -D__ARMVFP_SP__=1 | |||||
| -D__ARMVFP__=__ARMVFPV4__ | |||||
| -D__ARM_32BIT_STATE=1 | |||||
| -D__ARM_ACLE=201 | |||||
| -D__ARM_ALIGN_MAX_PWR=8 | |||||
| -D__ARM_ALIGN_MAX_STACK_PWR=3 | |||||
| -D__ARM_ARCH=7 | |||||
| -D__ARM_ARCH_ISA_THUMB=2 | |||||
| -D__ARM_ARCH_PROFILE='M' | |||||
| -D__ARM_FEATURE_CLZ=1 | |||||
| -D__ARM_FEATURE_COPROC=15 | |||||
| -D__ARM_FEATURE_DSP=1 | |||||
| -D__ARM_FEATURE_FMA=1 | |||||
| -D__ARM_FEATURE_IDIV=1 | |||||
| -D__ARM_FEATURE_LDREX=7 | |||||
| -D__ARM_FEATURE_QBIT=1 | |||||
| -D__ARM_FEATURE_SAT=1 | |||||
| -D__ARM_FEATURE_SIMD32=1 | |||||
| -D__ARM_FEATURE_UNALIGNED=1 | |||||
| -D__ARM_FP=6 | |||||
| -D__ARM_FP16_ARGS=1 | |||||
| -D__ARM_FP16_FORMAT_IEEE=1 | |||||
| -D__ARM_MEDIA__=1 | |||||
| -D__ARM_PCS_VFP=1 | |||||
| -D__ARM_PROFILE_M__=1 | |||||
| -D__ARM_SIZE_MINIMAL_ENUM=1 | |||||
| -D__ARM_SIZE_WCHAR_T=4 | |||||
| -D__CODE_SIZE_LIMIT=0 | |||||
| -D__CORE__=__ARM7EM__ | |||||
| -D__CPU_MODE__=1 | |||||
| -D__ICCARM_INTRINSICS_VERSION__=2 | |||||
| -D__ICCARM__=1 | |||||
| -D__INTERWORKING__=1 | |||||
| -D__PLAIN_INT_BITFIELD_IS_SIGNED__=0 | |||||
| -D__HAS_WEAK__=1 | |||||
| -D__HAS_PACKED__=1 | |||||
| -D__HAS_JOINED_TYPES__=1 | |||||
| -D__HAS_LOCATED_DECLARATION__=1 | |||||
| -D__HAS_LOCATED_WITH_INIT__=1 | |||||
| -D__IAR_COMPILERBASE__=656646 | |||||
| -D__IAR_COMPILERBASE_STR__=10.5.6.1181 | |||||
| -D__UNICODE_SOURCE_SUPPORTED__=1 | |||||
| -D__VTABLE_MEM__= | |||||
| -D__PRAGMA_REDEFINE_EXTNAME=1 | |||||
| -D__STDC__=1 | |||||
| -D__STDC_VERSION__=201710L | |||||
| -D__STDC_NO_VLA__=1 | |||||
| -D__MEMORY_ORDER_RELAXED__=0 | |||||
| -D__MEMORY_ORDER_CONSUME__=1 | |||||
| -D__MEMORY_ORDER_ACQUIRE__=2 | |||||
| -D__MEMORY_ORDER_RELEASE__=3 | |||||
| -D__MEMORY_ORDER_ACQ_REL__=4 | |||||
| -D__MEMORY_ORDER_SEQ_CST__=5 | |||||
| -D__STDC_UTF_16__=1 | |||||
| -D__STDC_UTF_32__=1 | |||||
| -D__STDC_LIB_EXT1__=201112L | |||||
| -D__STDC_NO_THREADS__=1 | |||||
| -D__STDC_ISO_10646__=201103L | |||||
| -D__STDC_HOSTED__=1 | |||||
| -D__EDG_IA64_ABI=1 | |||||
| -D__EDG_IA64_ABI_VARIANT_CTORS_AND_DTORS_RETURN_THIS=1 | |||||
| -D__EDG_IA64_ABI_USE_INT_STATIC_INIT_GUARD=1 | |||||
| -D__cpp_hex_float=201603L | |||||
| -D__cpp_unicode_literals=200710L | |||||
| -D__cpp_static_assert=200410L | |||||
| -D__EDG__=1 | |||||
| -D__EDG_VERSION__=500 | |||||
| -D__EDG_SIZE_TYPE__=unsigned int | |||||
| -D__EDG_PTRDIFF_TYPE__=int | |||||
| -D__EDG_DELTA_TYPE=int | |||||
| -D__EDG_IA64_VTABLE_ENTRY_TYPE=int | |||||
| -D__EDG_VIRTUAL_FUNCTION_INDEX_TYPE=unsigned short | |||||
| -D__EDG_LOWER_VARIABLE_LENGTH_ARRAYS=1 | |||||
| -D__EDG_IA64_ABI_USE_VARIANT_ARRAY_COOKIES=1 | |||||
| -D__EDG_ABI_COMPATIBILITY_VERSION=9999 | |||||
| -D__EDG_ABI_CHANGES_FOR_RTTI=1 | |||||
| -D__EDG_ABI_CHANGES_FOR_ARRAY_NEW_AND_DELETE=1 | |||||
| -D__EDG_ABI_CHANGES_FOR_PLACEMENT_DELETE=1 | |||||
| -D__EDG_BSD=0 | |||||
| -D__EDG_SYSV=0 | |||||
| -D__EDG_ANSIC=1 | |||||
| -D__EDG_CPP11_IL_EXTENSIONS_SUPPORTED=1 | |||||
| -D__EDG_FLOAT80_ENABLING_POSSIBLE=0 | |||||
| -D__EDG_FLOAT128_ENABLING_POSSIBLE=0 | |||||
| -D__EDG_INT128_EXTENSIONS_ALLOWED=0 | |||||
| -DUSE_HAL_DRIVER=1 | |||||
| -DSTM32F407xx=1 | |||||
| -D_DLIB_CONFIG_FILE_HEADER_NAME="E:\\IAR\\arm\\inc\\c\\DLib_Config_Full.h" | |||||
| -D_DLIB_CONFIG_FILE_STRING="E:\\IAR\\arm\\inc\\c\\DLib_Config_Full.h" | |||||
| -D__VERSION__="IAR ANSI C/C++ Compiler V8.40.2.214/W32 for ARM" | |||||
| -D_VA_DEFINED= | |||||
| -D_VA_LIST=struct __va_list | |||||
| -D__ICCARM_OLD_DEFINED_VAARGS__=1 | |||||
| -D__VA_STACK_ALIGN__=8 | |||||
| -D__CODE_MEMORY_LIST1__()=__CODE_MEM_HELPER1__(__code, 0 ) | |||||
| -D__CODE_MEMORY_LIST2__(_P1)=__CODE_MEM_HELPER2__(__code, 0 , _P1 ) | |||||
| -D__CODE_MEMORY_LIST3__(_P1,_P2)=__CODE_MEM_HELPER3__(__code, 0 , _P1 , _P2 ) | |||||
| -D__DATA_MEMORY_LIST1__()=__DATA_MEM_HELPER1__(__data, 0 ) | |||||
| -D__DATA_MEMORY_LIST2__(_P1)=__DATA_MEM_HELPER2__(__data, 0 , _P1 ) | |||||
| -D__DATA_MEMORY_LIST3__(_P1,_P2)=__DATA_MEM_HELPER3__(__data, 0 , _P1 , _P2 ) | |||||
| -D__CODE_PTR_MEMORY_LIST1__()=__CODE_PTR_MEM_HELPER1__(__code, 0 ) | |||||
| -D__CODE_PTR_MEMORY_LIST2__(_P1)=__CODE_PTR_MEM_HELPER2__(__code, 0 , _P1 ) | |||||
| -D__CODE_PTR_MEMORY_LIST3__(_P1,_P2)=__CODE_PTR_MEM_HELPER3__(__code, 0 , _P1 , _P2 ) | |||||
| -D__DATA_PTR_MEMORY_LIST1__()=__DATA_PTR_MEM_HELPER1__(__data, 0 ) | |||||
| -D__DATA_PTR_MEMORY_LIST2__(_P1)=__DATA_PTR_MEM_HELPER2__(__data, 0 , _P1 ) | |||||
| -D__DATA_PTR_MEMORY_LIST3__(_P1,_P2)=__DATA_PTR_MEM_HELPER3__(__data, 0 , _P1 , _P2 ) | |||||
| -D__VAR_MEMORY_LIST1__()=__VAR_MEM_HELPER1__(__data, 0 ) | |||||
| -D__VAR_MEMORY_LIST2__(_P1)=__VAR_MEM_HELPER2__(__data, 0 , _P1 ) | |||||
| -D__VAR_MEMORY_LIST3__(_P1,_P2)=__VAR_MEM_HELPER3__(__data, 0 , _P1 , _P2 ) | |||||
| -D__VARD_MEMORY_LIST1__()=__VARD_MEM_HELPER1__(__data, 0, _ ) | |||||
| -D__HEAP_MEMORY_LIST1__()=__HEAP_MEM_HELPER1__(__data, 0 ) | |||||
| -D__HEAP_MEMORY_LIST2__(_P1)=__HEAP_MEM_HELPER2__(__data, 0 , _P1 ) | |||||
| -D__HEAP_MEMORY_LIST3__(_P1,_P2)=__HEAP_MEM_HELPER3__(__data, 0 , _P1 , _P2 ) | |||||
| -D__HVAR_MEMORY_LIST1__()=__HVAR_MEM_HELPER1__(__data, 0 ) | |||||
| -D__HEAPD_MEMORY_LIST1__()=__HEAPD_MEM_HELPER1__(__data, 0, _ ) | |||||
| -D__HEAPU_MEMORY_LIST1__()=__HEAPU_MEM_HELPER1__(__data, 0 ) | |||||
| -D__TOPM_DATA_MEMORY_LIST1__()= | |||||
| -D__TOPM_DATA_MEMORY_LIST2__(_P1)= | |||||
| -D__TOPM_DATA_MEMORY_LIST3__(_P1,_P2)= | |||||
| -D__TOPP_DATA_MEMORY_LIST1__()=__TOPP_DATA_MEM_HELPER1__(__data, 0 ) | |||||
| -D__TOPP_DATA_MEMORY_LIST2__(_P1)=__TOPP_DATA_MEM_HELPER2__(__data, 0 , _P1 ) | |||||
| -D__TOPP_DATA_MEMORY_LIST3__(_P1,_P2)=__TOPP_DATA_MEM_HELPER3__(__data, 0 , _P1 , _P2 ) | |||||
| -D__DATA_MEM0_SIZE_TYPE__=unsigned int | |||||
| -D__DATA_MEM0_INDEX_TYPE__=signed int | |||||
| -D__iar_fp2bits32(x)=0 | |||||
| -D__iar_fp2bits64(x)=0 | |||||
| -D__iar_fpgethi64(x)=0 | |||||
| -D__iar_atomic_add_fetch(x,y,z)=0 | |||||
| -D__iar_atomic_sub_fetch(x,y,z)=0 | |||||
| -D__iar_atomic_load(x,y)=0ULL | |||||
| -D__iar_atomic_compare_exchange_weak(a,b,c,d,e)=0 | |||||
| @@ -1,5 +1,5 @@ | |||||
| stm32f4xx_hal.pbi: \ | stm32f4xx_hal.pbi: \ | ||||
| d:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal.c \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal.c \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/STM32F4xx_HAL_Driver/Inc\stm32f4xx_hal.h \ | D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/STM32F4xx_HAL_Driver/Inc\stm32f4xx_hal.h \ | ||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/Inc\stm32f4xx_hal_conf.h \ | D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/Inc\stm32f4xx_hal_conf.h \ | ||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/STM32F4xx_HAL_Driver/Inc\stm32f4xx_hal_rcc.h \ | D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/STM32F4xx_HAL_Driver/Inc\stm32f4xx_hal_rcc.h \ | ||||
| @@ -1,4 +1,4 @@ | |||||
| "d:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal.c" | |||||
| "D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal.c" | |||||
| -std=c11 | -std=c11 | ||||
| -ferror-limit=0 | -ferror-limit=0 | ||||
| -fbracket-depth=512 | -fbracket-depth=512 | ||||
| @@ -8,7 +8,7 @@ D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\Modbus\Obj\stm32 | |||||
| -o | -o | ||||
| stm32f4xx_hal.pbi | stm32f4xx_hal.pbi | ||||
| -I | -I | ||||
| d:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\Drivers\STM32F4xx_HAL_Driver\Src | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\Drivers\STM32F4xx_HAL_Driver\Src | |||||
| -I | -I | ||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/Inc | D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/Inc | ||||
| -I | -I | ||||
| @@ -1,5 +1,5 @@ | |||||
| stm32f4xx_hal_cortex.pbi: \ | stm32f4xx_hal_cortex.pbi: \ | ||||
| d:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal_cortex.c \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal_cortex.c \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/STM32F4xx_HAL_Driver/Inc\stm32f4xx_hal.h \ | D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/STM32F4xx_HAL_Driver/Inc\stm32f4xx_hal.h \ | ||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/Inc\stm32f4xx_hal_conf.h \ | D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/Inc\stm32f4xx_hal_conf.h \ | ||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/STM32F4xx_HAL_Driver/Inc\stm32f4xx_hal_rcc.h \ | D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/STM32F4xx_HAL_Driver/Inc\stm32f4xx_hal_rcc.h \ | ||||
| @@ -1,4 +1,4 @@ | |||||
| "d:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal_cortex.c" | |||||
| "D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal_cortex.c" | |||||
| -std=c11 | -std=c11 | ||||
| -ferror-limit=0 | -ferror-limit=0 | ||||
| -fbracket-depth=512 | -fbracket-depth=512 | ||||
| @@ -8,7 +8,7 @@ D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\Modbus\Obj\stm32 | |||||
| -o | -o | ||||
| stm32f4xx_hal_cortex.pbi | stm32f4xx_hal_cortex.pbi | ||||
| -I | -I | ||||
| d:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\Drivers\STM32F4xx_HAL_Driver\Src | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\Drivers\STM32F4xx_HAL_Driver\Src | |||||
| -I | -I | ||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/Inc | D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/Inc | ||||
| -I | -I | ||||
| @@ -1,4 +1,4 @@ | |||||
| "d:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal_gpio.c" | |||||
| "D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal_gpio.c" | |||||
| -std=c11 | -std=c11 | ||||
| -ferror-limit=0 | -ferror-limit=0 | ||||
| -fbracket-depth=512 | -fbracket-depth=512 | ||||
| @@ -8,7 +8,7 @@ D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\Modbus\Obj\stm32 | |||||
| -o | -o | ||||
| stm32f4xx_hal_gpio.pbi | stm32f4xx_hal_gpio.pbi | ||||
| -I | -I | ||||
| d:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\Drivers\STM32F4xx_HAL_Driver\Src | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\Drivers\STM32F4xx_HAL_Driver\Src | |||||
| -I | -I | ||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/Inc | D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/Inc | ||||
| -I | -I | ||||
| @@ -34,6 +34,7 @@ stm32f4xx_hal_msp.pbi: \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_pwr_ex.h \ | D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_pwr_ex.h \ | ||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/STM32F4xx_HAL_Driver/Inc\stm32f4xx_hal_uart.h \ | D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/STM32F4xx_HAL_Driver/Inc\stm32f4xx_hal_uart.h \ | ||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/OS/Cfg\app_cfg.h \ | D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/OS/Cfg\app_cfg.h \ | ||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/Modbus\modbus.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/OS/Cfg\os_cfg.h \ | D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/OS/Cfg\os_cfg.h \ | ||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/OS/Source\ucos_ii.h \ | D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/OS/Source\ucos_ii.h \ | ||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/OS/Ports\os_cpu.h \ | D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/OS/Ports\os_cpu.h \ | ||||
| @@ -34,6 +34,7 @@ stm32f4xx_it.pbi: \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_pwr_ex.h \ | D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_pwr_ex.h \ | ||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/STM32F4xx_HAL_Driver/Inc\stm32f4xx_hal_uart.h \ | D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Drivers/STM32F4xx_HAL_Driver/Inc\stm32f4xx_hal_uart.h \ | ||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/OS/Cfg\app_cfg.h \ | D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/OS/Cfg\app_cfg.h \ | ||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/Modbus\modbus.h \ | |||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/OS/Cfg\os_cfg.h \ | D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/OS/Cfg\os_cfg.h \ | ||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/OS/Source\ucos_ii.h \ | D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/OS/Source\ucos_ii.h \ | ||||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/OS/Ports\os_cpu.h \ | D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/OS/Ports\os_cpu.h \ | ||||
| @@ -20,7 +20,7 @@ | |||||
| <stlinkResetStrategy>2</stlinkResetStrategy> | <stlinkResetStrategy>2</stlinkResetStrategy> | ||||
| </StLinkDriver> | </StLinkDriver> | ||||
| <DebugChecksum> | <DebugChecksum> | ||||
| <Checksum>3430922322</Checksum> | |||||
| <Checksum>3495406535</Checksum> | |||||
| </DebugChecksum> | </DebugChecksum> | ||||
| <Exceptions> | <Exceptions> | ||||
| <StopOnUncaught>_ 0</StopOnUncaught> | <StopOnUncaught>_ 0</StopOnUncaught> | ||||
| @@ -0,0 +1,322 @@ | |||||
| # Modbus Retention and Private Functions Implementation Plan | |||||
| > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. | |||||
| **Goal:** Replace the communication-history extension with `0x42` extended-register write and `0x43` odd-count register read, while retaining RTC persistence and documenting the TouchWin functions. | |||||
| **Architecture:** `modbus.c` remains the protocol-only layer: it validates request frames, reads or writes the configured `0x00010000` storage, and builds RTU responses. `main.c` owns persistence policy, adding `0x42` to successful write operations so the existing RTC backup service saves it. The host protocol harness executes the parser against explicit backing storage; the TouchWin document contains the matching HMI C functions and PSW bindings. | |||||
| **Tech Stack:** STM32F407 C firmware, uC/OS-II, IAR project, Modbus RTU, RTC backup registers, GNU GCC host test executable with `assert`. | |||||
| ## Global Constraints | |||||
| - Treat `0x41`, `0x42`, and `0x43` as this training project's private protocol, not standard Modbus function codes. | |||||
| - Keep standard data address range `0x0000..0x270F`; only private `0x41` and `0x42` access `0x00010000`. | |||||
| - `0x42` writes one 16-bit value to `0x00010000`, has a fixed 10-byte RTU ADU, and echoes its complete request PDU on success. | |||||
| - `0x43` reads one to 123 contiguous holding registers and rejects even quantities and ranges beyond `0x270F`. | |||||
| - Remove all communication-history state and API, including RAM records, `0x43` history parsing, HMI PSW `500..509`, and history display text. | |||||
| - Keep TouchWin clear local: it writes only `PSW350 = 0` and does not transmit or modify PLC data. | |||||
| - Follow the supplied Xinje C conventions: four-space indentation, braces on separate lines, explicit validation, named constants, and concise Doxygen-style API comments. | |||||
| - Preserve all unrelated uncommitted user changes. Do not alter the pre-existing `.git/index.lock`. | |||||
| --- | |||||
| ## File Structure | |||||
| - `Core/Modbus/modbus.h`: private function-code constants and the minimal `MODBUS_SLAVE` data model. | |||||
| - `Core/Modbus/modbus.c`: private `0x42` write builder, `0x43` odd-count read builder, request dispatch, and removal of history code. | |||||
| - `Core/Modbus/modbus_test.c`: host-only parser regression harness using caller-owned holding-register and coil storage. | |||||
| - `Core/Src/main.c`: persistence classification for `0x42` and removal of history collection from the application task. | |||||
| - `document/TouchWin自组Modbus功能码函数.md`: TouchWin C definitions and callable functions for read, write, clear, and odd-count read. | |||||
| - `document/TouchWin_Modbus_附加功能测试界面方案.svg`: revised training screen reference without communication history, using `PSW350`, `PSW351`, and `PSW370..PSW494`. | |||||
| ### Task 1: Add Failing Protocol Tests for the Remapped Extensions | |||||
| **Files:** | |||||
| - Create: `Core/Modbus/modbus_test.c` | |||||
| - Modify: none | |||||
| - Test: `Core/Modbus/modbus_test.c` | |||||
| **Interfaces:** | |||||
| - Consumes: `ModbusSlaveInit()`, `ModbusConfigureExtendedHoldingRegister()`, `ModbusProcessFrame()`, `ModbusCrc16()` from `modbus.h`. | |||||
| - Produces: `modbus_protocol_test.exe`, a host parser test binary that returns `0` only when every `assert` succeeds. | |||||
| - [ ] **Step 1: Restore a host test harness with explicit backing storage** | |||||
| Create `Core/Modbus/modbus_test.c` with project-compatible initialization. The test must not call the obsolete two-argument `ModbusSlaveInit()` from `HEAD`; it must supply real storage: | |||||
| ```c | |||||
| static uint16_t TestHoldingRegisters[MODBUS_DATA_POINT_COUNT]; | |||||
| static uint8_t TestCoilStorage[MODBUS_COIL_STORAGE_SIZE]; | |||||
| static uint16_t TestExtendedRegister; | |||||
| static void TestInitSlave(MODBUS_SLAVE *slave) | |||||
| { | |||||
| assert(ModbusSlaveInit(slave, TEST_SLAVE_ADDRESS, | |||||
| TestHoldingRegisters, TestCoilStorage) | |||||
| == MODBUS_STATUS_OK); | |||||
| assert(ModbusConfigureExtendedHoldingRegister(slave, | |||||
| &TestExtendedRegister) | |||||
| == MODBUS_STATUS_OK); | |||||
| } | |||||
| ``` | |||||
| - [ ] **Step 2: Write the failing `0x42` write test** | |||||
| Add `TestWriteExtendedRegister()` using a 10-byte request. It must send `01 42 00 01 00 00 BE EF CRC_L CRC_H`, expect `MODBUS_STATUS_OK`, a 10-byte response that echoes bytes `0..7`, a valid CRC, and `TestExtendedRegister == 0xBEEFU`. | |||||
| ```c | |||||
| static void TestWriteExtendedRegister(void) | |||||
| { | |||||
| uint8_t request[10U] = {1U, 0x42U, 0U, 1U, 0U, 0U, | |||||
| 0xBEU, 0xEFU, 0U, 0U}; | |||||
| uint8_t response[MODBUS_RTU_ADU_MAX_LENGTH]; | |||||
| uint16_t responseLength; | |||||
| MODBUS_SLAVE slave; | |||||
| TestInitSlave(&slave); | |||||
| TestAppendCrc(request, 8U); | |||||
| assert(ModbusProcessFrame(&slave, request, sizeof(request), response, | |||||
| sizeof(response), &responseLength) | |||||
| == MODBUS_STATUS_OK); | |||||
| assert(responseLength == sizeof(request)); | |||||
| assert(memcmp(response, request, 8U) == 0); | |||||
| assert(TestExtendedRegister == 0xBEEFU); | |||||
| TestAssertFrameCrc(response, responseLength); | |||||
| } | |||||
| ``` | |||||
| Add `<string.h>` for `memcmp`. Add a sibling test whose address bytes encode `0x00010001`; it must expect `MODBUS_STATUS_EXCEPTION`, function byte `0xC2`, and exception code `MODBUS_EXCEPTION_ILLEGAL_DATA_ADDR`. | |||||
| - [ ] **Step 3: Write the failing `0x43` odd-count read tests** | |||||
| Seed `TestHoldingRegisters[0]`, `[1]`, and `[2]` with `0x1234`, `0xABCD`, and `0x0001`. Send `01 43 00 00 00 03 CRC_L CRC_H`; assert status OK, response payload `01 43 06 12 34 AB CD 00 01`, and a valid CRC. Add a separate request with count `2` and assert exception function `0xC3` with code `MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE`. | |||||
| - [ ] **Step 4: Run tests to verify RED** | |||||
| Run: | |||||
| ```powershell | |||||
| gcc -std=c99 -Wall -Wextra -Werror -ICore/Modbus Core/Modbus/modbus.c Core/Modbus/modbus_test.c -o modbus_protocol_test.exe | |||||
| .\modbus_protocol_test.exe | |||||
| ``` | |||||
| Expected: the executable reaches an assertion because current `0x42` is an odd-count read and current `0x43` is communication history. The source must compile cleanly; a test compile error is not an acceptable RED result. | |||||
| - [ ] **Step 5: Commit the test-only change when the repository lock is released** | |||||
| ```powershell | |||||
| git add -- Core/Modbus/modbus_test.c | |||||
| git commit -m "test: cover remapped private modbus functions" | |||||
| ``` | |||||
| Do not run these commands while `.git/index.lock` exists. | |||||
| ### Task 2: Remap the Parser and Remove Communication History | |||||
| **Files:** | |||||
| - Modify: `Core/Modbus/modbus.h:45-132` | |||||
| - Modify: `Core/Modbus/modbus.c:56-85`, `Core/Modbus/modbus.c:530-755`, `Core/Modbus/modbus.c:785-890`, `Core/Modbus/modbus.c:990-1100` | |||||
| - Test: `Core/Modbus/modbus_test.c` | |||||
| **Interfaces:** | |||||
| - Consumes: the constants and `extendedHoldingRegister` supplied by `MODBUS_SLAVE`. | |||||
| - Produces: `MODBUS_FC_WRITE_EXTENDED_HOLDING_REGS (0x42U)`, `MODBUS_FC_READ_ODD_COUNT_REGS (0x43U)`, and successful protocol dispatch for both requests. | |||||
| - [ ] **Step 1: Change only the public protocol model** | |||||
| In `modbus.h`, replace the current private constants with: | |||||
| ```c | |||||
| #define MODBUS_FC_READ_EXTENDED_HOLDING_REGS (0x41U) | |||||
| #define MODBUS_FC_WRITE_EXTENDED_HOLDING_REGS (0x42U) | |||||
| #define MODBUS_FC_READ_ODD_COUNT_REGS (0x43U) | |||||
| #define MODBUS_EXTENDED_HOLDING_ADDRESS (0x00010000UL) | |||||
| ``` | |||||
| Delete `MODBUS_FC_READ_COMMUNICATION_HISTORY`, every `MODBUS_HISTORY_*` macro, `MODBUS_HISTORY_RECORD`, all history members of `MODBUS_SLAVE`, and the `ModbusRecordHistory()` declaration. Keep the extended-register pointer and its configuration API. | |||||
| - [ ] **Step 2: Implement the minimal `0x42` write response builder** | |||||
| Replace the old `ModbusBuildReadOddCountRegistersResponse()` at the `0x42` position with a builder accepting a 32-bit address and one 16-bit value. It must validate `ModbusIsExtendedRegisterAddressValid()`, write through `slave->extendedHoldingRegister`, and build the fixed response: | |||||
| ```c | |||||
| txBuffer[MODBUS_RTU_ADDRESS_INDEX] = slave->slaveAddress; | |||||
| txBuffer[MODBUS_RTU_FUNCTION_INDEX] = MODBUS_FC_WRITE_EXTENDED_HOLDING_REGS; | |||||
| ModbusWriteU32(&txBuffer[2U], startAddress); | |||||
| ModbusWriteU16(&txBuffer[6U], registerValue); | |||||
| *txLength = 8U; | |||||
| ``` | |||||
| Add `ModbusWriteU32()` beside `ModbusReadU32()` and then append CRC. Require `txCapacity >= 10U`. Return illegal address for any address except `0x00010000`. | |||||
| - [ ] **Step 3: Move odd-count read to `0x43`** | |||||
| Retain the existing odd-count validation and response data loop, but rename its comment to `0x43` and assign `MODBUS_FC_READ_ODD_COUNT_REGS` to the response function byte. Its PDU remains 16-bit start address plus 16-bit count; it must not retain any offset/count history interpretation. | |||||
| - [ ] **Step 4: Update frame dispatch and delete the complete history implementation** | |||||
| In `ModbusProcessFrame()`, use exact request lengths and payload parsing: | |||||
| ```c | |||||
| case MODBUS_FC_WRITE_EXTENDED_HOLDING_REGS: | |||||
| if (rxLength != 10U) { | |||||
| requestResult = MODBUS_REQUEST_ILLEGAL_VALUE; | |||||
| break; | |||||
| } | |||||
| requestResult = ModbusBuildWriteExtendedRegisterResponse( | |||||
| slave, ModbusReadU32(&rxBuffer[2U]), ModbusReadU16(&rxBuffer[6U]), | |||||
| txBuffer, txCapacity, txLength); | |||||
| break; | |||||
| case MODBUS_FC_READ_ODD_COUNT_REGS: | |||||
| if (rxLength != MODBUS_RTU_READ_REQUEST_LENGTH) { | |||||
| requestResult = MODBUS_REQUEST_ILLEGAL_VALUE; | |||||
| break; | |||||
| } | |||||
| requestResult = ModbusBuildReadOddCountRegistersResponse( | |||||
| slave, ModbusReadU16(&rxBuffer[2U]), ModbusReadU16(&rxBuffer[4U]), | |||||
| txBuffer, txCapacity, txLength); | |||||
| break; | |||||
| ``` | |||||
| Delete `ModbusBuildHistoryResponse()`, the history initialization loop in `ModbusSlaveInit()`, and the full `ModbusRecordHistory()` definition. Remove variables that become unused. | |||||
| - [ ] **Step 5: Run the host test to verify GREEN** | |||||
| Run the same two commands from Task 1. Expected: `modbus_protocol_test.exe` exits `0`, with no GCC warnings. Add a `0x41` regression assertion in the test harness before accepting the result, proving its 10-byte request still returns the configured 16-bit value. | |||||
| - [ ] **Step 6: Commit parser and tests when the repository lock is released** | |||||
| ```powershell | |||||
| git add -- Core/Modbus/modbus.h Core/Modbus/modbus.c Core/Modbus/modbus_test.c | |||||
| git commit -m "feat: remap private modbus extensions" | |||||
| ``` | |||||
| ### Task 3: Persist `0x42` Writes and Remove Application History Calls | |||||
| **Files:** | |||||
| - Modify: `Core/Src/main.c:107-109`, `Core/Src/main.c:488-536`, `Core/Src/main.c:739-840` | |||||
| - Test: `Core/Modbus/modbus_test.c` | |||||
| **Interfaces:** | |||||
| - Consumes: `MODBUS_FC_WRITE_EXTENDED_HOLDING_REGS` and existing `ModbusBackupSave()`. | |||||
| - Produces: an application-level successful `0x42` transaction that is classified as a write and therefore persisted by the existing RTC path. | |||||
| - [ ] **Step 1: Remove history-only declarations and helpers** | |||||
| Delete the `AppGetHistoryResultCode()` prototype and definition, the `historyResult` local variable, and each `ModbusRecordHistory()` call. Preserve existing request validation, link-state accounting, debug snapshots, response transmission, and protocol-error counting. | |||||
| - [ ] **Step 2: Add `0x42` to write classification** | |||||
| Update `AppIsModbusWriteFunction()` with the private write code: | |||||
| ```c | |||||
| case MODBUS_FC_WRITE_EXTENDED_HOLDING_REGS: | |||||
| return 1U; | |||||
| ``` | |||||
| It belongs in the same `switch` as `0x05`, `0x06`, `0x0F`, and `0x10`, so the existing success-only block invokes `ModbusBackupSave(&ModbusSlave)` for a valid `0x42` write. Do not call the backup service from a failed or exception response. | |||||
| - [ ] **Step 3: Build the embedded project** | |||||
| Open `EWARM/Modbus.ewp` in IAR Embedded Workbench and build the `Modbus` target. Expected: zero C errors from removed history symbols and the new `0x42` constant. The current environment does not provide `iccarm`, so record an unavailable IAR build rather than substituting a host build for firmware validation. | |||||
| - [ ] **Step 4: Run a controlled hardware persistence check** | |||||
| With machine outputs isolated and VBAT fitted, write a nonzero value through `0x42`, verify the `0x42` echo, power-cycle the test PLC, then use `0x41` to read back the same value. Clear only `PSW350` before the power cycle; do not send a PLC write as part of clearing. Record the write and read frames with the observed value. | |||||
| - [ ] **Step 5: Commit the application change when the repository lock is released** | |||||
| ```powershell | |||||
| git add -- Core/Src/main.c | |||||
| git commit -m "feat: persist extended register writes" | |||||
| ``` | |||||
| ### Task 4: Publish the TouchWin Functions and Remove History from the Screen Reference | |||||
| **Files:** | |||||
| - Modify: `document/TouchWin自组Modbus功能码函数.md` | |||||
| - Modify: `document/TouchWin_Modbus_附加功能测试界面方案.svg` | |||||
| - Test: `document/TouchWin自组Modbus功能码函数.md` | |||||
| **Interfaces:** | |||||
| - Consumes: the final `0x41/0x42/0x43` wire contracts and the shared TouchWin helpers `ModbusAppendCrc()`, `ModbusSendReceive()`, and `ModbusCheckResponse()`. | |||||
| - Produces: copyable TouchWin C functions plus a reference screen that has no communication-history controls. | |||||
| - [ ] **Step 1: Replace the private-function and PSW mapping tables** | |||||
| Document only these mappings: | |||||
| ```c | |||||
| #define MODBUS_FUNCTION_READ_EXTENDED_REGISTER (0x41) | |||||
| #define MODBUS_FUNCTION_WRITE_EXTENDED_REGISTER (0x42) | |||||
| #define MODBUS_FUNCTION_READ_ODD_COUNT_REGS (0x43) | |||||
| #define PSW_EXTENDED_RESULT (350) | |||||
| #define PSW_EXTENDED_WRITE_VALUE (351) | |||||
| #define PSW_ODD_START_ADDRESS (370) | |||||
| #define PSW_ODD_COUNT (371) | |||||
| #define PSW_ODD_RESULTS (372) | |||||
| ``` | |||||
| Delete all history constants, `PSW500..PSW509`, and the history section. State that `PSW372..PSW494` contains 123 WORD results, requiring 123 display components to show all values; a training screen may show the first eight or 16 only. | |||||
| - [ ] **Step 2: Add the `0x42` TouchWin write function** | |||||
| Add a complete function that reads `PSW351`, sends the fixed frame, verifies the echoed eight-byte PDU, and always updates `PSW319` and `PSW320`: | |||||
| ```c | |||||
| writeValue = PSW[PSW_EXTENDED_WRITE_VALUE]; | |||||
| request[0] = MODBUS_SLAVE_ADDRESS; | |||||
| request[1] = MODBUS_FUNCTION_WRITE_EXTENDED_REGISTER; | |||||
| request[2] = 0x00; | |||||
| request[3] = 0x01; | |||||
| request[4] = 0x00; | |||||
| request[5] = 0x00; | |||||
| request[6] = (BYTE)(writeValue >> 8); | |||||
| request[7] = (BYTE)writeValue; | |||||
| ModbusAppendCrc(request, 8); | |||||
| ``` | |||||
| The response capacity and required response length are both 10 bytes. When status is OK, compare `response[2]..response[7]` with `request[2]..request[7]`; on mismatch set `MODBUS_STATUS_RESPONSE_ERROR`. | |||||
| - [ ] **Step 3: Add the local clear function and move odd-count read to `0x43`** | |||||
| Document the clear function exactly as: | |||||
| ```c | |||||
| void ModbusClearExtendedResult(void) | |||||
| { | |||||
| PSW[PSW_EXTENDED_RESULT] = 0; | |||||
| } | |||||
| ``` | |||||
| It must not call `ModbusSendReceive()`. Change the odd-count example's function constant to `0x43` and retain its existing `PSW370`, `PSW371`, `PSW372..PSW494`, count, range, and byte-count validation. | |||||
| - [ ] **Step 4: Revise the SVG reference screen** | |||||
| Replace the history panel with the `0x42` write value (`PSW351`), Write button, `PSW350` read result, and Clear button. Change the right-hand read panel heading from `42` to `43`; keep its eight visible sample result boxes and state that the full backing range has 123 WORDs. Change the RTC trigger text to `05 / 06 / 0F / 10 / 42 成功后`; remove all history and PSW500-series text. | |||||
| - [ ] **Step 5: Verify documentation and commit when the repository lock is released** | |||||
| Run: | |||||
| ```powershell | |||||
| Select-String -Path 'document\TouchWin自组Modbus功能码函数.md','document\TouchWin_Modbus_附加功能测试界面方案.svg' -Pattern '通信历史|PSW500|PSW501|PSW502|MODBUS_FUNCTION_READ_HISTORY' | |||||
| ``` | |||||
| Expected: no matches. Then inspect the SVG at normal size to verify the `0x42` write and Clear controls do not overlap text. | |||||
| ```powershell | |||||
| git add -- document/TouchWin自组Modbus功能码函数.md document/TouchWin_Modbus_附加功能测试界面方案.svg | |||||
| git commit -m "docs: document retained modbus extensions" | |||||
| ``` | |||||
| ## Final Verification | |||||
| - [ ] Compile and run `modbus_protocol_test.exe` with GCC; it exits `0` without warnings. | |||||
| - [ ] Build `EWARM/Modbus.ewp` in IAR and resolve all project compile errors. | |||||
| - [ ] Confirm no production source references `history`, `History`, `MODBUS_HISTORY`, `ModbusRecordHistory`, or `MODBUS_FC_READ_COMMUNICATION_HISTORY`. | |||||
| - [ ] Confirm the documentation has exactly the `0x41` read, `0x42` write, and `0x43` odd-count-read private functions. | |||||
| - [ ] Perform the controlled write, display-clear, power-cycle, and readback test with VBAT fitted. | |||||
| @@ -0,0 +1,317 @@ | |||||
| # Modbus RTU Long-Connection Stability Implementation Plan | |||||
| > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. | |||||
| **Goal:** Keep the STM32 Modbus RTU slave responsive during 24-hour TouchWin communication without changing the existing Modbus contract. | |||||
| **Architecture:** Correct the uC/OS-II SysTick priority contract before any Modbus work runs. Make the Modbus task own all full receive restart operations: interrupt handlers record an error or request recovery, and the task retries the one-byte receive arm in bounded attempts. This avoids corrupting a complete frame and prevents an ignored HAL status from leaving USART1 unarmed. | |||||
| **Tech Stack:** STM32F407, STM32 HAL UART interrupt mode, TIM5 RTU timer, uC/OS-II, IAR Embedded Workbench, PowerShell regression checks, GCC host tests. | |||||
| ## Global Constraints | |||||
| - Preserve standard Modbus functions and private `0x41`, `0x42`, and `0x43` frames exactly. | |||||
| - Preserve current TouchWin PSW mapping and `0x42` RTC retention behavior. | |||||
| - Do not reintroduce D-register communication counters or history functions. | |||||
| - Keep all OS-aware interrupt priorities numerically greater than or equal to `CPU_CFG_KA_IPL_BOUNDARY`. | |||||
| - Do not overwrite unrelated dirty-worktree changes. | |||||
| --- | |||||
| ### Task 1: Add Runtime-Behavior Regression Test | |||||
| **Files:** | |||||
| - Create: `Core/Modbus/test_support/app_runtime/main.h` | |||||
| - Create: `Core/Modbus/app_runtime_test.c` | |||||
| - Modify: none | |||||
| - Test: `Core/Modbus/app_runtime_test.c` | |||||
| **Interfaces:** | |||||
| - Consumes: the real `Core/Src/main.c` and `Core/Src/stm32f4xx_it.c` through | |||||
| a fake HAL/uC-OS-II boundary. | |||||
| - Produces: exit code `0` only when SysTick uses an OS-aware priority and the | |||||
| receive recovery behavior is safe. | |||||
| - [ ] **Step 1: Write fake runtime support and a failing behavior test** | |||||
| Create `Core/Modbus/test_support/app_runtime/main.h` with the minimal HAL and | |||||
| uC/OS-II types, constants, GPIO/TIM register fakes, and function prototypes | |||||
| needed to compile the production sources. The fake APIs must record the | |||||
| SysTick priority, calls to `HAL_IncTick()` and `OS_CPU_SysTickHandler()`, | |||||
| scripted UART receive results, UART abort calls, and `OSSemPost()` calls. | |||||
| Create `Core/Modbus/app_runtime_test.c`. It must expose static functions only | |||||
| inside the test translation unit, then include the real production sources: | |||||
| ```c | |||||
| #define main AppFirmwareMain | |||||
| #define static | |||||
| #include "../Src/main.c" | |||||
| #undef static | |||||
| #undef main | |||||
| #include "../Src/stm32f4xx_it.c" | |||||
| ``` | |||||
| The test must assert the following observable behavior: | |||||
| ```c | |||||
| AppInitSystemTick(); | |||||
| assert(TestNvicIrq == SysTick_IRQn); | |||||
| assert(TestNvicPreemptPriority == CPU_CFG_KA_IPL_BOUNDARY); | |||||
| OSRunning = OS_TRUE; | |||||
| SysTick_Handler(); | |||||
| assert(TestHalTickCallCount == 1U); | |||||
| assert(TestOsCpuSysTickCallCount == 1U); | |||||
| TestReceiveResults[0U] = HAL_BUSY; | |||||
| TestReceiveResults[1U] = HAL_OK; | |||||
| assert(AppRecoverUartReception() == HAL_OK); | |||||
| assert(TestAbortReceiveCallCount == 1U); | |||||
| assert(TestReceiveCallCount == 2U); | |||||
| ``` | |||||
| A final test must call `HAL_UART_ErrorCallback()` with USART1 and an overrun | |||||
| error, then assert that it records a pending recovery and posts the frame | |||||
| semaphore without calling `HAL_UART_Receive_IT()` from the interrupt context. | |||||
| - [ ] **Step 2: Run the test and verify RED** | |||||
| Run: | |||||
| ```powershell | |||||
| gcc -std=c99 -Wall -Wextra -Werror -ICore/Modbus/test_support/app_runtime -ICore/Modbus Core/Modbus/app_runtime_test.c Core/Modbus/modbus.c -o app_runtime_test.exe | |||||
| .\app_runtime_test.exe | |||||
| ``` | |||||
| Expected: compilation fails because `AppInitSystemTick()`, | |||||
| `AppRecoverUartReception()`, and `ModbusReceptionNeedsRecovery` do not yet | |||||
| exist. This proves the test demands the missing runtime behavior. | |||||
| - [ ] **Step 3: Do not modify production sources in this task** | |||||
| The task is complete only when the real production translation units fail to | |||||
| compile against the intended new runtime behavior. | |||||
| ### Task 2: Fix the uC/OS-II SysTick Priority Contract | |||||
| **Files:** | |||||
| - Modify: `Core/Src/main.c:85-145` | |||||
| - Modify: `Core/Src/stm32f4xx_it.c:83-92` | |||||
| - Test: `Core/Modbus/app_runtime_test.c` | |||||
| **Interfaces:** | |||||
| - Consumes: `CPU_CFG_KA_IPL_BOUNDARY` from `app_cfg.h` and | |||||
| `OS_CPU_SysTickHandler()` from the uC/OS-II ARM port. | |||||
| - Produces: a SysTick handler at priority `4` that is allowed to invoke the | |||||
| uC/OS-II scheduler. | |||||
| - [ ] **Step 1: Add the application SysTick-priority initializer** | |||||
| Add this prototype with the other application initialization prototypes in | |||||
| `main.c`: | |||||
| ```c | |||||
| static void AppInitSystemTick(void); | |||||
| ``` | |||||
| Implement it immediately before `AppInitGpio()`: | |||||
| ```c | |||||
| static void AppInitSystemTick(void) | |||||
| { | |||||
| HAL_NVIC_SetPriority(SysTick_IRQn, CPU_CFG_KA_IPL_BOUNDARY, 0U); | |||||
| } | |||||
| ``` | |||||
| Call `AppInitSystemTick()` directly after `HAL_Init()` and before | |||||
| `SystemClock_Config()`. This overrides HAL's default priority `0` before the | |||||
| OS starts. | |||||
| - [ ] **Step 2: Replace the handwritten OS tick sequence** | |||||
| Replace the body of `SysTick_Handler()` with: | |||||
| ```c | |||||
| void SysTick_Handler(void) | |||||
| { | |||||
| HAL_IncTick(); | |||||
| if (OSRunning == OS_TRUE) { | |||||
| OS_CPU_SysTickHandler(); | |||||
| } | |||||
| } | |||||
| ``` | |||||
| Do not call `OSIntEnter()`, `OSTimeTick()`, or `OSIntExit()` directly from | |||||
| this handler after the replacement. | |||||
| - [ ] **Step 3: Run the runtime test and verify the SysTick behavior is green** | |||||
| Run: | |||||
| ```powershell | |||||
| gcc -std=c99 -Wall -Wextra -Werror -ICore/Modbus/test_support/app_runtime -ICore/Modbus Core/Modbus/app_runtime_test.c Core/Modbus/modbus.c -o app_runtime_test.exe | |||||
| .\app_runtime_test.exe | |||||
| ``` | |||||
| Expected: compilation may still fail only for the receive-rearm requirements; | |||||
| the SysTick-priority and SysTick-wrapper assertions must compile. | |||||
| ### Task 3: Make UART Receive Rearming Recoverable | |||||
| **Files:** | |||||
| - Modify: `Core/Src/main.c:43-110, 308-327, 732-786, 815-876` | |||||
| - Modify: `Core/Inc/main.h:37-54` | |||||
| - Test: `Core/Modbus/app_runtime_test.c` | |||||
| **Interfaces:** | |||||
| - Consumes: `HAL_UART_Receive_IT()`, `HAL_UART_AbortReceive_IT()`, | |||||
| `ModbusFrameSem`, and existing IAR debug fields. | |||||
| - Produces: `AppRecoverUartReception()` returning `HAL_OK` after one normal | |||||
| arm or one abort-and-rearm attempt; `ModbusReceptionNeedsRecovery` tells the | |||||
| Modbus task that an interrupt observed a rearm failure. | |||||
| - [ ] **Step 1: Add a pending-recovery state and recovery helper declaration** | |||||
| Beside the other volatile receive-state fields, add: | |||||
| ```c | |||||
| static volatile uint8_t ModbusReceptionNeedsRecovery; | |||||
| ``` | |||||
| Add this prototype with the receive helper declarations: | |||||
| ```c | |||||
| static HAL_StatusTypeDef AppRecoverUartReception(void); | |||||
| ``` | |||||
| - [ ] **Step 2: Implement a bounded task-context recovery helper** | |||||
| Keep `AppStartUartReception()` as the operation that clears frame state and | |||||
| arms byte zero. Add this helper immediately after it: | |||||
| ```c | |||||
| static HAL_StatusTypeDef AppRecoverUartReception(void) | |||||
| { | |||||
| HAL_StatusTypeDef halStatus; | |||||
| halStatus = AppStartUartReception(); | |||||
| if (halStatus != HAL_OK) { | |||||
| (void)HAL_UART_AbortReceive_IT(&Uart1Handle); | |||||
| halStatus = AppStartUartReception(); | |||||
| } | |||||
| AppModbusDebugRecoveryStatus = (int16_t)halStatus; | |||||
| return halStatus; | |||||
| } | |||||
| ``` | |||||
| This function is called only from `AppTaskModbus()`, never directly from a | |||||
| UART callback. | |||||
| - [ ] **Step 3: Request recovery from UART callbacks instead of silently failing** | |||||
| When either `HAL_UART_Receive_IT()` call in `HAL_UART_RxCpltCallback()` does | |||||
| not return `HAL_OK`, set `ModbusReceptionNeedsRecovery = 1U`, save the status | |||||
| to `AppModbusDebugRecoveryStatus`, and post `ModbusFrameSem` so the Modbus | |||||
| task can recover. Do not continue assembling the partial frame. | |||||
| In `HAL_UART_ErrorCallback()`, stop the RTU timer, save | |||||
| `uartHandle->ErrorCode`, set `ModbusReceptionNeedsRecovery = 1U`, and post | |||||
| `ModbusFrameSem`. Remove its direct calls to `HAL_UART_AbortReceive_IT()` and | |||||
| `AppStartUartReception()`; HAL has already ended the blocking receive transfer | |||||
| before invoking the error callback. | |||||
| - [ ] **Step 4: Make the Modbus task own retries** | |||||
| At startup, call `AppRecoverUartReception()` instead of | |||||
| `AppStartUartReception()`. In the task loop, after each frame processing and | |||||
| whenever `ModbusReceptionNeedsRecovery != 0U`, call the helper. On `HAL_OK`, | |||||
| clear `ModbusReceptionNeedsRecovery`. On failure, leave it set and delay one | |||||
| OS tick before the next semaphore-driven retry. Do not call `Error_Handler()` | |||||
| for a runtime receive-arm failure. | |||||
| - [ ] **Step 5: Run the runtime regression test and verify GREEN** | |||||
| Run: | |||||
| ```powershell | |||||
| gcc -std=c99 -Wall -Wextra -Werror -ICore/Modbus/test_support/app_runtime -ICore/Modbus Core/Modbus/app_runtime_test.c Core/Modbus/modbus.c -o app_runtime_test.exe | |||||
| .\app_runtime_test.exe | |||||
| ``` | |||||
| Expected: exit code `0`. | |||||
| ### Task 4: Build and Regression Verification | |||||
| **Files:** | |||||
| - Modify: none | |||||
| - Test: host protocol tests, RTC backup test, runtime behavior test, IAR build. | |||||
| **Interfaces:** | |||||
| - Consumes: final production sources and existing test harnesses. | |||||
| - Produces: evidence that the repair keeps protocol and RTC behavior intact. | |||||
| - [ ] **Step 1: Run existing host protocol tests** | |||||
| ```powershell | |||||
| gcc -std=c99 -Wall -Wextra -Werror -ICore/Modbus Core/Modbus/modbus.c Core/Modbus/modbus_test.c -o modbus_protocol_test.exe | |||||
| .\modbus_protocol_test.exe | |||||
| ``` | |||||
| Expected: exit code `0`. | |||||
| - [ ] **Step 2: Run RTC backup test** | |||||
| Use the existing test-support include paths and the same GCC command pattern | |||||
| already used by this project for `modbus_backup_test.c`. Expected: exit code | |||||
| `0` and preservation of only the extended register. | |||||
| - [ ] **Step 3: Build the IAR target** | |||||
| Open `EWARM/Modbus.ewp` in IAR Embedded Workbench and rebuild target | |||||
| `Modbus`. Expected: zero C compilation and link errors. | |||||
| - [ ] **Step 4: Inspect the final source diff** | |||||
| ```powershell | |||||
| git diff --check -- Core/Src/main.c Core/Src/stm32f4xx_it.c Core/Inc/main.h Core/Modbus/check_rtu_runtime_invariants.ps1 | |||||
| ``` | |||||
| Expected: no whitespace errors. | |||||
| ### Task 5: Hardware Long-Connection Acceptance | |||||
| **Files:** | |||||
| - Modify: none | |||||
| - Test: TouchWin and Modbus Poll against the rebuilt PLC. | |||||
| **Interfaces:** | |||||
| - Consumes: the final IAR binary, the existing TouchWin project, and Modbus | |||||
| Poll configured for the same RTU port. | |||||
| - Produces: a recorded stable long-duration TouchWin session. | |||||
| - [ ] **Step 1: Configure TouchWin cyclic requests** | |||||
| Keep the current serial settings and station `1`. Cycle at least one standard | |||||
| read (`0x01` or `0x03`), `0x41`, and `0x43`; periodically write and read back | |||||
| one test value with `0x42`. Do not use the screen Clear button as a PLC write. | |||||
| - [ ] **Step 2: Run the 24-hour TouchWin test** | |||||
| Start the cyclic TouchWin workload and leave the HMI and PLC powered for 24 | |||||
| hours. Record the start and finish times. The pass condition is zero HMI | |||||
| communication timeout/status errors and no restart of either device. | |||||
| - [ ] **Step 3: Perform Modbus Poll cross-check** | |||||
| After the TouchWin test, configure Modbus Poll for slave `1`, the same serial | |||||
| parameters, function `01`, scan rate `1000 ms`, and a safe coil read range. | |||||
| Run for one hour. Pass condition: `Err=0` throughout. | |||||
| - [ ] **Step 4: Capture failure evidence if a timeout occurs** | |||||
| Before restarting either device, halt IAR and record | |||||
| `AppModbusDebugSequence`, `AppModbusDebugUartErrorCode`, | |||||
| `AppModbusDebugRecoveryStatus`, `Uart1Handle.RxState`, and the program counter. | |||||
| This distinguishes application task lockup from a physical UART error. | |||||
| @@ -0,0 +1,161 @@ | |||||
| # Modbus Runtime Cleanup Implementation Plan | |||||
| > **For agentic workers:** Execute this plan inline. Do not create a worktree | |||||
| > because the current working directory contains the user's uncommitted project | |||||
| > changes and is the only complete current project state. | |||||
| **Goal:** Remove unused IAR build inputs and IAR-only Modbus snapshots while | |||||
| preserving the PLC protocol behavior and the two requested live-watch arrays. | |||||
| **Architecture:** The target remains an STM32F407/uC-OS-II Modbus RTU slave. | |||||
| Only the IAR project source list and application-level diagnostic path change; | |||||
| the protocol core, backup module, interrupt behavior, public data storage, and | |||||
| TouchWin contract remain intact. The final project design is generated after | |||||
| the source edits so every locator reflects the finished code. | |||||
| **Tech Stack:** IAR EWARM 8.3, STM32F4 HAL, uC/OS-II, C99 host tests, | |||||
| PowerShell static checks, Markdown. | |||||
| ## Global Constraints | |||||
| - Preserve `holdingRegisters`, `coilStorage`, and `extendedHoldingRegister`. | |||||
| - Preserve standard function codes 0x01/0x03/0x05/0x06/0x0F/0x10, private | |||||
| functions 0x41/0x42/0x43, and the 0x00010000 RTC retention path. | |||||
| - Do not edit vendor sources under `Drivers/` or RTOS sources under `Core/OS/`. | |||||
| - Retain `EWARM/Modbus/Exe/Modbus.hex`, `document/`, `uC-OS2_temp/`, and IAR | |||||
| settings files. | |||||
| - Do not stage or commit because the worktree contains unrelated user changes. | |||||
| --- | |||||
| ### Task 1: Remove unused HAL compilation entries | |||||
| **Files:** | |||||
| - Modify: `EWARM/Modbus.ewp:1140-1173` | |||||
| - Verify: `EWARM/Modbus/List/Modbus.map` | |||||
| **Interfaces:** | |||||
| - Consumes: the current link map, which records zero linked sections for the | |||||
| eight listed HAL translation units. | |||||
| - Produces: an IAR source list that compiles only the HAL modules used by the | |||||
| current target. | |||||
| - [ ] Delete the complete `<file>` XML blocks for these source entries, and no | |||||
| other HAL entries: | |||||
| `stm32f4xx_hal_dma_ex.c`, `stm32f4xx_hal_exti.c`, | |||||
| `stm32f4xx_hal_flash.c`, `stm32f4xx_hal_flash_ex.c`, | |||||
| `stm32f4xx_hal_flash_ramfunc.c`, `stm32f4xx_hal_pwr_ex.c`, | |||||
| `stm32f4xx_hal_tim.c`, and `stm32f4xx_hal_tim_ex.c`. | |||||
| - [ ] Confirm that the remaining target source list still includes | |||||
| `stm32f4xx_hal.c`, `_cortex.c`, `_dma.c`, `_gpio.c`, `_pwr.c`, `_rcc.c`, | |||||
| `_rcc_ex.c`, and `_uart.c`. | |||||
| - [ ] Build `EWARM/Modbus.ewp` configuration `Modbus` with | |||||
| `E:\IAR\common\bin\IarBuild.exe EWARM\Modbus.ewp -build Modbus -log warnings`. | |||||
| Expected result: `0 errors`, `0 warnings`. | |||||
| ### Task 2: Remove IAR-only Modbus snapshots while retaining live-watch data | |||||
| **Files:** | |||||
| - Modify: `Core/Src/main.c:30-41, 107-116, 630-710, 730-780, 818, 890-940` | |||||
| - Modify: `Core/Inc/main.h:37-51` | |||||
| - Modify: `Core/OS/Cfg/app_cfg.h:45-47` | |||||
| - Modify: `Core/Modbus/app_runtime_test.c:78-106` | |||||
| **Interfaces:** | |||||
| - Consumes: `holdingRegisters`, `coilStorage`, and `extendedHoldingRegister` | |||||
| as the only IAR Live Watch data interface. | |||||
| - Produces: the same UART recovery and RTU behavior without exported snapshots | |||||
| or their RAM buffers. | |||||
| - [ ] In `app_runtime_test.c`, remove assertions that require | |||||
| `AppModbusDebugRecoveryStatus` and `AppModbusDebugUartErrorCode`; retain the | |||||
| behavioral assertions for retry, abort, deferred recovery flagging, semaphore | |||||
| posting, and no receive re-arm inside the error callback. | |||||
| - [ ] Delete the three `APP_MODBUS_DEBUG_*` capacity macros from `app_cfg.h`. | |||||
| - [ ] Delete all `AppModbusDebug*` global definitions and all matching `extern` | |||||
| declarations. Do not change the three retained data storage declarations. | |||||
| - [ ] Delete `AppCopyDebugFrame`, `AppCaptureDebugDataModel`, and | |||||
| `AppCaptureDebugSnapshot`, their prototypes, and every call or assignment | |||||
| that exists solely to update their state. | |||||
| - [ ] Preserve the return paths, `ModbusReceptionNeedsRecovery`, and UART error | |||||
| callback behavior after snapshot calls are removed. | |||||
| - [ ] Compile and run the runtime host test with: | |||||
| `gcc -std=c99 -Wall -Wextra -Werror -ICore/Modbus/test_support/app_runtime -ICore/Modbus Core/Modbus/app_runtime_test.c Core/Modbus/modbus.c -o tmp/document_work/verification_bin/app_runtime_test.exe`. | |||||
| Expected result: exit code 0. | |||||
| ### Task 3: Improve comments in project-owned runtime and protocol modules | |||||
| **Files:** | |||||
| - Modify: `Core/Src/main.c` | |||||
| - Modify: `Core/Inc/main.h` | |||||
| - Modify: `Core/OS/Cfg/app_cfg.h` | |||||
| - Modify: `Core/Modbus/modbus.c` | |||||
| - Modify: `Core/Modbus/modbus.h` | |||||
| - Modify: `Core/Modbus/modbus_backup.c` | |||||
| - Modify: `Core/Modbus/modbus_backup.h` | |||||
| - Modify: `Core/Src/stm32f4xx_it.c` | |||||
| - Modify: `Core/Inc/stm32f4xx_it.h` | |||||
| **Interfaces:** | |||||
| - Consumes: existing APIs and symbol names. | |||||
| - Produces: Doxygen comments that explain ownership, protocol constraints, | |||||
| concurrency boundaries, parameter units, side effects, and error behavior. | |||||
| - [ ] Add or revise comments only where they explain non-obvious intent: | |||||
| RTU timer phases, ISR-to-task ownership transfer, UART recovery deferral, | |||||
| coil-to-GPIO polarity, private-function address rules, CRC byte order, and | |||||
| RTC metadata validation. | |||||
| - [ ] Ensure every public Modbus and backup API documents parameter ownership, | |||||
| return status, and state change; do not add comments that merely restate a | |||||
| variable assignment or modify third-party HAL/uC-OS-II code. | |||||
| - [ ] Keep indentation at four spaces, braces on separate lines, macro values | |||||
| parenthesized, and existing identifier conventions intact. | |||||
| - [ ] Re-run all host tests with `-Wall -Wextra -Werror` after comment changes. | |||||
| ### Task 4: Remove reproducible artifacts and prevent regeneration | |||||
| **Files:** | |||||
| - Modify: `.gitignore` | |||||
| - Delete: root `app_runtime_test.exe`, `modbus_backup_test.exe`, | |||||
| `modbus_protocol_test.exe`, and all root `*.stackdump` | |||||
| - Delete: `EWARM/Modbus/Obj/`, `EWARM/Modbus/List/Modbus.map`, | |||||
| `EWARM/Modbus/Exe/Modbus.out`, `EWARM/Modbus/Exe/Modbus.sim`, and | |||||
| `EWARM/Modbus.dep` | |||||
| **Interfaces:** | |||||
| - Consumes: `Modbus.hex` as the retained downloadable firmware artifact. | |||||
| - Produces: a source workspace without host-test binaries or IAR intermediate | |||||
| outputs. | |||||
| - [ ] Add project-specific ignore rules for IAR `Obj/`, generated map files, | |||||
| `.out`, `.sim`, `.dep`, and `*.stackdump`; keep `Modbus.hex` unignored. | |||||
| - [ ] Verify each deletion target is an exact generated artifact before removal. | |||||
| - [ ] Delete only the named artifact paths. Do not remove `EWARM/settings/`, | |||||
| `document/`, `docs/`, `tmp/`, or `uC-OS2_temp/`. | |||||
| ### Task 5: Generate the source-aligned project design and run regression gates | |||||
| **Files:** | |||||
| - Create: `docs/项目设计.md` | |||||
| - Verify: `Core/Modbus/modbus_test.c`, `Core/Modbus/modbus_backup_test.c`, | |||||
| `Core/Modbus/app_runtime_test.c`, `Core/Modbus/check_rtu_runtime_invariants.ps1`, | |||||
| `Core/Modbus/check_no_communication_counters.ps1`, `EWARM/Modbus.ewp` | |||||
| **Interfaces:** | |||||
| - Consumes: final source line numbers after Tasks 1-4. | |||||
| - Produces: a maintainable design document whose code references can be opened | |||||
| directly in the source tree. | |||||
| - [ ] Document the system boundary, initialization order, RTU receive state, | |||||
| T1.5/T3.5 timing, task/interrupt handoff, Modbus data model, standard and | |||||
| private function routing, coil GPIO mapping, RTC retention, error recovery, | |||||
| and test strategy. | |||||
| - [ ] Add a source index mapping each design responsibility to final | |||||
| `relative/path:line` locations. Include only application-owned modules and | |||||
| identify HAL/uC-OS-II as third-party dependencies rather than duplicating | |||||
| their implementation explanation. | |||||
| - [ ] Build and run protocol, RTC backup, and runtime host tests with | |||||
| `-std=c99 -Wall -Wextra -Werror`; run both PowerShell static checks; build the | |||||
| IAR `Modbus` target and verify `0 errors` and `0 warnings`. | |||||
| - [ ] Check `git diff --check` and `git status --short`; report only the files | |||||
| touched by this cleanup and any pre-existing unrelated modifications. | |||||
| @@ -0,0 +1,91 @@ | |||||
| # Modbus 掉电保持与私有功能码重映射设计 | |||||
| ## 目标 | |||||
| 保留并完善以下附加功能: | |||||
| - 固定寄存器 `0x00010000` 的读取、写入与掉电保持验证。 | |||||
| - 奇数数量的连续保持寄存器批量读取。 | |||||
| - TouchWin 读取结果的本地清除。 | |||||
| 通信历史功能及其界面展示不再保留。 | |||||
| ## 私有功能码 | |||||
| 下列功能码仅用于本训练项目的 TouchWin 与 STM32 Modbus RTU 从站, | |||||
| 不是 Modbus 国标功能码。 | |||||
| | 功能码 | 功能 | 请求 PDU(CRC 前) | 正常响应 PDU(CRC 前) | | |||||
| | --- | --- | --- | --- | | |||||
| | `0x41` | 读取 `0x00010000` | `41 00 01 00 00 00 01` | `41 02 <value_hi> <value_lo>` | | |||||
| | `0x42` | 写入 `0x00010000` | `42 00 01 00 00 <value_hi> <value_lo>` | 回显请求 PDU | | |||||
| | `0x43` | 读取奇数数量的连续保持寄存器 | `43 <start_hi> <start_lo> <count_hi> <count_lo>` | `43 <byte_count> <data...>` | | |||||
| 请求和响应帧均以前置从站地址、后置 Modbus CRC16(低字节在前)。 | |||||
| ### `0x41` 读取规则 | |||||
| - 32 位地址必须为 `0x00010000`,数量必须为 `1`。 | |||||
| - 正常响应返回一个 16 位寄存器值。 | |||||
| - 任何其他地址返回异常码 `0x02`;数量错误返回异常码 `0x03`。 | |||||
| ### `0x42` 写入规则 | |||||
| - 32 位地址必须为 `0x00010000`。 | |||||
| - 写入值为一个 16 位 WORD。 | |||||
| - 正常响应完整回显地址和值,便于 TouchWin 确认从站接受的实际数据。 | |||||
| - 每次 `0x42` 写入成功后,由主程序调用 RTC 备份保存;重启时恢复该值。 | |||||
| - `0x42` 不使用数量字段,帧长度固定为 10 字节(含 CRC)。 | |||||
| ### `0x43` 奇数数量读取规则 | |||||
| - 起始地址范围为 `0x0000..0x270F`。 | |||||
| - 数量范围为 `1..123`,且必须是奇数。 | |||||
| - 读取地址是连续地址,不是隔一个地址读取;例如起始 `0`、数量 `123` | |||||
| 返回地址 `0..122`。 | |||||
| - 数量为偶数或大于 `123` 返回异常码 `0x03`;读取越过 `0x270F` 返回异常码 `0x02`。 | |||||
| ## 数据保持与清除 | |||||
| MCU 的 RTC 备份寄存器保存保持寄存器 `0..31`、线圈 `0..15` 和 | |||||
| 固定寄存器 `0x00010000`。保存发生在标准写功能码和私有写功能码 | |||||
| `0x42` 成功后;上电启动时 CRC 校验通过才恢复。该功能依赖硬件 VBAT | |||||
| 已接入,不能声明为完整 10000 个寄存器掉电保持。 | |||||
| TouchWin 的“清除”只将本地显示用 `PSW350` 写为 `0`: | |||||
| - 不发送 Modbus 帧。 | |||||
| - 不改写 PLC 的 `0x00010000`。 | |||||
| - 不触发 RTC 备份保存。 | |||||
| 因此操作顺序为:写入一个值、清除 `PSW350`、PLC 断电再上电、读取; | |||||
| 读回相同值即验证掉电保持。 | |||||
| ## TouchWin PSW 分配 | |||||
| | 用途 | PSW | 说明 | | |||||
| | --- | --- | --- | | |||||
| | 状态码 / 实际接收长度 | `PSW319` / `PSW320` | 所有私有功能共用。 | | |||||
| | `0x41` 读取结果 | `PSW350` | 清除按钮仅清除此显示值。 | | |||||
| | `0x42` 写入值 | `PSW351` | 供写入按钮读取。 | | |||||
| | `0x43` 起始地址 / 数量 | `PSW370` / `PSW371` | 数量限制为 `1..123` 的奇数。 | | |||||
| | `0x43` 结果 | `PSW372..PSW494` | 共 `494 - 372 + 1 = 123` 个 WORD。 | | |||||
| 若画面要显示全部批量读取结果,需要 123 个数值显示组件,每个组件依次绑定 | |||||
| `PSW372` 至 `PSW494`。也可以只显示前 8 或前 16 个组件用于演示,响应仍会完整 | |||||
| 写入 123 个 PSW,未显示的 WORD 可在监控页查看。 | |||||
| ## 删除范围 | |||||
| - 删除 `0x43` 通信历史的协议常量、历史记录结构体、从站状态字段、响应构造函数 | |||||
| 和 `ModbusRecordHistory()` 公共接口。 | |||||
| - 删除主程序中历史结果码转换和所有记录调用。 | |||||
| - 删除 `PSW500..PSW509`、通信历史按钮及画面说明。 | |||||
| - 旧的 `0x42` 奇数数量读取改为 `0x43`;原 `0x43` 历史请求不再受支持。 | |||||
| ## 验证 | |||||
| - `0x41` 读取固定地址的值、非法地址和非法数量。 | |||||
| - `0x42` 写入后回显,非法扩展地址拒绝,写入成功触发备份保存。 | |||||
| - `0x43` 覆盖最小值 `1`、最大值 `123`、偶数数量、越界地址和错误 CRC。 | |||||
| - 清除仅写 `PSW350`,不产生 Modbus 请求;写入、清除、断电恢复、读取后数值一致。 | |||||
| @@ -0,0 +1,55 @@ | |||||
| # Modbus RTU Long-Connection Stability Design | |||||
| ## Goal | |||||
| Keep the existing Modbus RTU slave responsive during long-running TouchWin | |||||
| communication. The primary acceptance test is a 24-hour TouchWin cyclic | |||||
| read/write session with no timeout and no need to restart the HMI or slave. | |||||
| ## Confirmed Cause | |||||
| The uC/OS-II port defines `CPU_CFG_KA_IPL_BOUNDARY` as `4`. Therefore only | |||||
| interrupt priorities `4` through `15` are allowed to call uC/OS-II APIs. | |||||
| The current SysTick priority is `0`, but its handler calls `OSIntEnter()`, | |||||
| `OSTimeTick()`, and `OSIntExit()`. It can preempt an OS critical section and | |||||
| corrupt the scheduler or semaphore state. | |||||
| ## Scope | |||||
| - Keep the existing standard Modbus functions and private `0x41`, `0x42`, | |||||
| and `0x43` wire contracts unchanged. | |||||
| - Keep the current TouchWin PSW mapping and RTC retention behavior unchanged. | |||||
| - Assign SysTick an OS-aware priority of `4` and use the uC/OS-II Cortex-M | |||||
| SysTick wrapper so the handler obeys the port's BASEPRI contract. | |||||
| - Preserve TIM5 priority `5` and USART1 priority `6`; both are already | |||||
| OS-aware and invoke OS services. | |||||
| - Make UART receive rearming observable and recoverable. A failed | |||||
| `HAL_UART_Receive_IT()` result must not be discarded. The recovery path must | |||||
| first reset the UART RX state, then arm a one-byte receive operation, and | |||||
| record the result in the existing IAR debug snapshot. | |||||
| - Do not add communication counters back into D registers or TouchWin. | |||||
| ## Error Handling | |||||
| The receive path must attempt recovery only after a frame has been processed | |||||
| or after a UART error. It must not overwrite a completed frame. If rearming | |||||
| fails, the code must capture the HAL status and continue with a bounded retry | |||||
| path rather than silently leaving USART1 unarmed. | |||||
| ## Verification | |||||
| Static regression checks must prove that: | |||||
| - SysTick priority is not lower-numbered than the OS-aware boundary. | |||||
| - SysTick uses the port's OS tick handler. | |||||
| - `AppTaskModbus()` handles, rather than discards, receive-rearm failures. | |||||
| Firmware verification consists of an IAR build followed by two hardware | |||||
| tests with the final binary: | |||||
| 1. TouchWin cyclic standard/private Modbus requests for 24 hours, with zero | |||||
| status-code timeout and no HMI reconnect. | |||||
| 2. Modbus Poll one-second reads for one hour as an independent stress check. | |||||
| The existing host Modbus protocol and RTC-backup tests remain required | |||||
| regressions, but cannot replace the hardware communication tests. | |||||
| @@ -0,0 +1,37 @@ | |||||
| # Modbus Runtime Cleanup Design | |||||
| ## Goal | |||||
| Reduce the XDM-60T4-E Modbus RTU slave build surface and remove IAR-only | |||||
| communication snapshots without changing Modbus behavior, TouchWin mapping, | |||||
| RTC-backed register retention, or the two live-watch data arrays requested by | |||||
| the user. | |||||
| ## Confirmed Decisions | |||||
| - Retain `holdingRegisters`, `coilStorage`, and `extendedHoldingRegister` as | |||||
| global data objects for IAR Live Watch. | |||||
| - Remove request/response frame snapshots, register/coil mirror arrays, | |||||
| diagnostic status values, UART error snapshots, and snapshot sequence state. | |||||
| - Remove only unused HAL source entries from `EWARM/Modbus.ewp`; retain all | |||||
| vendor files under `Drivers/STM32F4xx_HAL_Driver`. | |||||
| - Preserve test sources, reference documents, the `uC-OS2_temp` reference | |||||
| directory, IAR settings, and the downloadable `EWARM/Modbus/Exe/Modbus.hex`. | |||||
| - Remove generated host test binaries, crash dumps, and IAR intermediate build | |||||
| artifacts. Add ignore rules so they do not return to version control. | |||||
| ## Safety Constraints | |||||
| - The ordinary Modbus data range, coils, 0x41/0x42/0x43 private functions, | |||||
| USART1 receive recovery, T1.5/T3.5 timing, and RTC backup behavior must not | |||||
| change. | |||||
| - `Modbus.hex` is retained as the deployable firmware image. `Modbus.out`, | |||||
| `Modbus.sim`, IAR object files, and map files are reproducible outputs. | |||||
| - Existing host tests and the IAR target build are the regression gate. No | |||||
| pre-existing user modifications are reverted, staged, or committed. | |||||
| ## Deliverables | |||||
| - Leaner IAR source list and runtime source code with focused Doxygen comments. | |||||
| - `docs/项目设计.md` describing the final architecture and exact `file:line` | |||||
| source locations after cleanup. | |||||
| @@ -0,0 +1,253 @@ | |||||
| # Modbus RTU 从站测试操作指南 | |||||
| ## 1. 目的和范围 | |||||
| 本指南将《测试报告.doc》中的测试项转换为现场可执行步骤,适用于 XDM-60T4-E(STM32F407)上的 Modbus RTU 从站工程。测试范围包括: | |||||
| - 标准功能码:`0x01`、`0x03`、`0x05`、`0x06`、`0x0F`、`0x10`。 | |||||
| - 私有功能码:`0x41`、`0x42`、`0x43`。 | |||||
| - GPIO 输出、通信状态、RTU 帧间隔、UART 错误恢复、RTC 掉电保持。 | |||||
| - TouchWin 的扩展寄存器显示清除功能。 | |||||
| 本工程的普通线圈和保持寄存器地址范围均为 `0x0000..0x270F`;从站地址为 `1`。私有功能码仅适用于本项目,不能作为标准 Modbus 功能码使用。 | |||||
| ## 2. 测试前准备 | |||||
| ### 2.1 测试设备 | |||||
| | 设备 | 用途 | | |||||
| | --- | --- | | |||||
| | XDM-60T4-E | 被测 Modbus RTU 从站 | | |||||
| | TG765-MT / TouchWin | HMI 及私有功能码联调 | | |||||
| | Modbus Poll | 标准功能码读写与回读 | | |||||
| | USB-COM/RS-485 转换器 | 主站与从站通信 | | |||||
| | ST-LINK、IAR Embedded Workbench | 下载、观察变量和调试 | | |||||
| | Sscom 或可构造原始帧的串口工具 | CRC、短帧、站号和异常帧测试 | | |||||
| | ModbusTimingTester_3Baud | 测量 T1.5/T3.5 和连续通信时序 | | |||||
| ### 2.2 接线与参数 | |||||
| 1. 下载当前项目固件,重新上电或复位 XDM-60T4-E。 | |||||
| 2. 按板卡接线图连接 USART1 的通信链路;确认 RS-485 A/B 极性、公共地和终端电阻。 | |||||
| 3. 将主站配置为:`115200 bit/s`、`8E1`(8 位数据、偶校验、1 位停止)、从站地址 `1`。 | |||||
| 4. Modbus Poll 使用零基地址。若软件显示 `40001`、`00001` 风格地址,先确认其地址换算设置,避免把显示地址当作协议报文地址。 | |||||
| 5. Q1-Q7 输出测试前断开危险负载,或改接测试灯。板级输出为低有效,逻辑线圈置 `1` 时对应 GPIO 输出低电平。 | |||||
| ### 2.3 通用记录规则 | |||||
| 每条测试记录均应保存:测试时间、固件版本、主站工具及版本、请求帧、响应帧、响应时间、实际现象和判定结果。原始 RTU 帧中的 CRC 按低字节在前发送。 | |||||
| 对于错误用例,先记录待验证的数据值,再发送错误帧,最后重新读取该数据值;只有数据未被改变才能判定数据安全测试通过。 | |||||
| ## 3. 已知初始数据和 HMI 映射 | |||||
| ### 3.1 上电后的初始数据 | |||||
| 首次启动且没有有效 RTC 备份时,确认以下数据: | |||||
| | 数据对象 | 预期值 | | |||||
| | --- | --- | | |||||
| | 线圈 `0..2` | `1` | | |||||
| | 保持寄存器 `10..15` | `0x0000`、`0x0001`、`0x0010`、`0x0011`、`0x0100`、`0x0110` | | |||||
| | 链路状态线圈 `100` | 无有效通信时为 `0` | | |||||
| | 扩展寄存器 `0x00010000` | 默认 `0x1000`,或有效 RTC 备份值 | | |||||
| 线圈 `1..7` 映射到 Q1-Q7。线圈 `100` 仅用于表示通信状态,不是普通输出线圈。 | |||||
| ### 3.2 TouchWin 私有功能码映射 | |||||
| | 功能 | TouchWin PSW | | |||||
| | --- | --- | | |||||
| | 状态码 / 实际接收长度 | `PSW319` / `PSW320` | | |||||
| | `0x41` 读取结果 | `PSW350` | | |||||
| | `0x42` 写入值 | `PSW351` | | |||||
| | `0x43` 起始基准地址 / 数量 | `PSW370` / `PSW371` | | |||||
| | `0x43` 结果区 | `PSW372..PSW494` | | |||||
| 状态码 `0` 表示成功;`1` 为输入错误;`2` 为发送失败;`3` 为接收失败;`4` 为 CRC 错误;`5` 为响应错误;大于等于 `0x8000` 表示异常响应,低字节为 Modbus 异常码。 | |||||
| ## 4. 功能测试 | |||||
| ### 4.1 初始化与默认数据 | |||||
| **目的:**确认程序完成从站初始化、数据区初始化、GPIO 同步和通信状态初始化。 | |||||
| 1. 复位被测设备,复位后不要立刻发送任何 Modbus 请求。 | |||||
| 2. 使用 IAR Live Watch,或用 `0x01`/`0x03` 读取第 3.1 节列出的线圈和寄存器。 | |||||
| 3. 读取线圈 `100`,确认通信状态未被提前置位。 | |||||
| 4. 通过 `0x41` 读取扩展寄存器 `0x00010000`。 | |||||
| **通过条件:**所有初始数据与第 3.1 节一致;从站能响应地址为 `1` 的合法请求;线圈 `100` 在首次有效通信前为 `0`。 | |||||
| ### 4.2 `0x01` 读取线圈 | |||||
| 1. 读取线圈 `0`、线圈 `1..7`、线圈 `100`,记录各位值。 | |||||
| 2. 分别测试数量 `1`、`8`、跨字节数量以及最大数量 `2000`。 | |||||
| 3. 读取起始地址 `0x0000`、`0x270F`,再测试数量为 `0`、数量大于 `2000`、末地址超过 `0x270F` 的请求。 | |||||
| **通过条件:** | |||||
| - 合法响应的功能码为 `0x01`,位图低位先对应较小地址,字节数为 `ceil(数量/8)`。 | |||||
| - 线圈 `1..7` 的读取结果与 Q1-Q7 的逻辑状态一致。 | |||||
| - 数量非法返回异常码 `0x03`;地址非法返回异常码 `0x02`;异常请求不得改变任何线圈。 | |||||
| ### 4.3 `0x03` 读取保持寄存器 | |||||
| 1. 读取寄存器 `10..15`,确认初始数据。 | |||||
| 2. 分别读取数量 `1`、连续多个及最大数量 `125`。 | |||||
| 3. 测试起始地址 `0x0000`、`0x270F`、数量为 `0`、数量大于 `125` 及越界地址。 | |||||
| **通过条件:**合法响应的寄存器数据为高字节在前;数量非法返回 `0x03`,地址非法返回 `0x02`,错误请求不修改寄存器。 | |||||
| ### 4.4 `0x05` 和 `0x06` 单点写入 | |||||
| 1. 对线圈 `1` 发送 `0x05`,依次写入 `0xFF00` 和 `0x0000`;每次写入后用 `0x01` 读回并观察 Q1。 | |||||
| 2. 对保持寄存器 `10` 发送 `0x06`,依次写入 `0x0000`、`0x1234`、`0xFFFF`;每次写入后用 `0x03` 读回。 | |||||
| 3. 向 `0x05` 发送非 `0xFF00`/`0x0000` 的值,向两个功能码发送越界地址。 | |||||
| **通过条件:** | |||||
| - `0x05`、`0x06` 成功响应回显完整请求内容。 | |||||
| - 写入值可被后续读取正确读回;线圈 `1` 的逻辑状态正确驱动 Q1。 | |||||
| - 非法线圈值返回 `0x03`,越界地址返回 `0x02`,原数据不变。 | |||||
| ### 4.5 `0x0F` 和 `0x10` 批量写入 | |||||
| 1. 使用 `0x0F` 写入 1 个线圈、8 个线圈、跨字节数量和最大数量 `1968`;读回确认每个线圈。 | |||||
| 2. 使用 `0x10` 写入 1 个、多个和最大数量 `123` 个保持寄存器;用 `0x03` 读回确认。 | |||||
| 3. 分别构造数量为 `0`、数量超限、字节数不匹配、实际帧长度不匹配和末地址越界的请求。 | |||||
| **通过条件:** | |||||
| - `0x0F` 响应回显起始地址和线圈数量;字节数必须为 `ceil(线圈数量/8)`。 | |||||
| - `0x10` 响应回显起始地址和寄存器数量;字节数必须为 `寄存器数量 x 2`。 | |||||
| - 所有非法请求返回 `0x02` 或 `0x03`,且不得出现部分写入。 | |||||
| ### 4.6 私有功能码 `0x41`、`0x42` 和 `0x43` | |||||
| #### 4.6.1 `0x41` 读取扩展寄存器 | |||||
| 1. 在 TouchWin 执行 `ReadExtendedRegister`,或发送如下请求数据并补充 CRC:`01 41 00 01 00 00 00 01`。 | |||||
| 2. 记录响应长度和返回的 16 位数据。 | |||||
| 3. 分别发送错误扩展地址、数量不为 `1` 和帧长度不为 10 字节的请求。 | |||||
| **通过条件:**仅地址 `0x00010000` 且数量为 `1` 时成功;正常响应总长度为 7 字节,数据字节数为 2;错误地址返回 `0x02`,错误数量或帧长度返回 `0x03`。 | |||||
| #### 4.6.2 `0x42` 写入扩展寄存器 | |||||
| 1. 在 `PSW351` 依次填写 `0x0000`、`0xBEEF` 和 `0xFFFF`,执行 `WriteExtendedRegister`;或发送 `01 42 00 01 00 00 <值高字节> <值低字节> CRC_L CRC_H`。 | |||||
| 2. 检查响应是否回显 32 位地址和写入值。 | |||||
| 3. 立即使用 `0x41` 读取,确认返回值与写入值一致。 | |||||
| 4. 使用错误扩展地址测试异常响应。 | |||||
| **通过条件:**正常响应总长度为 10 字节且与请求的地址、数值一致;错误地址返回 `0x02`;仅成功的 `0x42` 写入触发 RTC 备份。 | |||||
| #### 4.6.3 本地清除显示 | |||||
| 1. 使用 `0x42` 向扩展寄存器写入非零值,例如 `0xBEEF`。 | |||||
| 2. 用 `0x41` 读取,确认 `PSW350` 显示 `0xBEEF`。 | |||||
| 3. 执行 TouchWin 的 `ClearExtendedResult`,确认 `PSW350` 变为 `0`。 | |||||
| 4. 再次执行 `0x41` 读取。 | |||||
| **通过条件:**清除操作只修改 TouchWin 本地 `PSW350`,不发送 Modbus 帧、不修改 PLC 扩展寄存器、不触发 RTC 保存;第 4 步仍应读回 `0xBEEF`。 | |||||
| #### 4.6.4 `0x43` 奇数地址读取 | |||||
| 1. 先用 `0x06` 或 `0x10` 写入可识别数据:`D1=0x1234`、`D3=0xABCD`、`D5=0x0001`;必要时也为 `D11`、`D13`、`D15` 写入不同值。 | |||||
| 2. 设置 `PSW370=0`、`PSW371=3`,执行 `ReadOddCountRegisters`。 | |||||
| 3. 确认 `PSW372..PSW374` 分别显示 D1、D3、D5 的值。 | |||||
| 4. 设置 `PSW370=10`、`PSW371=3`,确认依次返回 D11、D13、D15。 | |||||
| 5. 设置数量为 `1` 和 `123`;再设置为 `0`、`2`、`122`、`124`,并测试最后一个奇数地址越界。 | |||||
| **通过条件:** | |||||
| - 从不小于起始地址的第一个奇数地址开始,每次地址加 2。 | |||||
| - 数量必须是 `1..123` 的奇数;数量非法返回 `0x03`。 | |||||
| - 最后一个待读奇数地址超过 `0x270F` 时返回 `0x02`。 | |||||
| - 数量 `123` 时响应长度为 251 字节,数据字节数为 246。 | |||||
| ### 4.7 GPIO 和通信状态 | |||||
| 1. 用 `0x05` 或 `0x0F` 逐一置位、复位线圈 `1..7`,观察 Q1-Q7 或测试灯。 | |||||
| 2. 发送一帧已寻址的正常请求,读取线圈 `100` 并观察通信指示灯。 | |||||
| 3. 停止发送已寻址的有效请求,直到超过配置的 `3000` 个系统节拍,再读取线圈 `100`。 | |||||
| 4. 连续发送 CRC 错误帧、非本机站号帧和广播读请求,确认通信状态不被错误刷新。 | |||||
| 5. 发送一帧产生 Modbus 异常响应的已寻址请求,确认其能够刷新通信状态。 | |||||
| **通过条件:**线圈 `1..7` 与 Q1-Q7 一一对应;逻辑 `1` 输出低电平;有效已寻址的正常响应、广播写和异常响应会使线圈 `100=1` 并点亮指示灯;通信超时后线圈 `100=0` 且指示灯熄灭。 | |||||
| ### 4.8 广播、异常帧和非本机站号 | |||||
| 1. 对 `0x05`、`0x06`、`0x0F`、`0x10` 使用站号 `0` 发送合法广播写请求,随后读取目标数据。 | |||||
| 2. 使用站号 `0` 发送读请求及私有功能码请求。 | |||||
| 3. 分别发送错误 CRC、短帧、超过 256 字节的长帧、站号 `2` 的请求、未支持功能码、非法地址和非法数量请求。 | |||||
| 4. 每个错误请求前后都读取相关线圈、保持寄存器和扩展寄存器。 | |||||
| **通过条件:**合法广播写修改数据但不产生响应;广播读和私有功能码广播被忽略;错误 CRC、短帧、长帧和非本机站号不得修改数据;非广播的非法功能码、地址和值分别返回异常码 `0x01`、`0x02`、`0x03`。 | |||||
| ### 4.9 错误码与异常响应 | |||||
| **目的:**验证从站对可识别的非法请求返回正确 Modbus 异常码,并验证 TouchWin 能正确显示异常结果。 | |||||
| 1. 使用 Sscom 或能发送原始 RTU 帧的工具,对站号 `1` 发送下表中的请求。每一帧均应使用正确 CRC。 | |||||
| 2. 记录响应的站号、异常功能码、异常码和 CRC;异常功能码必须等于原功能码与 `0x80` 按位或的结果。 | |||||
| 3. 使用 TouchWin 发送 `0x41`、`0x42`、`0x43` 的错误请求,确认 `PSW319` 大于等于 `0x8000`,且其低字节等于下表的异常码。 | |||||
| | 编号 | 请求场景 | 预期异常功能码 | 预期异常码 | 说明 | | |||||
| | --- | --- | --- | --- | --- | | |||||
| | EC-01 | 发送未支持的功能码,例如 `0x7F` | `0xFF` | `0x01` | 非法功能码 | | |||||
| | EC-02 | `0x01`/`0x03` 读取数量为 `0` 或超过上限 | 原功能码 `\| 0x80` | `0x03` | 非法数据值 | | |||||
| | EC-03 | 标准读写的起始地址或末地址超出 `0x270F` | 原功能码 `\| 0x80` | `0x02` | 非法数据地址 | | |||||
| | EC-04 | `0x05` 写入值不是 `0xFF00` 或 `0x0000` | `0x85` | `0x03` | 非法线圈写入值 | | |||||
| | EC-05 | `0x0F`/`0x10` 的数量、字节数或帧长度不匹配 | `0x8F`/`0x90` | `0x03` | 非法数量或数据长度 | | |||||
| | EC-06 | `0x41` 的地址不是 `0x00010000` | `0xC1` | `0x02` | 非法扩展地址 | | |||||
| | EC-07 | `0x41` 的数量不是 `1` | `0xC1` | `0x03` | 非法扩展寄存器数量 | | |||||
| | EC-08 | `0x42` 的地址不是 `0x00010000` | `0xC2` | `0x02` | 非法扩展地址 | | |||||
| | EC-09 | `0x43` 的数量为 `0`、偶数或大于 `123` | `0xC3` | `0x03` | 非法奇数地址读取数量 | | |||||
| | EC-10 | `0x43` 的最后一个待读地址超过 `0x270F` | `0xC3` | `0x02` | 奇数地址读取越界 | | |||||
| **无响应场景:**错误 CRC、非本机站号、广播读请求和私有功能码广播请求都不返回 Modbus 异常帧。此时应记录为“无响应”,并确认目标数据没有变化,不能误判为异常码返回失败。 | |||||
| **通过条件:**每个可识别的非法单播请求均返回 5 字节异常响应,响应站号为 `1`,异常功能码和异常码与表格一致,CRC 正确;每个无响应场景均不修改线圈、普通保持寄存器或扩展寄存器。 | |||||
| ## 5. 性能和稳定性测试 | |||||
| ### 5.1 RTU 帧间隔和连续通信 | |||||
| 1. 使用串口工具或逻辑分析仪构造三类报文:正常字节间隔、小于 T1.5 的连续字节间隔、超过 T1.5 的字节间隔。 | |||||
| 2. 在一帧结束后保持至少 T3.5 的静默时间,再发送下一帧。 | |||||
| 3. 循环发送 `0x01`、`0x03`、`0x05`、`0x10` 的混合请求。建议连续运行不少于 10 分钟,并记录总帧数和异常次数。 | |||||
| **通过条件:**正常帧完整处理;字节间隔超时的帧被丢弃且不执行写入;达到 T3.5 后仅处理一次完整帧;连续运行不出现粘帧、重复处理、接收停止或任务阻塞。 | |||||
| ### 5.2 UART 错误恢复 | |||||
| 1. 在接收一帧请求中途拔插通信线、制造串口溢出,或使用工具中断接收。 | |||||
| 2. 读取目标数据,确认异常帧未执行。 | |||||
| 3. 恢复通信线后发送一帧合法 `0x03` 或 `0x06` 请求。 | |||||
| 4. 重复上述过程至少 3 次。 | |||||
| **通过条件:**异常帧不参与协议解析且不修改数据;接收恢复后可稳定处理下一帧有效请求;恢复动作不依赖系统复位。 | |||||
| ## 6. 掉电保持测试 | |||||
| 1. 确认 PLC 的 VBAT 供电条件满足 RTC 备份要求。 | |||||
| 2. 使用 `0x42` 将扩展寄存器写为 `0xBEEF`。 | |||||
| 3. 使用标准写功能码修改普通保持寄存器,例如 D0/D31,以及线圈,例如 0/15。 | |||||
| 4. 对 PLC 断电后重新上电。 | |||||
| 5. 用 `0x41` 读取扩展寄存器,用 `0x01`/`0x03` 读取普通线圈和保持寄存器。 | |||||
| **通过条件:**地址 `0x00010000` 恢复为 `0xBEEF`;普通保持寄存器和线圈不应被 RTC 备份数据覆盖;若 VBAT 未接入或备份记录的魔数、版本、CRC 无效,则扩展寄存器应使用默认值而不是损坏值。 | |||||
| ## 7. 测试完成判定 | |||||
| 所有测试项均应记录为“通过、失败或阻塞”。满足以下条件时可判定本轮测试通过: | |||||
| - 所有标准和私有功能码的正常用例通过。 | |||||
| - 所有异常用例均返回预期结果,且未修改不应修改的数据。 | |||||
| - Q1-Q7、线圈 `100` 和通信指示灯行为正确。 | |||||
| - 所有可识别的错误请求均返回正确异常功能码和异常码;无响应场景未产生数据修改。 | |||||
| - 连续通信、RTU 帧间隔和 UART 错误恢复未出现卡死或错误写入。 | |||||
| - RTC 掉电保持只恢复扩展寄存器。 | |||||
| 失败项应记录:复现步骤、请求/响应原始帧、固件版本、影响范围、修复提交和回归测试结果。 | |||||
| @@ -0,0 +1,158 @@ | |||||
| # XDM-60T4-E Modbus RTU 从站项目设计 | |||||
| > 文档版本:2.1(2026-08-03)。本版本按当前 `Core/Src`、`Core/Modbus` 和 IAR 工程配置核对,描述的是现行固件实现。 | |||||
| ## 1. 设计目标与边界 | |||||
| 本项目运行在 XDM-60T4-E 的 STM32F407 平台上,作为 Modbus RTU 从站, | |||||
| 通过 USART1 接收 TouchWin 或 Modbus Poll 的请求。目标是提供标准线圈/保持 | |||||
| 寄存器读写、项目私有功能码、稳定的 RTU 帧边界识别,以及唯一扩展寄存器的 | |||||
| RTC 掉电保持。 | |||||
| 本项目的应用代码位于 `Core/Src`、`Core/Modbus` 和 `Core/OS/Cfg`。 | |||||
| `Core/Src/main.c` 负责启动、任务、UART 接收和 RTU 定时器;`Core/Modbus` | |||||
| 负责协议解析、响应构造和 RTC 备份;`Core/OS/Cfg/app_cfg.h` 负责通信时序和任务配置。 | |||||
| `Drivers/STM32F4xx_HAL_Driver` 与 `Core/OS` 是 HAL 和 uC/OS-II 依赖,不在本设计中重复解释。 | |||||
| | 角色 | 责任 | | |||||
| | --- | --- | | |||||
| | TouchWin / Modbus Poll | Modbus RTU 主站,发送请求并处理从站响应。 | | |||||
| | XDM-60T4-E | Modbus RTU 从站,完成帧校验、数据访问、异常响应、GPIO 同步和 RTC 保持。 | | |||||
| | RTC 备份域 | 只保存私有地址 `0x00010000` 对应的 16 位扩展寄存器;需要 VBAT 供电。 | | |||||
| ## 2. 总体运行思路 | |||||
| ```text | |||||
| 主站 RTU 字节流 | |||||
| -> USART1 接收完成回调 | |||||
| -> TIM5 T1.5 / T3.5 单次定时 | |||||
| -> Modbus 帧就绪信号量 | |||||
| -> Modbus 任务校验并分发功能码 | |||||
| -> 数据区 / GPIO / RTC 备份 | |||||
| -> USART1 异步发送响应 | |||||
| ``` | |||||
| 系统入口是 `Core/Src/main.c` 的 `main`。它依次完成 HAL、SysTick、系统时钟、 | |||||
| GPIO、USART1 和 TIM5 初始化,然后启动 uC/OS-II。启动任务创建信号量,并创建 | |||||
| Modbus 工作任务和 RTU/链路状态任务;具体函数为 `AppTaskStart`、`AppTaskModbus` | |||||
| 和 `AppTaskT35`。 | |||||
| ## 3. 通信与 RTU 时序设计 | |||||
| ### 3.1 串口参数 | |||||
| USART1 使用 `115200 bit/s`、1 个停止位和偶校验,代码配置为 | |||||
| `UART_WORDLENGTH_9B + UART_PARITY_EVEN`。在 STM32 HAL 中,这表示 8 位数据加 1 位 | |||||
| 校验,主站应配置为 `8E1`;从站地址为 `1`。一个字符按 10 bit 计算,因此 | |||||
| `APP_MODBUS_BITS_PER_CHARACTER` 为 10,字符时间由 `app_cfg.h` 中的向上取整公式计算。 | |||||
| 波特率高于 19200 bit/s 时,RTU 使用固定时序:`T1.5 = 750 us`、 | |||||
| `T3.5 = 1750 us`。代码还将接收超时设置为 `T1.5 + 一个字符时间`,并用第二阶段 | |||||
| 补足到 T3.5。TIM5 按 1 MHz 计数,因此超时值可以直接以微秒装载。 | |||||
| ### 3.2 接收、封帧与恢复 | |||||
| - `HAL_UART_RxCpltCallback` 每次只接收一个字节, | |||||
| 保存到接收缓冲区后重启静默计时。 | |||||
| - `AppHandleModbusRtuTimer` 先处理 T1.5 字符间隔,再确认 T3.5 帧间静默; | |||||
| 完整帧通过 `ModbusFrameSem` 交给任务。 | |||||
| - `AppStartUartReception` 清空软件帧状态并挂接一个字节的中断接收; | |||||
| `AppRecoverUartReception` 只在 Modbus 任务上下文中执行 Abort、清错和重挂接。 | |||||
| - `TIM5_IRQHandler` 只推进 RTU 状态机,`USART1_IRQHandler` 只调用 HAL;中断中不解析帧, | |||||
| 也不执行阻塞发送。 | |||||
| - `HAL_UART_ErrorCallback` 只停止定时器、标记当前帧无效并设置恢复请求,之后由任务执行恢复。 | |||||
| - 完整帧正常处理后调用 `AppStartUartReception`;只有 UART 错误或 HAL 状态丢失时才走完整恢复流程, | |||||
| 避免清读 SR/DR 时丢掉重连后的首字节。 | |||||
| ## 4. 数据模型与 IAR 调试 | |||||
| | 数据对象 | 位置 | 用途与保持属性 | | |||||
| | --- | --- | --- | | |||||
| | `holdingRegisters` | `Core/Src/main.c` | 地址 `0x0000..0x270F` 的普通保持寄存器,可在 IAR Live Watch 查看;不保持。 | | |||||
| | `coilStorage` | `Core/Src/main.c` | 按位压缩的普通线圈区,可在 IAR Live Watch 查看;不保持。 | | |||||
| | `extendedHoldingRegister` | `Core/Src/main.c` | 私有地址 `0x00010000` 的唯一扩展值;仅它可被 RTC 保存。 | | |||||
| 协议层不再维护请求帧、响应帧或数据镜像副本,调试时应直接查看上表三个真实数据对象, | |||||
| 避免镜像数据与协议数据不同步。为便于断线、插拔和 UART 恢复测试,`main.c` 仍保留 | |||||
| `ModbusUartLastErrorCode`、`ModbusUartLastRxState`、`ModbusUartRecoveryCount`、 | |||||
| `ModbusUartRecoveryFailureCount`、`ModbusUartRxArmCount`、 | |||||
| `ModbusUartRxArmFailureCount`、`ModbusUartLastRxByte` 和 | |||||
| `ModbusLastCompleteFrameLength` 等运行时诊断变量;这些变量只记录收发恢复过程, | |||||
| 不参与 Modbus 地址映射,也不会作为额外寄存器对外提供。 | |||||
| `AppSyncCoilsToGpio` 将线圈 1 至 7 写入 Q1 至 Q7。 | |||||
| 板级输出为低有效,因此逻辑线圈置 1 时写入 `GPIO_PIN_RESET`。线圈 0 是链路 | |||||
| 数据区中的普通线圈,不参与 Q0/Q1 至 Q7 的输出映射;链路状态线圈 `100` 和物理 | |||||
| Q0 指示灯由 `AppUpdateLinkStatus` 根据最近一次有效、已寻址的通信刷新。CRC 错误帧、 | |||||
| 非本站号帧和广播读不会刷新在线状态。 | |||||
| ## 5. Modbus 协议核心 | |||||
| 协议模块入口是 `Core/Modbus/modbus.c` 的 `ModbusProcessFrame`。固定的处理 | |||||
| 顺序为:参数校验、帧长度、CRC、站号、功能码、字段和地址范围校验。CRC 错误或 | |||||
| 非本机站号的帧不会修改数据区。 | |||||
| `ModbusCrc16` 计算 RTU CRC16,发送时 CRC 低字节在前。`ModbusSlaveInit` 初始化 | |||||
| 普通数据模型,`ModbusConfigureExtendedHoldingRegister` 绑定扩展保持寄存器, | |||||
| `ModbusGet/SetCoil` 和 `ModbusGet/SetHoldingRegister` 提供带地址检查的访问接口。 | |||||
| ### 5.1 标准功能码 | |||||
| | 功能码 | 行为 | 分发位置 | | |||||
| | --- | --- | --- | | |||||
| | `0x01` | 读取线圈,按 Modbus 位顺序打包响应。 | `ModbusBuildReadCoilsResponse` | | |||||
| | `0x03` | 读取普通保持寄存器,高字节在前。 | `ModbusBuildReadRegistersResponse` | | |||||
| | `0x05` | 写单个线圈,只接受 `0xFF00` 或 `0x0000`,成功时回显请求。 | `ModbusProcessWriteSingleCoil` | | |||||
| | `0x06` | 写单个保持寄存器并回显。 | `ModbusProcessWriteSingleRegister` | | |||||
| | `0x0F` | 批量写线圈,校验数量、字节数、帧长度和地址范围。 | `ModbusProcessWriteMultipleCoils` | | |||||
| | `0x10` | 批量写保持寄存器,校验数量、字节数、帧长度和地址范围。 | `ModbusProcessWriteMultipleRegisters` | | |||||
| ### 5.2 项目私有功能码 | |||||
| | 功能码 | 行为 | 实现位置 | | |||||
| | --- | --- | --- | | |||||
| | `0x41` | 读取固定 32 位地址 `0x00010000`,数量必须为 1。 | `ModbusBuildReadExtendedRegistersResponse` | | |||||
| | `0x42` | 写入 `0x00010000` 并回显地址和值。协议核心只改 RAM;应用层确认成功后保存 RTC。 | `ModbusBuildWriteExtendedRegisterResponse` + `AppProcessFrame` | | |||||
| | `0x43` | 从不小于起始基准地址的第一个奇数地址开始,每隔 2 读取一个值;数量必须为 `1..123` 的奇数。 | `ModbusBuildReadOddAddressRegistersResponse` | | |||||
| 私有功能码只供当前 TouchWin 与本 PLC 工程配套使用,不应声明为标准 Modbus | |||||
| 功能码。触摸屏侧的 `PSW372..PSW494` 最多显示 123 个 `0x43` 返回值;一次请求只读取 | |||||
| 本帧指定的数量,不会自动读取整个 `0x0000..0x270F` 区间。 | |||||
| ## 6. 掉电保持设计 | |||||
| `ModbusBackupSave` 在应用层确认 `0x42` | |||||
| 成功后保存扩展值。写入顺序为:先清除魔数、写扩展值、写版本与 CRC、最后写魔数。 | |||||
| 这使掉电中断的半写入记录不会被误认为有效数据。 | |||||
| `ModbusBackupRestore` 只在 RTC 可访问、 | |||||
| 魔数匹配、版本匹配和 CRC 正确时恢复。RTC 访问和 LSI 就绪超时控制在 | |||||
| `ModbusBackupEnableRtcAccess`,CRC 计算由模块内部函数完成。普通寄存器、 | |||||
| 线圈和触摸屏本地显示不会通过该模块恢复。 | |||||
| ## 7. 工程构建与文件边界 | |||||
| IAR 工程文件为 `EWARM/Modbus.ewp`,实际源文件清单以 IAR 工程配置为准。 | |||||
| 业务代码应至少包含 `Core/Src/main.c`、`Core/Src/stm32f4xx_it.c`、 | |||||
| `Core/Modbus/modbus.c` 和 `Core/Modbus/modbus_backup.c`,并链接 STM32 HAL、 | |||||
| uC/OS-II 和对应的启动文件。`modbus_backup.c` 使用 RTC 备份寄存器,因此目标硬件 | |||||
| 必须具备有效的 RTC/备份域供电条件。 | |||||
| 根目录 `test/` 只存放主机测试源码、静态检查脚本和测试支持头文件,不在 | |||||
| `EWARM/Modbus.ewp` 中引用。交付可运行固件时只需保留 IAR 工程、`Core` 正式源码、 | |||||
| `Drivers`、启动文件及必要的工程配置;`test/` 可以作为开发归档单独保留或从交付包移除。 | |||||
| ## 8. 验证与维护 | |||||
| | 验证项 | 源码或命令位置 | 目的 | | |||||
| | --- | --- | --- | | |||||
| | 协议主机测试 | `test/modbus_test.c` | 覆盖 CRC、标准/私有功能码、边界和异常响应;不参与目标固件。 | | |||||
| | RTC 主机测试 | `test/modbus_backup_test.c` | 证明只恢复扩展寄存器,并校验版本和 CRC 失效场景。 | | |||||
| | 运行时主机测试 | `test/app_runtime_test.c` | 验证 SysTick 优先级、UART 恢复重试、错误延后恢复和并发恢复请求。 | | |||||
| | 运行时静态检查 | `test/check_rtu_runtime_invariants.ps1` | 检查 SysTick 的 OS 感知调用和 UART 恢复边界。 | | |||||
| | 通信计数静态检查 | `test/check_no_communication_counters.ps1` | 确认接收、发送和错误计数逻辑未重新引入。 | | |||||
| | 目标构建 | 使用 IAR 对 `EWARM/Modbus.ewp` 执行 `Modbus` 配置构建 | 验证目标工程实际可编译,要求 0 errors、0 warnings。 | | |||||
| 构建产生的 `Obj/`、`.map`、`.out`、`.sim`、`.dep`、主机测试 `.exe` 和 | |||||
| `*.stackdump` 都是可重建产物,已通过 `.gitignore` 排除。可下载的 | |||||
| `EWARM/Modbus/Exe/Modbus.hex` 保留在工程中。 | |||||
| @@ -0,0 +1,230 @@ | |||||
| # TouchWin 自组 Modbus RTU 附加功能 | |||||
| ## 1. 功能定义 | |||||
| 标准功能码仍为 `0x01`、`0x03`、`0x05`、`0x06`、`0x0F`、`0x10`。线圈和保持寄存器地址范围均为 `0x0000..0x270F`。 | |||||
| 以下三个功能码是本训练项目的私有扩展,不属于 Modbus 国标,只能由本项目 TouchWin 与 PLC 配套使用: | |||||
| | 功能码 | 用途 | 请求数据 | 限制 | | |||||
| | --- | --- | --- | --- | | |||||
| | `0x41` | 读取 `0x00010000` | 32 位地址、16 位数量 | 地址固定为 `0x10000`,数量固定为 `1` | | |||||
| | `0x42` | 写入 `0x00010000` | 32 位地址、16 位写入值 | 地址固定为 `0x10000`,正常响应回显地址和值 | | |||||
| | `0x43` | 读取奇数地址的寄存器 | 16 位起始基准地址、16 位数量 | 数量为 `1..123` 中的奇数 | | |||||
| `0x43` 按地址 `+2` 跳读,只返回奇数地址的值。起始基准地址会自动向上取奇数:基准地址为 `0`、数量为 `123` 时,读取地址 `1, 3, 5, ..., 245`;基准地址为 `10` 时,读取 `11, 13, 15, ...`。数量表示返回值个数,仍必须是奇数。 | |||||
| ## 2. TouchWin 地址 | |||||
| `PSW319` 为状态码,`PSW320` 为实际接收长度。附加画面使用: | |||||
| | 用途 | PSW | | |||||
| | --- | --- | | |||||
| | `0x41` 读取结果 | `PSW350` | | |||||
| | `0x42` 写入值 | `PSW351` | | |||||
| | `0x43` 起始基准地址、数量 | `PSW370`、`PSW371` | | |||||
| | `0x43` 结果 | `PSW372..PSW494`,共 123 个 WORD | | |||||
| `PSW371` 只能输入奇数,最大 `123`。`PSW370 = 0` 时,`PSW372` 对应 PLC 的 `D1`,`PSW373` 对应 `D3`,依次类推;`PSW370 = 10` 时,结果依次对应 `D11`、`D13`、`D15`。若要在一个画面显示全部批量读取结果,需要 123 个数值显示组件,依次绑定 `PSW372` 至 `PSW494`。用于演示时,可以只放前 8 或前 16 个显示组件,全部 123 个结果仍会写入 PSW。 | |||||
| 状态码:`0` 成功;`1` 输入错误;`2` 发送失败;`3` 接收失败;`4` CRC 错误;`5` 应答错误;`0x8000` 以上表示异常应答,低字节为异常码。 | |||||
| ## 3. 公共定义 | |||||
| ```c | |||||
| #define MODBUS_SLAVE_ADDRESS (1) | |||||
| #define MODBUS_FUNCTION_READ_EXTENDED_REGISTER (0x41) | |||||
| #define MODBUS_FUNCTION_WRITE_EXTENDED_REGISTER (0x42) | |||||
| #define MODBUS_FUNCTION_READ_ODD_COUNT_REGS (0x43) | |||||
| #define MODBUS_ODD_COUNT_MAX (123) | |||||
| #define MODBUS_MIN_RESPONSE_LENGTH (5) | |||||
| #define MODBUS_ADDRESS_MAX (0x270F) | |||||
| #define PSW_STATUS (319) | |||||
| #define PSW_RECEIVE_LENGTH (320) | |||||
| #define PSW_EXTENDED_RESULT (350) | |||||
| #define PSW_EXTENDED_WRITE_VALUE (351) | |||||
| #define PSW_ODD_START_ADDRESS (370) | |||||
| #define PSW_ODD_COUNT (371) | |||||
| #define PSW_ODD_RESULTS (372) | |||||
| ``` | |||||
| 以下示例使用已有的 `ModbusAppendCrc`、`ModbusSendReceive` 和 `ModbusCheckResponse` 公共函数。 | |||||
| > **TouchWin 粘贴规则**:以上公共定义和公共函数写入“公共函数”页。以下四段代码写入“函数功能块”中新建的功能函数编辑区,**只粘贴代码块内部内容**,不要写 `void 函数名(void)`,也不要写最外层的 `{`、`}`。功能函数名称必须是合法 C 标识符,不能使用 `0x41`、`0x42`、`0x43` 作为函数名。 | |||||
| > | |||||
| > 请依次新建并命名为:`ReadExtendedRegister`、`WriteExtendedRegister`、`ClearExtendedResult`、`ReadOddCountRegisters`。其中 `0x41`、`0x42`、`0x43` 只用于 `request[1]` 中的 Modbus 功能码。 | |||||
| ## 4. `ReadExtendedRegister`:`0x41` 读取 `0x10000` | |||||
| 地址和数量固定,不在画面输入: | |||||
| ```c | |||||
| WORD receiveLength; | |||||
| WORD status; | |||||
| BYTE request[10]; | |||||
| BYTE response[7]; | |||||
| request[0] = MODBUS_SLAVE_ADDRESS; | |||||
| request[1] = MODBUS_FUNCTION_READ_EXTENDED_REGISTER; | |||||
| request[2] = 0x00; | |||||
| request[3] = 0x01; | |||||
| request[4] = 0x00; | |||||
| request[5] = 0x00; | |||||
| request[6] = 0x00; | |||||
| request[7] = 0x01; | |||||
| ModbusAppendCrc(request, 8); | |||||
| receiveLength = 0; | |||||
| status = ModbusSendReceive(request, 10, response, 7, &receiveLength); | |||||
| if (status == MODBUS_STATUS_OK) | |||||
| { | |||||
| status = ModbusCheckResponse(response, receiveLength, | |||||
| MODBUS_FUNCTION_READ_EXTENDED_REGISTER); | |||||
| } | |||||
| if ((status == MODBUS_STATUS_OK) && | |||||
| ((receiveLength != 7) || (response[2] != 2))) | |||||
| { | |||||
| status = MODBUS_STATUS_RESPONSE_ERROR; | |||||
| } | |||||
| if (status == MODBUS_STATUS_OK) | |||||
| { | |||||
| PSW[PSW_EXTENDED_RESULT] = ((WORD)response[3] << 8) | response[4]; | |||||
| } | |||||
| PSW[PSW_STATUS] = status; | |||||
| PSW[PSW_RECEIVE_LENGTH] = receiveLength; | |||||
| ``` | |||||
| ## 5. `WriteExtendedRegister`:`0x42` 写入 `0x10000` | |||||
| 写入值从 `PSW351` 取得。请求和正常响应均为 10 字节,正常响应回显 32 位地址与 16 位写入值: | |||||
| ```c | |||||
| WORD receiveLength; | |||||
| WORD status; | |||||
| WORD writeValue; | |||||
| BYTE request[10]; | |||||
| BYTE response[10]; | |||||
| writeValue = PSW[PSW_EXTENDED_WRITE_VALUE]; | |||||
| request[0] = MODBUS_SLAVE_ADDRESS; | |||||
| request[1] = MODBUS_FUNCTION_WRITE_EXTENDED_REGISTER; | |||||
| request[2] = 0x00; | |||||
| request[3] = 0x01; | |||||
| request[4] = 0x00; | |||||
| request[5] = 0x00; | |||||
| request[6] = (BYTE)(writeValue >> 8); | |||||
| request[7] = (BYTE)writeValue; | |||||
| ModbusAppendCrc(request, 8); | |||||
| receiveLength = 0; | |||||
| status = ModbusSendReceive(request, 10, response, 10, &receiveLength); | |||||
| if (status == MODBUS_STATUS_OK) | |||||
| { | |||||
| status = ModbusCheckResponse(response, receiveLength, | |||||
| MODBUS_FUNCTION_WRITE_EXTENDED_REGISTER); | |||||
| } | |||||
| if ((status == MODBUS_STATUS_OK) && | |||||
| ((receiveLength != 10) || (response[2] != request[2]) || | |||||
| (response[3] != request[3]) || (response[4] != request[4]) || | |||||
| (response[5] != request[5]) || (response[6] != request[6]) || | |||||
| (response[7] != request[7]))) | |||||
| { | |||||
| status = MODBUS_STATUS_RESPONSE_ERROR; | |||||
| } | |||||
| PSW[PSW_STATUS] = status; | |||||
| PSW[PSW_RECEIVE_LENGTH] = receiveLength; | |||||
| ``` | |||||
| PLC 收到并成功处理 `0x42` 后,会将 `0x10000` 的最新值保存到 RTC 备份区。 | |||||
| ## 6. `ClearExtendedResult`:清除读取显示 | |||||
| 清除按钮绑定以下函数。该函数只将触摸屏上的读取结果清零,不发送通信帧,不修改 PLC 的 `0x10000`,也不触发掉电保存: | |||||
| ```c | |||||
| PSW[PSW_EXTENDED_RESULT] = 0; | |||||
| ``` | |||||
| 用于验证掉电保持时,操作顺序为:使用 `0x42` 写入非零值,调用清除函数,PLC 断电再上电,最后使用 `0x41` 读取;若读回原写入值,则掉电保持生效。 | |||||
| ## 7. `ReadOddCountRegisters`:`0x43` 读取奇数地址寄存器 | |||||
| 起始基准地址可以是任意合法地址,PLC 会从第一个不小于该值的奇数地址开始,每次地址加 `2`。数量必须为奇数且不超过 `123`。响应容量为 251 字节,满足 123 个 WORD 的数据: | |||||
| ```c | |||||
| WORD startAddress; | |||||
| WORD firstOddAddress; | |||||
| WORD registerCount; | |||||
| WORD registerOffset; | |||||
| WORD receiveLength; | |||||
| WORD expectedLength; | |||||
| WORD status; | |||||
| BYTE request[8]; | |||||
| BYTE response[251]; | |||||
| startAddress = PSW[PSW_ODD_START_ADDRESS]; | |||||
| firstOddAddress = (WORD)(startAddress | 0x0001); | |||||
| registerCount = PSW[PSW_ODD_COUNT]; | |||||
| receiveLength = 0; | |||||
| status = MODBUS_STATUS_INPUT_ERROR; | |||||
| if ((registerCount != 0) && (registerCount <= MODBUS_ODD_COUNT_MAX) && | |||||
| ((registerCount & 1) != 0) && | |||||
| (firstOddAddress <= MODBUS_ADDRESS_MAX) && | |||||
| (registerCount <= (((MODBUS_ADDRESS_MAX - firstOddAddress) / 2) + 1))) | |||||
| { | |||||
| request[0] = MODBUS_SLAVE_ADDRESS; | |||||
| request[1] = MODBUS_FUNCTION_READ_ODD_COUNT_REGS; | |||||
| request[2] = (BYTE)(startAddress >> 8); | |||||
| request[3] = (BYTE)startAddress; | |||||
| request[4] = (BYTE)(registerCount >> 8); | |||||
| request[5] = (BYTE)registerCount; | |||||
| ModbusAppendCrc(request, 6); | |||||
| expectedLength = MODBUS_MIN_RESPONSE_LENGTH + (registerCount * 2); | |||||
| status = ModbusSendReceive(request, 8, response, expectedLength, | |||||
| &receiveLength); | |||||
| if (status == MODBUS_STATUS_OK) | |||||
| { | |||||
| status = ModbusCheckResponse(response, receiveLength, | |||||
| MODBUS_FUNCTION_READ_ODD_COUNT_REGS); | |||||
| } | |||||
| if ((status == MODBUS_STATUS_OK) && | |||||
| ((receiveLength != expectedLength) || | |||||
| (response[2] != (BYTE)(registerCount * 2)))) | |||||
| { | |||||
| status = MODBUS_STATUS_RESPONSE_ERROR; | |||||
| } | |||||
| if (status == MODBUS_STATUS_OK) | |||||
| { | |||||
| for (registerOffset = 0; registerOffset < registerCount; | |||||
| registerOffset++) | |||||
| { | |||||
| PSW[PSW_ODD_RESULTS + registerOffset] = | |||||
| ((WORD)response[3 + (registerOffset * 2)] << 8) | | |||||
| response[4 + (registerOffset * 2)]; | |||||
| } | |||||
| } | |||||
| } | |||||
| PSW[PSW_STATUS] = status; | |||||
| PSW[PSW_RECEIVE_LENGTH] = receiveLength; | |||||
| ``` | |||||
| 示例请求:起始基准地址 `0`、数量 `123` 的请求数据为 `00 00 00 7B`,响应数据字节数为 `246`,返回地址为 `1, 3, 5, ..., 245`。若数量为 `122` 或 `124`,PLC 返回异常码 `03`;若最后一个奇数地址越过 `0x270F`,返回异常码 `02`。 | |||||
| ## 8. RTC 断电保持 | |||||
| 不设置保存或恢复按钮。仅私有写操作 `0x42` 成功后,PLC 自动将固定寄存器 `0x10000` 写入 RTC 备份寄存器;启动时校验通过则自动恢复。保持寄存器 `0..31` 和线圈 `0..15` 均不保存、不恢复,也不会被 RTC 旧值覆盖。 | |||||
| STM32F407 的 RTC 备份区只有 20 个 32 位寄存器,不能保存完整的 10000 个寄存器和线圈。实际掉电保持还要求 PLC 板的 `VBAT` 电池已经接入;没有 VBAT 时,RTC 备份数据也会丢失。 | |||||
| ## 9. 联调帧 | |||||
| ```text | |||||
| 读取 0x10000:01 41 00 01 00 00 00 01 2C C3 | |||||
| 写入 0x10000:01 42 00 01 00 00 <value_hi> <value_lo> CRC_L CRC_H | |||||
| 读取 1, 3, ..., 245:01 43 00 00 00 7B CRC_L CRC_H | |||||
| ``` | |||||
| `0x43` 每次按钮操作只发送一帧,最多处理 123 个奇数地址寄存器。 | |||||
| @@ -0,0 +1,387 @@ | |||||
| #include <assert.h> | |||||
| #include <stdint.h> | |||||
| #include <string.h> | |||||
| #define main AppFirmwareMain | |||||
| #define static | |||||
| #include "../Core/Src/main.c" | |||||
| #undef static | |||||
| #undef main | |||||
| #include "../Core/Src/stm32f4xx_it.c" | |||||
| TIM_TypeDef TestTim5; | |||||
| GPIO_TypeDef TestGpioE; | |||||
| GPIO_TypeDef TestGpioF; | |||||
| GPIO_TypeDef TestGpioI; | |||||
| USART_TypeDef TestUsart1; | |||||
| OS_EVENT TestFrameSem; | |||||
| IRQn_Type TestNvicIrq; | |||||
| uint32_t TestNvicPreemptPriority; | |||||
| uint32_t TestNvicSubPriority; | |||||
| uint32_t TestHalTickCallCount; | |||||
| uint32_t TestOsCpuSysTickCallCount; | |||||
| HAL_StatusTypeDef TestReceiveResults[4U]; | |||||
| uint32_t TestReceiveResultCount; | |||||
| uint32_t TestReceiveCallCount; | |||||
| uint32_t TestReceiveErrorInjectionCall; | |||||
| uint32_t TestAbortReceiveCallCount; | |||||
| uint32_t TestUartFlagClearCallCount; | |||||
| uint8_t TestRecoveryCallOrder[4U]; | |||||
| uint32_t TestRecoveryCallOrderLength; | |||||
| uint32_t TestSemPostCallCount; | |||||
| uint8_t OSRunning; | |||||
| static void TestResetFakes(void) | |||||
| { | |||||
| (void)memset(&TestTim5, 0, sizeof(TestTim5)); | |||||
| (void)memset(&TestGpioE, 0, sizeof(TestGpioE)); | |||||
| (void)memset(&TestGpioF, 0, sizeof(TestGpioF)); | |||||
| (void)memset(&TestGpioI, 0, sizeof(TestGpioI)); | |||||
| (void)memset(&TestUsart1, 0, sizeof(TestUsart1)); | |||||
| (void)memset(&TestFrameSem, 0, sizeof(TestFrameSem)); | |||||
| (void)memset(&Uart1Handle, 0, sizeof(Uart1Handle)); | |||||
| (void)memset(TestReceiveResults, 0, sizeof(TestReceiveResults)); | |||||
| TestNvicIrq = 0; | |||||
| TestNvicPreemptPriority = 0U; | |||||
| TestNvicSubPriority = 0U; | |||||
| TestHalTickCallCount = 0U; | |||||
| TestOsCpuSysTickCallCount = 0U; | |||||
| TestReceiveResultCount = 0U; | |||||
| TestReceiveCallCount = 0U; | |||||
| TestReceiveErrorInjectionCall = 0U; | |||||
| TestAbortReceiveCallCount = 0U; | |||||
| TestUartFlagClearCallCount = 0U; | |||||
| (void)memset(TestRecoveryCallOrder, 0, sizeof(TestRecoveryCallOrder)); | |||||
| TestRecoveryCallOrderLength = 0U; | |||||
| TestSemPostCallCount = 0U; | |||||
| OSRunning = 0U; | |||||
| ModbusUartLastErrorCode = 0U; | |||||
| ModbusUartLastRxState = 0U; | |||||
| ModbusUartRecoveryCount = 0U; | |||||
| ModbusUartRecoveryFailureCount = 0U; | |||||
| ModbusUartRxArmCount = 0U; | |||||
| ModbusUartRxArmFailureCount = 0U; | |||||
| ModbusUartLastRxByte = 0U; | |||||
| ModbusLastCompleteFrameLength = 0U; | |||||
| ModbusFrameSem = &TestFrameSem; | |||||
| ModbusReceptionNeedsRecovery = 0U; | |||||
| } | |||||
| static void TestSysTickUsesKernelAwarePriority(void) | |||||
| { | |||||
| TestResetFakes(); | |||||
| AppInitSystemTick(); | |||||
| assert(TestNvicIrq == SysTick_IRQn); | |||||
| assert(TestNvicPreemptPriority == CPU_CFG_KA_IPL_BOUNDARY); | |||||
| assert(TestNvicSubPriority == 0U); | |||||
| } | |||||
| static void TestSysTickDelegatesToOsPort(void) | |||||
| { | |||||
| TestResetFakes(); | |||||
| OSRunning = OS_TRUE; | |||||
| SysTick_Handler(); | |||||
| assert(TestHalTickCallCount == 1U); | |||||
| assert(TestOsCpuSysTickCallCount == 1U); | |||||
| } | |||||
| static void TestReceiveRecoveryRetriesAfterBusy(void) | |||||
| { | |||||
| TestResetFakes(); | |||||
| Uart1Handle.Instance = USART1; | |||||
| TestReceiveResults[0U] = HAL_BUSY; | |||||
| TestReceiveResults[1U] = HAL_OK; | |||||
| TestReceiveResultCount = 2U; | |||||
| assert(AppRecoverUartReception() == HAL_OK); | |||||
| assert(TestReceiveCallCount == 2U); | |||||
| assert(TestAbortReceiveCallCount == 2U); | |||||
| assert(TestRecoveryCallOrderLength == 4U); | |||||
| assert(TestRecoveryCallOrder[0U] == 2U); | |||||
| assert(TestRecoveryCallOrder[1U] == 1U); | |||||
| assert(TestRecoveryCallOrder[2U] == 2U); | |||||
| assert(TestRecoveryCallOrder[3U] == 1U); | |||||
| assert(TestUartFlagClearCallCount == 2U); | |||||
| } | |||||
| static void TestUartErrorClearsFlagsBeforeRecovery(void) | |||||
| { | |||||
| TestResetFakes(); | |||||
| Uart1Handle.Instance = USART1; | |||||
| Uart1Handle.ErrorCode = HAL_UART_ERROR_ORE; | |||||
| HAL_UART_ErrorCallback(&Uart1Handle); | |||||
| assert(ModbusReceptionNeedsRecovery == 1U); | |||||
| assert(TestUartFlagClearCallCount == 1U); | |||||
| assert(TestReceiveCallCount == 0U); | |||||
| } | |||||
| static void TestUartErrorDefersRecoveryToTask(void) | |||||
| { | |||||
| TestResetFakes(); | |||||
| Uart1Handle.Instance = USART1; | |||||
| Uart1Handle.ErrorCode = HAL_UART_ERROR_ORE; | |||||
| HAL_UART_ErrorCallback(&Uart1Handle); | |||||
| assert(ModbusReceptionNeedsRecovery == 1U); | |||||
| assert(TestSemPostCallCount == 1U); | |||||
| assert(TestReceiveCallCount == 0U); | |||||
| } | |||||
| static void TestPendingRecoveryKeepsConcurrentErrorRequest(void) | |||||
| { | |||||
| TestResetFakes(); | |||||
| Uart1Handle.Instance = USART1; | |||||
| ModbusReceptionNeedsRecovery = 1U; | |||||
| TestReceiveErrorInjectionCall = 1U; | |||||
| assert(AppRecoverPendingUartReception() == HAL_OK); | |||||
| assert(TestReceiveCallCount == 1U); | |||||
| assert(TestSemPostCallCount == 1U); | |||||
| assert(ModbusReceptionNeedsRecovery == 1U); | |||||
| } | |||||
| static void TestReceptionWatchdogRearmsWhenHalStateIsLost(void) | |||||
| { | |||||
| TestResetFakes(); | |||||
| Uart1Handle.Instance = USART1; | |||||
| Uart1Handle.RxState = HAL_UART_STATE_READY; | |||||
| TestReceiveResults[0U] = HAL_OK; | |||||
| TestReceiveResultCount = 1U; | |||||
| assert(AppEnsureUartReception() == HAL_OK); | |||||
| assert(TestAbortReceiveCallCount == 1U); | |||||
| assert(TestReceiveCallCount == 1U); | |||||
| assert(ModbusUartRecoveryCount == 1U); | |||||
| } | |||||
| int main(void) | |||||
| { | |||||
| TestSysTickUsesKernelAwarePriority(); | |||||
| TestSysTickDelegatesToOsPort(); | |||||
| TestReceiveRecoveryRetriesAfterBusy(); | |||||
| TestUartErrorClearsFlagsBeforeRecovery(); | |||||
| TestUartErrorDefersRecoveryToTask(); | |||||
| TestPendingRecoveryKeepsConcurrentErrorRequest(); | |||||
| TestReceptionWatchdogRearmsWhenHalStateIsLost(); | |||||
| return 0; | |||||
| } | |||||
| void HAL_Init(void) | |||||
| { | |||||
| } | |||||
| HAL_StatusTypeDef HAL_RCC_OscConfig(const RCC_OscInitTypeDef *config) | |||||
| { | |||||
| (void)config; | |||||
| return HAL_OK; | |||||
| } | |||||
| HAL_StatusTypeDef HAL_RCC_ClockConfig(const RCC_ClkInitTypeDef *config, | |||||
| uint32_t latency) | |||||
| { | |||||
| (void)config; | |||||
| (void)latency; | |||||
| return HAL_OK; | |||||
| } | |||||
| void HAL_GPIO_Init(GPIO_TypeDef *port, const GPIO_InitTypeDef *config) | |||||
| { | |||||
| (void)port; | |||||
| (void)config; | |||||
| } | |||||
| void HAL_GPIO_WritePin(GPIO_TypeDef *port, uint16_t pin, | |||||
| GPIO_PinState state) | |||||
| { | |||||
| (void)port; | |||||
| (void)pin; | |||||
| (void)state; | |||||
| } | |||||
| HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *uartHandle) | |||||
| { | |||||
| (void)uartHandle; | |||||
| return HAL_OK; | |||||
| } | |||||
| HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *uartHandle, | |||||
| uint8_t *data, uint16_t size) | |||||
| { | |||||
| HAL_StatusTypeDef result; | |||||
| (void)uartHandle; | |||||
| (void)data; | |||||
| (void)size; | |||||
| if (TestRecoveryCallOrderLength < sizeof(TestRecoveryCallOrder)) { | |||||
| TestRecoveryCallOrder[TestRecoveryCallOrderLength++] = 1U; | |||||
| } | |||||
| result = HAL_OK; | |||||
| if (TestReceiveCallCount < TestReceiveResultCount) { | |||||
| result = TestReceiveResults[TestReceiveCallCount]; | |||||
| } | |||||
| TestReceiveCallCount++; | |||||
| if (result == HAL_OK) { | |||||
| uartHandle->RxState = HAL_UART_STATE_BUSY_RX; | |||||
| } | |||||
| if ((TestReceiveErrorInjectionCall != 0U) | |||||
| && (TestReceiveCallCount == TestReceiveErrorInjectionCall)) { | |||||
| Uart1Handle.ErrorCode = HAL_UART_ERROR_ORE; | |||||
| HAL_UART_ErrorCallback(&Uart1Handle); | |||||
| } | |||||
| return result; | |||||
| } | |||||
| HAL_StatusTypeDef HAL_UART_AbortReceive_IT(UART_HandleTypeDef *uartHandle) | |||||
| { | |||||
| (void)uartHandle; | |||||
| TestAbortReceiveCallCount++; | |||||
| if (TestRecoveryCallOrderLength < sizeof(TestRecoveryCallOrder)) { | |||||
| TestRecoveryCallOrder[TestRecoveryCallOrderLength++] = 2U; | |||||
| } | |||||
| return HAL_OK; | |||||
| } | |||||
| HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *uartHandle, | |||||
| uint8_t *data, uint16_t size) | |||||
| { | |||||
| (void)uartHandle; | |||||
| (void)data; | |||||
| (void)size; | |||||
| return HAL_OK; | |||||
| } | |||||
| HAL_StatusTypeDef HAL_UART_AbortTransmit_IT(UART_HandleTypeDef *uartHandle) | |||||
| { | |||||
| (void)uartHandle; | |||||
| return HAL_OK; | |||||
| } | |||||
| void HAL_UART_IRQHandler(UART_HandleTypeDef *uartHandle) | |||||
| { | |||||
| (void)uartHandle; | |||||
| } | |||||
| void HAL_NVIC_SetPriority(IRQn_Type irqn, uint32_t preemptPriority, | |||||
| uint32_t subPriority) | |||||
| { | |||||
| TestNvicIrq = irqn; | |||||
| TestNvicPreemptPriority = preemptPriority; | |||||
| TestNvicSubPriority = subPriority; | |||||
| } | |||||
| void HAL_NVIC_EnableIRQ(IRQn_Type irqn) | |||||
| { | |||||
| (void)irqn; | |||||
| } | |||||
| void HAL_IncTick(void) | |||||
| { | |||||
| TestHalTickCallCount++; | |||||
| } | |||||
| void OSInit(void) | |||||
| { | |||||
| } | |||||
| void OSStart(void) | |||||
| { | |||||
| } | |||||
| INT8U OSTaskCreateExt(void (*task)(void *), void *argument, OS_STK *stackTop, | |||||
| INT8U priority, INT16U taskId, OS_STK *stackBottom, | |||||
| INT32U stackSize, void *extension, INT16U options) | |||||
| { | |||||
| (void)task; | |||||
| (void)argument; | |||||
| (void)stackTop; | |||||
| (void)priority; | |||||
| (void)taskId; | |||||
| (void)stackBottom; | |||||
| (void)stackSize; | |||||
| (void)extension; | |||||
| (void)options; | |||||
| return OS_ERR_NONE; | |||||
| } | |||||
| OS_EVENT *OSSemCreate(INT16U count) | |||||
| { | |||||
| (void)count; | |||||
| return &TestFrameSem; | |||||
| } | |||||
| void OSSemPend(OS_EVENT *event, uint32_t timeout, INT8U *error) | |||||
| { | |||||
| (void)event; | |||||
| (void)timeout; | |||||
| if (error != NULL) { | |||||
| *error = OS_ERR_NONE; | |||||
| } | |||||
| } | |||||
| INT8U OSSemPost(OS_EVENT *event) | |||||
| { | |||||
| (void)event; | |||||
| TestSemPostCallCount++; | |||||
| return OS_ERR_NONE; | |||||
| } | |||||
| void OSTaskSuspend(INT8U priority) | |||||
| { | |||||
| (void)priority; | |||||
| } | |||||
| void OSTimeDly(uint32_t ticks) | |||||
| { | |||||
| (void)ticks; | |||||
| } | |||||
| uint32_t OSTimeGet(void) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| void OSIntEnter(void) | |||||
| { | |||||
| } | |||||
| void OSIntExit(void) | |||||
| { | |||||
| } | |||||
| void OSTimeTick(void) | |||||
| { | |||||
| } | |||||
| void OS_CPU_SysTickHandler(void) | |||||
| { | |||||
| TestOsCpuSysTickCallCount++; | |||||
| } | |||||
| MODBUS_STATUS ModbusBackupRestore(MODBUS_SLAVE *slave) | |||||
| { | |||||
| (void)slave; | |||||
| return MODBUS_STATUS_OK; | |||||
| } | |||||
| MODBUS_STATUS ModbusBackupSave(const MODBUS_SLAVE *slave) | |||||
| { | |||||
| (void)slave; | |||||
| return MODBUS_STATUS_OK; | |||||
| } | |||||
| @@ -0,0 +1,34 @@ | |||||
| $projectRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path | |||||
| $sourcePaths = @( | |||||
| (Join-Path $projectRoot 'Core/OS/Cfg/app_cfg.h'), | |||||
| (Join-Path $projectRoot 'Core/Src/main.c') | |||||
| ) | |||||
| $forbiddenSymbols = @( | |||||
| 'APP_MODBUS_REG_OPERATION_RX_COUNT', | |||||
| 'APP_MODBUS_REG_OPERATION_TX_COUNT', | |||||
| 'APP_MODBUS_REG_ERROR_COUNT', | |||||
| 'ModbusOperationRxCount', | |||||
| 'ModbusOperationTxCount', | |||||
| 'ModbusProtocolErrorCount', | |||||
| 'ModbusReceiveErrorCount', | |||||
| 'ModbusSemaphoreErrorCount', | |||||
| 'AppUpdateStatusRegisters', | |||||
| 'AppRecordIsrError' | |||||
| ) | |||||
| $violations = @() | |||||
| foreach ($sourcePath in $sourcePaths) { | |||||
| $source = Get-Content -Raw $sourcePath | |||||
| foreach ($symbol in $forbiddenSymbols) { | |||||
| if ($source.Contains($symbol)) { | |||||
| $violations += "$sourcePath still contains $symbol" | |||||
| } | |||||
| } | |||||
| } | |||||
| if ($violations.Count -ne 0) { | |||||
| $violations | ForEach-Object { Write-Error $_ } | |||||
| exit 1 | |||||
| } | |||||
| Write-Output 'Communication counter logic is absent.' | |||||
| @@ -0,0 +1,49 @@ | |||||
| $projectRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path | |||||
| $mainSource = Get-Content -Raw (Join-Path $projectRoot 'Core/Src/main.c') | |||||
| $interruptSource = Get-Content -Raw (Join-Path $projectRoot 'Core/Src/stm32f4xx_it.c') | |||||
| function Require-SourcePattern | |||||
| { | |||||
| param( | |||||
| [string]$Source, | |||||
| [string]$Pattern, | |||||
| [string]$Message | |||||
| ) | |||||
| if ($Source -notmatch $Pattern) | |||||
| { | |||||
| throw $Message | |||||
| } | |||||
| } | |||||
| Require-SourcePattern $mainSource ` | |||||
| 'HAL_NVIC_SetPriority\(SysTick_IRQn, CPU_CFG_KA_IPL_BOUNDARY, 0U\)' ` | |||||
| 'SysTick priority must use CPU_CFG_KA_IPL_BOUNDARY.' | |||||
| Require-SourcePattern $interruptSource 'OS_CPU_SysTickHandler\(\)' ` | |||||
| 'SysTick must use the uC/OS-II port wrapper.' | |||||
| Require-SourcePattern $mainSource ` | |||||
| 'static HAL_StatusTypeDef AppRecoverUartReception\(void\)' ` | |||||
| 'UART recovery helper is missing.' | |||||
| Require-SourcePattern $mainSource 'ModbusReceptionNeedsRecovery' ` | |||||
| 'UART pending-recovery state is missing.' | |||||
| $sysTickHandler = [regex]::Match( | |||||
| $interruptSource, | |||||
| 'void SysTick_Handler\(void\)\s*\{(?s:.*?)\n\}\s*/\*\*').Value | |||||
| if (($sysTickHandler -match 'OSIntEnter\(\)') ` | |||||
| -or ($sysTickHandler -match 'OSTimeTick\(\)') ` | |||||
| -or ($sysTickHandler -match 'OSIntExit\(\)')) | |||||
| { | |||||
| throw 'SysTick must not contain a handwritten uC/OS-II tick sequence.' | |||||
| } | |||||
| $uartErrorCallback = [regex]::Match( | |||||
| $mainSource, | |||||
| 'void HAL_UART_ErrorCallback\(UART_HandleTypeDef \*uartHandle\)\s*\{(?s:.*?)\n\}\s*/\*\*').Value | |||||
| if (($uartErrorCallback -match 'HAL_UART_Receive_IT') ` | |||||
| -or ($uartErrorCallback -match 'HAL_UART_AbortReceive_IT')) | |||||
| { | |||||
| throw 'UART error callback must defer recovery to the Modbus task.' | |||||
| } | |||||
| Write-Output 'RTU runtime invariants are present.' | |||||
| @@ -0,0 +1,70 @@ | |||||
| #include <assert.h> | |||||
| #include <stdint.h> | |||||
| #include <string.h> | |||||
| #include "main.h" | |||||
| #include "modbus.h" | |||||
| #include "modbus_backup.h" | |||||
| #define TEST_SLAVE_ADDRESS (1U) | |||||
| TEST_RTC_TYPEDEF TestRtc; | |||||
| static uint16_t TestHoldingRegisters[MODBUS_DATA_POINT_COUNT]; | |||||
| static uint8_t TestCoilStorage[MODBUS_COIL_STORAGE_SIZE]; | |||||
| static uint16_t TestExtendedRegister; | |||||
| static void TestInitSlave(MODBUS_SLAVE *slave) | |||||
| { | |||||
| assert(slave != NULL); | |||||
| assert(ModbusSlaveInit(slave, TEST_SLAVE_ADDRESS, TestHoldingRegisters, | |||||
| TestCoilStorage) == MODBUS_STATUS_OK); | |||||
| assert(ModbusConfigureExtendedHoldingRegister(slave, | |||||
| &TestExtendedRegister) | |||||
| == MODBUS_STATUS_OK); | |||||
| } | |||||
| static void TestRestoreOnlyExtendedRegister(void) | |||||
| { | |||||
| MODBUS_SLAVE slave; | |||||
| uint8_t coilValue; | |||||
| (void)memset(&TestRtc, 0, sizeof(TestRtc)); | |||||
| (void)memset(TestHoldingRegisters, 0, sizeof(TestHoldingRegisters)); | |||||
| (void)memset(TestCoilStorage, 0, sizeof(TestCoilStorage)); | |||||
| TestExtendedRegister = 0U; | |||||
| TestInitSlave(&slave); | |||||
| assert(ModbusSetHoldingRegister(&slave, 0U, 0x1111U) | |||||
| == MODBUS_STATUS_OK); | |||||
| assert(ModbusSetHoldingRegister(&slave, 31U, 0x2222U) | |||||
| == MODBUS_STATUS_OK); | |||||
| assert(ModbusSetCoil(&slave, 0U, 1U) == MODBUS_STATUS_OK); | |||||
| assert(ModbusSetCoil(&slave, 15U, 1U) == MODBUS_STATUS_OK); | |||||
| TestExtendedRegister = 0xBEEFU; | |||||
| assert(ModbusBackupSave(&slave) == MODBUS_STATUS_OK); | |||||
| assert(ModbusSetHoldingRegister(&slave, 0U, 0xAAAAU) | |||||
| == MODBUS_STATUS_OK); | |||||
| assert(ModbusSetHoldingRegister(&slave, 31U, 0x5555U) | |||||
| == MODBUS_STATUS_OK); | |||||
| assert(ModbusSetCoil(&slave, 0U, 0U) == MODBUS_STATUS_OK); | |||||
| assert(ModbusSetCoil(&slave, 15U, 0U) == MODBUS_STATUS_OK); | |||||
| TestExtendedRegister = 0U; | |||||
| assert(ModbusBackupRestore(&slave) == MODBUS_STATUS_OK); | |||||
| assert(TestHoldingRegisters[0U] == 0xAAAAU); | |||||
| assert(TestHoldingRegisters[31U] == 0x5555U); | |||||
| assert(ModbusGetCoil(&slave, 0U, &coilValue) == MODBUS_STATUS_OK); | |||||
| assert(coilValue == 0U); | |||||
| assert(ModbusGetCoil(&slave, 15U, &coilValue) == MODBUS_STATUS_OK); | |||||
| assert(coilValue == 0U); | |||||
| assert(TestExtendedRegister == 0xBEEFU); | |||||
| } | |||||
| int main(void) | |||||
| { | |||||
| TestRestoreOnlyExtendedRegister(); | |||||
| return 0; | |||||
| } | |||||
| @@ -0,0 +1,306 @@ | |||||
| /** | |||||
| * @file modbus_test.c | |||||
| * @brief Modbus RTU 从站私有功能码单元测试。 | |||||
| * @version 2.1 | |||||
| * @author zengbingjie | |||||
| * @date 2026-08-02 | |||||
| */ | |||||
| #include <assert.h> | |||||
| #include <stdint.h> | |||||
| #include <string.h> | |||||
| #include "modbus.h" | |||||
| #define TEST_SLAVE_ADDRESS (1U) | |||||
| #define TEST_EXTENDED_READ_FRAME_LENGTH (10U) | |||||
| #define TEST_EXTENDED_READ_RESPONSE_LENGTH (7U) | |||||
| #define TEST_EXTENDED_WRITE_FRAME_LENGTH (10U) | |||||
| #define TEST_ODD_READ_FRAME_LENGTH (8U) | |||||
| #define TEST_ODD_READ_RESPONSE_LENGTH (11U) | |||||
| #define TEST_ODD_READ_MAX_RESPONSE_LENGTH (251U) | |||||
| #define TEST_FC_READ_EXTENDED_REGISTER (0x41U) | |||||
| #define TEST_FC_WRITE_EXTENDED_REGISTER (0x42U) | |||||
| #define TEST_FC_READ_ODD_COUNT_REGS (0x43U) | |||||
| static uint16_t TestHoldingRegisters[MODBUS_DATA_POINT_COUNT]; | |||||
| static uint8_t TestCoilStorage[MODBUS_COIL_STORAGE_SIZE]; | |||||
| static uint16_t TestExtendedRegister; | |||||
| /** | |||||
| * @brief 向测试请求末尾追加 Modbus RTU CRC。 | |||||
| * @param[in,out] frame 测试请求帧。 | |||||
| * @param[in] payloadLength 未含 CRC 的帧长度。 | |||||
| * @return 无。 | |||||
| */ | |||||
| static void TestAppendCrc(uint8_t *frame, uint16_t payloadLength) | |||||
| { | |||||
| uint16_t crcValue; | |||||
| assert(frame != NULL); | |||||
| crcValue = ModbusCrc16(frame, payloadLength); | |||||
| frame[payloadLength] = (uint8_t)(crcValue & 0x00FFU); | |||||
| frame[payloadLength + 1U] = (uint8_t)(crcValue >> 8U); | |||||
| } | |||||
| /** | |||||
| * @brief 校验响应帧的 CRC 字节序和数值。 | |||||
| * @param[in] frame 响应帧。 | |||||
| * @param[in] frameLength 含 CRC 的帧长度。 | |||||
| * @return 无。 | |||||
| */ | |||||
| static void TestAssertFrameCrc(const uint8_t *frame, uint16_t frameLength) | |||||
| { | |||||
| uint16_t expectedCrc; | |||||
| uint16_t actualCrc; | |||||
| assert(frame != NULL); | |||||
| assert(frameLength >= MODBUS_RTU_ADU_MIN_LENGTH); | |||||
| expectedCrc = ModbusCrc16(frame, frameLength - MODBUS_RTU_CRC_LENGTH); | |||||
| actualCrc = (uint16_t)frame[frameLength - MODBUS_RTU_CRC_LENGTH] | |||||
| | ((uint16_t)frame[frameLength - 1U] << 8U); | |||||
| assert(actualCrc == expectedCrc); | |||||
| } | |||||
| /** | |||||
| * @brief 使用真实数据存储初始化一个从站测试实例。 | |||||
| * @param[out] slave 待初始化的从站。 | |||||
| * @return 无。 | |||||
| */ | |||||
| static void TestInitSlave(MODBUS_SLAVE *slave) | |||||
| { | |||||
| assert(slave != NULL); | |||||
| assert(ModbusSlaveInit(slave, TEST_SLAVE_ADDRESS, TestHoldingRegisters, | |||||
| TestCoilStorage) == MODBUS_STATUS_OK); | |||||
| assert(ModbusConfigureExtendedHoldingRegister(slave, | |||||
| &TestExtendedRegister) | |||||
| == MODBUS_STATUS_OK); | |||||
| } | |||||
| /** | |||||
| * @brief 回归验证 0x41 仍可读取固定扩展寄存器。 | |||||
| * @return 无。 | |||||
| */ | |||||
| static void TestReadExtendedRegister(void) | |||||
| { | |||||
| uint8_t request[TEST_EXTENDED_READ_FRAME_LENGTH] = { | |||||
| TEST_SLAVE_ADDRESS, TEST_FC_READ_EXTENDED_REGISTER, | |||||
| 0x00U, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U, 0x00U, 0x00U | |||||
| }; | |||||
| uint8_t response[MODBUS_RTU_ADU_MAX_LENGTH]; | |||||
| uint16_t responseLength; | |||||
| MODBUS_SLAVE slave; | |||||
| TestInitSlave(&slave); | |||||
| TestExtendedRegister = 0x1000U; | |||||
| TestAppendCrc(request, 8U); | |||||
| assert(ModbusProcessFrame(&slave, request, sizeof(request), response, | |||||
| sizeof(response), &responseLength) | |||||
| == MODBUS_STATUS_OK); | |||||
| assert(responseLength == TEST_EXTENDED_READ_RESPONSE_LENGTH); | |||||
| assert(response[0U] == TEST_SLAVE_ADDRESS); | |||||
| assert(response[1U] == TEST_FC_READ_EXTENDED_REGISTER); | |||||
| assert(response[2U] == 2U); | |||||
| assert(response[3U] == 0x10U); | |||||
| assert(response[4U] == 0x00U); | |||||
| TestAssertFrameCrc(response, responseLength); | |||||
| } | |||||
| /** | |||||
| * @brief 验证 0x42 将指定值写入固定扩展寄存器并完整回显。 | |||||
| * @return 无。 | |||||
| */ | |||||
| static void TestWriteExtendedRegister(void) | |||||
| { | |||||
| uint8_t request[TEST_EXTENDED_WRITE_FRAME_LENGTH] = { | |||||
| TEST_SLAVE_ADDRESS, TEST_FC_WRITE_EXTENDED_REGISTER, | |||||
| 0x00U, 0x01U, 0x00U, 0x00U, 0xBEU, 0xEFU, 0x00U, 0x00U | |||||
| }; | |||||
| uint8_t response[MODBUS_RTU_ADU_MAX_LENGTH]; | |||||
| uint16_t responseLength; | |||||
| MODBUS_SLAVE slave; | |||||
| TestInitSlave(&slave); | |||||
| TestAppendCrc(request, 8U); | |||||
| assert(ModbusProcessFrame(&slave, request, sizeof(request), response, | |||||
| sizeof(response), &responseLength) | |||||
| == MODBUS_STATUS_OK); | |||||
| assert(responseLength == TEST_EXTENDED_WRITE_FRAME_LENGTH); | |||||
| assert(memcmp(response, request, 8U) == 0); | |||||
| assert(TestExtendedRegister == 0xBEEFU); | |||||
| TestAssertFrameCrc(response, responseLength); | |||||
| } | |||||
| /** | |||||
| * @brief 验证 0x42 拒绝非 0x00010000 的扩展地址。 | |||||
| * @return 无。 | |||||
| */ | |||||
| static void TestWriteExtendedRegisterRejectsInvalidAddress(void) | |||||
| { | |||||
| uint8_t request[TEST_EXTENDED_WRITE_FRAME_LENGTH] = { | |||||
| TEST_SLAVE_ADDRESS, TEST_FC_WRITE_EXTENDED_REGISTER, | |||||
| 0x00U, 0x01U, 0x00U, 0x01U, 0x12U, 0x34U, 0x00U, 0x00U | |||||
| }; | |||||
| uint8_t response[MODBUS_RTU_ADU_MAX_LENGTH]; | |||||
| uint16_t responseLength; | |||||
| MODBUS_SLAVE slave; | |||||
| TestInitSlave(&slave); | |||||
| TestAppendCrc(request, 8U); | |||||
| assert(ModbusProcessFrame(&slave, request, sizeof(request), response, | |||||
| sizeof(response), &responseLength) | |||||
| == MODBUS_STATUS_EXCEPTION); | |||||
| assert(responseLength == MODBUS_RTU_EXCEPTION_LENGTH); | |||||
| assert(response[0U] == TEST_SLAVE_ADDRESS); | |||||
| assert(response[1U] | |||||
| == (TEST_FC_WRITE_EXTENDED_REGISTER | 0x80U)); | |||||
| assert(response[2U] == MODBUS_EXCEPTION_ILLEGAL_DATA_ADDR); | |||||
| TestAssertFrameCrc(response, responseLength); | |||||
| } | |||||
| /** | |||||
| * @brief 验证 0x43 从起始基准地址后的奇数地址读取保持寄存器。 | |||||
| * @return 无。 | |||||
| */ | |||||
| static void TestReadOddAddressRegisters(void) | |||||
| { | |||||
| uint8_t request[TEST_ODD_READ_FRAME_LENGTH] = { | |||||
| TEST_SLAVE_ADDRESS, TEST_FC_READ_ODD_COUNT_REGS, | |||||
| 0x00U, 0x00U, 0x00U, 0x03U, 0x00U, 0x00U | |||||
| }; | |||||
| uint8_t response[MODBUS_RTU_ADU_MAX_LENGTH]; | |||||
| uint16_t responseLength; | |||||
| MODBUS_SLAVE slave; | |||||
| TestInitSlave(&slave); | |||||
| TestHoldingRegisters[0U] = 0xDEADU; | |||||
| TestHoldingRegisters[1U] = 0x1234U; | |||||
| TestHoldingRegisters[2U] = 0xBEEFU; | |||||
| TestHoldingRegisters[3U] = 0xABCDU; | |||||
| TestHoldingRegisters[4U] = 0xFEEDU; | |||||
| TestHoldingRegisters[5U] = 0x0001U; | |||||
| TestAppendCrc(request, 6U); | |||||
| assert(ModbusProcessFrame(&slave, request, sizeof(request), response, | |||||
| sizeof(response), &responseLength) | |||||
| == MODBUS_STATUS_OK); | |||||
| assert(responseLength == TEST_ODD_READ_RESPONSE_LENGTH); | |||||
| assert(response[0U] == TEST_SLAVE_ADDRESS); | |||||
| assert(response[1U] == TEST_FC_READ_ODD_COUNT_REGS); | |||||
| assert(response[2U] == 6U); | |||||
| assert(response[3U] == 0x12U); | |||||
| assert(response[4U] == 0x34U); | |||||
| assert(response[5U] == 0xABU); | |||||
| assert(response[6U] == 0xCDU); | |||||
| assert(response[7U] == 0x00U); | |||||
| assert(response[8U] == 0x01U); | |||||
| TestAssertFrameCrc(response, responseLength); | |||||
| } | |||||
| /** | |||||
| * @brief 验证 0x43 从偶数基准地址 10 开始读取 D11、D13 和 D15。 | |||||
| * @return 无。 | |||||
| */ | |||||
| static void TestReadOddAddressRegistersFromAddressTen(void) | |||||
| { | |||||
| uint8_t request[TEST_ODD_READ_FRAME_LENGTH] = { | |||||
| TEST_SLAVE_ADDRESS, TEST_FC_READ_ODD_COUNT_REGS, | |||||
| 0x00U, 0x0AU, 0x00U, 0x03U, 0x00U, 0x00U | |||||
| }; | |||||
| uint8_t response[MODBUS_RTU_ADU_MAX_LENGTH]; | |||||
| uint16_t responseLength; | |||||
| MODBUS_SLAVE slave; | |||||
| TestInitSlave(&slave); | |||||
| TestHoldingRegisters[10U] = 0xDEADU; | |||||
| TestHoldingRegisters[11U] = 0x1111U; | |||||
| TestHoldingRegisters[12U] = 0xBEEFU; | |||||
| TestHoldingRegisters[13U] = 0x1313U; | |||||
| TestHoldingRegisters[14U] = 0xFEEDU; | |||||
| TestHoldingRegisters[15U] = 0x1515U; | |||||
| TestAppendCrc(request, 6U); | |||||
| assert(ModbusProcessFrame(&slave, request, sizeof(request), response, | |||||
| sizeof(response), &responseLength) | |||||
| == MODBUS_STATUS_OK); | |||||
| assert(responseLength == TEST_ODD_READ_RESPONSE_LENGTH); | |||||
| assert(response[2U] == 6U); | |||||
| assert(response[3U] == 0x11U); | |||||
| assert(response[4U] == 0x11U); | |||||
| assert(response[5U] == 0x13U); | |||||
| assert(response[6U] == 0x13U); | |||||
| assert(response[7U] == 0x15U); | |||||
| assert(response[8U] == 0x15U); | |||||
| TestAssertFrameCrc(response, responseLength); | |||||
| } | |||||
| /** | |||||
| * @brief 验证 0x43 支持最大 123 个奇数地址寄存器。 | |||||
| * @return 无。 | |||||
| */ | |||||
| static void TestReadOddCountRegistersMaximum(void) | |||||
| { | |||||
| uint8_t request[TEST_ODD_READ_FRAME_LENGTH] = { | |||||
| TEST_SLAVE_ADDRESS, TEST_FC_READ_ODD_COUNT_REGS, | |||||
| 0x00U, 0x00U, 0x00U, 0x7BU, 0x00U, 0x00U | |||||
| }; | |||||
| uint8_t response[MODBUS_RTU_ADU_MAX_LENGTH]; | |||||
| uint16_t responseLength; | |||||
| MODBUS_SLAVE slave; | |||||
| TestInitSlave(&slave); | |||||
| TestHoldingRegisters[245U] = 0x7E57U; | |||||
| TestAppendCrc(request, 6U); | |||||
| assert(ModbusProcessFrame(&slave, request, sizeof(request), response, | |||||
| sizeof(response), &responseLength) | |||||
| == MODBUS_STATUS_OK); | |||||
| assert(responseLength == TEST_ODD_READ_MAX_RESPONSE_LENGTH); | |||||
| assert(response[0U] == TEST_SLAVE_ADDRESS); | |||||
| assert(response[1U] == TEST_FC_READ_ODD_COUNT_REGS); | |||||
| assert(response[2U] == 246U); | |||||
| assert(response[247U] == 0x7EU); | |||||
| assert(response[248U] == 0x57U); | |||||
| TestAssertFrameCrc(response, responseLength); | |||||
| } | |||||
| /** | |||||
| * @brief 验证 0x43 拒绝偶数数量。 | |||||
| * @return 无。 | |||||
| */ | |||||
| static void TestReadOddCountRegistersRejectsEvenCount(void) | |||||
| { | |||||
| uint8_t request[TEST_ODD_READ_FRAME_LENGTH] = { | |||||
| TEST_SLAVE_ADDRESS, TEST_FC_READ_ODD_COUNT_REGS, | |||||
| 0x00U, 0x00U, 0x00U, 0x02U, 0x00U, 0x00U | |||||
| }; | |||||
| uint8_t response[MODBUS_RTU_ADU_MAX_LENGTH]; | |||||
| uint16_t responseLength; | |||||
| MODBUS_SLAVE slave; | |||||
| TestInitSlave(&slave); | |||||
| TestAppendCrc(request, 6U); | |||||
| assert(ModbusProcessFrame(&slave, request, sizeof(request), response, | |||||
| sizeof(response), &responseLength) | |||||
| == MODBUS_STATUS_EXCEPTION); | |||||
| assert(responseLength == MODBUS_RTU_EXCEPTION_LENGTH); | |||||
| assert(response[0U] == TEST_SLAVE_ADDRESS); | |||||
| assert(response[1U] == (TEST_FC_READ_ODD_COUNT_REGS | 0x80U)); | |||||
| assert(response[2U] == MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE); | |||||
| TestAssertFrameCrc(response, responseLength); | |||||
| } | |||||
| /** | |||||
| * @brief 执行 Modbus RTU 私有功能码单元测试。 | |||||
| * @return 0 表示全部测试通过。 | |||||
| */ | |||||
| int main(void) | |||||
| { | |||||
| TestReadExtendedRegister(); | |||||
| TestWriteExtendedRegister(); | |||||
| TestWriteExtendedRegisterRejectsInvalidAddress(); | |||||
| TestReadOddAddressRegisters(); | |||||
| TestReadOddAddressRegistersFromAddressTen(); | |||||
| TestReadOddCountRegistersMaximum(); | |||||
| TestReadOddCountRegistersRejectsEvenCount(); | |||||
| return 0; | |||||
| } | |||||
| @@ -0,0 +1,308 @@ | |||||
| #ifndef APP_RUNTIME_TEST_MAIN_H | |||||
| #define APP_RUNTIME_TEST_MAIN_H | |||||
| #include <stdint.h> | |||||
| #include "modbus.h" | |||||
| #define CPU_CFG_KA_IPL_BOUNDARY (4U) | |||||
| #define APP_TASK_MODBUS_PRIO (4U) | |||||
| #define APP_TASK_START_PRIO (5U) | |||||
| #define APP_TASK_T35_PRIO (6U) | |||||
| #define APP_TASK_START_STK_SIZE (128U) | |||||
| #define APP_TASK_MODBUS_STK_SIZE (384U) | |||||
| #define APP_TASK_T35_STK_SIZE (128U) | |||||
| #define APP_MODBUS_BAUD_RATE (115200U) | |||||
| #define APP_MODBUS_BITS_PER_CHARACTER (10U) | |||||
| #define APP_MODBUS_RTU_CHARACTER_TIME_US (87U) | |||||
| #define APP_MODBUS_RTU_T15_US (750U) | |||||
| #define APP_MODBUS_RTU_T35_US (1750U) | |||||
| #define APP_MODBUS_RTU_T15_RX_TIMEOUT_US (837U) | |||||
| #define APP_MODBUS_RTU_T35_AFTER_T15_US (913U) | |||||
| #define APP_MODBUS_RTU_TIMER_CLOCK_HZ (84000000U) | |||||
| #define APP_MODBUS_RTU_TIMER_TICK_HZ (1000000U) | |||||
| #define APP_MODBUS_STATUS_CHECK_TICKS (1U) | |||||
| #define APP_MODBUS_STATUS_UPDATE_TICKS (100U) | |||||
| #define APP_MODBUS_LINK_TIMEOUT_TICKS (3000U) | |||||
| #define APP_MODBUS_TX_TIMEOUT_TICKS (100U) | |||||
| #define APP_MODBUS_DEBUG_REGISTER_COUNT (16U) | |||||
| #define APP_MODBUS_DEBUG_COIL_COUNT (16U) | |||||
| #define APP_MODBUS_DEBUG_FRAME_SIZE (256U) | |||||
| #define APP_MODBUS_OUTPUT_COIL_COUNT (8U) | |||||
| #define APP_MODBUS_FIRST_USER_OUTPUT_COIL (1U) | |||||
| #define APP_MODBUS_USER_OUTPUT_COIL_COUNT (7U) | |||||
| #define APP_MODBUS_LINK_STATE_COIL (100U) | |||||
| #define APP_MODBUS_LINK_DISCONNECTED (0U) | |||||
| #define APP_MODBUS_LINK_CONNECTED (1U) | |||||
| typedef uint8_t INT8U; | |||||
| typedef uint16_t INT16U; | |||||
| typedef uint32_t INT32U; | |||||
| typedef uint32_t OS_STK; | |||||
| typedef uint32_t OS_CPU_SR; | |||||
| typedef struct | |||||
| { | |||||
| uint8_t value; | |||||
| } OS_EVENT; | |||||
| typedef int32_t IRQn_Type; | |||||
| typedef struct | |||||
| { | |||||
| uint32_t CR1; | |||||
| uint32_t CNT; | |||||
| uint32_t ARR; | |||||
| uint32_t SR; | |||||
| uint32_t PSC; | |||||
| uint32_t EGR; | |||||
| uint32_t DIER; | |||||
| } TIM_TypeDef; | |||||
| typedef struct | |||||
| { | |||||
| uint32_t value; | |||||
| } GPIO_TypeDef; | |||||
| typedef struct | |||||
| { | |||||
| uint32_t value; | |||||
| uint32_t SR; | |||||
| uint32_t DR; | |||||
| } USART_TypeDef; | |||||
| typedef struct | |||||
| { | |||||
| uint32_t PLLState; | |||||
| uint32_t PLLSource; | |||||
| uint32_t PLLM; | |||||
| uint32_t PLLN; | |||||
| uint32_t PLLP; | |||||
| uint32_t PLLQ; | |||||
| } RCC_PLLInitTypeDef; | |||||
| typedef struct | |||||
| { | |||||
| uint32_t OscillatorType; | |||||
| uint32_t HSEState; | |||||
| RCC_PLLInitTypeDef PLL; | |||||
| } RCC_OscInitTypeDef; | |||||
| typedef struct | |||||
| { | |||||
| uint32_t ClockType; | |||||
| uint32_t SYSCLKSource; | |||||
| uint32_t AHBCLKDivider; | |||||
| uint32_t APB1CLKDivider; | |||||
| uint32_t APB2CLKDivider; | |||||
| } RCC_ClkInitTypeDef; | |||||
| typedef struct | |||||
| { | |||||
| uint32_t Pin; | |||||
| uint32_t Mode; | |||||
| uint32_t Pull; | |||||
| uint32_t Speed; | |||||
| uint32_t Alternate; | |||||
| } GPIO_InitTypeDef; | |||||
| typedef struct | |||||
| { | |||||
| uint32_t BaudRate; | |||||
| uint32_t WordLength; | |||||
| uint32_t StopBits; | |||||
| uint32_t Parity; | |||||
| uint32_t Mode; | |||||
| uint32_t HwFlowCtl; | |||||
| uint32_t OverSampling; | |||||
| } UART_InitTypeDef; | |||||
| typedef struct | |||||
| { | |||||
| void *Instance; | |||||
| UART_InitTypeDef Init; | |||||
| uint32_t ErrorCode; | |||||
| uint32_t RxState; | |||||
| } UART_HandleTypeDef; | |||||
| typedef enum | |||||
| { | |||||
| HAL_OK = 0, | |||||
| HAL_ERROR = 1, | |||||
| HAL_BUSY = 2, | |||||
| HAL_TIMEOUT = 3 | |||||
| } HAL_StatusTypeDef; | |||||
| typedef enum | |||||
| { | |||||
| GPIO_PIN_RESET = 0, | |||||
| GPIO_PIN_SET = 1 | |||||
| } GPIO_PinState; | |||||
| extern TIM_TypeDef TestTim5; | |||||
| extern GPIO_TypeDef TestGpioE; | |||||
| extern GPIO_TypeDef TestGpioF; | |||||
| extern GPIO_TypeDef TestGpioI; | |||||
| extern USART_TypeDef TestUsart1; | |||||
| extern OS_EVENT TestFrameSem; | |||||
| extern IRQn_Type TestNvicIrq; | |||||
| extern uint32_t TestNvicPreemptPriority; | |||||
| extern uint32_t TestNvicSubPriority; | |||||
| extern uint32_t TestHalTickCallCount; | |||||
| extern uint32_t TestOsCpuSysTickCallCount; | |||||
| extern HAL_StatusTypeDef TestReceiveResults[4U]; | |||||
| extern uint32_t TestReceiveResultCount; | |||||
| extern uint32_t TestReceiveCallCount; | |||||
| extern uint32_t TestReceiveErrorInjectionCall; | |||||
| extern uint32_t TestAbortReceiveCallCount; | |||||
| extern uint32_t TestUartFlagClearCallCount; | |||||
| extern uint8_t TestRecoveryCallOrder[4U]; | |||||
| extern uint32_t TestRecoveryCallOrderLength; | |||||
| extern uint32_t TestSemPostCallCount; | |||||
| extern uint8_t OSRunning; | |||||
| #define TIM5 (&TestTim5) | |||||
| #define GPIOE (&TestGpioE) | |||||
| #define GPIOF (&TestGpioF) | |||||
| #define GPIOI (&TestGpioI) | |||||
| #define USART1 (&TestUsart1) | |||||
| #define SysTick_IRQn ((IRQn_Type)-1) | |||||
| #define TIM5_IRQn ((IRQn_Type)50) | |||||
| #define USART1_IRQn ((IRQn_Type)37) | |||||
| #define OS_TRUE (1U) | |||||
| #define OS_ERR_NONE (0U) | |||||
| #define OS_PRIO_SELF (0U) | |||||
| #define OS_TASK_OPT_STK_CHK (0U) | |||||
| #define OS_TASK_OPT_STK_CLR (0U) | |||||
| #define TIM_CR1_CEN (0x0001U) | |||||
| #define TIM_SR_UIF (0x0001U) | |||||
| #define TIM_EGR_UG (0x0001U) | |||||
| #define TIM_DIER_UIE (0x0001U) | |||||
| #define GPIO_MODE_OUTPUT_PP (0U) | |||||
| #define GPIO_PULLUP (0U) | |||||
| #define GPIO_SPEED_FREQ_LOW (0U) | |||||
| #define GPIO_PIN_4 (0x0010U) | |||||
| #define GPIO_PIN_5 (0x0020U) | |||||
| #define GPIO_PIN_6 (0x0040U) | |||||
| #define GPIO_PIN_7 (0x0080U) | |||||
| #define GPIO_PIN_8 (0x0100U) | |||||
| #define GPIO_PIN_9 (0x0200U) | |||||
| #define PLC_Q0_PORT (GPIOF) | |||||
| #define PLC_Q0_PIN (GPIO_PIN_6) | |||||
| #define PLC_Q1_PORT (GPIOF) | |||||
| #define PLC_Q1_PIN (GPIO_PIN_8) | |||||
| #define PLC_Q2_PORT (GPIOF) | |||||
| #define PLC_Q2_PIN (GPIO_PIN_7) | |||||
| #define PLC_Q3_PORT (GPIOF) | |||||
| #define PLC_Q3_PIN (GPIO_PIN_9) | |||||
| #define PLC_Q4_PORT (GPIOI) | |||||
| #define PLC_Q4_PIN (GPIO_PIN_8) | |||||
| #define PLC_Q5_PORT (GPIOE) | |||||
| #define PLC_Q5_PIN (GPIO_PIN_6) | |||||
| #define PLC_Q6_PORT (GPIOE) | |||||
| #define PLC_Q6_PIN (GPIO_PIN_5) | |||||
| #define PLC_Q7_PORT (GPIOE) | |||||
| #define PLC_Q7_PIN (GPIO_PIN_4) | |||||
| #define PLC_LINK_LED_PORT (PLC_Q0_PORT) | |||||
| #define PLC_LINK_LED_PIN (PLC_Q0_PIN) | |||||
| #define RCC_OSCILLATORTYPE_HSE (0U) | |||||
| #define RCC_HSE_ON (0U) | |||||
| #define RCC_PLL_ON (0U) | |||||
| #define RCC_PLLSOURCE_HSE (0U) | |||||
| #define RCC_PLLP_DIV2 (0U) | |||||
| #define RCC_CLOCKTYPE_HCLK (0U) | |||||
| #define RCC_CLOCKTYPE_SYSCLK (0U) | |||||
| #define RCC_CLOCKTYPE_PCLK1 (0U) | |||||
| #define RCC_CLOCKTYPE_PCLK2 (0U) | |||||
| #define RCC_SYSCLKSOURCE_PLLCLK (0U) | |||||
| #define RCC_SYSCLK_DIV1 (0U) | |||||
| #define RCC_HCLK_DIV4 (0U) | |||||
| #define RCC_HCLK_DIV2 (0U) | |||||
| #define FLASH_LATENCY_5 (0U) | |||||
| #define PWR_REGULATOR_VOLTAGE_SCALE1 (0U) | |||||
| #define UART_WORDLENGTH_8B (0U) | |||||
| #define UART_STOPBITS_1 (0U) | |||||
| #define UART_PARITY_NONE (0U) | |||||
| #define UART_MODE_TX_RX (0U) | |||||
| #define UART_HWCONTROL_NONE (0U) | |||||
| #define UART_OVERSAMPLING_16 (0U) | |||||
| #define HAL_UART_ERROR_NONE (0U) | |||||
| #define HAL_UART_ERROR_ORE (0x00000008U) | |||||
| #define HAL_UART_STATE_READY (0U) | |||||
| #define HAL_UART_STATE_BUSY_RX (0x22U) | |||||
| #define __HAL_UART_CLEAR_PEFLAG(handle) \ | |||||
| do { (void)(handle); TestUartFlagClearCallCount++; } while (0) | |||||
| #define __HAL_UART_CLEAR_FEFLAG(handle) \ | |||||
| __HAL_UART_CLEAR_PEFLAG(handle) | |||||
| #define __HAL_UART_CLEAR_NEFLAG(handle) \ | |||||
| __HAL_UART_CLEAR_PEFLAG(handle) | |||||
| #define __HAL_UART_CLEAR_OREFLAG(handle) \ | |||||
| __HAL_UART_CLEAR_PEFLAG(handle) | |||||
| #define __HAL_UART_CLEAR_IDLEFLAG(handle) \ | |||||
| __HAL_UART_CLEAR_PEFLAG(handle) | |||||
| #define __HAL_RCC_PWR_CLK_ENABLE() do { } while (0) | |||||
| #define __HAL_PWR_VOLTAGESCALING_CONFIG(v) do { (void)(v); } while (0) | |||||
| #define __HAL_RCC_GPIOE_CLK_ENABLE() do { } while (0) | |||||
| #define __HAL_RCC_GPIOF_CLK_ENABLE() do { } while (0) | |||||
| #define __HAL_RCC_GPIOI_CLK_ENABLE() do { } while (0) | |||||
| #define __HAL_RCC_TIM5_CLK_ENABLE() do { } while (0) | |||||
| #define OS_ENTER_CRITICAL() do { (void)cpu_sr; } while (0) | |||||
| #define OS_EXIT_CRITICAL() do { } while (0) | |||||
| #define __disable_irq() do { } while (0) | |||||
| void HAL_Init(void); | |||||
| HAL_StatusTypeDef HAL_RCC_OscConfig(const RCC_OscInitTypeDef *config); | |||||
| HAL_StatusTypeDef HAL_RCC_ClockConfig(const RCC_ClkInitTypeDef *config, | |||||
| uint32_t latency); | |||||
| void HAL_GPIO_Init(GPIO_TypeDef *port, const GPIO_InitTypeDef *config); | |||||
| void HAL_GPIO_WritePin(GPIO_TypeDef *port, uint16_t pin, | |||||
| GPIO_PinState state); | |||||
| HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *uartHandle); | |||||
| HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *uartHandle, | |||||
| uint8_t *data, uint16_t size); | |||||
| HAL_StatusTypeDef HAL_UART_AbortReceive_IT(UART_HandleTypeDef *uartHandle); | |||||
| HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *uartHandle, | |||||
| uint8_t *data, uint16_t size); | |||||
| HAL_StatusTypeDef HAL_UART_AbortTransmit_IT(UART_HandleTypeDef *uartHandle); | |||||
| void HAL_UART_IRQHandler(UART_HandleTypeDef *uartHandle); | |||||
| void HAL_NVIC_SetPriority(IRQn_Type irqn, uint32_t preemptPriority, | |||||
| uint32_t subPriority); | |||||
| void HAL_NVIC_EnableIRQ(IRQn_Type irqn); | |||||
| void HAL_IncTick(void); | |||||
| void Error_Handler(void); | |||||
| void OSInit(void); | |||||
| void OSStart(void); | |||||
| INT8U OSTaskCreateExt(void (*task)(void *), void *argument, OS_STK *stackTop, | |||||
| INT8U priority, INT16U taskId, OS_STK *stackBottom, | |||||
| INT32U stackSize, void *extension, INT16U options); | |||||
| OS_EVENT *OSSemCreate(INT16U count); | |||||
| void OSSemPend(OS_EVENT *event, uint32_t timeout, INT8U *error); | |||||
| INT8U OSSemPost(OS_EVENT *event); | |||||
| void OSTaskSuspend(INT8U priority); | |||||
| void OSTimeDly(uint32_t ticks); | |||||
| uint32_t OSTimeGet(void); | |||||
| void OSIntEnter(void); | |||||
| void OSIntExit(void); | |||||
| void OSTimeTick(void); | |||||
| void OS_CPU_SysTickHandler(void); | |||||
| MODBUS_STATUS ModbusBackupRestore(MODBUS_SLAVE *slave); | |||||
| MODBUS_STATUS ModbusBackupSave(const MODBUS_SLAVE *slave); | |||||
| #endif | |||||
| @@ -0,0 +1,16 @@ | |||||
| #ifndef APP_RUNTIME_TEST_STM32F4XX_IT_H | |||||
| #define APP_RUNTIME_TEST_STM32F4XX_IT_H | |||||
| void NMI_Handler(void); | |||||
| void HardFault_Handler(void); | |||||
| void MemManage_Handler(void); | |||||
| void BusFault_Handler(void); | |||||
| void UsageFault_Handler(void); | |||||
| void SVC_Handler(void); | |||||
| void DebugMon_Handler(void); | |||||
| void PendSV_Handler(void); | |||||
| void SysTick_Handler(void); | |||||
| void TIM5_IRQHandler(void); | |||||
| void USART1_IRQHandler(void); | |||||
| #endif | |||||
| @@ -0,0 +1,46 @@ | |||||
| #ifndef MODBUS_BACKUP_TEST_MAIN_H | |||||
| #define MODBUS_BACKUP_TEST_MAIN_H | |||||
| #include <stdint.h> | |||||
| typedef struct | |||||
| { | |||||
| volatile uint32_t BKP0R; | |||||
| volatile uint32_t BKP1R; | |||||
| volatile uint32_t BKP2R; | |||||
| volatile uint32_t BKP3R; | |||||
| volatile uint32_t BKP4R; | |||||
| volatile uint32_t BKP5R; | |||||
| volatile uint32_t BKP6R; | |||||
| volatile uint32_t BKP7R; | |||||
| volatile uint32_t BKP8R; | |||||
| volatile uint32_t BKP9R; | |||||
| volatile uint32_t BKP10R; | |||||
| volatile uint32_t BKP11R; | |||||
| volatile uint32_t BKP12R; | |||||
| volatile uint32_t BKP13R; | |||||
| volatile uint32_t BKP14R; | |||||
| volatile uint32_t BKP15R; | |||||
| volatile uint32_t BKP16R; | |||||
| volatile uint32_t BKP17R; | |||||
| volatile uint32_t BKP18R; | |||||
| volatile uint32_t BKP19R; | |||||
| } TEST_RTC_TYPEDEF; | |||||
| extern TEST_RTC_TYPEDEF TestRtc; | |||||
| #define RTC (&TestRtc) | |||||
| #define RCC_RTCCLKSOURCE_NO_CLK (0U) | |||||
| #define RCC_FLAG_LSIRDY (0U) | |||||
| #define RESET (0U) | |||||
| #define __HAL_RCC_PWR_CLK_ENABLE() do { } while (0) | |||||
| #define HAL_PWR_EnableBkUpAccess() do { } while (0) | |||||
| #define __HAL_RCC_GET_RTC_SOURCE() (1U) | |||||
| #define __HAL_RCC_LSI_ENABLE() do { } while (0) | |||||
| #define __HAL_RCC_GET_FLAG(flag) (1U) | |||||
| #define __HAL_RCC_RTC_CONFIG(source) do { } while (0) | |||||
| #define __HAL_RCC_RTC_ENABLE() do { } while (0) | |||||
| #define HAL_GetTick() (0U) | |||||
| #endif | |||||