双角色设计

STM32F103 同时扮演两个独立的 Modbus 角色,使用两个不同的 USART 外设:

角色USART波特率总线方向控制用途
主站USART238400RS-485PD7 (MAX485 DE/RE)轮询 PT100、称重、继电器模块
从站USART3115200RS-485PA5 (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 请求/响应事务不被其他线程打断。

线程架构

线程优先级周期职责
maint610ms按键处理、云端继电器命令、G780s_Process()、JSON 命令、升级确认、LED、IWDG
sensor820ms轮询 PT100 → 称重 → 继电器 DO → 继电器 DI(各间隔 30ms 重试延迟)、更新流量计、DI 去抖、推送数据到从站寄存器

现场数据寄存器区(0x0000 ~ 0x0015)

寄存器存储在 static uint16_t g_registers[91] 中,共 91 个寄存器,地址 0x0000 ~ 0x005A。

地址名称R/W说明
0x0000REG_PUSH_SEQR遥测推送序列号,每轮递增
0x0001REG_PT100_CH1RPT100 通道 1 温度 ×10(int16,单位 0.1°C)
0x0002REG_PT100_CH2RPT100 通道 2
0x0003REG_PT100_CH3RPT100 通道 3
0x0004REG_PT100_CH4RPT100 通道 4
0x0005~0x000CREG_WEIGHT_CH1~CH4R称重通道 1~4,每通道 32 位大端序(H/L 寄存器对),未使用通道 = -1(0xFFFFFFFF)
0x000DREG_FLOW_RATER瞬时流量 ×100
0x000E~0x000FREG_FLOW_TOTALR累计流量,32 位(H/L)×1000
0x0010REG_RELAY_CTRLR/W继电器翻转命令(1~16 = 翻转对应通道,0 = 全关)
0x0011REG_RELAY_DOR继电器输出状态位图(bit0bit15 = CH1CH16)
0x0012REG_RELAY_DIR继电器数字输入状态位图
0x0013REG_SYSTEM_STATUSR状态位:bit0=PT100 正常、bit1=称重正常、bit2=继电器正常、bit3=自动模式
0x0014REG_RELAY_BITSR继电器输出状态位图(镜像 relay_do)
0x0015REG_RELAY_CMD_BITSR/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说明有效范围
0x0020REG_CFG_SENSOR_PERIOD_MSR/W传感器轮询周期200~60000 ms
0x0021REG_CFG_FLOW_SAMPLE_MSR/W流量计采样周期100~10000 ms
0x0022REG_CFG_DI_DEBOUNCE_MSR/WDI 去抖时间10~5000 ms
0x0023REG_CFG_TEMP_THRESHOLD_X10R/W温度变化阈值 ×101~500
0x0024~0x0025REG_CFG_PPL_X100R/W每升脉冲数 ×100,32 位(H/L)100~10000000
0x0026~0x0027REG_CFG_HZ_PER_LPM_X100R/W每 LPM 的 Hz 数 ×100,32 位(H/L)
0x0028REG_CFG_CONTROL_MODER/W0=手动,1=自动0 或 1

配置写入是暂存式的:写入寄存器仅更新 g_staged_config,不会立即生效或持久化到 Flash。生效流程:

  1. 向 0x0030 写入 0xA55A 解锁维护窗口
  2. 写入目标配置寄存器
  3. 向 0x0031 写入 0x0001(应用/保存)

维护控制寄存器区(0x0030 ~ 0x0037)

地址名称R/W说明
0x0030REG_MAINT_UNLOCKW写入 0xA55A 打开 30 秒维护窗口
0x0031REG_MAINT_COMMANDW命令触发:0x0001=应用/保存、0x0002=丢弃、0x0003=恢复默认、0x0004=清除错误、0x0005=进入 Bootloader 升级
0x0032REG_MAINT_STATUSR状态位图:bit0=已解锁、bit1=暂存有变更、bit2=配置已加载、bit3=保存成功、bit4=错误
0x0033REG_MAINT_LAST_ERRORR最后错误码(0~17)
0x0034REG_MAINT_CFG_VERSIONR配置结构体版本(0x0001)
0x0035~0x0036REG_MAINT_CFG_SEQUENCER当前配置序列号,32 位(H/L)
0x0037REG_MAINT_UNLOCK_REMAIN_SR剩余维护窗口时间(秒)

维护窗口超时 30 秒后自动关闭,所有暂存配置被丢弃。

诊断寄存器区(0x0038 ~ 0x005A)

地址名称说明
0x0038REG_DIAG_FW_VERSION固件版本(0x0100 = v1.0.0)
0x0039REG_DIAG_PROTOCOL_VERSION协议版本(0x0100)
0x003A~0x003C编译日期/时间年、月/日、时/分打包
0x003D~0x003EREG_DIAG_UPTIME系统运行时间(秒),32 位
0x003F~0x0040REG_DIAG_POWER_ON_COUNT上电次数,32 位
0x0041REG_DIAG_RESET_REASON1=上电、2=NRST 引脚、3=软件、4=IWDG、5=WWDG、6=低功耗
0x0042~0x0043最后非法写入地址和值
0x0044最后配置来源1=默认、2=Flash、3=远程保存、4=恢复默认
0x0045~0x0048UART 错误计数器CRC 错误、总 UART 错误(各 32 位,H/L)
0x0049最后命令结果最近维护命令的错误码
0x004A最后非法读地址最近被拒绝的读地址
0x004B~0x004E最后保存快照保存时的运行时间和序列号
0x004F~0x0051升级状态来源、状态、上次 OTA 错误
0x0052~0x0059UART RX 错误计数器ORE、FE、NE、RX 溢出(各 16 位,H/L)
0x005AREG_DIAG_RUNNING_SLOT0=未知、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

三种控制范式:

  1. 单线圈翻转Relay_ToggleOutput):读取当前状态 → 反转 → 写入,整个操作在单次总线锁内完成,保证原子性
  2. 位图命令Relay_SetOutputMask):FC06 写入寄存器 0x0035,一次性设置 16 位输出掩码
  3. 位图读取Relay_ReadAllCoils):FC01 读取 16 个线圈,返回 2 字节打包位图