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

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值