RTOS-F429-HAL-中断管理(2026/7/28)

RTOS-F429-HAL-中断管理(2026/7/28)
目录一STM32 中断优先级分组1. 硬件基础2. 优先级分组中断优先级分组的核心3. 抢占优先级 vs 子优先级4. 完整配置流程5. 一个直观例子6.FreeRTOS 下的特殊约束1. 分组建议全部设为抢占优先级2. API 调用门槛3. 优先级数值方向的颠倒二系统异常优先级设置寄存器SHPR1/2/31简介2为什么需要它们3寄存器与系统异常的对应关系4FreeRTOS 中的典型配置5和外设中断的关系6一句话总结三FreeRTOS 配置 PendSV 和 SysTick 优先级1配置做了什么2写入 SHPR3 配置PendSV和SysTick 的步骤1第一步2第二步3第三步4总结四三个中断屏蔽寄存器1区别2cotex内核ARM系列优先级分布3FreeRTOS 为什么选 BASEPRI4FreeRTOS 临界区实际操作5BASEPRI 0x50的移位过程一STM32 中断优先级分组1. 硬件基础ARM Cortex-M 优先级寄存器: 8 位 STM32 实际使用: 高 4 位 [7:4] → 共 16 级优先级 ​ Bit7 Bit6 Bit5 Bit4 | Bit3 Bit2 Bit1 Bit0 有效 有效 有效 有效 | 保留 保留 保留 保留2. 优先级分组中断优先级分组的核心4 个有效位通过分组配置切分为抢占优先级和子优先级。STM32 通过NVIC_PriorityGroupConfig()设置共 5 种分组分组抢占优先级位数子优先级位数抢占级数子优先级级数NVIC_PriorityGroup_00 bit4 bit1 级16 级NVIC_PriorityGroup_11 bit3 bit2 级8 级NVIC_PriorityGroup_22 bit2 bit4 级4 级NVIC_PriorityGroup_33 bit1 bit8 级2 级NVIC_PriorityGroup_44 bit0 bit16 级0 级无子优先级关键约束分组只需设置一次必须在 main 开头、任何中断使能之前执行。3. 抢占优先级 vs 子优先级抢占优先级Preemption Priority └─ 高抢占优先级的中断 → 可打断低抢占优先级的中断正在执行 └─ 数值越小 → 优先级越高 ​ 子优先级Sub Priority └─ 两中断抢占优先级相同、同时到来时 → 子优先级数值小的先执行 └─ 子优先级不同不能相互打断只决定排队顺序打断规则抢占优先级不同 → 高抢占的可以打断低抢占的抢占优先级相同 →不能相互打断同时到来时看子优先级排队4. 完整配置流程// ① 设置优先级分组全局一次 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 2位抢占, 2位子优先级 ​ // ② 配置具体中断 NVIC_InitTypeDef NVIC_InitStruct; NVIC_InitStruct.NVIC_IRQChannel USART1_IRQn; // 中断源 NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority 1; // 抢占优先级 NVIC_InitStruct.NVIC_IRQChannelSubPriority 2; // 子优先级 NVIC_InitStruct.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStruct);5. 一个直观例子假设分组设为NVIC_PriorityGroup_2抢占 2 bit子优先级 2 bit中断 A中断 B行为抢占0, 子0抢占1, 子0A 可打断 BA 抢占更高抢占1, 子0抢占1, 子3互不打断同时到来时 A 先执行抢占1, 子2抢占1, 子2在向量表中的位置靠前的先执行最后一层兜底抢占和子优先级都相同时硬件按中断向量表中的顺序决定IRQ 编号小的优先。6.FreeRTOS 下的特殊约束1. 分组建议全部设为抢占优先级HAL_INit 默认配置 HAL_StatusTypeDef HAL_Init(void) { /* Configure Flash prefetch, Instruction cache, Data cache */ #if (INSTRUCTION_CACHE_ENABLE ! 0U) __HAL_FLASH_INSTRUCTION_CACHE_ENABLE(); #endif /* INSTRUCTION_CACHE_ENABLE */ ​ #if (DATA_CACHE_ENABLE ! 0U) __HAL_FLASH_DATA_CACHE_ENABLE(); #endif /* DATA_CACHE_ENABLE */ ​ #if (PREFETCH_ENABLE ! 0U) __HAL_FLASH_PREFETCH_BUFFER_ENABLE(); #endif /* PREFETCH_ENABLE */ ​ /* Set Interrupt Group Priority */ HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4); ⭐ ​ /* Use systick as time base source and configure 1ms tick (default clock after Reset is HSI) */ HAL_InitTick(TICK_INT_PRIORITY); ​ /* Init the low level hardware */ HAL_MspInit(); ​ /* Return function status */ return HAL_OK; }原因去掉子优先级后只有抢占优先级一维FreeRTOS 管理起来更简单——中断能否互相打断只看一个数值。2. API 调用门槛configMAX_SYSCALL_INTERRUPT_PRIORITY中断优先级 0 ~ configMAX_SYSCALL_INTERRUPT_PRIORITY-1: └─ 不能调用 FreeRTOS APIFromISR 函数 └─ 这些是不受 RTOS 管理的极高优先级中断 ​ 中断优先级 configMAX_SYSCALL_INTERRUPT_PRIORITY ~ 15: └─ 可以调用 FreeRTOS API如 xQueueSendFromISR 等 └─ 这些中断受 RTOS 管理不会破坏内核数据结构典型配置configMAX_SYSCALL_INTERRUPT_PRIORITY 5则优先级 5~15 的中断可以安全调用 FreeRTOS API。3. 优先级数值方向的颠倒这是最容易搞混的地方你表格总结得很到位中断优先级 任务优先级 (数值小→优先) (数值大→优先) ──────────── ──────────── 高 ← 0 31 1 30 ... ... 14 1 低 ← 15 0记忆口诀中断看排名第0名最优先任务看分数分越高越优先。二系统异常优先级设置寄存器SHPR1/2/31简介这三个寄存器专门用来配置Cortex-M 内核自带系统异常的优先级跟之前讲的外设中断如 USART、TIM走的是两套不同的寄存器。2为什么需要它们之前我们配置的都是外设中断USART1_IRQn 等等那些通过NVIC_Init()配置。但 Cortex-M 内核自己还有 15 个系统异常Memory Fault、Bus Fault、SysTick 等它们的优先级不归 NVIC 寄存器管而是由这三个 SHPR 寄存器负责。3寄存器与系统异常的对应关系SHPR1 (0xE000ED18) SHPR2 (0xE000ED1C) SHPR3 (0xE000ED20) ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │ PRI_4 MemManage │ │ 保留 │ │ PRI_12 调试监视器 │ │ PRI_5 BusFault │ │ 保留 │ │ 保留 │ │ PRI_6 UsageFault │ │ 保留 │ │ PRI_14 PendSV │ │ 保留 │ │ PRI_11 SVC │ │ PRI_15 SysTick │ └──────────────────┘ └──────────────────┘ └──────────────────┘寄存器配置谁常见用途SHPR1MemManage、BusFault、UsageFault这三个是硬件异常一般不改保持最高优先级0SHPR2SVCFreeRTOS 用SVC 指令触发任务切换优先级通常设得较低SHPR3PendSV SysTickFreeRTOS 核心PendSV 做上下文切换设最低SysTick 做时钟节拍4FreeRTOS 中的典型配置// FreeRTOS 在 port.c 中设置这三个寄存器 ​ // SysTick 和 PendSV 都设为最低优先级15 // 确保任务切换等所有中断处理完再执行 NVIC_SetPriority(SysTick_IRQn, 0x0F); // 最低 NVIC_SetPriority(PendSV_IRQn, 0x0F); // 最低 ​ // SVC 留一点余地 NVIC_SetPriority(SVCall_IRQn, 0x0E); // 次低仅在启动调度时用为什么 PendSV 和 SysTick 优先级设为最低如果它们在某个外设中断服务中途就切走了任务会破坏 ISR 的完整性。设为最低后它们会等所有 ISR 都跑完再执行任务切换。5和外设中断的关系优先级数值: 0 ←──── 最高 ────→ 15 ←── 最低 ──→ 系统异常 ─────┬─────────────────────────────┬────── Memory Fault, Bus Fault 等 PendSV, SysTick (SHPR1/2/3 配置) (SHPR3 配置) 外设中断 ─────┬─────────────────────────────┬────── USART, TIM, DMA 等 (NVIC 寄存器配置)两者共用同一套优先级数值空间0~15只是配置用的寄存器不同。NVIC_PriorityGroupConfig()的分组设置可以设置外设也可以设置内核的PendSV 和SysTick 都生效。6一句话总结SHPR1/2/3 系统异常的NVIC。普通项目不需要深入只有写 RTOS 移植层port.c才会直接操作它们。理解了PendSV 和 SysTick 设为最低优先级是为了不打断 ISR这个概念就够了。三FreeRTOS 配置 PendSV 和 SysTick 优先级核心逻辑非常简洁就一句话把 PendSV 和 SysTick 的优先级写到 SHPR3 寄存器里。1配置做了什么SHPR3 寄存器 (0xE000ED20)32 位 [31:24] SysTick 优先级 ← 写入 15 [23:16] PendSV 优先级 ← 写入 15 [15:8] 保留 [7:0] 调试监视器 优先级2写入 SHPR3 配置PendSV和SysTick 的步骤1第一步ARM Cortex-M 的优先级寄存器是 8 位但 STM32 只实现了高 4 位 [7:4]因此第一步就是先移位设置好优先级Bit: 7 6 5 4 | 3 2 1 0 有效 有效 有效 有效 | 保留 保留 保留 保留读出为 0C 语言里写15是0x0F二进制0000 1111但硬件读的是[7:4]所以必须把值挪到高 4 位才生效。定义一句宏#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15 中断最低优先级然后再定义另外一个宏其中configPRIO_BITS 4#define configKERNEL_INTERRUPT_PRIORITY (configLIBRARY_LOWEST_INTERRUPT_PRIORITY (8 - configPRIO_BITS))这样实现了 0000 1111 到1111 0000 的移位。得到了设置中断优先级的宏configKERNEL_INTERRUPT_PRIORITY15 也即0xF0 1111 0000 2第二步configKERNEL_INTERRUPT_PRIORITY只是把数据位准备好了0xF0还没真正写进硬件寄存器。真正写入是这两行portNVIC_SHPR3_REG | portNVIC_PENDSV_PRI; // 或进 SHPR3 portNVIC_SHPR3_REG | portNVIC_SYSTICK_PRI; // 或进 SHPR3因此我们要先得到 portNVIC_PENDSV_PRI和portNVIC_SYSTICK_PRI#define portNVIC_PENDSV_PRI ( ( ( uint32_t ) configKERNEL_INTERRUPT_PRIORITY ) 16UL ) #define portNVIC_SYSTICK_PRI ( ( ( uint32_t ) configKERNEL_INTERRUPT_PRIORITY ) 24UL )移位结果configKERNEL_INTERRUPT_PRIORITY 0xF0 0000 0000 1111 0000 (16位视角) 强转 uint32_t 后: 0000 0000 0000 0000 0000 0000 1111 0000 16 → portNVIC_PENDSV_PRI: 0000 0000 1111 0000 0000 0000 0000 0000 0x00F0_0000 ✓ 24 → portNVIC_SYSTICK_PRI: 1111 0000 0000 0000 0000 0000 0000 0000 0xF000_0000 ✓3第三步portNVIC_SHPR3_REG是什么#define portNVIC_SHPR3_REG ( *( ( volatile uint32_t * ) 0xe000ed20 ) )拆开看0xe000ed20 ← 一个数字即 SHPR3 寄存器的地址 (volatile uint32_t *) 0xe000ed20 ← 把数字强转成指向 uint32_t 的指针 告诉编译器这个地址存着一个 32 位寄存器 *( ... ) ← 解引用读写这个地址的内容 直接操作硬件寄存器所以它不是一个普通变量而是一个指向硬编码地址0xE000ED20的指针解引用。读它就等于读 SHPR3 寄存器写它就等于写 SHPR3 寄存器。最后进行或运算portNVIC_SHPR3_REG | portNVIC_PENDSV_PRI; // 或进 SHPR3 portNVIC_SHPR3_REG | portNVIC_SYSTICK_PRI; // 或进 SHPR34总结三行代码的分工总结代码做的事情阶段configKERNEL_INTERRUPT_PRIORITY算出0xF0把 15 对齐到 [7:4]准备数据portNVIC_PENDSV_PRI/portNVIC_SYSTICK_PRI把0xF0移到寄存器对应 bit 段定位位置portNVIC_SHPR3_REG | ...写入硬件寄存器0xE000ED20生效三步走算好数据 → 移对位置 → 写进硬件。四三个中断屏蔽寄存器1区别优先级设定”寄存器SHPRx作用决定谁更重要。它负责给各种中断源比如定时器、串口、系统异常等排座次规定哪个先执行哪个后执行。通俗理解这就是在“选秀”决定谁能上台表演。“中断屏蔽”寄存器PRIMASK、FAULTMASK、BASEPRI作用决定谁能上台。它负责管理“大门”控制哪些中断被允许进入 CPU 核心去请求处理。通俗理解这就是在“安检”符合条件优先级高于阈值或未被屏蔽的中断才能放行进去找 CPU。2cotex内核ARM系列优先级分布ARM 优先级体系的完整分布 -3 Reset ← 最高的不可屏蔽 -2 NMI ← 最高可屏蔽不NMI 字面意思就是不可屏蔽中断 -1 HardFault ← 固定负数比所有可配优先级都高 ──────────────────────── BASEPRI 的管辖上限只管 0~15 0 (最高可配优先级) 1 ... 15 (最低可配优先级)PRIMASK只屏蔽优先级 0~15 的可配中断优先级为负数的NMI、HardFault它管不着。FAULTMASK更狠把优先级提升到了仅低于 NMI 的层级连 HardFault 也给你按住了。BASEPRI只在 0~15 范围内按阈值屏蔽负数优先级的异常根本不在它的管辖范围内。PRIMASK1 FAULTMASK1 BASEPRI阈值 ───────── ─────────── ─────────── NMI (优先级固定 -2) ✗ 关不掉 ✗ 关不掉 ✗ 关不掉永远最高 HardFault (优先级固定 -1) ✗ 关不掉 ✓ 关掉了 ✗ 管不了优先级是负的 MemManage/BusFault/ ✓ 关掉了 ✓ 关掉了 ✗ 管不了这些是异常不是中断 UsageFault (优先级可配) SysTick/PendSV ✓ 关掉了 ✓ 关掉了 ✓ 按阈值关 (优先级 0~15) USART/TIM/DMA 等外设中断 ✓ 关掉了 ✓ 关掉了 ✓ 按阈值关 (优先级 0~15)3FreeRTOS 为什么选 BASEPRIBASEPRI 0x50 (即优先级阈值设为 5) 优先级: 0 ─── 不受屏蔽可打断一切 ───→ 这些是极紧急中断不能调 RTOS API 1 2 3 4 ──────────────────────── threshold 5 5 ─── 被 BASEPRI 屏蔽 ────→ FreeRTOS 的临界区期间这些中断老实等着 ... 15好处FreeRTOS 进入临界区时只需要把 BASEPRI 设为configMAX_SYSCALL_INTERRUPT_PRIORITY比如 5就可以只屏蔽能用 RTOS API的中断而不影响那些极高优先级的紧急中断。出临界区时把 BASEPRI 恢复为 0不屏蔽0~15之间的任1中断响应。FreeRTOS 的临界区不应该耽误优先级 0~4 的紧急中断比如电机控制、生命维持所以用 BASEPRI 而不是 PRIMASK。4FreeRTOS 临界区实际操作进入临界区: taskENTER_CRITICAL() → 读当前 BASEPRI → 写 BASEPRI 0x50 ← 屏蔽优先级 ≥ 5 的中断 → 此时能调 RTOS API 的中断全部暂停内核数据结构安全 操作共享数据比如链表插入... 退出临界区: taskEXIT_CRITICAL() → 恢复 BASEPRI 原来的值通常是 0 → 被屏蔽的中断恢复整条知识链到这儿就完整了——从硬件寄存器位分布到优先级分组到 SHPR 配置两个内核异常再到 BASEPRI 做运行时屏蔽。5BASEPRI 0x50的移位过程BASEPRI 的情况和 SHPR3不一样——SHPR3 是一个 32 位寄存器里挤了 4 个不同的优先级所以需要把数据挪到对应的 byte lane。BASEPRI 没那么复杂。BASEPRI 地址0xE000ED04它只存一个东西阈值。所以 0x50 直接写在 **bit[7:0]**不需要再往高处挪BASEPRI (0xE000ED04), 32 位寄存器: [31:8] 保留读出为 0 [7:0] 阈值STM32 只用了高 4 位 [7:4] 所以 0x50 直接写在 **bit[7:0]**不需要再往高处挪 BASEPRI 0x00000050 [31:24] [23:16] [15:8] [7:0] ┌──────┬──────┬──────┬──────────┐ │ 00000000│ 00000000│ 00000000│ 01010000│ └──────┴──────┴──────┴──────────┘ ↑ [7:4] 0101 5 有效阈值 5 和 SHPR3 的对比为什么一个要移位、一个不用 SHPR3 (0xE000ED20) — 一个寄存器塞了 4 个优先级: [31:24] [23:16] [15:8] [7:0] SysTick PendSV 保留 调试监视器 ↑ ↑ 需要 24 需要 16 ← 因为每个字段在不同的 byte 位置 BASEPRI (0xE000ED04) — 一个寄存器只放一个阈值: [31:8] 全部保留[7:0] 就它自己 不需要在寄存器内部再移位0x50 直接进 [7:0] ✓configMAX_SYSCALL_INTERRUPT_PRIORITY 5 ​ │ (8 - 4) 即 4 ▼ 0x50 ← 数据已对齐 [7:4]可以直接写进 BASEPRI ​ │ 直接赋值给 BASEPRI 寄存器 (0xE000ED04) ▼ BASEPRI 0x00000050 [7:4]0101 → 阈值5 优先级 5~15 的中断被屏蔽 ✓