【电路笔记 STM32】Cortex-M7 UART 中断模式(串口接收中断+串口空闲中断)

文章目录

轮询模式 (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 通常放在较低优先级,避免它频繁打断业务中断。

  • STM32 HAL库中断中断优先级,最佳教程,没有之一~

中断实现数据包的接收(串口接收中断+串口空闲中断)

核心思路

  • 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。
相关推荐
小李不想当小白1 小时前
USART串口协议(STM32标准库学习笔记)
经验分享·笔记·stm32·单片机·嵌入式硬件·学习
无垠的广袤2 小时前
拆解电子价签
单片机·蓝牙·diy·调试器·jlink·墨水屏
小何好运暴富开心幸福2 小时前
嵌入式历史与生态
stm32·单片机·mcu
AOI小白新手上路2 小时前
视频去雨·论文蒸馏笔记:从物理先验到状态空间模型
数据库·笔记·音视频
甜甜的大香瓜3 小时前
【MT32F006】MT32F006之Stimer延时(轮询)
单片机
辰哥单片机设计3 小时前
MP3播放器模块(STM32)
stm32·单片机·嵌入式硬件
AOI小白新手上路3 小时前
DRSformer·论文蒸馏笔记:可学习 Top-k 稀疏注意力去雨网络
网络·笔记·学习
Zw-awa3 小时前
传感器模块都在交出什么:从模拟电压到数字协议
stm32
Brilliantwxx4 小时前
【STM32】 工程实战 WS2812B 全彩 LED 灯控制(DMA 驱动)
stm32·单片机·嵌入式硬件·mcu