瀏覽代碼

[Mod]完成modbus项目功能

master
杰杰的笔记本\杰杰 1 天之前
父節點
當前提交
b3de7e1cbe
共有 69 個文件被更改,包括 137969 次插入114877 次删除
  1. +15
    -1
      .gitignore
  2. +46
    -3
      Core/Inc/main.h
  3. +5
    -0
      Core/Inc/stm32f4xx_it.h
  4. +323
    -25
      Core/Modbus/modbus.c
  5. +42
    -14
      Core/Modbus/modbus.h
  6. +232
    -0
      Core/Modbus/modbus_backup.c
  7. +27
    -0
      Core/Modbus/modbus_backup.h
  8. +0
    -256
      Core/Modbus/modbus_test.c
  9. +32
    -14
      Core/OS/Cfg/app_cfg.h
  10. +393
    -180
      Core/Src/main.c
  11. +24
    -5
      Core/Src/stm32f4xx_it.c
  12. +215
    -403
      EWARM/Modbus.dep
  13. +3
    -24
      EWARM/Modbus.ewp
  14. +1382
    -0
      EWARM/Modbus/Exe/Modbus.hex
  15. 二進制
      EWARM/Modbus/Exe/Modbus.sim
  16. +102
    -71
      EWARM/Modbus/Obj/.ninja_log
  17. 二進制
      EWARM/Modbus/Obj/Modbus.pbd
  18. 二進制
      EWARM/Modbus/Obj/Modbus.pbd.browse
  19. +112753
    -113368
      EWARM/Modbus/Obj/Modbus.pbw
  20. 二進制
      EWARM/Modbus/Obj/Modbus_part0.pbi
  21. 二進制
      EWARM/Modbus/Obj/Modbus_part1.pbi
  22. 二進制
      EWARM/Modbus/Obj/Modbus_part2.pbi
  23. 二進制
      EWARM/Modbus/Obj/Modbus_part3.pbi
  24. 二進制
      EWARM/Modbus/Obj/Modbus_part4.pbi
  25. 二進制
      EWARM/Modbus/Obj/app_hooks.pbi
  26. +2
    -2
      EWARM/Modbus/Obj/app_hooks.xcl
  27. +6
    -13
      EWARM/Modbus/Obj/build.ninja
  28. 二進制
      EWARM/Modbus/Obj/main.pbi
  29. +39
    -39
      EWARM/Modbus/Obj/main.pbi.dep
  30. 二進制
      EWARM/Modbus/Obj/modbus.pbi
  31. +6
    -8
      EWARM/Modbus/Obj/modbus.pbi.dep
  32. 二進制
      EWARM/Modbus/Obj/os_cpu_c.pbi
  33. +2
    -2
      EWARM/Modbus/Obj/os_cpu_c.xcl
  34. 二進制
      EWARM/Modbus/Obj/os_dbg.pbi
  35. +0
    -384
      EWARM/Modbus/Obj/os_dbg_r.xcl
  36. 二進制
      EWARM/Modbus/Obj/stm32f4xx_hal.pbi
  37. +1
    -1
      EWARM/Modbus/Obj/stm32f4xx_hal.pbi.dep
  38. +2
    -2
      EWARM/Modbus/Obj/stm32f4xx_hal.xcl
  39. 二進制
      EWARM/Modbus/Obj/stm32f4xx_hal_cortex.pbi
  40. +1
    -1
      EWARM/Modbus/Obj/stm32f4xx_hal_cortex.pbi.dep
  41. +2
    -2
      EWARM/Modbus/Obj/stm32f4xx_hal_cortex.xcl
  42. +2
    -2
      EWARM/Modbus/Obj/stm32f4xx_hal_gpio.xcl
  43. 二進制
      EWARM/Modbus/Obj/stm32f4xx_hal_msp.pbi
  44. +1
    -0
      EWARM/Modbus/Obj/stm32f4xx_hal_msp.pbi.dep
  45. 二進制
      EWARM/Modbus/Obj/stm32f4xx_it.pbi
  46. +1
    -0
      EWARM/Modbus/Obj/stm32f4xx_it.pbi.dep
  47. 二進制
      EWARM/Modbus/Obj/ucos_ii.pbi
  48. +79
    -27
      EWARM/settings/Modbus.dbgdt
  49. +1
    -1
      EWARM/settings/Modbus.dnx
  50. +30
    -29
      EWARM/settings/Project.wsdt
  51. +322
    -0
      docs/superpowers/plans/2026-08-02-modbus-retention-private-functions.md
  52. +317
    -0
      docs/superpowers/plans/2026-08-02-modbus-rtu-long-connection-stability.md
  53. +161
    -0
      docs/superpowers/plans/2026-08-02-modbus-runtime-cleanup.md
  54. +91
    -0
      docs/superpowers/specs/2026-08-02-modbus-retention-and-private-functions-design.md
  55. +55
    -0
      docs/superpowers/specs/2026-08-02-modbus-rtu-long-connection-design.md
  56. +37
    -0
      docs/superpowers/specs/2026-08-02-modbus-runtime-cleanup-design.md
  57. +253
    -0
      docs/测试操作指南.md
  58. +158
    -0
      docs/项目设计.md
  59. +19360
    -0
      document/Modbus国标.pdf
  60. 二進制
      document/TouchWin编辑软件用户手册(HC 02 20240527 1.2)-2024.5.27.pdf
  61. +230
    -0
      document/TouchWin自组Modbus功能码函数.md
  62. +387
    -0
      test/app_runtime_test.c
  63. +34
    -0
      test/check_no_communication_counters.ps1
  64. +49
    -0
      test/check_rtu_runtime_invariants.ps1
  65. +70
    -0
      test/modbus_backup_test.c
  66. +306
    -0
      test/modbus_test.c
  67. +308
    -0
      test/support/app_runtime/main.h
  68. +16
    -0
      test/support/app_runtime/stm32f4xx_it.h
  69. +46
    -0
      test/support/main.h

