4. 低功耗串口接收不定长数据
任务定义
在电池供电的IoT节点中,MCU大部分时间处于睡眠状态,但需要随时响应串口到来的不定长命令帧。本案例实现:MCU在睡眠模式下由低功耗UART(LPUART)检测起始位并唤醒,接收完整一帧不定长数据(以空闲线或超时判定帧结束),随后执行数据处理并重新进入睡眠。目标平台为STM32L0系列,代码基于HAL库。
硬件连接与数据帧格式
- LPUART1_TX:PA2,LPUART1_RX:PA3(默认复用)
- 帧格式:1字节地址(0xA5)+ N字节负载 + 1字节CRC8校验,N=1~16不定长
- 帧结束判定:接收空闲超过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字节时间时触发,表示一帧数据接收完毕。
验证方法
- 将PA2(TX)与外部USB转串口模块连接,波特率9600,8N1。
- 发送测试帧:
0xA5 0x01 0x02 0x03 0x??(CRC8由发送端计算,例如负载为0x01 0x02 0x03时CRC=0x5A)。 - 观察现象:MCU从睡眠唤醒,串口回显
0x01 0x02 0x03,随后再次进入睡眠(电流降至µA级)。 - 发送错误CRC帧(如
0xA5 0x01 0x02 0x03 0x00),MCU不应回显数据。
判定标准:正确帧触发唤醒并回显负载;错误帧被丢弃;空闲超过2字节时间后帧结束判定生效;MCU在无数据时处于Stop模式(功耗<5µA)。