STM32单线串口通讯实战(三):协议层设计 —— 帧结构、多机寻址与硬件唤醒

前两章我们打通了"物理连接"和"数据搬运(链路层)",现在的状态是:我们能从总线上收到一堆字节,但不知道这些字节代表什么,也不知道是不是发给我的。

这一章我们将设计协议层(Protocol Layer),解决三个核心问题:

  1. 数据怎么打包?(帧结构设计)

  2. 发给谁?(多机寻址策略)

  3. G0 怎么偷懒?(利用 STM32 硬件静默模式实现地址唤醒)。

1. 为什么不能只发"裸数据"?

在单线多机(One-to-Many)网络中,如果 Master 只是简单地发送 0x01(代表开灯),所有 Slave 都会收到。

  • Slave A 以为是开灯。

  • Slave B 以为是电机转速设为 1。

  • Slave C 可能把它当成了校验和的一部分。

因此,我们需要给数据穿上"衣服",这就是协议帧(Frame) 。考虑到单线带宽通常有限(如 9600 或 115200),我们设计一个比 Modbus 更轻量的Mini-Frame

2. Mini-Frame 帧结构设计

我们要追求的是:最小的开销(Overhead) + 足够的可靠性

推荐结构[Header] [Target_ID] [Len] [Cmd] [Payload...] [CRC]

  • Header (1 Byte) : 帧头,如 0xAA0x55。用于在数据流中快速定位一帧的开始。

  • Target_ID (1 Byte): 目标设备地址(0x00-0xFF)。

  • Len (1 Byte): 后续数据长度,防止解析越界。

  • Cmd (1 Byte): 功能码(如 0x01=读状态, 0x02=写配置)。

  • Payload (N Bytes): 具体的参数数据。

  • CRC (1/2 Bytes) : 校验和。单线抗干扰差,必须有校验


3. 多机寻址策略 (Addressing Strategy)

3.1 单播 (Unicast) ------ "点名提问"

  • 逻辑 :Master 发出的 Target_ID 等于 Slave 的本地 ID。

  • 行为 :Slave 收到后,执行指令,并必须回复(ACK 或 数据)。

  • 超时:Master 发送后启动定时器(如 50ms)。若超时未收到回复,判定 Slave 离线或重发。

3.2 广播 (Broadcast) ------ "全员通告"

  • 逻辑 :Master 发出的 Target_ID 为广播地址(通常定义为 0x000xFF)。

  • 行为:所有 Slave 收到后执行指令。

  • 红线警告(Critical)Slave 收到广播包后,绝对禁止回复!

    • 原因:如果 10 个 Slave 同时拉低总线回复 "OK",总线电平会打架,甚至烧毁 IO 口(在推挽模式下),数据也会完全乱码。

3.3 组播 (Multicast) ------ "分组行动" (可选)

  • 逻辑:利用 ID 的高 4 位作为 Group ID,低 4 位作为 Device ID。

  • 场景:比如让"所有空调"(Group 1)关机,而"所有灯光"(Group 2)保持不变。


4. STM32G0 杀手锏:9-bit 模式与硬件地址唤醒

在传统的 8-bit 模式下,Slave 的 CPU 非常辛苦。总线上每一字节数据(不管是发给谁的),CPU 都要进中断,醒来看看:"是我的地址吗?" -> "不是" -> 继续睡。这在 RTOS 或低功耗场景下极其浪费资源。

STM32G0 提供了 Address Mark Wakeup (9-bit mode),可以实现**"不是叫我,我就装聋"**。

4.1 工作原理

利用串口的第 9 个数据位(MSB):

  • 第 9 位 = 1 :表示这个字节是地址

  • 第 9 位 = 0 :表示这个字节是数据

STM32G0 硬件逻辑

  1. 配置从机进入 Mute Mode (静默模式)。此时除了"地址字节",其他所有数据都不会触发 RXNE 中断。

  2. 当收到一个 9th bit = 1 的字节时,硬件自动比对该字节与 USART_CR2 中的 ADD(节点地址)。

  3. 匹配 :硬件自动清除 Mute 状态,产生中断。后续收到的 9th bit = 0 的数据(Payload)也会正常触发中断。

  4. 不匹配:硬件保持 Mute,CPU 继续睡觉,完全不被打扰。

4.2 STM32G0 代码实战 (HAL库)

Master 发送端配置: 需要将 Word Length 改为 9 Bits。

// 发送地址字节 (标记位=1)

