3、LL库中断机制初探:LL_SYSTICK_EnableIT、NVIC_SetPriorityGrouping、NVIC_SetPriority、NVIC_EnableIRQ等LL库原生函数
从 HAL 库迁移到 LL 库,最核心的转变在于对底层寄存器的直接操控。中断优先级配置是系统稳定性的基石,LL 库提供了一系列轻量级、无状态的原生函数,让开发者能够以接近汇编的效率完成配置。本章将深入剖析 LL 库中与中断机制相关的四个关键函数:LL_SYSTICK_EnableIT、NVIC_SetPriorityGrouping、NVIC_SetPriority 和 NVIC_EnableIRQ,并揭示它们与 Cortex-M 内核寄存器的直接映射关系。
3.1 SysTick 中断使能:LL_SYSTICK_EnableIT
LL_SYSTICK_EnableIT 函数用于使能 SysTick 定时器的溢出中断。SysTick 是 Cortex-M 内核自带的 24 位递减定时器,通常用作 RTOS 的系统心跳或简单的周期性时基。
该函数直接操作 SysTick->CTRL 寄存器的第 1 位(TICKINT 位)。其实现极为精简,不包含任何参数校验或状态缓存。
// LL 库函数原型 (典型实现)
__STATIC_INLINE void LL_SYSTICK_EnableIT(void)
{
// 设置 SysTick 控制和状态寄存器的第 1 位 (TICKINT)
SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk;
}
// 对应的 HAL 库函数 (对比)
HAL_StatusTypeDef HAL_SYSTICK_Config(uint32_t TicksNumb)
{
// HAL 库会同时配置重装载值、时钟源和中断使能,并包含错误检查
// ...
}
HAL_SYSTICK_Config 会一次性完成重装载值、时钟源选择和中断使能,并返回 HAL 状态。LL 库将选择权完全交给开发者,你需要手动调用 LL_SYSTICK_SetCounter 和 LL_SYSTICK_SetClkSource 来完成完整配置。
下图展示了 SysTick 控制寄存器的位域结构,以及 LL_SYSTICK_EnableIT 操作的具体位置:
3.2 中断优先级分组:NVIC_SetPriorityGrouping
ARM Cortex-M 内核支持中断优先级嵌套,通过 优先级分组 机制来划分抢占优先级和子优先级(响应优先级)的位数。这个分组由系统控制块(SCB)中的 SCB->AIRCR 寄存器控制。
NVIC_SetPriorityGrouping 函数是 CMSIS-Core 标准函数,LL 库直接继承使用。它负责写入 SCB->AIRCR 寄存器的 PRIGROUP 位段。
// CMSIS 函数原型 (LL 库直接调用)
void NVIC_SetPriorityGrouping(uint32_t PriorityGroup)
{
uint32_t reg_value;
// 读取当前 AIRCR 值,清除 PRIGROUP 位段,设置新的分组值
reg_value = SCB->AIRCR;
reg_value &= ~SCB_AIRCR_VECTKEY_Msk; // 清除 VECTKEY 位
reg_value &= ~SCB_AIRCR_PRIGROUP_Msk; // 清除 PRIGROUP 位
reg_value = (reg_value |
(uint32_t)0x5FA << SCB_AIRCR_VECTKEY_Pos | // 写入密钥 0x5FA
(PriorityGroup << SCB_AIRCR_PRIGROUP_Pos)); // 写入分组值
SCB->AIRCR = reg_value;
}
SCB->AIRCR 时必须同时写入 VECTKEY 密钥 (0x5FA),否则写操作无效。HAL 库的 HAL_NVIC_SetPriorityGrouping 内部封装了此操作,而 LL 库直接暴露了 CMSIS 函数,开发者必须确保密钥正确。
下表列出了 STM32 中常见的优先级分组配置及其对应的位数分配:
| 分组值 (PriorityGroup) | NVIC_PriorityGroup_x 宏 | 抢占优先级位数 | 子优先级位数 | 可配置的优先级等级数 |
|---|---|---|---|---|
| 0 | NVIC_PriorityGroup_0 | 0 位 | 4 位 | 16 (仅子优先级) |
| 1 | NVIC_PriorityGroup_1 | 1 位 | 3 位 | 2 抢占 × 8 子 |
| 2 | NVIC_PriorityGroup_2 | 2 位 | 2 位 | 4 抢占 × 4 子 |
| 3 | NVIC_PriorityGroup_3 | 3 位 | 1 位 | 8 抢占 × 2 子 |
| 4 | NVIC_PriorityGroup_4 | 4 位 | 0 位 | 16 (仅抢占优先级) |
NVIC_PriorityGroup_4(即分组值 4),将所有优先级位都用作抢占优先级,简化嵌套逻辑。HAL 库默认初始化时通常设置为 NVIC_PriorityGroup_4。
3.3 设置中断优先级:NVIC_SetPriority
NVIC_SetPriority 函数用于设置单个外设中断的优先级数值。它操作的是 NVIC->IP[x] 寄存器数组,每个中断对应一个 8 位寄存器(实际只使用高 4 位,低 4 位为保留位)。
// CMSIS 函数原型
void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
{
// 对于 Cortex-M3/M4/M7,优先级寄存器为 8 位宽
NVIC->IP[IRQn] = (uint8_t)((priority << (8 - __NVIC_PRIO_BITS)) & 0xFF);
}
// LL 库中的典型调用方式
NVIC_SetPriority(TIM2_IRQn, 0x02); // 设置 TIM2 中断优先级为 2
__NVIC_PRIO_BITS 宏定义了实际使用的优先级位数。对于 STM32F4/F7/H7 系列,该值通常为 4。因此,priority 参数的低 4 位会被左移到寄存器的高 4 位。例如,传入 0x02,实际写入寄存器的值为 0x20。
下图展示了中断优先级寄存器 NVIC->IP[x] 的位映射关系:
3.4 使能外设中断:NVIC_EnableIRQ
NVIC_EnableIRQ 函数用于在 NVIC 中使能指定的外设中断请求。它操作的是 NVIC->ISER[x](中断设置使能寄存器)数组。
// CMSIS 函数原型
void NVIC_EnableIRQ(IRQn_Type IRQn)
{
// 计算中断号对应的 ISER 寄存器索引和位偏移
NVIC->ISER[(uint32_t)((int32_t)IRQn) >> 5] =
(uint32_t)(1 << (((uint32_t)(int32_t)IRQn) & 0x1F));
}
// 使用示例
NVIC_EnableIRQ(USART1_IRQn); // 使能 USART1 中断
NVIC_EnableIRQ 之前,必须确保已经通过 NVIC_SetPriority 设置了该中断的优先级,并且全局中断已通过 __enable_irq() 或 cpsie i 指令使能。否则,中断可能无法正确响应。
3.5 中断配置完整流程与对比
以下流程图展示了使用 LL 库配置一个外设中断的完整步骤,并与 HAL 库的流程进行对比:
3.6 函数对比总结表
下表总结了 LL 库与 HAL 库在中断优先级配置方面的关键函数差异:
| 功能 | LL 库 / CMSIS 函数 | HAL 库对应函数 | 关键差异 |
|---|---|---|---|
| SysTick 中断使能 | LL_SYSTICK_EnableIT() |
HAL_SYSTICK_Config() |
LL 仅使能中断位;HAL 同时配置重装载值和时钟源 |
| 优先级分组 | NVIC_SetPriorityGrouping() |
HAL_NVIC_SetPriorityGrouping() |
两者底层相同,HAL 增加了参数范围检查 |
| 设置优先级 | NVIC_SetPriority(IRQn, priority) |
HAL_NVIC_SetPriority(IRQn, Preempt, Sub) |
LL 直接传入 0-15 数值;HAL 传入抢占/子优先级并自动转换 |
| 使能中断 | NVIC_EnableIRQ(IRQn) |
HAL_NVIC_EnableIRQ(IRQn) |
LL 直接写 ISER 寄存器;HAL 增加临界段保护和状态检查 |
HAL_NVIC_SetPriority(IRQn, PreemptPriority, SubPriority) 替换为 NVIC_SetPriority(IRQn, (PreemptPriority << (SubPriorityBits)) | SubPriority) 的数值计算,或者直接使用 NVIC_EncodePriority() 辅助函数进行转换。
通过本章的学习,你已经掌握了 LL 库中断优先级配置的四个核心函数。它们直接映射到 Cortex-M 内核的寄存器层面,提供了极高的执行效率和灵活性。在下一阶段的移植工作中,你将基于这些函数构建稳定可靠的中断系统。