| @@ -124,4 +124,18 @@ debug | |||
| *.py[cod] | |||
| *$py.class | |||
| 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 | |||
| * @brief 主程序公共定义和 PLC 输出引脚映射。 | |||
| * @brief 主程序公共定义、PLC 输出和连接指示灯引脚映射。 | |||
| * @version 2.0 | |||
| * @author zengbingjie | |||
| * @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 | |||
| @@ -18,11 +18,50 @@ extern "C" { | |||
| #include "stm32f4xx_hal.h" | |||
| #include "app_cfg.h" | |||
| #include "modbus.h" | |||
| #include "os_cfg.h" | |||
| #include "ucos_ii.h" | |||
| /** @brief USART1 的 HAL 句柄,仅由初始化和中断回调路径共享。 */ | |||
| 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_PIN (GPIO_PIN_6) | |||
| #define PLC_Q1_PORT (GPIOF) | |||
| @@ -40,6 +79,10 @@ extern UART_HandleTypeDef Uart1Handle; | |||
| #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) | |||
| /** @brief 关闭中断并停留在不可恢复错误状态。 */ | |||
| void Error_Handler(void); | |||
| #ifdef __cplusplus | |||
| @@ -56,6 +56,11 @@ void SVC_Handler(void); | |||
| void DebugMon_Handler(void); | |||
| void PendSV_Handler(void); | |||
| void SysTick_Handler(void); | |||
| /** @brief 将 TIM5 更新中断转交给 Modbus RTU 静默计时状态机。 */ | |||
| void TIM5_IRQHandler(void); | |||
| /** @brief 将 USART1 收发事件转交给 HAL 回调和 Modbus 接收路径。 */ | |||
| void USART1_IRQHandler(void); | |||
| /* USER CODE BEGIN EFP */ | |||
| @@ -4,12 +4,14 @@ | |||
| * @version 2.0 | |||
| * @author zengbingjie | |||
| * @date 2026-07-29 | |||
| * @copyright 版权所有 (c) 2026 Xinje Electric。 | |||
| * @copyright 版权所有信捷科技股份有限公司 | |||
| * @note 修改记录:2.0 增加严格 RTU 校验和广播处理。 | |||
| */ | |||
| #include "modbus.h" | |||
| /* RTU ADU 布局为:地址和功能码在前,后面是 PDU 和两字节 CRC。 | |||
| * 以下偏移量由所有请求和响应处理路径共用。 */ | |||
| #define MODBUS_RTU_ADDRESS_INDEX (0U) | |||
| #define MODBUS_RTU_FUNCTION_INDEX (1U) | |||
| #define MODBUS_RTU_DATA_INDEX (2U) | |||
| @@ -24,7 +26,10 @@ | |||
| typedef enum { | |||
| MODBUS_REQUEST_OK = 0, | |||
| 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; | |||
| /** | |||
| @@ -51,6 +56,8 @@ static uint8_t ModbusIsUnicastAddress(uint8_t slaveAddress) | |||
| static uint8_t ModbusIsDataRangeValid(uint16_t startAddress, | |||
| uint16_t dataCount) | |||
| { | |||
| /* 使用减法而不是 startAddress + dataCount,避免 16 位溢出后错误地将越界 | |||
| * 请求判断为有效。 */ | |||
| if ((dataCount == 0U) || (startAddress > MODBUS_DATA_ADDRESS_MAX)) { | |||
| return 0U; | |||
| } | |||
| @@ -62,6 +69,23 @@ static uint8_t ModbusIsDataRangeValid(uint16_t startAddress, | |||
| 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 位数值。 | |||
| * @param[in] buffer 数据缓冲区。 | |||
| @@ -71,11 +95,42 @@ static uint16_t ModbusReadU16(const uint8_t *buffer) | |||
| { | |||
| uint16_t value; | |||
| /* Modbus 字段采用大端序(高字节在前),但 CRC 在总线上按低字节在前发送。 */ | |||
| value = ((uint16_t)buffer[0] << 8U) | (uint16_t)buffer[1]; | |||
| 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 位数值。 | |||
| * @param[out] buffer 数据缓冲区。 | |||
| @@ -108,6 +163,7 @@ static MODBUS_STATUS ModbusAppendCrc(uint8_t *buffer, uint16_t capacity, | |||
| return MODBUS_STATUS_FRAME_ERROR; | |||
| } | |||
| /* 按 Modbus RTU 要求,以小端序追加 CRC 值。 */ | |||
| crcValue = ModbusCrc16(buffer, *length); | |||
| buffer[*length] = (uint8_t)(crcValue & 0x00FFU); | |||
| 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; | |||
| } | |||
| /* 对去掉末尾两个字节的 ADU 重新计算 CRC,再与收到的低字节/高字节表示进行比较。 */ | |||
| expectedCrc = ModbusCrc16(buffer, length - MODBUS_RTU_CRC_LENGTH); | |||
| actualCrc = (uint16_t)buffer[length - MODBUS_RTU_CRC_LENGTH] | |||
| | ((uint16_t)buffer[length - 1U] << 8U); | |||
| @@ -168,10 +225,15 @@ static MODBUS_STATUS ModbusBuildException(uint8_t slaveAddress, | |||
| return MODBUS_STATUS_INVALID_ARGUMENT; | |||
| } | |||
| if ((functionCode & MODBUS_RTU_EXCEPTION_MASK) != 0U) { | |||
| return MODBUS_STATUS_FRAME_ERROR; | |||
| } | |||
| if (txCapacity < MODBUS_RTU_EXCEPTION_LENGTH) { | |||
| return MODBUS_STATUS_FRAME_ERROR; | |||
| } | |||
| /* 标准异常响应格式为:原功能码 | 0x80,后跟一个字节的异常码和重新计算的 CRC。 */ | |||
| txBuffer[MODBUS_RTU_ADDRESS_INDEX] = slaveAddress; | |||
| txBuffer[MODBUS_RTU_FUNCTION_INDEX] = functionCode | |||
| | MODBUS_RTU_EXCEPTION_MASK; | |||
| @@ -197,6 +259,8 @@ static uint8_t ModbusReadCoilBit(const MODBUS_SLAVE *slave, uint16_t address) | |||
| uint8_t bitIndex; | |||
| uint8_t value; | |||
| /* 线圈按低位在前压缩:线圈 0 是字节 0 的 bit 0,线圈 8 是字节 1 的 bit 0。 | |||
| * 构造 0x01/0x0F 帧时使用相同的映射关系。 */ | |||
| byteIndex = address / MODBUS_BITS_PER_BYTE; | |||
| bitIndex = (uint8_t)(address % MODBUS_BITS_PER_BYTE); | |||
| 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 bitMask; | |||
| /* 只更新选中的位,保持同一压缩字节中相邻线圈的值不变。 */ | |||
| byteIndex = address / MODBUS_BITS_PER_BYTE; | |||
| bitIndex = (uint8_t)(address % MODBUS_BITS_PER_BYTE); | |||
| bitMask = (uint8_t)(1U << bitIndex); | |||
| @@ -257,7 +322,7 @@ static MODBUS_REQUEST_RESULT ModbusBuildReadCoilsResponse( | |||
| byteCount = (coilCount + 7U) / MODBUS_BITS_PER_BYTE; | |||
| 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; | |||
| @@ -267,6 +332,7 @@ static MODBUS_REQUEST_RESULT ModbusBuildReadCoilsResponse( | |||
| txBuffer[3U + byteIndex] = 0U; | |||
| } | |||
| /* 响应将第一个请求线圈放入 bit 0,并保持最后一个数据字节中未使用的高位为 0。 */ | |||
| for (coilOffset = 0U; coilOffset < coilCount; coilOffset++) { | |||
| if (ModbusReadCoilBit(slave, startAddress + coilOffset) != 0U) { | |||
| byteIndex = coilOffset / MODBUS_BITS_PER_BYTE; | |||
| @@ -278,7 +344,7 @@ static MODBUS_REQUEST_RESULT ModbusBuildReadCoilsResponse( | |||
| *txLength = 3U + byteCount; | |||
| status = ModbusAppendCrc(txBuffer, txCapacity, txLength); | |||
| if (status != MODBUS_STATUS_OK) { | |||
| return MODBUS_REQUEST_ILLEGAL_VALUE; | |||
| return MODBUS_REQUEST_RESPONSE_ERROR; | |||
| } | |||
| return MODBUS_REQUEST_OK; | |||
| @@ -314,7 +380,7 @@ static MODBUS_REQUEST_RESULT ModbusBuildReadRegistersResponse( | |||
| byteCount = registerCount * 2U; | |||
| 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; | |||
| @@ -329,7 +395,7 @@ static MODBUS_REQUEST_RESULT ModbusBuildReadRegistersResponse( | |||
| *txLength = 3U + byteCount; | |||
| status = ModbusAppendCrc(txBuffer, txCapacity, txLength); | |||
| if (status != MODBUS_STATUS_OK) { | |||
| return MODBUS_REQUEST_ILLEGAL_VALUE; | |||
| return MODBUS_REQUEST_RESPONSE_ERROR; | |||
| } | |||
| return MODBUS_REQUEST_OK; | |||
| @@ -355,6 +421,8 @@ static MODBUS_REQUEST_RESULT ModbusProcessWriteSingleCoil( | |||
| uint16_t coilValue; | |||
| uint16_t index; | |||
| /* 功能码 0x05 的 ADU 固定为 8 字节,必须检查两种合法线圈值;接受任意非零值 | |||
| * 会违反 Modbus 规范。 */ | |||
| if (rxLength != MODBUS_RTU_READ_REQUEST_LENGTH) { | |||
| return MODBUS_REQUEST_ILLEGAL_VALUE; | |||
| } | |||
| @@ -370,16 +438,18 @@ static MODBUS_REQUEST_RESULT ModbusProcessWriteSingleCoil( | |||
| 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)); | |||
| if (isBroadcast != 0U) { | |||
| *txLength = 0U; | |||
| return MODBUS_REQUEST_OK; | |||
| } | |||
| if (txCapacity < MODBUS_RTU_WRITE_RESPONSE_LENGTH) { | |||
| return MODBUS_REQUEST_ILLEGAL_VALUE; | |||
| } | |||
| /* 单播写响应必须完整回显请求(包括原 CRC)。广播写入会执行,但按规范不发送响应。 */ | |||
| for (index = 0U; index < rxLength; index++) { | |||
| txBuffer[index] = rxBuffer[index]; | |||
| } | |||
| @@ -416,16 +486,17 @@ static MODBUS_REQUEST_RESULT ModbusProcessWriteSingleRegister( | |||
| 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]); | |||
| if (isBroadcast != 0U) { | |||
| *txLength = 0U; | |||
| return MODBUS_REQUEST_OK; | |||
| } | |||
| if (txCapacity < MODBUS_RTU_WRITE_RESPONSE_LENGTH) { | |||
| return MODBUS_REQUEST_ILLEGAL_VALUE; | |||
| } | |||
| for (index = 0U; index < rxLength; index++) { | |||
| txBuffer[index] = rxBuffer[index]; | |||
| } | |||
| @@ -456,6 +527,8 @@ static MODBUS_REQUEST_RESULT ModbusProcessWriteMultipleCoils( | |||
| uint16_t coilOffset; | |||
| uint8_t coilValue; | |||
| /* 读取数据或修改线圈前,先校验数量、字节数和总长度,防止畸形帧只修改请求范围 | |||
| * 的前一部分。 */ | |||
| if (rxLength < MODBUS_RTU_WRITE_REQUEST_BASE_LENGTH) { | |||
| return MODBUS_REQUEST_ILLEGAL_VALUE; | |||
| } | |||
| @@ -479,6 +552,12 @@ static MODBUS_REQUEST_RESULT ModbusProcessWriteMultipleCoils( | |||
| 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++) { | |||
| coilValue = (uint8_t)((rxBuffer[MODBUS_RTU_WRITE_DATA_INDEX | |||
| + (coilOffset / MODBUS_BITS_PER_BYTE)] | |||
| @@ -491,17 +570,13 @@ static MODBUS_REQUEST_RESULT ModbusProcessWriteMultipleCoils( | |||
| 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_FUNCTION_INDEX] = MODBUS_FC_WRITE_MULTIPLE_COILS; | |||
| ModbusWriteU16(&txBuffer[2U], address); | |||
| ModbusWriteU16(&txBuffer[4U], coilCount); | |||
| *txLength = 6U; | |||
| if (ModbusAppendCrc(txBuffer, txCapacity, txLength) != MODBUS_STATUS_OK) { | |||
| return MODBUS_REQUEST_ILLEGAL_VALUE; | |||
| return MODBUS_REQUEST_RESPONSE_ERROR; | |||
| } | |||
| return MODBUS_REQUEST_OK; | |||
| @@ -528,6 +603,8 @@ static MODBUS_REQUEST_RESULT ModbusProcessWriteMultipleRegisters( | |||
| uint16_t byteCount; | |||
| uint16_t registerOffset; | |||
| /* 功能码 0x10 为每个寄存器携带一个大端序 16 位值。所有帧结构检查都在写入循环 | |||
| * 之前完成,避免部分写入。 */ | |||
| if (rxLength < MODBUS_RTU_WRITE_REQUEST_BASE_LENGTH) { | |||
| return MODBUS_REQUEST_ILLEGAL_VALUE; | |||
| } | |||
| @@ -552,6 +629,11 @@ static MODBUS_REQUEST_RESULT ModbusProcessWriteMultipleRegisters( | |||
| return MODBUS_REQUEST_ILLEGAL_ADDRESS; | |||
| } | |||
| if ((isBroadcast == 0U) | |||
| && (txCapacity < MODBUS_RTU_WRITE_RESPONSE_LENGTH)) { | |||
| return MODBUS_REQUEST_RESPONSE_ERROR; | |||
| } | |||
| for (registerOffset = 0U; registerOffset < registerCount; | |||
| registerOffset++) { | |||
| slave->holdingRegisters[address + registerOffset] = ModbusReadU16( | |||
| @@ -563,19 +645,140 @@ static MODBUS_REQUEST_RESULT ModbusProcessWriteMultipleRegisters( | |||
| 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_FUNCTION_INDEX] = MODBUS_FC_WRITE_MULTIPLE_REGS; | |||
| ModbusWriteU16(&txBuffer[2U], address); | |||
| ModbusWriteU16(&txBuffer[4U], registerCount); | |||
| *txLength = 6U; | |||
| 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; | |||
| } | |||
| 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; | |||
| } | |||
| @@ -595,6 +798,8 @@ uint16_t ModbusCrc16(const uint8_t *buffer, uint16_t length) | |||
| return 0U; | |||
| } | |||
| /* Modbus CRC-16 使用初值 0xFFFF 和反射多项式 0xA001。按最低有效位优先处理, | |||
| * 与 RTU 总线字节序一致,也不依赖 MCU 本身的字节序。 */ | |||
| crcValue = MODBUS_RTU_CRC_INIT; | |||
| for (byteIndex = 0U; byteIndex < length; 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 的单播从站地址。 | |||
| * @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 coilByteIndex; | |||
| uint16_t coilAddress; | |||
| if (slave == NULL) { | |||
| if ((slave == NULL) || (holdingRegisterStorage == NULL) | |||
| || (coilBitStorage == NULL)) { | |||
| return MODBUS_STATUS_INVALID_ARGUMENT; | |||
| } | |||
| @@ -632,6 +842,11 @@ MODBUS_STATUS ModbusSlaveInit(MODBUS_SLAVE *slave, uint8_t slaveAddress) | |||
| } | |||
| slave->slaveAddress = slaveAddress; | |||
| slave->holdingRegisters = holdingRegisterStorage; | |||
| slave->coilStorage = coilBitStorage; | |||
| slave->extendedHoldingRegister = NULL; | |||
| /* 启动时清空完整的普通数据模型。扩展值单独初始化,因为随后可能从 RTC 备份 | |||
| * 寄存器恢复。 */ | |||
| for (registerIndex = 0U; registerIndex < MODBUS_DATA_POINT_COUNT; | |||
| registerIndex++) { | |||
| slave->holdingRegisters[registerIndex] = 0U; | |||
| @@ -658,6 +873,21 @@ MODBUS_STATUS ModbusSlaveInit(MODBUS_SLAVE *slave, uint8_t slaveAddress) | |||
| 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 从从站数据模型中读取线圈值。 | |||
| * @param[in] slave 从站数据对象。 | |||
| @@ -759,6 +989,8 @@ MODBUS_STATUS ModbusSetHoldingRegister(MODBUS_SLAVE *slave, | |||
| * @param[in] txCapacity 响应 ADU 缓冲区容量,单位为字节。 | |||
| * @param[out] txLength 生成的响应长度,单位为字节。 | |||
| * @return 请求处理状态;广播写入请求不生成响应。 | |||
| * @note 处理顺序固定为参数、帧长度、CRC、站号、功能码和字段校验, | |||
| * 因而 CRC 错误或非本机帧绝不会写入数据区。 | |||
| */ | |||
| MODBUS_STATUS ModbusProcessFrame(MODBUS_SLAVE *slave, | |||
| const uint8_t *rxBuffer, uint16_t rxLength, | |||
| @@ -772,13 +1004,22 @@ MODBUS_STATUS ModbusProcessFrame(MODBUS_SLAVE *slave, | |||
| uint8_t isBroadcast; | |||
| uint16_t startAddress; | |||
| uint16_t dataCount; | |||
| uint16_t registerValue; | |||
| uint32_t extendedAddress; | |||
| if ((slave == NULL) || (rxBuffer == NULL) || (txBuffer == NULL) | |||
| || (txLength == NULL)) { | |||
| return MODBUS_STATUS_INVALID_ARGUMENT; | |||
| } | |||
| /* 始终从空响应开始。被忽略的帧、CRC 错误帧和广播写入都必须保持 txLength 为 0。 */ | |||
| *txLength = 0U; | |||
| if (rxLength > MODBUS_RTU_ADU_MAX_LENGTH) { | |||
| return MODBUS_STATUS_FRAME_ERROR; | |||
| } | |||
| /* 校验顺序是有意设计的:先拒绝格式错误或 CRC 错误的帧,再检查地址,最后才访问 | |||
| * 数据模型。 */ | |||
| status = ModbusValidateCrc(rxBuffer, rxLength); | |||
| if (status != MODBUS_STATUS_OK) { | |||
| return status; | |||
| @@ -791,8 +1032,15 @@ MODBUS_STATUS ModbusProcessFrame(MODBUS_SLAVE *slave, | |||
| } | |||
| 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); | |||
| requestResult = MODBUS_REQUEST_OK; | |||
| /* 每个处理函数负责校验自身的固定/可变帧格式和数据边界,然后返回内部结果, | |||
| * 由下面的逻辑映射为标准异常响应。 */ | |||
| switch (functionCode) { | |||
| case MODBUS_FC_READ_COILS: | |||
| if (isBroadcast != 0U) { | |||
| @@ -822,6 +1070,50 @@ MODBUS_STATUS ModbusProcessFrame(MODBUS_SLAVE *slave, | |||
| slave, startAddress, dataCount, txBuffer, txCapacity, txLength); | |||
| 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: | |||
| requestResult = ModbusProcessWriteSingleCoil( | |||
| slave, rxBuffer, rxLength, isBroadcast, txBuffer, txCapacity, | |||
| @@ -855,6 +1147,12 @@ MODBUS_STATUS ModbusProcessFrame(MODBUS_SLAVE *slave, | |||
| txBuffer, txCapacity, txLength); | |||
| } | |||
| /* 响应缓冲区或内部处理失败不是协议异常,应该报告给应用层,而不能伪造一帧 | |||
| * 发送到总线上的异常响应。 */ | |||
| if (requestResult == MODBUS_REQUEST_RESPONSE_ERROR) { | |||
| return MODBUS_STATUS_FRAME_ERROR; | |||
| } | |||
| if (requestResult != MODBUS_REQUEST_OK) { | |||
| if (isBroadcast != 0U) { | |||
| return MODBUS_STATUS_IGNORED; | |||
| @@ -4,7 +4,7 @@ | |||
| * @version 2.0 | |||
| * @author zengbingjie | |||
| * @date 2026-07-29 | |||
| * @copyright 版权所有 (c) 2026 Xinje Electric。 | |||
| * @copyright 版权所有信捷科技股份有限公司 | |||
| * @note 修改记录:2.0 重构帧校验和从站接口。 | |||
| */ | |||
| @@ -22,7 +22,8 @@ | |||
| #define MODBUS_RTU_WRITE_RESPONSE_LENGTH (8U) | |||
| #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_SLAVE_ADDRESS_MIN (1U) | |||
| @@ -30,8 +31,10 @@ | |||
| #define MODBUS_BROADCAST_ADDRESS (0U) | |||
| #define MODBUS_SLAVE_ADDR_DEFAULT (1U) | |||
| /* 普通线圈和保持寄存器共用从 0 开始的逻辑地址范围 0x0000..0x270F。 | |||
| * 两端都包含在内,所以数据点数量为最大地址加 1。 */ | |||
| #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_COIL_STORAGE_SIZE \ | |||
| ((MODBUS_DATA_POINT_COUNT + 7U) / 8U) | |||
| @@ -43,12 +46,20 @@ | |||
| #define MODBUS_FC_WRITE_MULTIPLE_COILS (0x0FU) | |||
| #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_DATA_ADDR (0x02U) | |||
| #define MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE (0x03U) | |||
| #define MODBUS_MAX_READ_COIL_COUNT (2000U) | |||
| #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_REGISTER_COUNT (123U) | |||
| @@ -56,19 +67,22 @@ | |||
| #define MODBUS_COIL_OFF_VALUE (0x0000U) | |||
| 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; | |||
| typedef struct { | |||
| struct MODBUS_SLAVE; | |||
| typedef struct MODBUS_SLAVE { | |||
| 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; | |||
| /** | |||
| @@ -85,7 +99,21 @@ uint16_t ModbusCrc16(const uint8_t *buffer, uint16_t length); | |||
| * @param[in] slaveAddress 范围为 1 至 247 的单播从站地址。 | |||
| * @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 从从站数据模型读取线圈值。 | |||
| @@ -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 | |||
| * @author zengbingjie | |||
| * @date 2026-07-29 | |||
| * @copyright 版权所有 (c) 2026 Xinje Electric。 | |||
| * @copyright 版权所有信捷科技股份有限公司 | |||
| * @note 修改记录:2.0 增加 Modbus RTU 从站任务配置。 | |||
| */ | |||
| @@ -18,31 +18,49 @@ | |||
| #define APP_TASK_START_PRIO (5U) | |||
| #define APP_TASK_T35_PRIO (6U) | |||
| // uC/OS-II 定时器管理任务使用低优先级保留任务槽。 | |||
| /* uC/OS-II 定时器管理任务使用低优先级保留任务槽。 */ | |||
| #define OS_TASK_TMR_PRIO (OS_LOWEST_PRIO - 2U) | |||
| #define APP_TASK_START_STK_SIZE (128U) | |||
| #define APP_TASK_MODBUS_STK_SIZE (384U) | |||
| #define APP_TASK_T35_STK_SIZE (128U) | |||
| /* USART1 使用 115200 bit/s、8E1:1 个起始位、8 个数据位、偶校验和 1 个停止位。 */ | |||
| #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_LINK_TIMEOUT_TICKS (5000U) | |||
| #define APP_MODBUS_LINK_TIMEOUT_TICKS (3000U) | |||
| #define APP_MODBUS_TX_TIMEOUT_TICKS (100U) | |||
| /* 线圈 0 是链路状态;线圈 1 至 7 映射为实际 Q1 至 Q7 输出。 */ | |||
| #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_CONNECTED (1U) | |||
| @@ -4,16 +4,18 @@ | |||
| * @version 2.0 | |||
| * @author zengbingjie | |||
| * @date 2026-07-29 | |||
| * @copyright 版权所有 (c) 2026 Xinje Electric。 | |||
| * @copyright 版权所有信捷科技股份有限公司 | |||
| * @note 修改记录:2.0 增加帧隔离、连接状态和可靠收发处理。 | |||
| */ | |||
| #include "main.h" | |||
| #include "modbus.h" | |||
| #include "modbus_backup.h" | |||
| #define APP_MODBUS_STATUS_REGISTER_COUNT (8U) | |||
| #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 AppTaskModbusStk[APP_TASK_MODBUS_STK_SIZE]; | |||
| @@ -21,27 +23,44 @@ static OS_STK AppTaskT35Stk[APP_TASK_T35_STK_SIZE]; | |||
| 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 OS_EVENT *ModbusFrameSem; | |||
| static OS_EVENT *ModbusTxSem; | |||
| /* 接收缓冲区由 UART 中断逐字节填充。只有 RTU 定时器置位 | |||
| * ModbusFrameReady 后,Modbus 任务才读取它,因此解析器不会读到仍在接收中的帧。 */ | |||
| static uint8_t ModbusRxBuffer[MODBUS_RX_BUF_SIZE]; | |||
| static uint8_t ModbusTxBuffer[MODBUS_TX_BUF_SIZE]; | |||
| /* 以下状态由中断和任务共享;更新应保持简短,读取完整帧快照时使用已有的临界区保护。 */ | |||
| static volatile uint16_t ModbusRxLength; | |||
| static volatile uint16_t ModbusFrameLength; | |||
| static volatile uint32_t ModbusLastRxTick; | |||
| static volatile uint8_t ModbusFrameReady; | |||
| 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 ModbusOperationRxCount; | |||
| static uint32_t ModbusOperationTxCount; | |||
| static uint32_t ModbusProtocolErrorCount; | |||
| static uint8_t ModbusHasActivity; | |||
| 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_Q2_PORT, | |||
| PLC_Q3_PORT, | |||
| @@ -51,8 +70,9 @@ static GPIO_TypeDef *const AppOutputPorts[APP_MODBUS_OUTPUT_COIL_COUNT] = { | |||
| 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_Q2_PIN, | |||
| PLC_Q3_PIN, | |||
| @@ -67,20 +87,27 @@ void SystemClock_Config(void); | |||
| static void AppTaskStart(void *argument); | |||
| static void AppTaskModbus(void *argument); | |||
| static void AppTaskT35(void *argument); | |||
| static void AppInitSystemTick(void); | |||
| static void AppInitGpio(void); | |||
| static void AppInitUart1(void); | |||
| static void AppInitModbusRtuTimer(void); | |||
| static INT8U AppCreateTasks(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 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 INT8U AppPostModbusFrameSem(void); | |||
| 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 void AppProcessFrame(uint16_t rxLength); | |||
| static void AppRecordIsrError(void); | |||
| /** | |||
| * @brief 应用程序入口。 | |||
| @@ -90,11 +117,17 @@ int main(void) | |||
| { | |||
| INT8U osError; | |||
| /* 启动调度器前必须先完成硬件初始化。尤其要先初始化 USART1 和 TIM5, | |||
| * 这样任务才能安全地启动接收和使用 RTU 帧间隔定时器。 */ | |||
| HAL_Init(); | |||
| AppInitSystemTick(); | |||
| SystemClock_Config(); | |||
| AppInitGpio(); | |||
| AppInitUart1(); | |||
| AppInitModbusRtuTimer(); | |||
| /* OSStart() 之后由 uC/OS-II 负责任务调度;启动任务会创建下面的 | |||
| * Modbus 工作任务和定时监测任务。 */ | |||
| OSInit(); | |||
| osError = OSTaskCreateExt( | |||
| 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 板卡输出为低电平有效,初始状态全部关闭。 | |||
| */ | |||
| static void AppInitGpio(void) | |||
| @@ -186,9 +227,11 @@ static void AppInitUart1(void) | |||
| { | |||
| Uart1Handle.Instance = USART1; | |||
| 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.Parity = UART_PARITY_NONE; | |||
| Uart1Handle.Init.Parity = UART_PARITY_EVEN; | |||
| Uart1Handle.Init.Mode = UART_MODE_TX_RX; | |||
| Uart1Handle.Init.HwFlowCtl = UART_HWCONTROL_NONE; | |||
| 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 工作任务与帧间隔检测任务。 | |||
| * @return uC/OS-II 错误码。 | |||
| @@ -258,19 +321,108 @@ static HAL_StatusTypeDef AppStartUartReception(void) | |||
| OS_CPU_SR cpu_sr = 0U; | |||
| HAL_StatusTypeDef halStatus; | |||
| /* 新帧必须从干净的软件状态开始。这里使用临界区,避免定时器或 UART 中断 | |||
| * 看到只清除了一半的状态字段。 */ | |||
| OS_ENTER_CRITICAL(); | |||
| AppStopModbusRtuTimer(); | |||
| ModbusRxLength = 0U; | |||
| ModbusFrameLength = 0U; | |||
| ModbusFrameReady = 0U; | |||
| ModbusRxOverflow = 0U; | |||
| ModbusLastRxTick = OSTimeGet(); | |||
| ModbusRtuTimerPhase = APP_MODBUS_RTU_TIMER_PHASE_T15; | |||
| ModbusInterCharacterTimeout = 0U; | |||
| ModbusRxDiscard = 0U; | |||
| OS_EXIT_CRITICAL(); | |||
| /* 每次只挂接一个字节的接收。接收完成回调负责推进缓冲区并重新挂接下一个 | |||
| * 字节,使组帧完全由中断驱动且不会阻塞 Modbus 任务。 */ | |||
| 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; | |||
| } | |||
| /** | |||
| * @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 从站响应帧。 | |||
| * @param[in] txLength 响应帧长度。 | |||
| @@ -285,6 +437,8 @@ static HAL_StatusTypeDef AppTransmitResponse(uint16_t txLength) | |||
| return HAL_ERROR; | |||
| } | |||
| /* 发送是异步进行的。任务等待 ModbusTxSem,确保 USART1 发出最后一个字节前 | |||
| * 不会复用同一个发送缓冲区。 */ | |||
| halStatus = HAL_UART_Transmit_IT(&Uart1Handle, ModbusTxBuffer, txLength); | |||
| if (halStatus != HAL_OK) { | |||
| 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; | |||
| uint16_t frameLength; | |||
| /* 定时器中断将 ModbusFrameLength 和 ModbusFrameReady 作为一个逻辑事件发布。 | |||
| * 这里原子地读取快照,避免任务拿到属于另一种缓冲区状态的长度。 */ | |||
| frameLength = 0U; | |||
| OS_ENTER_CRITICAL(); | |||
| if (ModbusFrameReady != 0U) { | |||
| @@ -366,6 +553,8 @@ static INT8U AppPostModbusFrameSem(void) | |||
| { | |||
| INT8U osError; | |||
| /* RTU 定时器、UART 错误路径和周期链路检查等多个来源共用一个信号量。 | |||
| * 任务会再次检查帧长度,因此没有完整帧的唤醒也是安全的,并能刷新链路指示。 */ | |||
| osError = OSSemPost(ModbusFrameSem); | |||
| return osError; | |||
| @@ -387,17 +576,15 @@ static uint8_t AppIsModbusStatusAccepted(MODBUS_STATUS status) | |||
| } | |||
| /** | |||
| * @brief 判断功能码是否为触摸屏的写操作。 | |||
| * @brief 判断功能码是否为需要保存到 RTC 的写操作。 | |||
| * @param[in] functionCode Modbus 功能码。 | |||
| * @return 写操作返回 1,否则返回 0。 | |||
| * @note 仅 0x42 修改 0x00010000;标准寄存器和线圈写入不具备掉电保持承诺。 | |||
| */ | |||
| static uint8_t AppIsModbusWriteFunction(uint8_t functionCode) | |||
| static uint8_t AppIsModbusRetentionWriteFunction(uint8_t 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; | |||
| default: | |||
| @@ -406,104 +593,75 @@ static uint8_t AppIsModbusWriteFunction(uint8_t functionCode) | |||
| } | |||
| /** | |||
| * @brief 更新供触摸屏读取的通信状态寄存器。 | |||
| * @brief 更新连接线圈和 PLC 连接指示灯。 | |||
| * @return 成功返回 MODBUS_STATUS_OK;失败返回错误状态。 | |||
| * @note 仅已寻址的正常响应、广播写或异常响应刷新活动时间;CRC 错误和 | |||
| * 非本机帧不会误报链路在线。 | |||
| */ | |||
| static MODBUS_STATUS AppUpdateStatusRegisters(void) | |||
| static MODBUS_STATUS AppUpdateLinkStatus(void) | |||
| { | |||
| MODBUS_STATUS status; | |||
| uint32_t currentTick; | |||
| uint8_t linkState; | |||
| uint16_t registerValue; | |||
| GPIO_PinState pinState; | |||
| currentTick = OSTimeGet(); | |||
| 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; | |||
| } | |||
| 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; | |||
| } | |||
| 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;失败返回错误状态。 | |||
| * @note 板级输出为低有效,因此逻辑线圈置 1 时向 GPIO 写入 GPIO_PIN_RESET。 | |||
| */ | |||
| static MODBUS_STATUS AppSyncCoilsToGpio(void) | |||
| { | |||
| MODBUS_STATUS status; | |||
| uint16_t coilOffset; | |||
| uint16_t coilAddress; | |||
| uint8_t coilValue; | |||
| 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); | |||
| if (status != MODBUS_STATUS_OK) { | |||
| if (status != MODBUS_STATUS_OK) | |||
| { | |||
| return status; | |||
| } | |||
| 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; | |||
| @@ -512,76 +670,72 @@ static MODBUS_STATUS AppSyncCoilsToGpio(void) | |||
| /** | |||
| * @brief 处理已封存的 Modbus RTU 请求帧。 | |||
| * @param[in] rxLength 请求帧长度。 | |||
| * @note 此函数在 Modbus 任务上下文执行。它负责协议分发、链路活动刷新、 | |||
| * 线圈 GPIO 同步、0x42 成功后的 RTC 保存和异步 UART 应答启动。 | |||
| */ | |||
| static void AppProcessFrame(uint16_t rxLength) | |||
| { | |||
| HAL_StatusTypeDef halStatus; | |||
| MODBUS_STATUS status; | |||
| uint16_t txLength; | |||
| uint8_t functionCode; | |||
| uint8_t isWriteOperation; | |||
| txLength = 0U; | |||
| functionCode = 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; | |||
| ModbusProtocolErrorCount++; | |||
| return; | |||
| } | |||
| if (ModbusRxOverflow != 0U) { | |||
| if (ModbusRxOverflow != 0U) | |||
| { | |||
| ModbusLastStatus = MODBUS_STATUS_FRAME_ERROR; | |||
| ModbusProtocolErrorCount++; | |||
| return; | |||
| } | |||
| /* 协议解析固定放在任务上下文中执行。这样解析器可以更新线圈/寄存器并构造 | |||
| * 响应,而不必在 UART 或定时器中断中执行较长的处理。 */ | |||
| status = ModbusProcessFrame(&ModbusSlave, ModbusRxBuffer, rxLength, | |||
| ModbusTxBuffer, MODBUS_TX_BUF_SIZE, | |||
| &txLength); | |||
| ModbusLastStatus = status; | |||
| if (AppIsModbusStatusAccepted(status) != 0U) { | |||
| /* 只有 CRC 正确且寻址到本站的帧(包括合法广播写入或已生成的异常响应)才算 | |||
| * 有效链路活动,线路上的随机干扰不能错误点亮连接指示灯。 */ | |||
| if (AppIsModbusStatusAccepted(status) != 0U) | |||
| { | |||
| ModbusLastActivityTick = OSTimeGet(); | |||
| 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 协议处理任务。 | |||
| * @param[in] argument 未使用。 | |||
| @@ -593,28 +747,48 @@ static void AppTaskModbus(void *argument) | |||
| uint16_t rxLength; | |||
| (void)argument; | |||
| if (ModbusSlaveInit(&ModbusSlave, MODBUS_SLAVE_ADDR_DEFAULT) | |||
| /* 先将协议模型绑定到 RAM 数据区,再恢复由 RTC 备份的扩展寄存器,确保第一条 | |||
| * 请求到达前数据已经准备好。 */ | |||
| if (ModbusSlaveInit(&ModbusSlave, MODBUS_SLAVE_ADDR_DEFAULT, | |||
| holdingRegisters, coilStorage) | |||
| != MODBUS_STATUS_OK) { | |||
| Error_Handler(); | |||
| } | |||
| if (ModbusConfigureExtendedHoldingRegister(&ModbusSlave, | |||
| &extendedHoldingRegister) | |||
| != MODBUS_STATUS_OK) { | |||
| Error_Handler(); | |||
| } | |||
| if (ModbusBackupRestore(&ModbusSlave) != MODBUS_STATUS_OK) { | |||
| Error_Handler(); | |||
| } | |||
| if (AppSyncCoilsToGpio() != MODBUS_STATUS_OK) { | |||
| Error_Handler(); | |||
| } | |||
| if (AppUpdateStatusRegisters() != MODBUS_STATUS_OK) { | |||
| if (AppUpdateLinkStatus() != MODBUS_STATUS_OK) { | |||
| Error_Handler(); | |||
| } | |||
| halStatus = AppStartUartReception(); | |||
| halStatus = AppRecoverUartReception(); | |||
| if (halStatus != HAL_OK) { | |||
| Error_Handler(); | |||
| ModbusReceptionNeedsRecovery = 1U; | |||
| } | |||
| while (1) { | |||
| /* 完整帧解析和 UART 恢复由本任务负责。中断回调只收集字节、标记帧边界或 | |||
| * 错误,并投递此信号量。 */ | |||
| halStatus = AppEnsureUartReception(); | |||
| if (halStatus != HAL_OK) { | |||
| OSTimeDly(1U); | |||
| continue; | |||
| } | |||
| OSSemPend(ModbusFrameSem, 0U, &osError); | |||
| if (osError != OS_ERR_NONE) { | |||
| ModbusSemaphoreErrorCount++; | |||
| continue; | |||
| } | |||
| @@ -622,13 +796,15 @@ static void AppTaskModbus(void *argument) | |||
| if (rxLength > 0U) { | |||
| AppProcessFrame(rxLength); | |||
| /* 帧定时器已经终止了本次完整帧的接收。正常挂接下一帧时不能再次清除 | |||
| * SR/DR,因为主站可能在 T3.5 后已经开始发送下一条请求。 */ | |||
| halStatus = AppStartUartReception(); | |||
| 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) | |||
| { | |||
| INT8U osError; | |||
| uint16_t updateElapsedTicks; | |||
| (void)argument; | |||
| updateElapsedTicks = 0U; | |||
| 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) { | |||
| updateElapsedTicks = 0U; | |||
| osError = AppPostModbusFrameSem(); | |||
| if (osError != OS_ERR_NONE) { | |||
| ModbusSemaphoreErrorCount++; | |||
| } | |||
| (void)AppPostModbusFrameSem(); | |||
| } | |||
| } | |||
| } | |||
| @@ -672,17 +845,37 @@ void HAL_UART_RxCpltCallback(UART_HandleTypeDef *uartHandle) | |||
| return; | |||
| } | |||
| /* 此回调每次只处理一个字节,不解析功能码,也不写入数据模型;这些操作由 | |||
| * AppTaskModbus 任务完成。 */ | |||
| if (ModbusFrameReady != 0U) { | |||
| 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) { | |||
| ModbusRxOverflow = 1U; | |||
| return; | |||
| } | |||
| /* HAL 已经将当前字节放入 ModbusRxBuffer[ModbusRxLength]。 */ | |||
| ModbusRxLength++; | |||
| ModbusLastRxTick = OSTimeGet(); | |||
| ModbusUartLastRxByte = ModbusRxBuffer[ModbusRxLength - 1U]; | |||
| AppStartModbusRtuTimer(); | |||
| if (ModbusRxLength >= MODBUS_RX_BUF_SIZE) { | |||
| ModbusRxOverflow = 1U; | |||
| return; | |||
| @@ -691,23 +884,47 @@ void HAL_UART_RxCpltCallback(UART_HandleTypeDef *uartHandle) | |||
| halStatus = HAL_UART_Receive_IT(&Uart1Handle, | |||
| &ModbusRxBuffer[ModbusRxLength], 1U); | |||
| if (halStatus != HAL_OK) { | |||
| AppRecordIsrError(); | |||
| ModbusReceptionNeedsRecovery = 1U; | |||
| (void)AppPostModbusFrameSem(); | |||
| } | |||
| } | |||
| /** | |||
| * @brief USART1 通信错误回调。 | |||
| * @param[in] uartHandle 触发回调的串口句柄。 | |||
| * @note 中断仅停止计时、标记当前帧无效并通知任务;接收重挂接由 | |||
| * AppRecoverPendingUartReception 在任务上下文执行。 | |||
| */ | |||
| void HAL_UART_ErrorCallback(UART_HandleTypeDef *uartHandle) | |||
| { | |||
| if ((uartHandle == NULL) || (uartHandle->Instance != USART1)) { | |||
| if ((uartHandle == NULL) || (uartHandle->Instance != USART1)) | |||
| { | |||
| return; | |||
| } | |||
| /* UART 错误会使当前整帧失效。中断只记录错误并唤醒任务,不在这里终止或重新 | |||
| * 进入 HAL 接收流程,从而避免 HAL 重入,并让恢复操作集中在一个上下文中。 */ | |||
| AppStopModbusRtuTimer(); | |||
| ModbusUartLastErrorCode = uartHandle->ErrorCode; | |||
| ModbusUartLastRxState = (uint32_t)uartHandle->RxState; | |||
| __HAL_UART_CLEAR_PEFLAG(uartHandle); | |||
| 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) | |||
| { | |||
| INT8U osError; | |||
| if ((uartHandle == NULL) || (uartHandle->Instance != USART1)) { | |||
| return; | |||
| } | |||
| osError = OSSemPost(ModbusTxSem); | |||
| if (osError != OS_ERR_NONE) { | |||
| ModbusSemaphoreErrorCount++; | |||
| } | |||
| /* 只有最后一个字节已经移出串口后,才唤醒发送任务。 */ | |||
| (void)OSSemPost(ModbusTxSem); | |||
| } | |||
| /** | |||
| @@ -79,23 +79,42 @@ void PendSV_Handler(void) | |||
| /** | |||
| * @brief 处理 1 ms 系统节拍中断。 | |||
| * @note OS_CPU_SysTickHandler 封装了 uC/OS-II 的中断进入、节拍和退出顺序; | |||
| * 不得在此处改为手工调用 OSIntEnter、OSTimeTick 和 OSIntExit。 | |||
| */ | |||
| 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 收发中断。 | |||
| * @note HAL 回调只接收字节或登记恢复请求,不在中断上下文解析协议或等待发送。 | |||
| */ | |||
| void USART1_IRQHandler(void) | |||
| { | |||
| /* HAL 会分发接收完成、发送完成和 UART 错误回调。回调只记录状态并通知任务, | |||
| * 不阻塞,也不解析 ADU。 */ | |||
| OSIntEnter(); | |||
| HAL_UART_IRQHandler(&Uart1Handle); | |||
| OSIntExit(); | |||
| @@ -1136,42 +1136,18 @@ | |||
| <file> | |||
| <name>$PROJ_DIR$\..\Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal_dma.c</name> | |||
| </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> | |||
| <name>$PROJ_DIR$\..\Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal_gpio.c</name> | |||
| </file> | |||
| <file> | |||
| <name>$PROJ_DIR$\..\Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal_pwr.c</name> | |||
| </file> | |||
| <file> | |||
| <name>$PROJ_DIR$\..\Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal_pwr_ex.c</name> | |||
| </file> | |||
| <file> | |||
| <name>$PROJ_DIR$\..\Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal_rcc.c</name> | |||
| </file> | |||
| <file> | |||
| <name>$PROJ_DIR$\..\Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal_rcc_ex.c</name> | |||
| </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> | |||
| <name>$PROJ_DIR$\..\Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal_uart.c</name> | |||
| </file> | |||
| @@ -1182,5 +1158,8 @@ | |||
| <file> | |||
| <name>$PROJ_DIR$\..\Core\Modbus\modbus.c</name> | |||
| </file> | |||
| <file> | |||
| <name>$PROJ_DIR$\..\Core\Modbus\modbus_backup.c</name> | |||
| </file> | |||
| </group> | |||
| </project> | |||
| @@ -1,72 +1,103 @@ | |||
| # 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 | |||
| -ferror-limit=0 | |||
| -fbracket-depth=512 | |||
| @@ -8,7 +8,7 @@ D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\Modbus\Obj\app_h | |||
| -o | |||
| app_hooks.pbi | |||
| -I | |||
| d:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\Core\OS\Cfg | |||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\Core\OS\Cfg | |||
| -I | |||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/Inc | |||
| -I | |||
| @@ -31,25 +31,18 @@ build system_stm32f4xx.pbi : index system_stm32f4xx.xcl | |||
| build stm32f4xx_hal.pbi : index stm32f4xx_hal.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_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_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_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 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.pbw : browsedata Modbus.pbd | |||
| @@ -1,41 +1,41 @@ | |||
| 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 \ | |||
| 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 | |||
| -ferror-limit=0 | |||
| -fbracket-depth=512 | |||
| @@ -8,7 +8,7 @@ D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\Modbus\Obj\os_cp | |||
| -o | |||
| os_cpu_c.pbi | |||
| -I | |||
| d:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\Core\OS\Ports | |||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\Core\OS\Ports | |||
| -I | |||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/Inc | |||
| -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: \ | |||
| 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/../Core/Inc\stm32f4xx_hal_conf.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 | |||
| -ferror-limit=0 | |||
| -fbracket-depth=512 | |||
| @@ -8,7 +8,7 @@ D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\Modbus\Obj\stm32 | |||
| -o | |||
| stm32f4xx_hal.pbi | |||
| -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 | |||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/Inc | |||
| -I | |||
| @@ -1,5 +1,5 @@ | |||
| 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/../Core/Inc\stm32f4xx_hal_conf.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 | |||
| -ferror-limit=0 | |||
| -fbracket-depth=512 | |||
| @@ -8,7 +8,7 @@ D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\Modbus\Obj\stm32 | |||
| -o | |||
| stm32f4xx_hal_cortex.pbi | |||
| -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 | |||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/Inc | |||
| -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 | |||
| -ferror-limit=0 | |||
| -fbracket-depth=512 | |||
| @@ -8,7 +8,7 @@ D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM\Modbus\Obj\stm32 | |||
| -o | |||
| stm32f4xx_hal_gpio.pbi | |||
| -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 | |||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/Inc | |||
| -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_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/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/Source\ucos_ii.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_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/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/Source\ucos_ii.h \ | |||
| D:\xinjiedianqi\Study\TrainCamp_zengbingjie_Modbus\Modbus\EWARM/../Core/OS/Ports\os_cpu.h \ | |||
| @@ -20,7 +20,7 @@ | |||
| <stlinkResetStrategy>2</stlinkResetStrategy> | |||
| </StLinkDriver> | |||
| <DebugChecksum> | |||
| <Checksum>3430922322</Checksum> | |||
| <Checksum>3495406535</Checksum> | |||
| </DebugChecksum> | |||
| <Exceptions> | |||
| <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 | |||