架构演进 协议设计
DGUS 串口屏协议封装:在一个协处理引擎里的权衡
串口屏(DGUS/迪文)曾是 USART1 的默认协议。从 V1.1 开始,它已完全被 HMIS(HMI Session Protocol)取代。本文记录这次协议演进的原因、改动范围和迁移经验。
HMIS 协议帧格式见博文 542。本文不再重复协议细节,只聚焦"为什么弃用 DGUS"和"迁移中踩的坑"。
为什么放弃 DGUS
DGUS 变量协议的核心是 VP 地址模型:固件向屏的指定地址写数据,屏显示该地址的内容。HMI 上位机通过抓取屏的通信来间接获取设备信息。这个模式存在三个根本问题:
- 两套协议栈— HMI 同时维护 USART3 20B 帧和 USART1 DGUS 5A A5
- 三处耦合— 参数地址、HMI 配置、固件参数定义各存一份,新增参数要改三处
- 无会话管理— 无 SEQ 序号、无心跳、无能力声明,断线后无法自动恢复
// HMIS 帧格式(详细见博文 542)
| SOF(2) | VER(1) | TYPE(1) | SEQ(2) | CMD(1) | FLAGS(1) | LEN(2) | PAYLOAD(N) | CRC16(2) |
| 55 AA | 0x01 | 0x01 | 小端 | 0x30 | 0x00 | 0+.. | 0..128B | Modbus |
协处理引擎设计
我们在 Service 层做了一个专门处理 DGUS 帧的模块 packer_dbus(文件名来自历史兼容,实际处理 DGUS 协议):
// 初始化
void packer_dbus_init(void);
// 每周期轮询 — 读取 USART1 数据流,解析 5A A5 帧
void packer_dbus_task_once(void);
// 日志推送 — 将格式化文本封装为 DGUS 0x82 帧写入屏的文本区
void packer_dbus_log_send(uint8_t level, const char *text, uint16_t len);
轮询处理器的职责
packer_dbus_task_once() 每周期做三件事:
1. 从 USART1 接收缓冲读取字节流
2. 查找帧头 5A A5,提取完整帧
3. 按命令分发:
0x83 (屏主动上报) — 通常是触摸按键事件
→ 映射到操作 (如"保存参数"→ packer_runtime_config_save())
0x82 (上位机/屏写参数)
→ 解析目标 VP 地址 → 映射到参数 ID
→ packer_runtime_config_write(id, value)
0x81 (读参数请求)
→ 查参数表 → 封装 0x82 回复帧 → USART1 发送
日志推送路径
BSP 日志模块 bsp_log 是一个纯格式化层,不关心输出到哪里。它的输出通过回调函数分发给多个后端:
// bsp_log 输出 → 回调函数 →
// ├── USART1: packer_dbus_log_send() → DGUS 5A A5 帧 → 屏显
// ├── HMI 端口 B: 同一 USART1 线路收到 → HMI 日志面板
// └── (未来) RingBuffer / SD 卡日志
// 日志帧格式:
// 5A A5 + Length + 0x82 + VP地址(0x3000) + 日志文本
// 日志写入 DGUS 预设的文本显示区 VP 0x3000~0x301F
与 HMI 端口 B 共存
| 发送者 | 帧特征 | 下位机响应 |
|---|---|---|
| DGUS 屏触摸 | 5A A5 + 0x83 上报 | 解析执行 |
| HMI 端口 B | 5A A5 + 0x82 写参数 | 解析执行 |
| 下位机推送 | 5A A5 + 0x82 日志帧 | —(屏显 + HMI 接收) |
三者在同一物理线路上通过帧头(5A A5)和前导码区分。HMI 端口 B 只监听不冲突——它不主动发帧时不会干扰 DGUS 屏和下位机的正常通信。
两个设计权衡
1. 轮询 vs 中断
packer_dbus_task_once() 由 ProtoTask(优先级 AboveNormal,50ms 周期)中的协议分发调用,而非独立的 USART1 接收中断。
为什么不是中断?DGUS 帧是低速的(9600 波特),一帧最长约 20 字节,传输约 20ms。50ms 的轮询周期足够捕获完整帧,且避免了中断上下文做帧解析的复杂性。
2. 参数 ID 映射表
DGUS 端的 VP 地址(0x3000 等)和下位机的参数 ID(RCFG_ID_xxx)之间通过一张映射表关联:
static const struct {
uint16_t vp_addr; // DGUS 端的变量地址
rcfg_id_t param_id; // 下位机参数 ID
uint8_t rw; // 读/写权限
} s_vp_map[] = {
{0x1000, RCFG_ID_SPEED_BAG_OUT_HZ, 2}, // 读写
{0x1002, RCFG_ID_SPEED_TEAR_OFF_HZ, 2},
{0x1010, RCFG_ID_POWER_ON_DELAY_MS, 2},
};
这个映射表是 DGUS 协议封装的核心——它把"屏端的地址"和"固件端的参数 ID"解耦。换屏时只需改映射表,不动业务逻辑。
Comments NOTHING