// 0x1xx -> 第9位为1

uint16_t address_byte = 0x100 | Target_ID;

HAL_UART_Transmit(&huart1, (uint8_t*)&address_byte, 1, 10);

// 发送数据字节 (标记位=0)

uint8_t payload[] = {0x01, 0x02};

HAL_UART_Transmit(&huart1, payload, 2, 10);

Slave 接收端配置 (初始化)

void MX_USART1_UART_Init(void)

{

// ... 基础配置 Baudrate 等 ...

huart1.Init.WordLength = UART_WORDLENGTH_9B; // 必须是9位

huart1.Init.StopBits = UART_STOPBITS_1;

// 关键配置:多处理器通讯

if (HAL_UART_Init(&huart1) != HAL_OK) Error_Handler();

// 1. 设置本机地址 (例如 0x05)

// G0支持4位或7位地址检测,这里用7位

HAL_MultiProcessor_Init(&huart1, 0x05, UART_WAKEUPMETHOD_ADDRESSMARK);

// 2. 立刻进入静默模式

HAL_MultiProcessor_EnterMuteMode(&huart1);

// 3. 开启中断 (RXNE)

__HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE);

}

Slave 中断处理 : 在中断中,我们不需要手动判断地址了。只要中断进来了,说明这就是发给我的(或者是广播)! 处理完一帧后,记得再次调用 HAL_MultiProcessor_EnterMuteMode 让硬件重新静默,等待下一次呼唤。


5. 数据完整性:CRC 校验

单线总线极易受干扰(尤其是长距离 Mode B/C)。简单的"异或校验"或"累加和"往往不够。 STM32G0 全系内置了 硬件 CRC 计算单元(AHB 总线外设),速度极快。

建议

  • 在发送前,用硬件 CRC 计算 Payload 的校验值,附在帧尾。

  • 接收端收到后,再次计算 CRC,比对一致才执行 Cmd。

/* G0 硬件 CRC 使用示例 */

__HAL_RCC_CRC_CLK_ENABLE(); // 别忘了开时钟

uint32_t Calculate_CRC(uint8_t *data, uint32_t len)

{

// 复位 CRC 计算单元

__HAL_CRC_DR_RESET(&hcrc);

// 累积计算

return HAL_CRC_Accumulate(&hcrc, (uint32_t*)data, len);

}

6. 本章小结

我们定义了单线通讯的"语言规则":

  1. 帧结构:定义了 Header, Cmd, Len, CRC 等字段。

  2. 寻址规则:明确了单播必须回、广播绝不回的铁律。

  3. 硬件加速 :利用 STM32G0 的 9-bit Address Mark Wakeup 功能,极大降低了从机 CPU 负载,这是相比普通 GPIO 模拟串口最大的优势。

下一章预告 : 协议定好了,如何用代码把它跑起来? 如果是简单的温湿度采集,用 裸机 (Bare Metal) 怎么写状态机才不会卡死? 在第四章《架构实现 I:裸机下的事件驱动与状态机设计》中,我们将构建一个基于 SysTick 的非阻塞收发框架。

/*******************************************
* Description:
* 本文为作者《嵌入式开发基础与工程实践》系列文之一。
* 关注我即可订阅后续内容更新,采用异步推送机制。
* 转发本文可视为广播分发,有助于信息传播至更多节点。
*******************************************/

相关推荐
Love Song残响2 小时前
高效自动化清理临时文件方案
java·开发语言·spring
古城小栈2 小时前
Rust 中符号语法 一文全晓
开发语言·后端·rust
爱吃生蚝的于勒2 小时前
【Linux】零基础深入学习动静态库+深入学习地址
linux·运维·服务器·c语言·数据结构·c++·学习
沃斯堡&蓝鸟2 小时前
DAY34 文件的规范拆分和写法
开发语言·python
ss2732 小时前
final关键字如何创造线程安全的对象
开发语言·python
专业开发者2 小时前
Auracast™ 广播音频将如何掀起新一轮音频创新浪潮
物联网·音视频
flysh052 小时前
深度解析 C# 核心:类(Class)的设计精髓与高级特性
开发语言·c#
Feibo20112 小时前
R-3east
开发语言·r语言
_OP_CHEN2 小时前
【从零开始的Qt开发指南】(十四)Qt 窗口之“三剑客”:工具栏、状态栏、浮动窗口进阶实战指南
开发语言·c++·qt·前端开发·gui开发·qt窗口