一、中断是什么
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 主程序: while(1) { do_task_A(); // ← 执行到这里 do_task_B(); } ↑ │ 按键突然按下 │ 中断发生: ├─ 硬件自动保存现场 ├─ 跳到中断服务函数 ISR ├─ ISR 执行:GPIO_ReadPin() + 标志位 ├─ 硬件自动恢复现场 ├─ 返回主程序断点 ↓ do_task_A(); // ← 接着刚才的位置继续
|
核心价值:CPU 不需要轮询 “按键按了吗?按键按了吗?”,中断来了自动处理。
二、NVIC 嵌套向量中断控制器
2.1 优先级体系
1 2 3 4 5 6 7 8 9 10 11 12
| STM32F103 Cortex-M3: 16 个可编程优先级(4 位)
优先级 = 抢占优先级 + 子优先级
NVIC_PriorityGroup_0: 0 bit 抢占 + 4 bit 子 → 16 级子优先级,无抢占嵌套 NVIC_PriorityGroup_1: 1 bit 抢占 + 3 bit 子 → 2 级抢占 + 8 级子 NVIC_PriorityGroup_2: 2 bit 抢占 + 2 bit 子 → 4 级抢占 + 4 级子 NVIC_PriorityGroup_3: 3 bit 抢占 + 1 bit 子 → 8 级抢占 + 2 级子 NVIC_PriorityGroup_4: 4 bit 抢占 + 0 bit 子 → 16 级抢占,无子优先级
抢占优先级:数值越小越优先。高抢占可打断低抢占的中断(嵌套)。 子优先级:数值越小越优先。但不会嵌套,只决定同时挂起时谁先执行。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 示例(Group_2):
ISR_A: 抢占=0, 子=0 ← 最高,可打断 B 和 C ISR_B: 抢占=1, 子=0 ← 与 C 同抢占,子=0 优先 ISR_C: 抢占=1, 子=1
时间线: 正在执行 ISR_C(1,1) ↓ ISR_A(0,0) 挂起 → 抢占,打断 C → 执行 A → 返回 C ↓ 继续执行 ISR_C
ISR_C 和 ISR_B 同时挂起 → B(子=0) 先执行
|
2.2 HAL 优先级配置
1 2 3 4 5 6 7 8 9 10 11
| HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_2);
HAL_NVIC_SetPriority(USART1_IRQn, 0, 0); HAL_NVIC_SetPriority(TIM2_IRQn, 0, 1); HAL_NVIC_SetPriority(EXTI0_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn); HAL_NVIC_EnableIRQ(TIM2_IRQn); HAL_NVIC_EnableIRQ(EXTI0_IRQn);
|
三、外部中断 EXTI
3.1 硬件连接
1 2 3 4 5 6 7
| GPIO 输入 → 边沿检测电路 → EXTI 控制器 → NVIC → CPU
支持上升沿/下降沿/双沿触发。
PA0~PG0 都映射到 EXTI0 PA1~PG1 都映射到 EXTI1 ...(同一编号的引脚共享 EXTI 线)
|
3.2 按键中断示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| void EXTI_Init(void) { GPIO_InitTypeDef gpio = {0};
__HAL_RCC_GPIOC_CLK_ENABLE();
gpio.Pin = GPIO_PIN_13; gpio.Mode = GPIO_MODE_IT_FALLING; gpio.Pull = GPIO_PULLUP; HAL_GPIO_Init(GPIOC, &gpio);
HAL_NVIC_SetPriority(EXTI15_10_IRQn, 1, 0); HAL_NVIC_EnableIRQ(EXTI15_10_IRQn); }
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin == GPIO_PIN_13) { HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); } }
|
3.3 EXTI 中断向量映射
| EXTI 线 |
中断向量 |
映射引脚 |
| EXTI0 |
EXTI0_IRQn |
PA0, PB0, PC0… |
| EXTI1 |
EXTI1_IRQn |
PA1, PB1, PC1… |
| EXTI2 |
EXTI2_IRQn |
PA2, PB2, PC2… |
| EXTI3 |
EXTI3_IRQn |
PA3, PB3, PC3… |
| EXTI4 |
EXTI4_IRQn |
PA4, PB4, PC4… |
| EXTI5~9 |
EXTI9_5_IRQn |
共用一个向量 |
| EXTI10~15 |
EXTI15_10_IRQn |
共用一个向量 |
四、定时器中断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| TIM_HandleTypeDef htim2;
void TIM2_Init(void) { __HAL_RCC_TIM2_CLK_ENABLE();
htim2.Instance = TIM2; htim2.Init.Prescaler = 7199; htim2.Init.Period = 9999; htim2.Init.CounterMode = TIM_COUNTERMODE_UP; HAL_TIM_Base_Init(&htim2);
HAL_NVIC_SetPriority(TIM2_IRQn, 0, 1); HAL_NVIC_EnableIRQ(TIM2_IRQn);
HAL_TIM_Base_Start_IT(&htim2); }
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == TIM2) { HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); } }
|
五、串口中断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| uint8_t rx_byte;
void UART_IT_Init(void) {
HAL_NVIC_SetPriority(USART1_IRQn, 0, 2); HAL_NVIC_EnableIRQ(USART1_IRQn);
HAL_UART_Receive_IT(&huart1, &rx_byte, 1); }
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart->Instance == USART1) { HAL_UART_Receive_IT(&huart1, &rx_byte, 1); } }
|
空闲中断(接收不定长数据)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
void USART1_IRQHandler(void) { if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE)) { __HAL_UART_CLEAR_IDLEFLAG(&huart1);
uint16_t len = __HAL_DMA_GET_COUNTER(&hdma_usart1_rx); uint16_t rx_len = RX_BUF_SIZE - len;
} HAL_UART_IRQHandler(&huart1); }
|
六、中断优先级实战分配
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 项目示例:MPU6050 姿态解算
ISR 抢占 子 原因 ─────────────────────── ── ── ────────────────── SysTick_Handler 0 0 系统节拍,FreeRTOS 心跳 TIM2 采样中断 0 1 125Hz 读 MPU6050,不可被延迟 USART1 接收 0 2 指令接收,实时性高 EXTI 按键 1 0 不紧急,可被打断 TIM3 PWM 更新 1 1 灯效,延迟无感 USART2 日志输出 2 0 最低,断掉也无所谓
规则: 1. 硬实时(采样/控制)→ 抢占 0 2. 通信(串口/CAN) → 抢占 0~1 3. UI(按键/显示) → 抢占 1 4. 日志/调试 → 抢占 2
|
七、中断编写注意事项
7.1 ISR 要短
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { HAL_Delay(100); printf("tick\r\n"); MPU6050_Calibrate(); }
volatile uint8_t flag = 0;
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { flag = 1; }
int main(void) { while (1) { if (flag) { flag = 0; do_heavy_work(); } } }
|
7.2 共享变量加 volatile
1 2 3
| volatile uint8_t rx_complete = 0;
|
7.3 中断抢占导致的竞态
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| volatile int counter = 0;
__disable_irq(); counter++; __enable_irq();
|
7.4 清除中断标志
1 2 3 4 5 6 7
| void EXTI15_10_IRQHandler(void) { if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_13)) { __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_13); } }
|
八、中断调试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 1. 中断是否进入? → ISR 开头加 GPIO 翻转,示波器测引脚 → 或 ISR 里设置断点(注意:断点不阻塞其他中断)
2. 中断优先级是否对? → NVIC 配置打印出来 → 高抢占的 ISR 能否打断低抢占的 ISR
3. 中断频率是否如预期? → 在 ISR 里计数,每秒打印一次计数值 → TIM2 1Hz 中断,计数器应该是 1
4. 是否漏中断? → 示波器测中断引脚波形 → 检查 ISR 执行时间是否过长(> 一个中断周期)
|