news 2026/4/18 8:22:10

STM32 USART接收中断:如何判断数据接收完成?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32 USART接收中断:如何判断数据接收完成?

引言

在STM32的串口通信开发中,中断方式接收数据是最常见的方式之一。然而,很多开发者都会遇到一个关键问题:"如何判断一帧数据已经接收完成?"今天我们就来深入探讨这个问题,并提供几种实用的解决方案。

一、为什么需要判断数据接收完成?

在串口通信中,数据是以字节流的形式传输的。当我们在中断服务函数中每次只接收一个字节时,需要一种机制来判断当前接收的数据是否构成一个完整的消息帧。常见的应用场景包括:

  • 接收不定长数据帧

  • 解析协议数据包(如Modbus、自定义协议等)

  • 处理命令行指令

  • 接收传感器数据

二、基本原理

在USART接收中断服务函数中,我们通常会这样开始:

void USART1_IRQHandler(void) { if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { // 读取接收到的数据 uint8_t rx_data = USART_ReceiveData(USART1); // ... 处理数据 } }

但这里只接收了一个字节,如何知道数据接收完成了呢?

三、四种判断数据接收完成的方法

方法1:超时判断法

这是最常用的方法之一,通过判断相邻两个字节之间的时间间隔来判断数据是否接收完成。

// 定义接收结构体 typedef struct { uint8_t buffer[256]; uint16_t index; uint8_t flag; uint32_t last_time; } UART_RxTypeDef; UART_RxTypeDef uart1_rx; // 中断服务函数 void USART1_IRQHandler(void) { if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { uint8_t data = USART_ReceiveData(USART1); // 记录当前时间(可以使用SysTick或定时器) uart1_rx.last_time = SysTick->VAL; // 存储数据 if(uart1_rx.index < 256) { uart1_rx.buffer[uart1_rx.index++] = data; } USART_ClearITPendingBit(USART1, USART_IT_RXNE); } } // 主循环或定时器中断中检查超时 void Check_UART_Timeout(void) { uint32_t current_time = SysTick->VAL; uint32_t time_diff = abs(current_time - uart1_rx.last_time); // 如果超过设定的超时时间(如10ms) if(time_diff > 10000 && uart1_rx.index > 0) // 10ms超时 { uart1_rx.flag = 1; // 标记数据接收完成 } }

优点

  • 适用于不定长数据

  • 实现相对简单

缺点

  • 需要额外的定时器资源

  • 超时时间需要根据波特率调整

方法2:特定帧头帧尾法

这种方法适用于有固定格式的协议。

#define FRAME_HEADER 0xAA #define FRAME_FOOTER 0x55 typedef enum { RX_STATE_IDLE, RX_STATE_HEADER, RX_STATE_DATA, RX_STATE_COMPLETE } RxStateTypeDef; void USART1_IRQHandler(void) { static RxStateTypeDef rx_state = RX_STATE_IDLE; static uint8_t rx_index = 0; static uint8_t rx_buffer[256]; if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { uint8_t data = USART_ReceiveData(USART1); switch(rx_state) { case RX_STATE_IDLE: if(data == FRAME_HEADER) { rx_index = 0; rx_state = RX_STATE_HEADER; } break; case RX_STATE_HEADER: rx_buffer[rx_index++] = data; rx_state = RX_STATE_DATA; break; case RX_STATE_DATA: if(data == FRAME_FOOTER) { rx_state = RX_STATE_COMPLETE; // 数据接收完成处理 Process_Complete_Frame(rx_buffer, rx_index); } else if(rx_index < 255) { rx_buffer[rx_index++] = data; } else { // 缓冲区溢出,重置状态 rx_state = RX_STATE_IDLE; } break; default: rx_state = RX_STATE_IDLE; break; } USART_ClearITPendingBit(USART1, USART_IT_RXNE); } }

优点

  • 可靠性高

  • 适合有固定格式的协议

缺点

  • 数据中不能出现与帧头帧尾相同的字符

  • 需要转义机制或使用字节填充

方法3:固定长度法

如果数据长度是固定的,这种方法最简单。

#define FIXED_LENGTH 10 void USART1_IRQHandler(void) { static uint8_t rx_buffer[FIXED_LENGTH]; static uint8_t rx_count = 0; if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { rx_buffer[rx_count++] = USART_ReceiveData(USART1); if(rx_count >= FIXED_LENGTH) { // 数据接收完成 Process_Complete_Frame(rx_buffer, FIXED_LENGTH); rx_count = 0; } USART_ClearITPendingBit(USART1, USART_IT_RXNE); } }

优点

  • 实现简单

  • 效率高

缺点

  • 只适用于固定长度数据

  • 缺乏灵活性

方法4:长度字段法

在数据包中包含长度信息,这是最专业的方法。

typedef struct { uint8_t header; // 帧头 uint8_t length; // 数据长度 uint8_t data[255]; // 数据 uint8_t checksum; // 校验和 } UART_FrameTypeDef; void USART1_IRQHandler(void) { static uint8_t rx_state = 0; static uint8_t rx_length = 0; static uint8_t rx_count = 0; static uint8_t rx_buffer[256]; if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { uint8_t data = USART_ReceiveData(USART1); switch(rx_state) { case 0: // 等待帧头 if(data == 0xAA) { rx_state = 1; rx_count = 0; } break; case 1: // 获取长度 rx_length = data; if(rx_length > 0 && rx_length <= 255) { rx_state = 2; } else { rx_state = 0; // 长度错误,重新开始 } break; case 2: // 接收数据 rx_buffer[rx_count++] = data; if(rx_count >= rx_length) { rx_state = 3; } break; case 3: // 接收校验和 if(Verify_Checksum(rx_buffer, rx_length, data)) { // 数据接收完成且校验通过 Process_Complete_Frame(rx_buffer, rx_length); } rx_state = 0; break; } USART_ClearITPendingBit(USART1, USART_IT_RXNE); } }

优点

  • 灵活,支持变长数据

  • 可靠性高

缺点

  • 实现相对复杂

  • 需要处理异常情况

四、实战建议

1.结合使用多种方法

在实际项目中,我推荐结合使用超时判断和协议解析。例如:

  • 使用超时机制作为安全保障

  • 使用协议解析作为主要判断依据

2.使用DMA+IDLE中断(高级方法)

对于STM32的高端型号,可以使用DMA配合IDLE中断,这是最高效的方法:

// 启用IDLE中断 USART_ITConfig(USART1, USART_IT_IDLE, ENABLE); void USART1_IRQHandler(void) { // 接收中断 if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { // DMA自动接收,无需在此处理 USART_ClearITPendingBit(USART1, USART_IT_RXNE); } // IDLE中断 - 检测到总线空闲 if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET) { // 清除IDLE中断标志(先读USART_SR,再读USART_DR) volatile uint32_t temp = USART1->SR; temp = USART1->DR; (void)temp; // 获取DMA接收的数据长度 uint16_t rx_len = BUFFER_SIZE - DMA_GetCurrDataCounter(DMA1_Channel5); if(rx_len > 0) { // 处理接收到的数据 Process_Complete_Frame(dma_buffer, rx_len); // 重置DMA DMA_Cmd(DMA1_Channel5, DISABLE); DMA_SetCurrDataCounter(DMA1_Channel5, BUFFER_SIZE); DMA_Cmd(DMA1_Channel5, ENABLE); } } }

3.错误处理

不要忘记处理通信错误:

if(USART_GetITStatus(USART1, USART_IT_ORE) != RESET || USART_GetITStatus(USART1, USART_IT_NE) != RESET || USART_GetITStatus(USART1, USART_IT_FE) != RESET) { // 处理溢出、噪声、帧错误 USART_ClearITPendingBit(USART1, USART_IT_ORE | USART_IT_NE | USART_IT_FE); // 重置接收状态 Reset_Rx_State(); }

五、性能优化建议

  1. 使用双缓冲区:一个用于接收,一个用于处理,避免数据竞争

  2. 合理设置超时时间:根据波特率调整,通常为3-5个字符时间

  3. 避免在中断中长时间处理:只做必要的标志设置,数据处理放在主循环

  4. 使用RTOS的消息队列:在中断中发送消息,在任务中处理

总结

判断USART接收数据是否完成有多种方法,选择哪种方法取决于:

  • 数据格式(固定长度/可变长度)

  • 协议要求

  • 系统资源

  • 可靠性要求

对于大多数应用,超时判断法长度字段法的组合是最佳选择。对于高性能要求,DMA+IDLE中断是不二之选。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 5:31:24

如何在C++26中精准绑定线程到指定CPU核心?(附完整代码示例)

第一章&#xff1a;C26中CPU核心绑定的背景与意义在现代高性能计算和实时系统开发中&#xff0c;程序对底层硬件资源的控制能力愈发重要。C26标准正计划引入对CPU核心绑定&#xff08;CPU affinity&#xff09;的原生支持&#xff0c;标志着语言在系统级编程能力上的进一步深化…

作者头像 李华
网站建设 2026/4/18 5:39:06

Teambition任务分配明确lora-scripts各成员职责分工

Teambition任务分配明确lora-scripts各成员职责分工 在AIGC&#xff08;生成式人工智能&#xff09;迅速渗透内容创作、企业服务与个性化应用的今天&#xff0c;越来越多团队希望基于大模型训练专属能力——无论是打造具有个人艺术风格的图像生成器&#xff0c;还是构建面向特定…

作者头像 李华
网站建设 2026/4/18 5:40:51

vue+uniapp基于微信小程序的快递上门取件服务平台

文章目录摘要主要技术与实现手段系统设计与实现的思路系统设计方法java类核心代码部分展示结论源码lw获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;摘要 该平台基于Vue.js和UniApp框架开发&#xff0c;旨在为微信小程序用户提供便捷的快递上门…

作者头像 李华
网站建设 2026/4/17 10:00:02

C++多线程资源死锁频发?:5步定位并根除资源管理隐患

第一章&#xff1a;C多线程资源死锁频发&#xff1f;&#xff1a;5步定位并根除资源管理隐患在高并发的C应用中&#xff0c;资源死锁是导致程序挂起甚至崩溃的主要元凶之一。多个线程因争夺有限资源而相互等待&#xff0c;形成循环依赖&#xff0c;最终陷入永久阻塞。要有效解决…

作者头像 李华
网站建设 2026/4/18 3:47:58

揭秘C++26反射系统:如何用5行代码完成复杂对象序列化?

第一章&#xff1a;C26反射系统概述C26 的反射系统标志着语言在元编程能力上的重大飞跃。通过原生支持编译时反射&#xff0c;开发者能够直接查询和操作类型、成员变量、函数及属性的结构信息&#xff0c;而无需依赖宏或外部代码生成工具。核心特性 编译时类型检查与属性提取无…

作者头像 李华
网站建设 2026/4/18 3:47:34

CSDN博客矩阵运营覆盖更多‘markdown’‘git commit’搜索人群

CSDN博客矩阵运营覆盖更多“markdown”“git commit”搜索人群 在当前AIGC内容爆发的时代&#xff0c;技术创作者面临的不再是“有没有内容可写”&#xff0c;而是“如何高效产出高质量、有差异化的专业内容”。尤其对于深耕AI、开发工具链的博主而言&#xff0c;单纯讲解理论或…

作者头像 李华