STM32异步响应核心:中断机制全解析,从原理到多中断实战

一、中断是什么?STM32 的 “紧急呼叫系统”

在嵌入式系统中,很多事件的发生具有 “随机性”—— 比如按键突然按下、传感器检测到异常信号、定时器计数完成,这些都需要 CPU 立即响应处理。如果 CPU 一直 “轮询” 检测这些事件(比如反复检查按键引脚电平),会严重浪费资源。而中断(Interrupt) 就是解决这一问题的 “紧急呼叫系统”:当外部事件触发时,CPU 会暂停当前正在执行的任务,转而去处理紧急事件(中断服务程序),处理完成后再回到原任务继续执行,实现 “异步响应”。

举个生活中的例子:你正在写报告(主任务),突然电话铃响了(中断触发),你暂停写报告去接电话(执行中断服务程序),挂掉电话后继续写报告(恢复主任务)。这里的 “电话铃” 就是中断信号,“接电话” 就是中断处理过程。

STM32F103 系列共支持60 个可屏蔽中断(不包含不可屏蔽的硬 fault 中断),涵盖定时器、串口、ADC、GPIO 等几乎所有外设,每个中断都对应一个 “中断通道”,通过中断控制器统一管理。

二、中断的核心硬件:NVIC 与 EXTI 的 “协作机制”

STM32 的中断响应依赖两大核心硬件:NVIC(嵌套向量中断控制器)EXTI(外部中断 / 事件控制器)。前者负责中断的优先级管理与调度,后者负责外部 GPIO 中断的信号检测与触发,二者协同实现中断的完整流程。

1. 核心硬件拆解
(1)NVIC:中断的 “调度中心”

NVIC 是 Cortex-M3 内核自带的组件,相当于中断的 “交通指挥官”,主要负责三件事:

  • 中断使能 / 禁用:控制某个中断通道是否允许触发(比如允许 TIM2 中断,禁用 USART1 中断)。
  • 优先级管理:当多个中断同时触发时,按优先级高低决定处理顺序(比如电机故障中断优先级高于 LED 闪烁中断)。
  • 中断响应与返回:保存 CPU 当前运行状态(寄存器值),引导 CPU 执行中断服务程序,处理完成后恢复状态并返回主任务。

STM32 的中断优先级通过 “抢占优先级” 和 “响应优先级” 两级划分:

  • 抢占优先级:高抢占优先级的中断可以打断正在执行的低抢占优先级中断(实现中断嵌套)。
  • 响应优先级:抢占优先级相同时,响应优先级高的中断先被处理,但不能打断已执行的同级中断。

优先级的位数可通过 “优先级组” 配置(共 5 组),最常用的是 “组 2”:2 位抢占优先级(0-3 级,0 级最高)、2 位响应优先级(0-3 级,0 级最高)。

(2)EXTI:外部中断的 “信号探测器”

EXTI 负责处理 GPIO 引脚的外部中断信号,由 “中断线” 和 “触发方式” 两部分组成:

  • 中断线:STM32F103 有 19 条中断线(EXTI0-EXTI18),其中 EXTI0-EXTI15 分别对应 GPIOA-GPIOG 的同名引脚(如 EXTI0 对应 PA0、PB0...PG0),EXTI16-EXTI18 对应内部外设信号(如 RTC 闹钟)。
  • 触发方式:可配置为上升沿触发(如按键按下时电平从低变高)、下降沿触发(按键松开时电平从高变低)或双边沿触发(电平变化时均触发)。

EXTI 的核心特点是 “多引脚共享一条中断线”—— 比如 PA0、PB0 都连接到 EXTI0,触发时会进入同一个中断服务程序,需在程序中判断具体是哪个引脚触发的中断。

2. 中断响应的完整流程

STM32 的中断响应遵循严格的硬件时序,从信号触发到程序执行需经历 5 个步骤:

  1. 中断触发:外设(如定时器计数完成)或 GPIO 引脚(如按键按下)产生中断请求信号。
  1. EXTI 检测:若为外部中断,EXTI 检测到引脚电平变化,向 NVIC 发送中断请求。
  1. NVIC 裁决:NVIC 检查该中断是否被使能,若使能则判断优先级,暂停当前低优先级任务。
  1. 执行中断服务程序:CPU 根据中断向量表找到对应的服务程序入口,执行中断处理代码。
  1. 中断返回:清除中断标志位,恢复 CPU 运行状态,返回主任务继续执行。

三、中断的典型类型:从外部到内部的应用场景

STM32 的中断按触发源可分为 “外部中断” 和 “内部外设中断” 两大类,新手需重点掌握以下 4 种典型类型:

1. 外部 GPIO 中断(最常用)

