STM32 串口不定长数据接收 实战方案 | 多种实现方式 + 工程落地避坑
摘要
串口不定长数据接收是嵌入式常用功能,单纯接收中断无法判定帧结束。本文结合项目实战,精简给出空闲中断、定时器超时、帧头帧尾状态机三种主流方案,代码极简、可直接移植,同时说明选型与常见问题,适合快速落地使用。
一、通用基础定义
统一缓冲区与标志位,所有方案共用:
#define RX_BUF_LEN 256
uint8_t rx_buf[RX_BUF_LEN];
uint16_t rx_len = 0;
uint8_t frame_ok = 0;
二、方案一:串口空闲中断(STM32 首选,最简)
-
串口初始化(开启接收 + 空闲中断)
void USART1_Init(uint32_t baud)
{
GPIO/USART/NVIC 基础配置省略
USART_ITConfig(USART1, USART_IT_RXNE | USART_IT_IDLE, ENABLE);
USART_Cmd(USART1, ENABLE);
} -
中断服务函数
void USART1_IRQHandler(void)
{
// 接收字节
if(USART_GetITStatus(USART1, USART_IT_RXNE))
{
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
if(rx_len < RX_BUF_LEN)
rx_buf[rx_len++] = USART_ReceiveData(USART1);
}
// 总线空闲 = 一帧接收完成
if(USART_GetITStatus(USART1, USART_IT_IDLE))
{
(void)USART_GetStatus(USART1);
(void)USART_ReceiveData(USART1); // 空闲中断标准清中断方式
if(rx_len > 0) frame_ok = 1;
}
} -
主循环调用
int main(void)
{
USART1_Init(9600);
while(1)
{
if(frame_ok)
{
// 此处解析 rx_buf[0] ~ rx_buf[rx_len-1]
rx_len = 0;
frame_ok = 0;
}
}
}
特点:无额外外设、代码最少;短间隔连发数据会粘帧。
三、方案二:接收中断 + 定时器超时(通用兼容方案)
无空闲中断的芯片 / 场景使用,依靠超时判定帧结束。
1、 定时器 + 串口初始化(精简)
void TIM_Out_Init(void) // 5ms定时中断
{
// 基础分频、中断配置省略
TIM_Cmd(TIM2, DISABLE);
}
2、串口中断
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1, USART_IT_RXNE))
{
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
if(rx_len < RX_BUF_LEN)
rx_buf[rx_len++] = USART_ReceiveData(USART1);TIM_SetCounter(TIM2, 0); TIM_Cmd(TIM2, ENABLE); // 收到数据,重置并开启定时器}
}
3、 定时器中断(超时判帧)
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2, TIM_IT_Update))
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
TIM_Cmd(TIM2, DISABLE);
if(rx_len > 0) frame_ok = 1;
}
}
特点:通用性最强;占用 1 个定时器,超时时间按需调整。
四、方案三:帧头帧尾状态机(自定义协议,最稳定)
自主定义通信协议场景使用,示例:帧头0xAA 0x55、帧尾0x0D 0x0A。
#define HEAD1 0xAA
#define HEAD2 0x55
#define END1 0x0D
#define END2 0x0A
typedef enum {WAIT_HEAD, REC_DATA, WAIT_END} RxState;
RxState rx_sta = WAIT_HEAD;
uint8_t tmp_dat;
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1, USART_IT_RXNE))
{
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
tmp_dat = USART_ReceiveData(USART1);
switch(rx_sta)
{
case WAIT_HEAD:
if(tmp_dat == HEAD1) rx_sta = REC_DATA;
break;
case REC_DATA:
if(tmp_dat == HEAD2) { rx_len = 0; }
else if(tmp_dat == END1) { rx_sta = WAIT_END; }
else if(rx_len < RX_BUF_LEN) { rx_buf[rx_len++] = tmp_dat; }
break;
case WAIT_END:
if(tmp_dat == END2) frame_ok = 1;
rx_sta = WAIT_HEAD;
break;
default: rx_sta = WAIT_HEAD; break;
}
}
}
特点:抗干扰、不粘帧;需要上下位机统一协议。
五、方案选型 & 常见问题
选型建议
STM32 常规项目 → 空闲中断(首选)
芯片无空闲中断 / 通用 MCU → 定时器超时
自研协议、工业强干扰 → 帧头帧尾状态机
常见问题
丢字节:中断内只做缓存,解析放到主循环,提高中断优先级;
空帧 / 误触发:增加 rx_len > 0 判断过滤;
帧粘连:调大帧间隔 / 缩短定时器超时时间。
六、总结
三套代码均做精简处理,去掉冗余配置,可直接嵌入现有工程。核心原则:中断只收数据,业务解析移出中断,是串口长期稳定运行的关键。
更多推荐
所有评论(0)