+ 15
- 1
.gitignore 查看文件

@@ -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

+ 46
- 3
Core/Inc/main.h 查看文件

@@ -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


+ 5
- 0
Core/Inc/stm32f4xx_it.h 查看文件

@@ -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 */



+ 323
- 25
Core/Modbus/modbus.c 查看文件

@@ -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;


+ 42
- 14
Core/Modbus/modbus.h 查看文件

@@ -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 从从站数据模型读取线圈值。


+ 232
- 0
Core/Modbus/modbus_backup.c 查看文件

@@ -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;
}

+ 27
- 0
Core/Modbus/modbus_backup.h 查看文件

@@ -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

+ 0
- 256
Core/Modbus/modbus_test.c 查看文件

@@ -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,
&registerValue) == MODBUS_STATUS_OK);
assert(registerValue == TEST_BROADCAST_WRITE_VALUE);
}

/**
* @brief 执行 Modbus RTU 从站协议单元测试。
* @return 0 表示全部测试通过。
*/
int main(void)
{
TestCrcByteOrder();
TestCoilReadWrite();
TestHoldingRegisterReadWrite();
TestIllegalAddressException();
TestBroadcastWrite();

return 0;
}

+ 32
- 14
Core/OS/Cfg/app_cfg.h 查看文件

@@ -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)



+ 393
- 180
Core/Src/main.c 查看文件

@@ -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);
}

