STM32 串口不定长接收|空闲中断 IDLE 方案,无固定帧长限制,项目实战

一、项目硬件说明

主控:STM32F103RCT6

外设:USART1,波特率 115200

场景:上位机下发不定长指令,单片机接收并解析,报文长度动态变化,没有固定长度。

核心设计原则:中断只做数据缓存,业务解析全部放到 while 主循环,禁止在中断函数做复杂处理,防止丢字节。

二、全局缓冲区定义

#include "stm32f10x.h"

#define USART_RX_BUF_SIZE 256 // 接收缓存大小,根据项目调整

uint8_t g_usart_rx_bufUSART_RX_BUF_SIZE;

uint16_t g_usart_rx_cnt = 0; // 当前接收字节计数

uint8_t g_usart_frame_ready = 0; // 一帧数据接收完成标志

三、USART1 初始化(开启接收中断 + 空闲中断)

GPIO、NVIC 基础配置省略,重点开启RXNE接收中断、IDLE空闲中断。

void USART1_Config(uint32_t baudrate)

{

GPIO_InitTypeDef GPIO_InitStruct;

USART_InitTypeDef USART_InitStruct;

NVIC_InitTypeDef NVIC_InitStruct;

复制代码
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);

// PA9 TX
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);

// PA10 RX
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStruct);

USART_InitStruct.USART_BaudRate = baudrate;
USART_InitStruct.USART_WordLength = USART_WordLength_8b;
USART_InitStruct.USART_StopBits = USART_StopBits_1;
USART_InitStruct.USART_Parity = USART_Parity_No;
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &USART_InitStruct);

// 关键:开启接收中断 + 空闲中断IDLE
USART_ITConfig(USART1, USART_IT_RXNE | USART_IT_IDLE, ENABLE);

NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);

USART_Cmd(USART1, ENABLE);

}

四、中断服务函数(核心)

⚠️重点坑点:空闲中断 IDLE 标志不能直接调用 ClearITPendingBit 清除 ,必须先读 SR 状态寄存器,再读 DR 数据寄存器完成标志清零,很多人在这里踩坑导致空闲中断只触发一次。

void USART1_IRQHandler(void)

{

// 收到1字节数据

if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)

{

USART_ClearITPendingBit(USART1, USART_IT_RXNE);

复制代码
    // 防止缓冲区溢出
    if(g_usart_rx_cnt < USART_RX_BUF_SIZE)
    {
        g_usart_rx_buf[g_usart_rx_cnt++] = USART_ReceiveData(USART1);
    }
}

// 总线空闲,判定一帧不定长数据接收完毕
if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)
{
    // IDLE中断标准清除序列,固定写法,不要省略
    (void)USART_GetFlagStatus(USART1, USART_FLAG_IDLE);
    (void)USART_ReceiveData(USART1);

    // 过滤空帧
    if(g_usart_rx_cnt > 0)
    {
        g_usart_frame_ready = 1;
    }
}

}

五、主循环处理不定长报文

中断仅仅置位标志位,解析、打印、业务逻辑全部放在 main 的 while 循环

int main(void)

{

USART1_Config(115200);

复制代码
while(1)
{
    if(g_usart_frame_ready)
    {
        /*****************
         * 此处处理不定长报文
         * g_usart_rx_buf:数据缓存
         * g_usart_rx_cnt:当前帧有效字节数
         *****************/
        
        // 示例:printf打印收到的不定长数据
        for(uint16_t i = 0; i < g_usart_rx_cnt; i++)
        {
            printf("%02X ", g_usart_rx_buf[i]);
        }
        printf("\r\n");

        // 清空计数与标志,准备接收下一帧
        g_usart_rx_cnt = 0;
        g_usart_frame_ready = 0;
    }
}

}

六、HAL 库简易版本补充(可选)

CubeMX 生成工程,使用 HAL 库 IDLE 空闲中断思路一致,供快速参考

// usart.c

extern UART_HandleTypeDef huart1;

#define RX_BUF_SIZE 256

uint8_t rx_bufRX_BUF_SIZE;

uint16_t rx_cnt = 0;

uint8_t frame_ok = 0;

void USART1_IRQHandler(void)

{

HAL_UART_IRQHandler(&huart1);

}

// 回调函数

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)

{

if(huart->Instance == USART1)

{

if(rx_cnt < RX_BUF_SIZE)

{

rx_bufrx_cnt++ = huart->Instance->DR;

}

HAL_UART_Receive_IT(&huart1, &rx_bufrx_cnt, 1);

}

}

void HAL_UART_IdleCallback(UART_HandleTypeDef *huart)

{

if(huart->Instance == USART1)

{

if(rx_cnt > 0) frame_ok = 1;

}

}

七、项目实战坑点汇总

  1. 空闲中断只触发一次
    IDLE 标志清除方式错误,不能使用USART_ClearITPendingBit,必须执行读 SR、读 DR 操作。
  2. 短时间连续发送出现粘帧
    空闲中断依靠总线空闲电平判定帧结束;如果上位机报文间隔极短,硬件识别不到空闲,多包数据合并。
    解决:通信协议增加帧间隔;高频场景改用帧头帧尾状态机方案。
  3. 丢字节现象
    中断函数内部不要写 printf、延时、复杂解析;解析逻辑移出中断;适当调高串口中断抢占优先级。
  4. 出现空帧
    判断g_usart_rx_cnt > 0,过滤总线扰动产生的空闲中断误触发。
  5. 缓冲区溢出 USART_RX_BUF_SIZE根据项目最大报文长度预留,接收时做长度判断,防止数组越界。

八、方案优缺点总结

✅ 优点

  • 不需要定时器,不占用额外片上外设资源
  • 无需预先约定报文长度,原生支持不定长接收
  • 代码量少,移植简单,大部分 STM32 芯片支持 IDLE 空闲中断

❌ 缺点

  • 报文间隔过小会发生粘帧,不适合无间隔高速连续数据流

适用场景:上位机指令交互、传感器回传报文、人机交互串口命令。

源码设计思路:硬件空闲事件判定帧边界,中断层只做最轻量化 IO 搬运,业务逻辑和底层驱动解耦,满足中小型项目稳定运行要求。

相关推荐
我和头发拼了3 小时前
STM32--10硬件读写MPU6050
stm32·单片机·嵌入式硬件
糖糖单片机设计5 小时前
基于STM32的鱼缸水质检测系统
stm32·单片机·嵌入式硬件
HRTOS5 小时前
HRTOS 4.0 正式发布:Driver Library 基础外设驱动模块介绍
驱动开发·单片机·嵌入式硬件·51单片机
染予6 小时前
20个点位下发该如何正常操作?
单片机·嵌入式硬件
wuyk5556 小时前
从零吃透 MQTT 通信|第 5 章 MQTT 心跳保活机制深度优化,断线检测与智能自动重连
c语言·开发语言·stm32·学习
jianqiang.xue7 小时前
ESP-IDF保姆级入门23|MQTT物联网通信全解:发布订阅模式/QoS等级/遗嘱消息/主题设计/断线重连,掌握工业级标准消息通信
stm32·单片机·mcu·物联网·51单片机·iot
HRTOS7 小时前
HRTOS 4.0 Driver Library:03_Sensor 传感器驱动
驱动开发·单片机·嵌入式硬件·51单片机
youcans_7 小时前
【嵌入式软件AI编程】01. 基于 STM32/Claude Code的AI编程
stm32·mcu·嵌入式·ai编程·claude code
小麦嵌入式7 小时前
FPGA入门(十一):受控线性序列机 UART 发送升级
stm32·单片机·嵌入式硬件·mcu·fpga开发·硬件工程