4. USART 唤醒源:配置串口在停止模式下接收唤醒

低功耗系统里,串口唤醒是个很实用的功能。设备睡下去了,但还得随时准备接收上位机的指令。这时候如果每次都用外部中断去唤醒,布线麻烦不说,协议层还得额外处理。STM32L4 的 USART 支持在停止模式下直接检测起始位唤醒,说白了就是串口线上一个下降沿就能把芯片叫醒。

我个人习惯把这种唤醒方式叫做「静默监听」。MCU 睡大觉,串口模块还在偷偷盯着 RX 线。一旦检测到有效起始位,硬件自动拉高时钟,把系统从 Stop 模式拉回来,然后继续接收剩下的数据字节。整个过程对用户代码是透明的。

核心要点: USART 唤醒不依赖外部中断线,完全由串口外设自身完成。唤醒后数据不会丢,前提是配置正确。

4.1 停止模式下的串口行为

先搞清楚一件事:STM32L4 的 Stop 模式分两种——Stop 0 和 Stop 1。Stop 0 保留 SRAM 和寄存器,唤醒更快;Stop 1 更省电,但唤醒稍慢。USART 在两种模式下都能工作,但时钟源必须选对。

USART 在停止模式下需要依赖 LSE 或 HSI16 作为时钟源。为什么?因为主时钟 PLL 和 HSI/HSE 在 Stop 模式下都关了。我刚开始做这个功能时踩过一个坑——用默认的 SYSCLK 去配 USART,进 Stop 后串口直接罢工,数据发不出去也收不到。后来查手册才发现,停止模式下 USART 必须切换到 LSE 或 HSI16。

时钟源 频率 适用场景 唤醒时间
LSE 32.768 kHz 超低功耗,波特率 ≤ 9600 稍慢
HSI16 16 MHz 高波特率,功耗略高 快
我的建议: 如果系统波特率在 9600 以下,优先用 LSE。功耗能低一个数量级。我在一个电池供电的传感器节点上就是这么做的,整机休眠电流从 12 µA 降到了 3.5 µA。

4.2 唤醒源配置步骤

配置 USART 唤醒其实就三步。别被手册里那一堆寄存器吓到,核心逻辑很清晰。

  1. 使能 USART 的停止模式时钟 —— 在进入 Stop 前,调用 HAL_UARTEx_StopModeClockConfig() 或直接操作 UART_CR3_UCESM 位。
  2. 选择唤醒条件 —— 设置 UART_CR3_WUS 位域,可选起始位唤醒、数据匹配唤醒或空闲总线唤醒。最常用的是起始位唤醒。
  3. 使能唤醒功能 —— 置位 UART_CR3_WUFIE 中断使能位,然后调用 HAL_UARTEx_EnableStopMode()。

代码实现也不复杂。下面是我在一个 LoRa 网关项目里用过的配置片段:

/* 配置 USART2 停止模式唤醒 */
UART_StopModeConfigTypeDef stopModeCfg;

stopModeCfg.ClockSource = UART_STOPMODE_CLOCKSOURCE_LSE;  /* 用 LSE 省电 */
stopModeCfg.WakeUpSource = UART_STOPMODE_WAKEUPSOURCE_STARTBIT; /* 起始位唤醒 */
stopModeCfg.EnableLPUART = DISABLE;  /* 这里用普通 USART,不是 LPUART */

HAL_UARTEx_StopModeConfig(&huart2, &stopModeCfg);
HAL_UARTEx_EnableStopMode(&huart2);

/* 使能唤醒中断 */
__HAL_UART_ENABLE_IT(&huart2, UART_IT_WUF);
注意: 配置完唤醒后,不要调用 HAL_UART_Receive_IT() 启动接收中断。唤醒中断和接收中断是两回事。唤醒中断只负责把系统叫醒,数据接收由后续的接收中断或 DMA 完成。

4.3 唤醒后的数据接收处理

系统被唤醒后,USART 会继续接收当前帧的剩余字节。但有个细节:唤醒中断触发时,第一个字节可能已经接收了一部分。这时候 RXNE 标志位可能还没置起来。

我建议的处理流程是这样的:

  • 在 HAL_UARTEx_WakeupCallback() 中,先清除 WUF 标志,然后启动一次单字节接收中断。
  • 收到第一个字节后,再根据协议决定是否启动 DMA 或继续中断接收。
  • 如果数据量不大,直接用中断接收完整个报文。

我曾经在一个项目中偷懒,直接在唤醒回调里调用 HAL_UART_Receive_DMA(),结果发现 DMA 启动时第一个字节已经丢了。原因是唤醒到 DMA 启动之间有几十微秒的延迟,串口数据已经流过去了。后来改成先收一个字节再切 DMA,问题解决。

4.4 知识结构图:USART 唤醒流程

下面这张图把整个唤醒流程串起来了。从系统进入 Stop 模式开始,到串口检测起始位、触发唤醒、恢复时钟、接收数据,最后回到低功耗状态。

USART 唤醒源工作流程 系统进入 Stop 模式 USART 监听 RX 线(LSE/HSI16) 检测到起始位下降沿 → 触发 WUF 系统唤醒,恢复主时钟 接收完整数据帧 处理完数据 → 再次进入 Stop 模式 关键配置项 • 时钟源:LSE / HSI16 • 唤醒条件:起始位 • 中断使能:WUFIE

4.5 实测中的避坑指南

配置看起来简单,实际调试时问题不少。我列几个自己踩过的坑:

  • 唤醒后 RXNE 不置位 —— 检查波特率是否匹配。如果唤醒时钟源是 LSE,波特率不能超过 9600。我试过用 LSE 跑 115200,结果唤醒后收到的全是乱码。
  • 频繁误唤醒 —— 如果 RX 线上有噪声,起始位检测可能被误触发。可以在 RX 引脚上加一个 10kΩ 上拉电阻,或者使能 USART 的噪声滤波功能。
  • 唤醒后系统卡死 —— 检查中断优先级。WUF 中断优先级不能高于 SysTick,否则在唤醒过程中可能造成调度混乱。
调试小技巧: 用逻辑分析仪抓 RX 线的波形,同时监控一个 GPIO 翻转脚。在唤醒回调里翻转这个 GPIO,就能精确测量从起始位出现到系统恢复执行代码的时间。我测下来,用 HSI16 唤醒大约需要 5-8 µs,用 LSE 则要 15-20 µs。

4.6 功耗与性能的权衡

最后聊点实际的。USART 唤醒虽然方便,但不是免费的。即使芯片在 Stop 模式,USART 模块本身还在耗电。LSE 作为时钟源时,USART 的额外功耗大约 1-2 µA;如果用 HSI16,这个数字会升到 8-10 µA。

如果你的系统对功耗极其敏感,比如纽扣电池供电、要求待机电流低于 2 µA,那 USART 唤醒可能就不太合适了。这时候可以考虑 LPUART——低功耗串口,用 LSE 时钟时额外功耗只有 0.5 µA 左右。不过 LPUART 的波特率上限只有 9600,而且 FIFO 深度也浅一些。

嗯,USART 唤醒这部分内容就到这里。配置不复杂,但细节不少。记住时钟源、唤醒条件、中断处理这三个关键点,基本不会出大问题。