/**


+ 24
- 5
Core/Src/stm32f4xx_it.c 查看文件

@@ -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();


+ 215
- 403
EWARM/Modbus.dep
文件差異過大導致無法顯示
查看文件


+ 3
- 24
EWARM/Modbus.ewp 查看文件

@@ -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>

+ 1382
- 0
EWARM/Modbus/Exe/Modbus.hex
文件差異過大導致無法顯示
查看文件


二進制
EWARM/Modbus/Exe/Modbus.sim 查看文件


+ 102
- 71
EWARM/Modbus/Obj/.ninja_log 查看文件

@@ -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

二進制
EWARM/Modbus/Obj/Modbus.pbd 查看文件


二進制
EWARM/Modbus/Obj/Modbus.pbd.browse 查看文件


+ 112753
- 113368
EWARM/Modbus/Obj/Modbus.pbw
文件差異過大導致無法顯示
查看文件


二進制
EWARM/Modbus/Obj/Modbus_part0.pbi 查看文件


二進制
EWARM/Modbus/Obj/Modbus_part1.pbi 查看文件


二進制
EWARM/Modbus/Obj/Modbus_part2.pbi 查看文件


二進制
EWARM/Modbus/Obj/Modbus_part3.pbi 查看文件


二進制
EWARM/Modbus/Obj/Modbus_part4.pbi 查看文件


二進制
EWARM/Modbus/Obj/app_hooks.pbi 查看文件


+ 2
- 2
EWARM/Modbus/Obj/app_hooks.xcl 查看文件

@@ -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


+ 6
- 13
EWARM/Modbus/Obj/build.ninja 查看文件

@@ -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



二進制
EWARM/Modbus/Obj/main.pbi 查看文件


+ 39
- 39
EWARM/Modbus/Obj/main.pbi.dep 查看文件

@@ -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

二進制
EWARM/Modbus/Obj/modbus.pbi 查看文件


+ 6
- 8
EWARM/Modbus/Obj/modbus.pbi.dep 查看文件

@@ -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

二進制
EWARM/Modbus/Obj/os_cpu_c.pbi 查看文件


+ 2
- 2
EWARM/Modbus/Obj/os_cpu_c.xcl 查看文件

@@ -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


二進制
EWARM/Modbus/Obj/os_dbg.pbi 查看文件


+ 0
- 384
EWARM/Modbus/Obj/os_dbg_r.xcl 查看文件

@@ -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

二進制
EWARM/Modbus/Obj/stm32f4xx_hal.pbi 查看文件


+ 1
- 1
EWARM/Modbus/Obj/stm32f4xx_hal.pbi.dep 查看文件

@@ -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 \


+ 2
- 2
EWARM/Modbus/Obj/stm32f4xx_hal.xcl 查看文件

@@ -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


二進制
EWARM/Modbus/Obj/stm32f4xx_hal_cortex.pbi 查看文件


+ 1
- 1
EWARM/Modbus/Obj/stm32f4xx_hal_cortex.pbi.dep 查看文件

@@ -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 \


+ 2
- 2
EWARM/Modbus/Obj/stm32f4xx_hal_cortex.xcl 查看文件

@@ -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


+ 2
- 2
EWARM/Modbus/Obj/stm32f4xx_hal_gpio.xcl 查看文件

@@ -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


二進制
EWARM/Modbus/Obj/stm32f4xx_hal_msp.pbi 查看文件


+ 1
- 0
EWARM/Modbus/Obj/stm32f4xx_hal_msp.pbi.dep 查看文件

@@ -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 \


二進制
EWARM/Modbus/Obj/stm32f4xx_it.pbi 查看文件


+ 1
- 0
EWARM/Modbus/Obj/stm32f4xx_it.pbi.dep 查看文件

@@ -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 \


二進制
EWARM/Modbus/Obj/ucos_ii.pbi 查看文件


+ 79
- 27
EWARM/settings/Modbus.dbgdt
文件差異過大導致無法顯示
查看文件


+ 1
- 1
EWARM/settings/Modbus.dnx 查看文件

@@ -20,7 +20,7 @@
<stlinkResetStrategy>2</stlinkResetStrategy>
</StLinkDriver>
<DebugChecksum>
<Checksum>3430922322</Checksum>
<Checksum>3495406535</Checksum>
</DebugChecksum>
<Exceptions>
<StopOnUncaught>_ 0</StopOnUncaught>


+ 30
- 29
EWARM/settings/Project.wsdt
文件差異過大導致無法顯示
查看文件


+ 322
- 0
docs/superpowers/plans/2026-08-02-modbus-retention-private-functions.md 查看文件

@@ -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.

+ 317
- 0
docs/superpowers/plans/2026-08-02-modbus-rtu-long-connection-stability.md 查看文件

@@ -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.

+ 161
- 0
docs/superpowers/plans/2026-08-02-modbus-runtime-cleanup.md 查看文件

@@ -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.

+ 91
- 0
docs/superpowers/specs/2026-08-02-modbus-retention-and-private-functions-design.md 查看文件

@@ -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 请求;写入、清除、断电恢复、读取后数值一致。

+ 55
- 0
docs/superpowers/specs/2026-08-02-modbus-rtu-long-connection-design.md 查看文件

@@ -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.

+ 37
- 0
docs/superpowers/specs/2026-08-02-modbus-runtime-cleanup-design.md 查看文件

@@ -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.

+ 253
- 0
docs/测试操作指南.md 查看文件

@@ -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 掉电保持只恢复扩展寄存器。

失败项应记录:复现步骤、请求/响应原始帧、固件版本、影响范围、修复提交和回归测试结果。

+ 158
- 0
docs/项目设计.md 查看文件

@@ -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` 保留在工程中。

+ 19360
- 0
document/Modbus国标.pdf
文件差異過大導致無法顯示
查看文件


二進制
document/TouchWin编辑软件用户手册(HC 02 20240527 1.2)-2024.5.27.pdf 查看文件


+ 230
- 0
document/TouchWin自组Modbus功能码函数.md 查看文件

@@ -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 个奇数地址寄存器。

+ 387
- 0
test/app_runtime_test.c 查看文件

@@ -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;
}

+ 34
- 0
test/check_no_communication_counters.ps1 查看文件

@@ -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.'

+ 49
- 0
test/check_rtu_runtime_invariants.ps1 查看文件

@@ -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.'

+ 70
- 0
test/modbus_backup_test.c 查看文件

@@ -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;
}

+ 306
- 0
test/modbus_test.c 查看文件

@@ -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;
}

+ 308
- 0
test/support/app_runtime/main.h 查看文件

@@ -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

+ 16
- 0
test/support/app_runtime/stm32f4xx_it.h 查看文件

@@ -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

+ 46
- 0
test/support/main.h 查看文件

@@ -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

Loading…
取消
儲存