由 GPIO 引脚的电平变化触发,适用于 “随机发生的外部事件”:

  • 适用场景:按键触发(如紧急停止按钮)、传感器电平唤醒(如红外人体感应)、外部设备状态反馈(如电机到位信号)。
  • 关键特点:需配置 EXTI 中断线、触发方式,中断服务程序中需判断引脚并清除中断标志位。
2. 定时器中断(基础核心)

由定时器的更新事件、捕获事件等触发,适用于 “周期性或定时性事件”:

  • 适用场景:精准定时(如 1 秒采集一次温度)、PWM 周期中断(如电机转速闭环控制)、输入捕获中断(如测量脉冲宽度)。
  • 关键特点:无需外部信号,由硬件定时触发,中断频率可通过预分频器和 ARR 精确控制。
3. 串口中断(通信核心)

由串口接收 / 发送数据完成触发,适用于 “异步通信场景”:

  • 适用场景:串口接收上位机指令(如手机 APP 发送控制命令)、串口数据发送完成(如向云端上传传感器数据)。
  • 关键特点:避免 CPU 轮询检测串口缓冲区,数据到达后立即触发中断处理,提升通信效率。
4. ADC 中断(数据采集)

由 ADC 转换完成触发,适用于 “模拟信号采集场景”:

  • 适用场景:温度、电压等模拟量的定时采集(如电池电压监测)、快速信号采样(如音频信号采集)。
  • 关键特点:可与定时器配合实现 “定时采样 + 中断处理”,避免 CPU 等待 ADC 转换完成。

四、中断配置实操:CubeMX+HAL 库实战三大典型场景

以 STM32F103C8T6 为例,分别演示 “外部 GPIO 中断(按键)”“定时器中断(定时)”“串口接收中断” 的配置与代码实现,覆盖中断开发的核心要点。

1. 实战 1:外部 GPIO 中断(按键控制 LED 翻转)
(1)硬件连接
  • PA0:接按键(一端接 PA0,一端接地,PA0 配置上拉输入)
  • PA1:接 LED(串联 1kΩ 限流电阻,另一端接地)
(2)CubeMX 配置步骤
  1. GPIO 配置
  • PA0:GPIO_Input,Pull-Up(上拉输入)。
  • PA1:GPIO_Output,Push-Pull(推挽输出)。
  1. EXTI 配置
  • 在 “Pinout & Configuration” 中找到 PA0,点击 “GPIO Settings”→“EXTI Line 0”,勾选 “Enable”。
  • 配置触发方式:Falling edge trigger detection(下降沿触发,按键按下时电平从高变低)。
  1. NVIC 配置
  • 在 “NVIC Settings” 中找到 “EXTI Line 0 interrupt”,勾选 “Enabled”,设置优先级(如抢占优先级 1,响应优先级 0)。
  1. 生成代码
(3)核心代码

// 中断服务程序(CubeMX自动生成,在stm32f1xx_it.c中)

void EXTI0_IRQHandler(void) {

HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0); // 调用HAL库中断处理函数

}

// 中断回调函数(在main.c中重写,处理具体逻辑)

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {

if (GPIO_Pin == GPIO_PIN_0) { // 判断是PA0触发的中断

HAL_Delay(20); // 软件消抖

if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET) { // 确认按键按下

HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_1); // 翻转PA1电平,LED亮灭切换

}

HAL_GPIO_EXTI_ClearITPendingBit(GPIO_PIN_0); // 清除中断标志位(HAL库会自动清除,此处可省略)

}

}

// main函数无需额外处理,中断自动响应

int main(void) {

HAL_Init();

SystemClock_Config();

MX_GPIO_Init();

while (1) {

// 主循环可执行其他任务,不受按键中断影响

}

}
2. 实战 2:定时器中断(1 秒定时翻转 LED)
(1)CubeMX 配置步骤
  1. 定时器配置:选择 TIM2,Internal Clock(内部时钟),Prescaler=7199,Counter Period=9999(定时 1 秒)。
  1. 中断配置:在 “NVIC Settings” 中勾选 “TIM2 global interrupt”,设置优先级(抢占优先级 1,响应优先级 1)。
  1. GPIO 配置:PA1 设为推挽输出(控制 LED)。
  1. 生成代码
(2)核心代码

// 定时器初始化(CubeMX自动生成,在tim.c中)

TIM_HandleTypeDef htim2;

void MX_TIM2_Init(void) {

TIM_ClockConfigTypeDef sClockSourceConfig = {0};

TIM_MasterConfigTypeDef sMasterConfig = {0};

htim2.Instance = TIM2;

htim2.Init.Prescaler = 7199;

htim2.Init.CounterMode = TIM_COUNTERMODE_UP;

htim2.Init.Period = 9999;

htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;

htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;

if (HAL_TIM_Base_Init(&htim2) != HAL_OK) {

Error_Handler();

}

sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;

if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK) {

Error_Handler();

}

sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;

sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;

if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK) {

Error_Handler();

}

}

// 定时器中断服务程序(stm32f1xx_it.c中)

void TIM2_IRQHandler(void) {

HAL_TIM_IRQHandler(&htim2); // 调用HAL库中断处理函数

}

// 定时器中断回调函数(main.c中重写)

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {

if (htim->Instance == TIM2) { // 判断是TIM2中断

HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_1); // 翻转LED电平

}

}

// main函数启动定时器中断

int main(void) {

HAL_Init();

SystemClock_Config();

MX_GPIO_Init();

MX_TIM2_Init();

HAL_TIM_Base_Start_IT(&htim2); // 启动TIM2定时中断

while (1) {

// 主循环可执行其他任务,定时中断后台运行

}

}
3. 实战 3:串口接收中断(接收上位机指令控制 LED)
(1)硬件连接
  • PA9(USART1_TX):接 USB 转 TTL 模块的 RX
  • PA10(USART1_RX):接 USB 转 TTL 模块的 TX
  • PA1:接 LED
(2)CubeMX 配置步骤
  1. 串口配置:选择 USART1,Mode=Asynchronous(异步模式),Baud Rate=9600(波特率),Word Length=8 Bits,Parity=None,Stop Bits=1。
  1. 中断配置:在 “NVIC Settings” 中勾选 “USART1 global interrupt”,设置优先级(抢占优先级 2,响应优先级 0)。
  1. GPIO 配置:PA1 设为推挽输出。
  1. 生成代码
(3)核心代码

// 串口初始化(CubeMX自动生成,在usart.c中)

UART_HandleTypeDef huart1;

void MX_USART1_UART_Init(void) {

huart1.Instance = USART1;

huart1.Init.BaudRate = 9600;

huart1.Init.WordLength = UART_WORDLENGTH_8B;

huart1.Init.StopBits = UART_STOPBITS_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;

if (HAL_UART_Init(&huart1) != HAL_OK) {

Error_Handler();

}

}

// 串口接收缓冲区(存储接收的指令)

uint8_t uart_rx_data;

// 串口中断服务程序(stm32f1xx_it.c中)

void USART1_IRQHandler(void) {

HAL_UART_IRQHandler(&huart1); // 调用HAL库中断处理函数

}

// 串口接收完成回调函数(main.c中重写)

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {

if (huart->Instance == USART1) { // 判断是USART1接收中断

// 处理接收的指令:收到 '1' 点亮LED,收到 '0' 熄灭LED

if (uart_rx_data == '1') {

HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);

} else if (uart_rx_data == '0') {

HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);

}

// 重新启动中断接收(HAL库单次接收后需重新使能)

HAL_UART_Receive_IT(&huart1, &uart_rx_data, 1);

}

}

// main函数启动串口中断接收

int main(void) {

HAL_Init();

SystemClock_Config();

MX_GPIO_Init();

MX_USART1_UART_Init();

// 启动串口1的中断接收(接收1个字节)

HAL_UART_Receive_IT(&huart1, &uart_rx_data, 1);

while (1) {

// 主循环可执行其他任务,串口接收中断后台处理

}

}

五、中断使用避坑指南:新手常犯的 7 个错误

忘记使能中断

中断配置分 “外设级使能” 和 “NVIC 级使能” 两步:比如定时器中断需先通过HAL_TIM_Base_Start_IT使能外设中断,再在 NVIC 中勾选使能。遗漏任何一步都会导致中断无法触发。

未清除中断标志位

中断触发后,硬件会置位中断标志位,若未清除(HAL 库会自动清除,但手动操作寄存器时需注意),会导致中断反复触发,CPU 陷入死循环。

中断服务程序执行时间过长

中断服务程序应 “短小精悍”,避免包含延时(如HAL_Delay)或复杂运算。若执行时间过长,会阻塞主任务和其他低优先级中断(如电机控制中断不能超过 1ms)。

优先级配置不当

高优先级中断未正确配置会导致关键任务无法及时响应(如紧急停止按钮中断优先级应最高);同级中断未考虑响应优先级会导致事件处理顺序混乱。

外部中断未消抖

机械按键触发的外部中断若未做消抖处理(软件延时 20ms 或硬件 RC 电路),会导致一次按键被识别为多次中断,出现 LED 频繁翻转等异常。

串口中断接收未重启动

HAL 库的HAL_UART_Receive_IT是 “单次接收”,接收完成后需在回调函数中重新调用该函数,否则无法接收后续数据。

中断嵌套失控

过多

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值