文章目录
- 轮询模式 (Polling)
- 中断模式 (Interrupt)
-
-
- [CUBEMX 优先级配置](#CUBEMX 优先级配置)
-
- 中断实现数据包的接收(串口接收中断+串口空闲中断)
- CG
轮询模式 (Polling)
- 轮询模式最大的优点是实现极其简单、执行路径完全可控,不需要配置 NVIC、中断优先级和 ISR,调试时单步跟踪也直观,适合在串口功能验证或简单裸机场景下快速跑通。
- 但它的缺点也很明显:
HAL_UART_Receive()在超时时间内会阻塞主循环,导致 CPU 无法及时处理其他任务,实时性差且 CPU 占用高;波特率越高或数据越密集,越容易因为主循环被卡住而漏收数据,甚至触发 ORE 溢出错误。因此它只适合低波特率、单任务或对实时性要求不高的场景,正式工程里如果后续还要跑多任务或处理高频通信,建议尽早切换其他模式。
c
/* main.c */
#include "main.h"
#include "usart.h"
/* USER CODE BEGIN PV */
volatile uint8_t usart3_rx_buffer[USART3_RX_BUFFER_SIZE];
volatile uint16_t usart3_rx_count = 0;
volatile uint8_t usart3_rx_flag = 0;
/* USER CODE END PV */
int main(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_t rx_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 库中配置流程通常是:
c
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 通常放在较低优先级,避免它频繁打断业务中断。
中断实现数据包的接收(串口接收中断+串口空闲中断)
核心思路
- RXNE中断:每收到1个字节触发,将数据存入缓冲区并计数
- IDLE中断:总线空闲超过1个字符时间后触发,标志一帧数据接收完毕
- 当前纯中断方案适合波特率 ≤ 115200、帧长较短的场景,调试简单、逻辑清晰。
- CPU占用较高:每字节都进中断,921600bps下约每11μs一次中断,不适合持续大数据流
- 粘帧风险:如果两帧数据间隔小于1个字符时间,IDLE无法区分,会粘成一帧
代码实现(HAL库)
- CubeMX 配置: NVIC 配置使能 USARTx 全局中断,抢占优先级建议设高(如1),避免被其他中断阻塞导致ORE错误
定义全局变量
c
/* 接收缓冲区与状态变量 */
#define USART3_RX_BUFFER_SIZE 512
volatile uint8_t usart3_rx_buffer[USART3_RX_BUFFER_SIZE]; // 接收缓冲区
volatile uint16_t usart3_rx_count = 0; // 当前已接收字节数
volatile uint8_t usart3_rx_flag = 0; // 一帧接收完成标志
串口初始化(使能RXNE + IDLE中断)
- 在生成的
MX_USART3_UART_Init()函数的/* USER CODE BEGIN USART3_Init 2 */区域中添加:
c
/* 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 */
- 整体代码如下:
bash
void MX_USART3_UART_Init(void)
{
/* USER CODE BEGIN USART3_Init 0 */
/* USER CODE END USART3_Init 0 */
/* USER CODE BEGIN USART3_Init 1 */
/* USER CODE END USART3_Init 1 */
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();
}
/* 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 */
}
| 项目 | 当前状态 | 建议 |
|---|---|---|
| FIFO 模式 | 已禁用(DisableFifoMode) |
开启可用 HAL_UARTEx_EnableFifoMode 并设置合适的 FIFO 阈值(如 8/16),可显著减少中断次数(8 个字节才触发一次中断) |
| 波特率 | 9600 | |
| ORE 处理 | 已在 ISR 中添加 | ORE是Overrun Error(溢出错误),必须保留,否则连续接收时一旦溢出会导致中断卡死 |
中断服务函数
- 找到
USART3_IRQHandler,修改为:
c
void USART3_IRQHandler(void)
{
uint8_t res;
/* ========== 接收中断(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;。
主循环处理
c
/* 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。