双角色设计
STM32F103 同时扮演两个独立的 Modbus 角色,使用两个不同的 USART 外设:
| 角色 | USART | 波特率 | 总线 | 方向控制 | 用途 |
|---|---|---|---|---|---|
| 主站 | USART2 | 38400 | RS-485 | PD7 (MAX485 DE/RE) | 轮询 PT100、称重、继电器模块 |
| 从站 | USART3 | 115200 | RS-485 | PA5 (DE/RE) | 暴露寄存器给 G780s / 远程维护 |
主站侧(USART2)
使用 nanoMODBUS 库作为传输层。半双工 485 方向切换内联处理:发送前设置 TX 模式,等待 TC(发送完成,20ms 超时避免阻塞看门狗),然后切回 RX 模式。每次接收前清除 ORE/NE/FE 错误标志并排空 FIFO(最多 256 次迭代),防止帧粘连。
从站侧(USART3)
RXNE 中断驱动接收,数据存入 256 字节环形缓冲区。帧检测依赖 RTU 静默间隔:当 g_last_rx_tick 停止更新超过 10ms(MODBUS_SLAVE_FRAME_TIMEOUT_MS),设置 g_frame_ready 标志。
支持功能码:FC03(读保持寄存器)、FC04(读输入寄存器)、FC05(写单个线圈)、FC06(写单个寄存器)。未知功能码返回异常码 0x01。
缓冲区溢出处理:RX 缓冲区满时设置 g_rx_corrupted,帧完成时丢弃损坏帧而非误解析。
总线序列化
所有主站总线事务通过 RT-Thread 优先级继承互斥锁保护:
// BusService.c
rt_mutex_t bus_mutex = rt_mutex_create("bus", RT_IPC_FLAG_PRIO);
每个传感器驱动在操作总线前调用 BusService_Lock(timeout=1000ms),操作完成后调用 BusService_Unlock(),确保完整的 Modbus 请求/响应事务不被其他线程打断。
线程架构
| 线程 | 优先级 | 周期 | 职责 |
|---|---|---|---|
maint | 6 | 10ms | 按键处理、云端继电器命令、G780s_Process()、JSON 命令、升级确认、LED、IWDG |
sensor | 8 | 20ms | 轮询 PT100 → 称重 → 继电器 DO → 继电器 DI(各间隔 30ms 重试延迟)、更新流量计、DI 去抖、推送数据到从站寄存器 |
现场数据寄存器区(0x0000 ~ 0x0015)
寄存器存储在 static uint16_t g_registers[91] 中,共 91 个寄存器,地址 0x0000 ~ 0x005A。
| 地址 | 名称 | R/W | 说明 |
|---|---|---|---|
| 0x0000 | REG_PUSH_SEQ | R | 遥测推送序列号,每轮递增 |
| 0x0001 | REG_PT100_CH1 | R | PT100 通道 1 温度 ×10(int16,单位 0.1°C) |
| 0x0002 | REG_PT100_CH2 | R | PT100 通道 2 |
| 0x0003 | REG_PT100_CH3 | R | PT100 通道 3 |
| 0x0004 | REG_PT100_CH4 | R | PT100 通道 4 |
| 0x0005~0x000C | REG_WEIGHT_CH1~CH4 | R | 称重通道 1~4,每通道 32 位大端序(H/L 寄存器对),未使用通道 = -1(0xFFFFFFFF) |
| 0x000D | REG_FLOW_RATE | R | 瞬时流量 ×100 |
| 0x000E~0x000F | REG_FLOW_TOTAL | R | 累计流量,32 位(H/L)×1000 |
| 0x0010 | REG_RELAY_CTRL | R/W | 继电器翻转命令(1~16 = 翻转对应通道,0 = 全关) |
| 0x0011 | REG_RELAY_DO | R | 继电器输出状态位图(bit0 |
| 0x0012 | REG_RELAY_DI | R | 继电器数字输入状态位图 |
| 0x0013 | REG_SYSTEM_STATUS | R | 状态位:bit0=PT100 正常、bit1=称重正常、bit2=继电器正常、bit3=自动模式 |
| 0x0014 | REG_RELAY_BITS | R | 继电器输出状态位图(镜像 relay_do) |
| 0x0015 | REG_RELAY_CMD_BITS | R/W | 继电器命令位图(按位直接控制) |
状态位图在 App_UpdateSlaveData 中组装:
status_raw = (pt100_ok ? 0x01u : 0u) |
(weight_ok ? 0x02u : 0u) |
(relay_ok ? 0x04u : 0u) |
((control_mode == G780S_MODE_AUTO) ? 0x08u : 0u);
远程配置寄存器区(0x0020 ~ 0x0028)
| 地址 | 名称 | R/W | 说明 | 有效范围 |
|---|---|---|---|---|
| 0x0020 | REG_CFG_SENSOR_PERIOD_MS | R/W | 传感器轮询周期 | 200~60000 ms |
| 0x0021 | REG_CFG_FLOW_SAMPLE_MS | R/W | 流量计采样周期 | 100~10000 ms |
| 0x0022 | REG_CFG_DI_DEBOUNCE_MS | R/W | DI 去抖时间 | 10~5000 ms |
| 0x0023 | REG_CFG_TEMP_THRESHOLD_X10 | R/W | 温度变化阈值 ×10 | 1~500 |
| 0x0024~0x0025 | REG_CFG_PPL_X100 | R/W | 每升脉冲数 ×100,32 位(H/L) | 100~10000000 |
| 0x0026~0x0027 | REG_CFG_HZ_PER_LPM_X100 | R/W | 每 LPM 的 Hz 数 ×100,32 位(H/L) | — |
| 0x0028 | REG_CFG_CONTROL_MODE | R/W | 0=手动,1=自动 | 0 或 1 |
配置写入是暂存式的:写入寄存器仅更新 g_staged_config,不会立即生效或持久化到 Flash。生效流程:
- 向 0x0030 写入 0xA55A 解锁维护窗口
- 写入目标配置寄存器
- 向 0x0031 写入 0x0001(应用/保存)
维护控制寄存器区(0x0030 ~ 0x0037)
| 地址 | 名称 | R/W | 说明 |
|---|---|---|---|
| 0x0030 | REG_MAINT_UNLOCK | W | 写入 0xA55A 打开 30 秒维护窗口 |
| 0x0031 | REG_MAINT_COMMAND | W | 命令触发:0x0001=应用/保存、0x0002=丢弃、0x0003=恢复默认、0x0004=清除错误、0x0005=进入 Bootloader 升级 |
| 0x0032 | REG_MAINT_STATUS | R | 状态位图:bit0=已解锁、bit1=暂存有变更、bit2=配置已加载、bit3=保存成功、bit4=错误 |
| 0x0033 | REG_MAINT_LAST_ERROR | R | 最后错误码(0~17) |
| 0x0034 | REG_MAINT_CFG_VERSION | R | 配置结构体版本(0x0001) |
| 0x0035~0x0036 | REG_MAINT_CFG_SEQUENCE | R | 当前配置序列号,32 位(H/L) |
| 0x0037 | REG_MAINT_UNLOCK_REMAIN_S | R | 剩余维护窗口时间(秒) |
维护窗口超时 30 秒后自动关闭,所有暂存配置被丢弃。
诊断寄存器区(0x0038 ~ 0x005A)
| 地址 | 名称 | 说明 |
|---|---|---|
| 0x0038 | REG_DIAG_FW_VERSION | 固件版本(0x0100 = v1.0.0) |
| 0x0039 | REG_DIAG_PROTOCOL_VERSION | 协议版本(0x0100) |
| 0x003A~0x003C | 编译日期/时间 | 年、月/日、时/分打包 |
| 0x003D~0x003E | REG_DIAG_UPTIME | 系统运行时间(秒),32 位 |
| 0x003F~0x0040 | REG_DIAG_POWER_ON_COUNT | 上电次数,32 位 |
| 0x0041 | REG_DIAG_RESET_REASON | 1=上电、2=NRST 引脚、3=软件、4=IWDG、5=WWDG、6=低功耗 |
| 0x0042~0x0043 | 最后非法写入 | 地址和值 |
| 0x0044 | 最后配置来源 | 1=默认、2=Flash、3=远程保存、4=恢复默认 |
| 0x0045~0x0048 | UART 错误计数器 | CRC 错误、总 UART 错误(各 32 位,H/L) |
| 0x0049 | 最后命令结果 | 最近维护命令的错误码 |
| 0x004A | 最后非法读地址 | 最近被拒绝的读地址 |
| 0x004B~0x004E | 最后保存快照 | 保存时的运行时间和序列号 |
| 0x004F~0x0051 | 升级状态 | 来源、状态、上次 OTA 错误 |
| 0x0052~0x0059 | UART RX 错误计数器 | ORE、FE、NE、RX 溢出(各 16 位,H/L) |
| 0x005A | REG_DIAG_RUNNING_SLOT | 0=未知、1=A、2=B |
所有诊断寄存器在每次 G780s_Process() 调用时通过 G780s_UpdateMaintenanceRegisters() 刷新。
传感器驱动详解
PT100 温度传感器
- 从站地址:0x01
- 功能码:FC04(读输入寄存器)
- 驱动文件:
BSP/Temperature.c - 4 通道,每通道返回值 ×10(单位 0.1°C)
称重传感器
- 从站地址:0x03
- 功能码:FC03(读保持寄存器)/ FC06(写单个寄存器)
- 驱动文件:
BSP/Weight.c - 4 通道,每通道 32 位有符号整数(克),未使用通道返回 -1
流量计
- 驱动文件:
BSP/Flowmeter.c - 使用 TIM2 外部脉冲计数器,软件采样
- 通过脉冲频率计算瞬时流量,脉冲累计计算总流量
- 配置参数:每升脉冲数(PPL)、每 LPM 的 Hz 数
继电器模块
- 从站地址:0x02
- 16 通道,线圈地址 0x0000~0x000F
- 功能码:FC01(读线圈)、FC05(写单个线圈)、FC06(写单个寄存器)
- 驱动文件:
BSP/Relay.c
三种控制范式:
- 单线圈翻转(
Relay_ToggleOutput):读取当前状态 → 反转 → 写入,整个操作在单次总线锁内完成,保证原子性 - 位图命令(
Relay_SetOutputMask):FC06 写入寄存器 0x0035,一次性设置 16 位输出掩码 - 位图读取(
Relay_ReadAllCoils):FC01 读取 16 个线圈,返回 2 字节打包位图