07-中断系统.md 4.0 KB

07 中断系统(HAL)

目标:理解优先级分组与嵌套,实现 EXTI 外部中断和 UART 中断接收

中断优先级

优先级分组

中断优先级分组

NVIC 优先级分组(由 SCB->AIRCR 寄存器的 PRIGROUP 位决定)。STM32 把中断优先级分为抢占优先级子优先级两部分,但两者的位数比例可以调整:

分组 抢占优先级位数 子优先级位数 可配置优先级数量
NVIC_PRIORITYGROUP_0 0 4 抢占: 1种, 子: 16级
NVIC_PRIORITYGROUP_1 1 3 抢占: 2种, 子: 8级
NVIC_PRIORITYGROUP_2 2 2 抢占: 4种, 子: 4级
NVIC_PRIORITYGROUP_3 3 1 抢占: 8种, 子: 2级
NVIC_PRIORITYGROUP_4(默认) 4 0 抢占: 16种, 子: 1级

数值越小,优先级越高!("越小越优先"是 ARM Cortex 的统一规则)

中断排队 vs 中断嵌套

中断排队与嵌套

中断排队:多个中断等待 CPU 执行,NVIC 根据优先级决定谁先执行。

中断嵌套:一个中断正在执行时,另一个更高优先级的中断到来,暂停当前中断去执行更高的中断。

判定规则

抢占优先级不同 → 高抢占优先级可以嵌套低抢占优先级的中断
抢占优先级相同 → 不嵌套,按子优先级排队
子优先级也相同 → 按中断向量号(硬件固定),号小的先执行

完整示例

优先级分组 Group 2(2位抢占 + 2位子优先级)。五个中断按到达时间排列:

中断 抢占优先级 子优先级 到达顺序
A 1 0
B 0 1
C 0 2
D 2 0
E 3 0

执行流(数值越小优先级越高):

① A(抢占1) 开始执行
   └─ ② B(抢占0) 到来 → 0 > 1 → **嵌套!暂停 A,B 开始**
      └─ ③ C(抢占0, 子2) 到来 → 同组不嵌套 → 排队(子2 < B的子1,C在B之后)
      └─ B 完成 → C 开始执行
   └─ C 完成 → **回到 A**(A 被抢占后恢复)
   └─ ④ D(抢占2) 到来 → 2 < 1 → 排队(D在A后面)
   └─ A 完成 → D 开始 → ⑤ E(抢占3) 在 D 后面排队

最终执行顺序:A → B → C → A(恢复) → D → E

NVIC 配置

CubeMX → NVIC Settings 中:

  1. 使能要用到的中断
  2. 设置抢占优先级 (Preemption Priority) 和子优先级 (Sub Priority)
  3. HAL_NVIC_SetPriority(IRQn, PreemptPriority, SubPriority) 或在 CubeMX 中图形化设置

EXTI 外部中断

CubeMX 配置

  • PB0 → GPIO_EXTI0
  • GPIO 参数:Pull-up
  • NVIC → EXTI line0 interrupt → Enable
  • GPIO mode:External Interrupt Mode with Rising/Falling edge detection

关键函数

函数 说明
HAL_GPIO_EXTI_IRQHandler(PIN) ISR 中调用,检测边沿并调回调
HAL_GPIO_EXTI_Callback(PIN) 用户重写此回调(弱函数)

完整代码

/* stm32f1xx_it.c — 中断服务函数 */
void EXTI0_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
}

/* main.c — 回调函数 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    if (GPIO_Pin == GPIO_PIN_0)
    {
        HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
    }
}

UART 中断接收

已在 04-UART串口.md 详细说明,核心模式:

HAL_UART_Receive_IT() 启动中断接收
         ↓
硬件接收 1 字节 → 触发中断
         ↓
HAL_UART_RxCpltCallback() 被调用
         ↓
用户在回调中重新调用 HAL_UART_Receive_IT()

常见坑点

  • ⚠️ 中断服务函数写在 stm32f1xx_it.c,不要直接修改
  • ⚠️ 用户只重写回调函数(__weak 弱函数),不要改 ISR
  • ⚠️ 回调函数中不要使用 HAL_Delay()(它依赖 SysTick 中断,同优先级可能导致死锁)
  • ⚠️ 中断函数应短小、快速执行,耗时操作放主循环