4. 低功耗串口接收不定长数据

任务定义

在电池供电的IoT节点中,MCU大部分时间处于睡眠状态,但需要随时响应串口到来的不定长命令帧。本案例实现:MCU在睡眠模式下由低功耗UART(LPUART)检测起始位并唤醒,接收完整一帧不定长数据(以空闲线或超时判定帧结束),随后执行数据处理并重新进入睡眠。目标平台为STM32L0系列,代码基于HAL库。

硬件连接与数据帧格式

  • LPUART1_TX:PA2,LPUART1_RX:PA3(默认复用)
  • 帧格式:1字节地址(0xA5)+ N字节负载 + 1字节CRC8校验,N=1~16不定长
  • 帧结束判定:接收空闲超过2字节时间(空闲线检测)
STM32L0 MCU Stop Mode LPUART1 Wakeup RX: PA3 TX: PA2 UART 帧格式(不定长) 0xA5 地址 N 字节负载 (N=1~16) 不定长部分 CRC8 校验 空闲线检测(2字节时间无数据 → 帧结束) 唤醒

代码实现

// main.c 核心逻辑(基于STM32L0 HAL库)
#include "stm32l0xx_hal.h"

#define FRAME_ADDR      0xA5
#define MAX_PAYLOAD_LEN 16
#define RX_TIMEOUT_MS   50   // 空闲超时判定

UART_HandleTypeDef hlpuart1;
uint8_t rx_buffer[MAX_PAYLOAD_LEN + 2];  // 地址 + 负载 + CRC
volatile uint8_t rx_index = 0;
volatile uint8_t frame_complete = 0;

// CRC8 多项式 0x07
uint8_t crc8(uint8_t *data, uint8_t len) {
    uint8_t crc = 0x00;
    for (uint8_t i = 0; i < len; i++) {
        crc ^= data[i];
        for (uint8_t j = 0; j < 8; j++) {
            crc = (crc & 0x80) ? (crc << 1) ^ 0x07 : (crc << 1);
        }
    }
    return crc;
}

void enter_stop_mode(void) {
    HAL_UART_DeInit(&hlpuart1);
    // 重新配置LPUART为低功耗模式,使能起始位唤醒
    hlpuart1.Instance = LPUART1;
    hlpuart1.Init.BaudRate = 9600;
    hlpuart1.Init.WordLength = UART_WORDLENGTH_8B;
    hlpuart1.Init.StopBits = UART_STOPBITS_1;
    hlpuart1.Init.Parity = UART_PARITY_NONE;
    hlpuart1.Init.Mode = UART_MODE_TX_RX;
    hlpuart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    HAL_UART_Init(&hlpuart1);

    // 使能RXNE中断和空闲中断
    __HAL_UART_ENABLE_IT(&hlpuart1, UART_IT_RXNE);
    __HAL_UART_ENABLE_IT(&hlpuart1, UART_IT_IDLE);

    // 进入Stop模式,LPUART时钟保持运行
    HAL_SuspendTick();
    HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
    HAL_ResumeTick();
}

void process_frame(uint8_t *frame, uint8_t len) {
    // 帧格式: [0]=地址, [1..len-2]=负载, [len-1]=CRC
    if (frame[0] != FRAME_ADDR) return;
    uint8_t payload_len = len - 2;
    uint8_t calc_crc = crc8(&frame[1], payload_len);
    if (calc_crc != frame[len-1]) return;

    // 处理负载数据(示例:将负载回显)
    HAL_UART_Transmit(&hlpuart1, &frame[1], payload_len, 100);
}

// LPUART1 中断处理
void LPUART1_IRQHandler(void) {
    if (__HAL_UART_GET_FLAG(&hlpuart1, UART_FLAG_RXNE)) {
        rx_buffer[rx_index++] = (uint8_t)(hlpuart1.Instance->RDR & 0xFF);
        if (rx_index >= MAX_PAYLOAD_LEN + 2) {
            rx_index = 0;  // 溢出保护
        }
    }
    if (__HAL_UART_GET_FLAG(&hlpuart1, UART_FLAG_IDLE)) {
        __HAL_UART_CLEAR_IDLEFLAG(&hlpuart1);
        if (rx_index > 0) {
            frame_complete = 1;
        }
    }
}

int main(void) {
    HAL_Init();
    // ... 系统时钟配置(MSI 2MHz) ...

    while (1) {
        enter_stop_mode();  // 进入睡眠,等待唤醒

        // 唤醒后处理接收到的帧
        if (frame_complete) {
            process_frame(rx_buffer, rx_index);
            rx_index = 0;
            frame_complete = 0;
        }
    }
}

关键配置说明

  • 时钟配置:LPUART1在Stop模式下使用MSI或LSE作为时钟源,需在CubeMX中设置LPUART1时钟为LSE(32.768kHz)或MSI,确保睡眠时外设仍可运行。
  • 唤醒机制:LPUART检测到起始位下降沿即产生唤醒事件,MCU从Stop模式恢复,随后RXNE中断逐字节接收数据。
  • 帧结束判定:利用UART空闲线中断(IDLE),当总线空闲超过1字节时间时触发,表示一帧数据接收完毕。

验证方法

  1. 将PA2(TX)与外部USB转串口模块连接,波特率9600,8N1。
  2. 发送测试帧:0xA5 0x01 0x02 0x03 0x??(CRC8由发送端计算,例如负载为0x01 0x02 0x03时CRC=0x5A)。
  3. 观察现象:MCU从睡眠唤醒,串口回显0x01 0x02 0x03,随后再次进入睡眠(电流降至µA级)。
  4. 发送错误CRC帧(如0xA5 0x01 0x02 0x03 0x00),MCU不应回显数据。

判定标准:正确帧触发唤醒并回显负载;错误帧被丢弃;空闲超过2字节时间后帧结束判定生效;MCU在无数据时处于Stop模式(功耗<5µA)。