文章目录
- 轮询模式 (Polling)
- 中断模式 (Interrupt)
- CUBEMX 优先级配置
- 中断实现数据包的接收(串口接收中断+串口空闲中断)
- 核心思路
- 代码实现(HAL库)
- 定义全局变量
- 串口初始化(使能RXNE + IDLE中断)
- 中断服务函数
- 主循环处理
- CG
轮询模式 (Polling)
- 轮询模式最大的优点是实现极其简单、执行路径完全可控,不需要配置 NVIC、中断优先级和 ISR,调试时单步跟踪也直观,适合在串口功能验证或简单裸机场景下快速跑通。
- 但它的缺点也很明显:
HAL_UART_Receive()在超时时间内会阻塞主循环,导致 CPU 无法及时处理其他任务,实时性差且 CPU 占用高;波特率越高或数据越密集,越容易因为主循环被卡住而漏收数据,甚至触发 ORE 溢出错误。因此它只适合低波特率、单任务或对实时性要求不高的场景,正式工程里如果后续还要跑多任务或处理高频通信,建议尽早切换其他模式。
/* main.c */#include"main.h"#include"usart.h"/* USER CODE BEGIN PV */volatileuint8_tusart3_rx_buffer[USART3_RX_BUFFER_SIZE];volatileuint16_tusart3_rx_count=0;volatileuint8_tusart3_rx_flag=0;/* USER CODE END PV */intmain(void){/* USER CODE BEGIN 1 *//* USER CODE END 1 */HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_USART3_UART_Init();/* USER CODE BEGIN 2 *//* USER CODE END 2 *//* USER CODE BEGIN WHILE */while(1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 *//* ---- 轮询接收 USART3 数据 ---- */uint8_trx_byte=0;HAL_StatusTypeDef status=HAL_UART_Receive(&huart3,&rx_byte,1,100);if(status==HAL_OK){/* 收到一个字节 */if(rx_byte=='\r'||rx_byte=='\n'){/* 一帧结束 */usart3_rx_flag=1;}else{if(usart3_rx_count<USART3_RX_BUFFER_SIZE){usart3_rx_buffer[usart3_rx_count++]=rx_byte;}}}/* HAL_TIMEOUT 表示没有新数据,继续循环即可 *//* ---- 处理一帧数据 ---- */if(usart3_rx_flag){usart3_rx_flag=0;/* 这里解析 usart3_rx_buffer,长度是 usart3_rx_count *//* 例如:回显、协议解析等 */usart3_rx_count=0;/* 清空计数 */}/* 其他任务放在这里 *//* USER CODE END 3 */}}HAL_UART_Receive的超时时间:这里用100毫秒,可以根据波特率调整。波特率越高、数据越密集,超时可以设短一点,减少主循环阻塞。
中断模式 (Interrupt)
STM32 的中断优先级由内核里的NVIC管理,本质是把每个中断的优先级拆成两部分:抢占优先级和子优先级。
- 抢占优先级:数值越小优先级越高,决定能不能打断正在执行的中断。抢占优先级高的中断可以抢占抢占优先级低的中断,形成中断嵌套。
- 子优先级:也叫响应优先级,数值越小越优先。但它只在抢占优先级相同时起作用,用来决定多个中断同时挂起时谁先执行;抢占优先级相同时,子优先级高的中断不能打断正在执行的同抢占优先级中断。
- 如果抢占优先级和子优先级都相同,则按硬件固定的IRQ 编号决定,编号小的优先响应。
- 中断嵌套:CPU 正在执行一个低抢占优先级的中断服务函数时,来了一个抢占优先级更高的中断,CPU 暂停当前中断,先去执行高优先级中断,执行完再回来继续原来的中断。在 STM32 里,嵌套只和 抢占优先级 有关,和 子优先级 无关。
STM32 使用优先级寄存器的高 4 位来配置优先级,这 4 位可以按 5 种分组方式划分:
| 分组 | 抢占优先级位数 | 子优先级位数 |
|---|---|---|
| Group 0 | 0 | 4 |
| Group 1 | 1 | 3 |
| Group 2 | 2 | 2 |
| Group 3 | 3 | 1 |
| Group 4 | 4 | 0 |
CUBEMX 优先级配置
- HAL 库中配置流程通常是:
HAL_NVIC_SetPriority(USART3_IRQn,1,0);HAL_NVIC_EnableIRQ(USART3_IRQn);“4 bits for pre-emption priotiry”:即 4 bits 全部用于抢占优先级(相当于 Group 4 / 无子优先级划分)。
实际工程中比较推荐的做法是:分组只设置一次,不要中途修改;抢占优先级留给真正紧急的中断,比如保护、高速控制、紧急故障;通信类中断如 USART、SPI、I2C 一般放在中低优先级;SysTick 通常放在较低优先级,避免它频繁打断业务中断。
[STM32 HAL库][中断]中断优先级,最佳教程,没有之一~
中断实现数据包的接收(串口接收中断+串口空闲中断)
核心思路
- RXNE中断:每收到1个字节触发,将数据存入缓冲区并计数
- IDLE中断:总线空闲超过1个字符时间后触发,标志一帧数据接收完毕
- 当前纯中断方案适合波特率 ≤ 115200、帧长较短的场景,调试简单、逻辑清晰。
- CPU占用较高:每字节都进中断,921600bps下约每11μs一次中断,不适合持续大数据流
- 粘帧风险:如果两帧数据间隔小于1个字符时间,IDLE无法区分,会粘成一帧
代码实现(HAL库)
- CubeMX 配置:NVIC 配置使能 USARTx 全局中断,抢占优先级建议设高(如1),避免被其他中断阻塞导致ORE错误
定义全局变量
/* 接收缓冲区与状态变量 */#defineUSART3_RX_BUFFER_SIZE512volatileuint8_tusart3_rx_buffer[USART3_RX_BUFFER_SIZE];// 接收缓冲区volatileuint16_tusart3_rx_count=0;// 当前已接收字节数volatileuint8_tusart3_rx_flag=0;// 一帧接收完成标志串口初始化(使能RXNE + IDLE中断)
- 在生成的
MX_USART3_UART_Init()函数的/* USER CODE BEGIN USART3_Init 2 */区域中添加:
/* USER CODE BEGIN USART3_Init 2 */// 使能串口3的接收中断(RXNE)__HAL_UART_ENABLE_IT(&huart3,UART_IT_RXNE);// 使能串口3的空闲中断(IDLE)__HAL_UART_ENABLE_IT(&huart3,UART_IT_IDLE);/* USER CODE END USART3_Init 2 */- 整体代码如下:
void MX_USART3_UART_Init(void){/*USERCODE BEGIN USART3_Init0*/ /*USERCODE END USART3_Init0*/ /*USERCODE BEGIN USART3_Init1*/ /*USERCODE END USART3_Init1*/ huart3.Instance=USART3;huart3.Init.BaudRate=9600;huart3.Init.WordLength=UART_WORDLENGTH_8B;huart3.Init.StopBits=UART_STOPBITS_1;huart3.Init.Parity=UART_PARITY_NONE;huart3.Init.Mode=UART_MODE_TX_RX;huart3.Init.HwFlowCtl=UART_HWCONTROL_NONE;huart3.Init.OverSampling=UART_OVERSAMPLING_16;huart3.Init.OneBitSampling=UART_ONE_BIT_SAMPLE_DISABLE;huart3.Init.ClockPrescaler=UART_PRESCALER_DIV1;huart3.AdvancedInit.AdvFeatureInit=UART_ADVFEATURE_NO_INIT;if(HAL_UART_Init(&huart3)!=HAL_OK){Error_Handler();}if(HAL_UARTEx_SetTxFifoThreshold(&huart3, UART_TXFIFO_THRESHOLD_1_8)!=HAL_OK){Error_Handler();}if(HAL_UARTEx_SetRxFifoThreshold(&huart3, UART_RXFIFO_THRESHOLD_1_8)!=HAL_OK){Error_Handler();}if(HAL_UARTEx_DisableFifoMode(&huart3)!=HAL_OK){Error_Handler();}/*USERCODE BEGIN USART3_Init2*/ // 使能串口3的接收中断(RXNE) __HAL_UART_ENABLE_IT(&huart3, UART_IT_RXNE);// 使能串口3的空闲中断(IDLE) __HAL_UART_ENABLE_IT(&huart3, UART_IT_IDLE);/*USERCODE END USART3_Init2*/}| 项目 | 当前状态 | 建议 |
|---|---|---|
| FIFO 模式 | 已禁用(DisableFifoMode) | 开启可用HAL_UARTEx_EnableFifoMode并设置合适的 FIFO 阈值(如 8/16),可显著减少中断次数(8 个字节才触发一次中断) |
| 波特率 | 9600 | |
| ORE 处理 | 已在 ISR 中添加 | ORE是Overrun Error(溢出错误),必须保留,否则连续接收时一旦溢出会导致中断卡死 |
中断服务函数
- 找到
USART3_IRQHandler,修改为:
voidUSART3_IRQHandler(void){uint8_tres;/* ========== 接收中断(RXNE)========== */if(__HAL_UART_GET_FLAG(&huart3,UART_FLAG_RXNE)!=RESET){// 读取数据寄存器(读RDR会自动清除RXNE标志)res=(uint8_t)(huart3.Instance->RDR&0xFF);if(usart3_rx_count<USART3_RX_BUFFER_SIZE){usart3_rx_buffer[usart3_rx_count++]=res;}else{// 缓冲区溢出保护:重置计数,防止数组越界usart3_rx_count=0;}}/* ========== 空闲中断(IDLE)========== */if(__HAL_UART_GET_FLAG(&huart3,UART_FLAG_IDLE)!=RESET){// 清除IDLE标志(H7系列推荐写法)__HAL_UART_CLEAR_IDLEFLAG(&huart3);// 一帧数据接收完成if(usart3_rx_count>0){usart3_rx_flag=1;// 通知主循环处理}}/* ========== 错误处理(防止ORE导致卡死)========== */if(__HAL_UART_GET_FLAG(&huart3,UART_FLAG_ORE)!=RESET){__HAL_UART_CLEAR_FLAG(&huart3,UART_FLAG_ORE);}}注意:H7系列清除IDLE标志推荐使用
__HAL_UART_CLEAR_IDLEFLAG(),它内部已正确处理SR/DR读取序列。在 STM32F1/F4 这类老系列里,USART 没有 ICR 寄存器,IDLE 标志的清除方式是硬件规定的软件序列:老版本F1/F4中常用USART1->SR; USART1->DR;。
主循环处理
/* main.c 的 while(1) 中 */while(1){if(usart3_rx_flag==1){usart3_rx_flag=0;// 清除标志/* 此处处理接收到的数据 * 有效数据范围:usart3_rx_buffer[0] ~ usart3_rx_buffer[usart3_rx_count - 1] * 数据长度:usart3_rx_count */// 示例:将接收到的数据原样回发(用于调试验证)HAL_UART_Transmit(&huart3,(uint8_t*)usart3_rx_buffer,usart3_rx_count,1000);// 重置计数器,准备接收下一帧usart3_rx_count=0;}// 其他任务...}CG
STM32入门——串口接收中断+串口空闲中断实现数据包的接收编程
【电路笔记 STM32】stm32f103 实现HAL_UART_Transmit_DMA + 普通模式/连续模式的 UART DMA 收发
串口中断里如果只自己读 RDR(UART 的接收数据寄存器),却没有处理 ORE overrun 错误(上一个字节还没被软件读走,新的字节又来了),也没有在清除 ORE 后再读一次 RDR,就可能丢字节或读到错误字节。
开 RTS/CTS 需要硬件支持:MCU 和上位机都要接 RTS/CTS 引脚,并且 CubeMX 里 USART3 的硬件流控要配置成
RTS/CTS。如果只有 TX/RX/GND 三根线,就没法用硬件流控,只能靠协议层控制发送节奏。- RTS:Request To Send,请求发送。
- CTS:Clear To Send,允许发送。
- 如果上位机是连续流,帧间几乎没有空闲,IDLE 可能不会及时触发,甚至可能把多帧拼成一帧;这时要么在上层按帧头/长度拆帧,要么考虑开启 RTS/CTS。
硬件 IDLE 空闲中断时长不可配置,固定约为 1 个字符时间;