一、为什么需要低功耗串口接收?

在电池供电的嵌入式设备中,MCU大部分时间应处于低功耗模式。但串口接收是异步事件,传统轮询或每字节中断会频繁唤醒CPU,增加功耗。理想方案是:无数据时MCU进入STOP模式,串口收到数据时自动唤醒,并用DMA搬运数据,空闲中断判断一帧结束。这样CPU仅在完整帧到达后处理一次,极大降低功耗。

二、原理讲解

2.1 STM32低功耗模式

  • Sleep:仅关闭内核时钟,外设仍运行,唤醒快但功耗较高。
  • STOP:关闭所有时钟(除LSI/LSE和唤醒源),保留SRAM和寄存器,功耗约几μA~几十μA。
  • Standby:功耗最低,但SRAM丢失,唤醒相当于复位。

本方案选用STOP模式,因为需要保留串口配置和DMA状态。

2.2 串口空闲中断(IDLE)

串口在接收完一帧数据后,总线会保持空闲(高电平)一个字节时间,此时触发IDLE中断。利用它可判断“一帧结束”,无需定时器。

2.3 DMA接收

DMA将串口数据自动搬运到内存缓冲区,不占用CPU。配合IDLE中断,可在中断中计算接收长度并处理。

2.4 STOP模式唤醒源

STM32的USART支持在STOP模式下通过RX引脚边沿唤醒(需配置USART时钟源为HSI或LSE,并使能唤醒中断)。当起始位到来时,USART产生唤醒事件,MCU退出STOP,随后DMA继续搬运数据。

三、CubeMX配置步骤

以STM32L4系列为例(其他系列类似):

  1. 时钟配置:使能HSI16作为USART时钟源(STOP模式下HSI可保持)。
  2. USART配置:
    • 模式:Asynchronous
    • 波特率:115200
    • 开启USART全局中断(NVIC中使能USARTx中断)
    • 使能DMA接收:添加USARTx_RX的DMA通道,模式为Normal(或Circular,视需求)
  3. NVIC:使能USARTx中断,并设置合适优先级。
  4. 低功耗:无需额外配置,代码中调用HAL_PWR_EnterSTOPMode。

四、完整代码示例

4.1 变量定义

#define RX_BUFFER_SIZE 128
uint8_t rx_dma_buffer[RX_BUFFER_SIZE];
volatile uint8_t rx_frame_ready = 0;
volatile uint16_t rx_frame_len = 0;

4.2 初始化

void USART_Init(void)
{
    // 启动DMA接收
    HAL_UART_Receive_DMA(&huart1, rx_dma_buffer, RX_BUFFER_SIZE);
    // 使能空闲中断
    __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
}

4.3 空闲中断处理

在stm32l4xx_it.c的USART1_IRQHandler中:

void USART1_IRQHandler(void)
{
    if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE))
    {
        __HAL_UART_CLEAR_IDLEFLAG(&huart1);
        // 停止DMA以获取已接收长度
        HAL_UART_DMAStop(&huart1);
        rx_frame_len = RX_BUFFER_SIZE - __HAL_DMA_GET_COUNTER(huart1.hdmarx);
        rx_frame_ready = 1;
        // 重新启动DMA接收
        HAL_UART_Receive_DMA(&huart1, rx_dma_buffer, RX_BUFFER_SIZE);
    }
    HAL_UART_IRQHandler(&huart1);
}

4.4 主循环与STOP模式

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_USART1_UART_Init();
    USART_Init();

    while (1)
    {
        if (rx_frame_ready)
        {
            // 处理接收到的数据
            process_frame(rx_dma_buffer, rx_frame_len);
            rx_frame_ready = 0;
        }
        else
        {
            // 进入STOP模式,等待串口唤醒
            HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
            // 唤醒后需重新配置系统时钟(STOP模式会关闭HSI以外时钟)
            SystemClock_Config();
        }
    }
}

4.5 唤醒配置

在HAL_UART_MspInit中确保USART时钟源为HSI,并调用:

__HAL_UART_ENABLE_IT(&huart1, UART_IT_WUF); // 使能唤醒中断
HAL_UARTEx_EnableStopMode(&huart1);         // 允许STOP模式唤醒

五、注意事项

  • 时钟恢复:STOP模式唤醒后,系统时钟可能切换为HSI,需重新调用SystemClock_Config()恢复原时钟。
  • DMA重启:每次空闲中断后需重新启动DMA,否则无法接收下一帧。
  • 缓冲区溢出:若一帧数据超过缓冲区大小,DMA会覆盖,建议使用循环模式或加大缓冲区。
  • 唤醒延迟:STOP唤醒需要几个时钟周期,波特率较高时可能丢失第一个字节,可适当降低波特率或使用硬件流控。
  • 中断优先级:USART中断优先级应高于DMA,避免竞争。
  • 功耗测量:实际功耗受外设漏电流影响,建议关闭未使用外设时钟。

六、总结

本文方案通过DMA+空闲中断+STOP模式,实现了低功耗串口接收。核心在于利用串口唤醒事件退出STOP,DMA自动搬运数据,空闲中断判断帧尾。代码简洁,适合电池供电的传感器节点、无线模块等场景。开发者可根据具体型号调整时钟和唤醒配置,即可获得μA级待机功耗与可靠接收。