一、中断是什么?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 个步骤:
- 中断触发:外设(如定时器计数完成)或 GPIO 引脚(如按键按下)产生中断请求信号。
- EXTI 检测:若为外部中断,EXTI 检测到引脚电平变化,向 NVIC 发送中断请求。
- NVIC 裁决:NVIC 检查该中断是否被使能,若使能则判断优先级,暂停当前低优先级任务。
- 执行中断服务程序:CPU 根据中断向量表找到对应的服务程序入口,执行中断处理代码。
- 中断返回:清除中断标志位,恢复 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 配置步骤
- GPIO 配置:
- PA0:GPIO_Input,Pull-Up(上拉输入)。
- PA1:GPIO_Output,Push-Pull(推挽输出)。
- EXTI 配置:
- 在 “Pinout & Configuration” 中找到 PA0,点击 “GPIO Settings”→“EXTI Line 0”,勾选 “Enable”。
- 配置触发方式:Falling edge trigger detection(下降沿触发,按键按下时电平从高变低)。
- NVIC 配置:
- 在 “NVIC Settings” 中找到 “EXTI Line 0 interrupt”,勾选 “Enabled”,设置优先级(如抢占优先级 1,响应优先级 0)。
- 生成代码。
(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 配置步骤
- 定时器配置:选择 TIM2,Internal Clock(内部时钟),Prescaler=7199,Counter Period=9999(定时 1 秒)。
- 中断配置:在 “NVIC Settings” 中勾选 “TIM2 global interrupt”,设置优先级(抢占优先级 1,响应优先级 1)。
- GPIO 配置:PA1 设为推挽输出(控制 LED)。
- 生成代码。
(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 配置步骤
- 串口配置:选择 USART1,Mode=Asynchronous(异步模式),Baud Rate=9600(波特率),Word Length=8 Bits,Parity=None,Stop Bits=1。
- 中断配置:在 “NVIC Settings” 中勾选 “USART1 global interrupt”,设置优先级(抢占优先级 2,响应优先级 0)。
- GPIO 配置:PA1 设为推挽输出。
- 生成代码。
(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是 “单次接收”,接收完成后需在回调函数中重新调用该函数,否则无法接收后续数据。
中断嵌套失控
过多
8235

被折叠的 条评论
为什么被折叠?



