【双MCU项目复盘与优化】02 -自定义串口通信协议

摘要:本文复盘了V3智控面板双MCU间自定义串口协议的固定长度、扩展性差等不足,并规划了V4的帧头+长度+类型码+载荷+校验+帧尾的可扩展协议框架


1. V3 串口协议复盘

1.1 协议内容

  • 以下是具体的协议内容
帧头 类型码 数据 1 数据 2 数据 3 数据 4 数据 5 数据 6 校验 帧尾
CD 系统状态:01 系统空闲:00
系统工作:01
00 00 00 00 00 —— FA
CD 继电器 1:03 关闭:00
开启:01
人体:02
超时时间:0~60 分钟 00 00 00 00 —— FA
CD 继电器 2:04 关闭:00
开启:01
人体:02
超时时间:0~60 分钟 00 00 00 00 —— FA
CD 灯光:05 关闭:00
开启:01
人体:02
超时时间:
0~60 分钟
亮度值:0~100 00 00 00 —— FA
CD 传感器:02 温度:
低八位
温度:
高八位
湿度:
低八位
湿度:
高八位
流明:
低八位
流明:
高八位
—— FA
  • 可见,V3 采用了固定字长的帧格式,格式为“帧头+数据+校验+帧尾
    • 校验字节在构建数据帧时使用 XOR 实时计算,此处用 “——” 占位
  • 修改配置时,STM32F103 会接收数据帧,然后解析并修改配置,然后把最新的配置打包成同样的数据帧回传给 ESP32-S3

1.2 分析不足

  • V3 协议在设计上满足了基本的数据交换需求,但随着项目迭代,以下问题逐渐暴露:
    1. 扩展性差:协议采用固定位置、固定含义的字段,添加新功能(如 NFC 卡号列表)需要重新定义帧结构,无法平滑升级。
    2. 带宽利用率低:每一帧固定为 7 个数据字节(数据1~数据7),但实际有效载荷往往只有 2~3 字节。例如配置继电器时,只有类型码、模式、超时三个有效字节,其余 4 字节填 0x00,浪费率接近 60%
  • 这些问题共同指向一个结论:V3 协议只能用于当前有限功能,V4 必须彻底重构

2. V4 串口协议规划

基本结构:帧头+长度+类型码+载荷+校验值+帧尾

  • 串口协议的解析顺序:接收方先查找帧头,然后读取长度 Len,接着读取 Len 字节载荷,再读 1 字节 XOR 校验和,最后读 1 字节帧尾用于可选验证。帧尾不参与定位,仅作完整性检查。

2.1 确定帧头帧尾

  • 在开始规划具体内容之前,可以先把帧头和帧尾确定下来,这里采用固定值,并且 ESP32-S3 发送的帧头帧尾和 STM32 发送的不一样
  • 我这里确定为以下这四个值。帧头帧尾可以随意分配(因为长度字段已存在,接收方按长度解析,不依赖帧尾定位,故不需要转义):
发送方 接收方 帧头 帧尾
ESP32-S3 STM32 0xBC 0x3A
STM32 ESP32-S3 0xCE 0x9D

2.2 结合场景规划类型码、载荷

  • 在开始之前,可以先约定一下类型码的分配规则,这样可以便于调试和扩展
    • 类型码高半字节 0xE 表示 ESP32 发出的指令,0xF 表示 STM32 发出的响应或数据,

2.2.1 配置场景

① 灯光、继电器 1/2 配置
  • ESP32-S3 会对 STM32 发送关于灯光、继电器 1/2 的配置参数,而 STM32 会根据接收到的配置参数修改硬件状态。在 STM32 修改完状态之后,最好给 ESP32 报告一下。
  • 所以可以这样设计(长度指的是载荷的字节数)
