# 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 诊断计数器,调试时应直接查看上表 三个真实数据对象。UART 错误恢复依靠 `ModbusReceptionNeedsRecovery` 状态标志和 HAL 当前状态完成,不依赖额外的调试变量,也不会对外提供诊断寄存器。 `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` 保留在工程中。