STM32嵌入式中断体系全解析:从核心原理到串口/定时器中断实战

前言

中断是嵌入式开发中实现实时性的核心技术,尤其是在STM32系列芯片开发中,中断体系贯穿串口通信、定时器计时、外部事件响应等关键场景。本文基于实战学习笔记,系统梳理STM32中断的核心原理、关键控制器、实战应用及编程注意事项,覆盖面试高频考点,助力开发者吃透中断技术。

一、中断核心基础(面试高频)

定义:中断是计算机系统中处理紧急事件的机制,当需要主机干预时,系统暂停当前程序转而执行中断服务程序,处理完成后恢复原程序运行。

1.1 中断核心要素与价值

  • 核心要素:中断源(触发中断的信号来源)、中断信号;
  • 核心价值
    • 提高计算机(单片机)系统效率。
    • 维持系统可靠正常工作。
    • 实现任务的实时响应,确保关键操作在规定时间内被执行;
  • 关键寄存器
    • PC(程序计数器):存储下一条要执行指令的地址;
    • SP(栈指针寄存器):存储栈顶地址,栈遵循“先进后出”原则;
    • LR(链接寄存器):等效于PC,关联中断返回地址;
    • XPSR:包含运算过程的状态变量(NZCV)。

1.2 中断执行流程(必背)

  1. 中断源触发中断信号;
  2. 硬件自动保护现场(将PC、寄存器等核心数据压栈);
  3. 执行中断服务程序(ISR);
  4. 恢复现场(将栈中数据出栈,还原原程序运行状态);
  5. 回到原程序继续执行。
    中断流程图

1.3 关键概念区分

对比维度 详情说明
异常 vs 中断(面试题) 异常是中断的子集(内核层面的中断);
异常:内核内部触发(如指令错误、系统调用);
普通中断:片上外设、外部硬件触发
Linux vs RTOS 中断实时性 Linux:多任务并行但实时性差,单核下低优先级任务可能被“饿死”;
RTOS:核心优势是强实时性,保证任务在规定时间内响应/完成
Linux(OS)多任务操作系统与RTOS(实时操作系统)的区别 (面试题) 有软件与硬件上的区别。
硬件上:Linux为多核,“多个大脑”;而单片机mcu是单核,需要用RTOS才能实现近似于多任务并发的功能。

1.4 其他概念

按触发方式分为硬件中断和软件中断。
向量中断技术、IRQ

二、STM32中断核心控制器

STM32的中断管理依赖EXTI和NVIC两大核心控制器,二者协同完成中断的触发、优先级管理与执行。
按管理区域分:有片内中断(异常中断(内核里)、外设中断)、片外中断(外部中断),关系图如下:

mcu中断架构(2)

2.1 EXTI(外部中断控制器)

EXTI专注于GPIO外部中断的管理,核心特性如下:

  • 核心功能:管理外部中断的触发逻辑;

  • 关键细节:

    1. 内置施密特触发器,用于判定高低电平(0/1);

    2. 触发方式:常用上升沿/下降沿触发,电平触发极少使用;

    3. 中断线规则:PA0~PA15对应16条中断线,同编号引脚(如PA0、PB0)共用一条中断线,无法同时使用;

    4. 选择器机制:从16条中断线中选择1条作为有效中断线。
      EXTI中断架构

      中断线

      16选1中断线

2.2 NVIC(嵌套向量中断控制器)

NVIC是内核级中断控制器,负责全局中断的统筹管理:

  • 核心功能:
    1. 中断管理功能:中断使能/禁用、中断优先级配置;
    2. 中断向量表:管理关联中断服务程序的地址;
    3. 中断嵌套:高优先级中断可打断低优先级中断,多中断同时触发时优先执行高优先级。

三、串口中断实战(HAL库)

串口通信是嵌入式开发的基础场景,串口中断(内部中断,无需配置EXTI)是实现非阻塞通信的关键。

3.1 串口中断配置要点(CubeMX)

  • 串口属于内部中断,无需配置EXTI(EXTI仅管理GPIO外部中断);
  • 核心配置项:串口参数(波特率、数据位、停止位等)、GPIO引脚、中断优先级与使能。

3.2 核心代码实现

(1)串口初始化+中断使能
// 串口参数初始化
void MX_USART1_UART_Init(void)
{
  huart1.Instance = USART1;
  huart1.Init.BaudRate = 9600;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;//8bit数据位
  huart1.Init.StopBits = UART_STOPBITS_1;//1个停止位
  huart1.Init.Parity = UART_PARITY_NONE;//无奇偶校验
  huart1.Init.Mode = UART_MODE_TX_RX;//收发模式
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;//无硬件流控
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;//16倍过采样
  if (HAL_UART_Init(&huart1) != HAL_OK)//初始化校验
  {
    Error_Handler();//初始化失败进入错误处理
  }
}