发送方 帧头 Head 长度 Len 类型码 Type 载荷 Payload XOR 帧尾 Tail 含义
ESP32-S3 0xBC 3 0xE1 uint8_t mode = 0 关闭 / 1 开启 / 2 人体检测;
uint8_t tout = 人体检测模式的超时时间(分钟);
uint8_t value = 亮度值(百分比);
—— 0x3A 配置 Light
0xBC 2 0xE2 uint8_t mode = 0 关闭 / 1 开启 / 2 人体检测;
uint8_t tout = 人体检测模式的超时时间(分钟);
—— 0x3A 配置 Relay1
0xBC 2 0xE3 uint8_t mode = 0 关闭 / 1 开启 / 2 人体检测;
uint8_t tout = 人体检测模式的超时时间(分钟);
—— 0x3A 配置 Relay2
STM32 0xCE 0 0xF1 —— 0x9D Light 配置成功
0xCE 0 0xF2 —— 0x9D Relay1 配置成功
0xCE 0 0xF3 —— 0x9D Relay2 配置成功
0xCE 0 0xF4 —— 0x9D Light 配置失败
0xCE 0 0xF5 —— 0x9D Relay1 配置失败
0xCE 0 0xF6 —— 0x9D Relay2 配置失败
  • 其中,XOR暂时留空,只需要知道是校验值即可
  • 另外,如果要更保险一定,可以选择在 STM32 发送“配置成功”消息时,可以在载荷里面把配置参数也一起回传,这样 ESP32 可以检验是否真的配置成功了。目前这个项目我觉得数据量不大,就不使用回传了。
② NFC 卡配置
  • 由于我们终端有需要有增加卡、删除卡的功能。
  • 我们需要定义一个增卡的配置消息,而 STM32 收到后就会读取当前识别到的卡号并且添加到已有的卡号列表,然后我们可以返回一个增卡成功的消息,并且把拿到的卡号也一并回传给 ESP32-S3 方便保存。当然,对应的增卡失败要有。
  • 同时,删卡的配置消息也需要有,STM32 收到后就会读取当前识别到的卡号,然后在已经有的卡号列表里面把它删除掉,然后我们可以返回一个删卡成功的消息,并且把删除的卡号也一并回传给 ESP32-S3 方便删除。对应的删卡失败也必不可少。
  • 由于需要在 ESP32-S3 保存卡号列表,而 STM32 内部不做掉电保存,于是必须在开机的时候,给 STM32 发送具体的卡号列表,所以还需要一个初始化卡号列表消息,以及对应的回复消息
发送方 帧头 Head 长度 Len 类型码 Type 载荷 Payload XOR 帧尾 Tail 含义
ESP32-S3 0xBC 2 + num*4 0xE4 uint8_t capacity:卡号列表最大容量
uint8_t num:当前卡 ID 数量
uint32_t nfc_card_id[num]
—— 0x3A 初始化 STM32 内部的卡号列表
0xBC 0 0xE5 —— 0x3A 增加卡
0xBC 0 0xE6 —— 0x3A 删除卡
STM32 0xCE 0 0xF7 —— 0x9D 初始化卡号列表失败
0xCE 0 0xF8 —— 0x9D 初始化卡号列表成功
0xCE 0 0xF9 —— 0x9D 增加卡失败
0xCE 1 0xFA 增加的卡号 —— 0x9D 增加卡成功
0xCE 0 0xFB —— 0x9D 删除卡失败
0xCE 1 0xFC 删除的卡号 —— 0x9D 删除卡成功

2.2.2 传感器数据场景

  • 由于终端需要显示室内的温湿度数据,以及屏幕自适应亮度需要知道环境光照,而传感器这种硬件基本由 STM32 进行驱动,所以,我们还需要设计关于传感器数据的协议部分。
  • 温度(Temp)
    • 室内温度通常只需要显示 3 位数即可,即两位整数和一位小数,范围大概在 5.0~32.0 之间,而串口通信一般数据是整数的,所以我们最好对温度数据进行定点化再通过串口发送。
    • 这里选择 10 为定点化倍数,如 25.6 定点化之后就是 256,可以用 uint16_t 来存储并发送,接收之后再进行去定点化即可获得 25.6
  • 湿度(Humi)
    • 室内湿度是百分比,范围是 0~100,一般显示无小数即可
    • 所以可以用 uint8_t 来存储并发送,无需定点化
  • 光照(Lux)
    • 光照的数据大小一般与环境光传感器的增益、积分时间等参数有关,目前不确定,可以先决定使用 uint16_t 来存储并发送,如果后续知道光照的值在 0~255 内,可以改为使用 uint8_t
  • 所以内容可以设置如下:
发送方 帧头 Head 长度 Len 类型码 Type 载荷 Payload XOR 帧尾 Tail 含义
STM32 0xCE 5 0xFD uint16_t temp:温度
uint8_t humi:湿度
uint16_t lux:光照
—— 0x9D 发送温度、湿度、光照
  • 由于传感器数据仅需最新值,所以为了减少冗余,无需应答设计,STM32 周期性发送、ESP32 保留最后有效值即可,而不是等待确认。

2.3 选择校验和算法

  • 除了硬件自带的奇偶检验之外,我们还应该给自己协议设计选择一个专属于协议的校验和算法
  • 可以选择异或校验(XOR)、累加和校验(Checksum)、CRC 校验(CRC-8/16/32)
  • 这里,我选择最简单的 XOR 检验,因为数据量小、通信环境良好(同一块板子内部或短距离),且后续会配合串口本身的硬件校验,所以够用
  • XOR 会对帧头、长度、类型码、载荷所有字节进行异或,结果放在 CRC 字段

2.4 大小端问题

  • 在之前的协议内容设计中,载荷里面出现了变量类型:uint16_t 和 uint32_t
  • 而这就涉及到大小端的问题,例如:uint32_t val = 0x12345678,在存储中的情况就是:
    • 小端:78 56 34 12
    • 大端:12 34 56 78
  • 所以,我们还需要约定好在协议里面 uint16_t 和 uint32_t 是使用大端还是小端
  • 如果是小端,例如 uint16_t 的变量, 串口则会先发送低八位再发送高八位,大端则相反
  • STM32 和 ESP32-S3,两者均为小端模式,但协议约定通常使用大端传输,于是本协议同样使用大端模式
  • 发送多字节整数时,先发送高位字节(MSB),后发送低位字节(LSB);接收方需按相同顺序重组。

2.5 收尾

  • 协议内容汇总
发送方 帧头 Head 长度 Len(载荷字节数) 类型码 Type 载荷 Payload XOR 帧尾 Tail 含义
ESP32-S3 0xBC 3 0xE1 uint8_t mode = 0 关闭 / 1 开启 / 2 人体检测;
uint8_t tout = 人体检测模式的超时时间(分钟);
uint8_t value = 亮度值(百分比);
—— 0x3A 配置 Light
0xBC 2 0xE2 uint8_t mode = 0 关闭 / 1 开启 / 2 人体检测;
uint8_t tout = 人体检测模式的超时时间(分钟);
—— 0x3A 配置 Relay1
0xBC 2 0xE3 uint8_t mode = 0 关闭 / 1 开启 / 2 人体检测;
uint8_t tout = 人体检测模式的超时时间(分钟);
—— 0x3A 配置 Relay2
0xBC 2 + num*4 0xE4 uint8_t capacity:卡号列表最大容量
uint8_t num:当前卡 ID 数量
uint32_t nfc_card_id[num]
—— 0x3A 初始化 STM32 内部的卡号列表
0xBC 0 0xE5 —— 0x3A 增加卡
0xBC 0 0xE6 —— 0x3A 删除卡
STM32 0xCE 0 0xF1 —— 0x9D Light 配置成功
0xCE 0 0xF2 —— 0x9D Relay1 配置成功
0xCE 0 0xF3 —— 0x9D Relay2 配置成功
0xCE 0 0xF4 —— 0x9D Light 配置失败
0xCE 0 0xF5 —— 0x9D Relay1 配置失败
0xCE 0 0xF6 —— 0x9D Relay2 配置失败
0xCE 0 0xF7 —— 0x9D 初始化卡号列表失败
0xCE 0 0xF8 —— 0x9D 初始化卡号列表成功
0xCE 0 0xF9 —— 0x9D 增加卡失败
0xCE 1 0xFA 增加的卡号 —— 0x9D 增加卡成功
0xCE 0 0xFB —— 0x9D 删除卡失败
0xCE 1 0xFC 删除的卡号 —— 0x9D 删除卡成功
0xCE 5 0xFD uint16_t temp:温度
uint8_t humi:湿度
uint16_t lux:光照
—— 0x9D 发送温度、湿度、光照

另外,由于本项目使用电路板,MCU 间串口线路短、电磁环境良好,误码率极低,所以本协议的帧头、帧尾、类型码的分配随意,不是某些约定俗成的固定值。如果希望协议健壮些,可以考虑是否是载荷里面的高概率数字,并引入转义。如果还希望更健壮,还进一步结合通信原理来进行编码。

posted @ 2026-06-14 20:23  临祁  阅读(3)  评论(0)    收藏  举报