// GPIO+中断配置
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(uartHandle->Instance==USART1)
  {
    __HAL_RCC_USART1_CLK_ENABLE();//串口使能
    __HAL_RCC_GPIOA_CLK_ENABLE();//GPIOA时钟使能
    // PA9(TX)、PA10(RX)配置
   GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10;//收发引脚
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;//收发均是复用推挽输出(建议接收配置为浮空/上拉输入)
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;//高速传输
    GPIO_InitStruct.Alternate = GPIO_AF7_USART1;//开启复用功能
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    // 中断优先级配置+使能
    HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);//设置中断优先级
    HAL_NVIC_EnableIRQ(USART1_IRQn);//使能中断
  }
}
(2)非阻塞发送与回调函数
// 非阻塞发送函数:启动中断驱动的UART发送
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size)
{
  if (huart->gState == HAL_UART_STATE_READY)
  {
    if ((pData == NULL) || (Size == 0U)) return HAL_ERROR;

    huart->pTxBuffPtr = pData;
    huart->TxXferSize = Size;
    huart->TxXferCount = Size;
    huart->ErrorCode = HAL_UART_ERROR_NONE;
    huart->gState = HAL_UART_STATE_BUSY_TX;

    __HAL_UART_ENABLE_IT(huart, UART_IT_TXE); // 使能TXE中断
      /*
      程序中也可以直接发送接着使能中断
      HAL_UART_Transmit();
      __HAL_UART_ENABLE_IT(huart, UART_IT_TXE);
      */
    return HAL_OK;
  }
  return HAL_BUSY;
}

// 发送完成回调函数(弱函数,可重写)
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
  if(huart->Instance==USART1)//判断串口号
  {
    // 自定义逻辑:如计数、触发下一次发送等
  }
}

3.3 串口中断注意事项

  • 串口发送完成后会自动关闭中断,需手动重新使能;
  • HAL_UART_Transmit_IT为非阻塞函数,调用后立即返回,实际发送由中断逐字节完成;
  • 接收端建议将GPIO配置为浮空/上拉输入(而非复用推挽)。

四、定时器中断实战

定时器中断是实现精准计时、周期性任务的核心手段,STM32中主要包含SysTick(内核定时器)和TIM(片上外设定时器)两类。

4.1 SysTick(滴答定时器)

  • 归属:Cortex-M4内核,24位递减定时器;
  • 作用:产生1ms时基(默认),为RTOS提供“心跳”,也用于基础计时(如HAL_Delay);
  • HAL_Delay实现原理:
__weak void HAL_Delay(uint32_t Delay)
{
  uint32_t tickstart = HAL_GetTick();
  uint32_t wait = Delay;

  // 防0操作:保证Delay=0时也有最小延时
  if (wait < HAL_MAX_DELAY) wait += (uint32_t)(uwTickFreq);

  while((HAL_GetTick() - tickstart) < wait) {}
}
  • 配置链路:main()HAL_Init()HAL_InitTick()HAL_SYSTICK_Config()

4.2 TIM定时器(片上外设)

  • 分类:高级定时器、通用定时器、基本定时器(按功能复杂度划分);
  • 关键参数:16位计数器(0~65535)、预分频因子(PSC)、自动重装载值(ARR);
  • 实战示例:1s定时中断+串口上报
    1. CubeMX配置:设置PSC和ARR实现1s定时,开启NVIC中断使能;
    2. 代码实现:
    // 开启定时器
    HAL_TIM_Base_Start_IT(&htim2);
    
    // 重写定时器中断回调函数
    void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
    {
      if(htim->Instance == TIM2)
      {
        // 1s触发一次,串口发送“我来了”
        HAL_UART_Transmit_IT(&huart1, (uint8_t*)"我来了", 5);
      }
    }
    

五、中断编程关键注意事项

5.1 关键字与数据类型

  • volatile:防止编译器优化,中断/定时器相关变量必须添加,确保每次读取都从内存获取;
  • 枚举类型:系统库常用U/UL(如1U2UL),减少CPU类型判断开销,提升效率:
    typedef enum {
        A = 1U,  // 无符号1
        B = 2UL, // 无符号长整型
        C = B,   // 继承B的值
        D        // 自动为前值+1(3)
    } Interrupt_State;
    

5.2 硬件与配置细节

  • 中断线复用:如TIM1/TIM10共用一条中断线,需注意优先级冲突;
  • TIM定时器总线挂载:分属APB1(低速)、APB2(高速),需参考芯片手册;
  • 防御性编程:函数带返回值(便于错误追溯),串口通信需增加帧头/帧尾/校验(如和校验)。

5.3 回调函数设计

  • HAL库中中断回调多为弱函数(__weak),需在用户代码中重写;
  • EXTI中断可能共用回调函数,需在函数内判断触发引脚(如PA0/PB0)。

六、总结

中断是STM32嵌入式开发的核心,掌握中断体系需从三层入手:

  1. 原理层:吃透中断流程、异常/中断区分、RTOS实时性等基础概念(面试高频);
  2. 控制器层:理解EXTI(外部中断)和NVIC(内核中断)的分工与协同;
  3. 实战层:结合串口、定时器中断实战,掌握HAL库中断函数、回调重写、优先级配置等细节。

同时,编程中需注意volatile、防御性编程、中断嵌套等细节,才能保证中断程序的稳定性和实时性。

拓展思考(进阶方向)

  1. 串口硬件流控的应用场景与配置;
  2. EXTI中断线复用的解决方案;
  3. SysTick寄存器(CTRL/LOAD/VAL)的底层配置;
    控制器层:理解EXTI(外部中断)和NVIC(内核中断)的分工与协同;
  4. 实战层:结合串口、定时器中断实战,掌握HAL库中断函数、回调重写、优先级配置等细节。
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