5.20 NVIC 使用说明
本章说明 N32H49x 标准外设库中 NVIC(misc)驱动的文件组成、5 个公开函数,以及 NVIC_VectorTable_Relocation、NVIC_IRQ_Mask、NVIC_IRQ_Priority 三个示例。中断优先级分组、向量表重定位与内核异常的行为请配合用户手册的“System”章节阅读。
5.20.1 相关文件
| 相对路径 | 作用 |
|---|---|
firmware/n32h49x_std_periph_driver/inc/misc.h |
定义 NVIC_InitType 结构体与 NVIC 优先级分组、低功耗条件、SysTick 时钟源等选择宏,并声明 5 个公开函数 |
firmware/n32h49x_std_periph_driver/src/misc.c |
实现上述函数:优先级分组、NVIC 通道初始化、向量表重定位、低功耗条件选择与 SysTick 时钟源配置 |
firmware/CMSIS/device/n32h49x.h |
定义 IRQn_Type 中断号枚举,以及 NVIC_SetPriority、NVIC_GetActive、NVIC_EncodePriority 等 CMSIS 内核接口 |
projects/n32h49x_EVAL/examples/NVIC/ |
NVIC 示例根目录,含 NVIC_VectorTable_Relocation、NVIC_IRQ_Mask、NVIC_IRQ_Priority 3 个示例 |
projects/n32h49x_EVAL/examples/NVIC/NVIC_VectorTable_Relocation/ |
该示例的工程目录组成:MDK-ARM/、EWARM/、inc/、src/、readme.txt |
projects/n32h49x_EVAL/bsp/ |
该示例使用的板级支持文件:日志初始化与打印串口重映射 |
5.20.2 API 参考
以下 5 个公开函数按 misc.h 的声明顺序分为 3 组;每个条目给出功能摘要与 misc.c 中该函数正上方的注释原文。
优先级分组与初始化
NVIC_PriorityGroupConfig、NVIC_Init
向量表
NVIC_SetVectorTable
低功耗与 SysTick
NVIC_SystemLPConfig、SysTick_CLKSourceConfig
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)配置优先级分组:抢占优先级与子优先级。
name NVIC_PriorityGroupConfig
fun Configures the priority grouping: pre-emption priority and subpriority.
param NVIC_PriorityGroup :
- NVIC_PriorityGroup_0 0 bits for pre-emption priority 4 bits for subpriority
- NVIC_PriorityGroup_1 1 bits for pre-emption priority 3 bits for subpriority
- NVIC_PriorityGroup_2 2 bits for pre-emption priority 2 bits for subpriority
- NVIC_PriorityGroup_3 3 bits for pre-emption priority 1 bits for subpriority
- NVIC_PriorityGroup_4 4 bits for pre-emption priority 0 bits for subpriority
return none
void NVIC_Init(NVIC_InitType* NVIC_InitStruct)按 NVIC_structure_initializes 中的指定参数初始化 NVIC 外设。
name NVIC_Init
fun Initializes the NVIC peripheral according to the specified parameters in the NVIC_structure_initializes.
param NVIC_InitStruct :
- NVIC_IRQChannel :
- IRQn_Type Interrupt Number Definition
if NVIC_PriorityGroup is NVIC_PriorityGroup_0 :
- NVIC_IRQChannelPreemptionPriority :
- NVIC_PRE_PRIORITY_0
- NVIC_IRQChannelSubPriority :
- NVIC_SUB_PRIORITY_0
- NVIC_SUB_PRIORITY_1
- NVIC_SUB_PRIORITY_2
- NVIC_SUB_PRIORITY_3
- NVIC_SUB_PRIORITY_4
- NVIC_SUB_PRIORITY_5
- NVIC_SUB_PRIORITY_6
- NVIC_SUB_PRIORITY_7
- NVIC_SUB_PRIORITY_8
- NVIC_SUB_PRIORITY_9
- NVIC_SUB_PRIORITY_10
- NVIC_SUB_PRIORITY_11
- NVIC_SUB_PRIORITY_12
- NVIC_SUB_PRIORITY_13
- NVIC_SUB_PRIORITY_14
- NVIC_SUB_PRIORITY_15
if NVIC_PriorityGroup is NVIC_PriorityGroup_1 :
- NVIC_IRQChannelPreemptionPriority :
- NVIC_PRE_PRIORITY_0
- NVIC_PRE_PRIORITY_1
- NVIC_IRQChannelSubPriority :
- NVIC_SUB_PRIORITY_0 to NVIC_SUB_PRIORITY_7
if NVIC_PriorityGroup is NVIC_PriorityGroup_2 :
- NVIC_IRQChannelPreemptionPriority :
- NVIC_PRE_PRIORITY_0 to NVIC_PRE_PRIORITY_3
- NVIC_IRQChannelSubPriority :
- NVIC_SUB_PRIORITY_0 to NVIC_SUB_PRIORITY_3
if NVIC_PriorityGroup is NVIC_PriorityGroup_3 :
- NVIC_IRQChannelPreemptionPriority :
- NVIC_PRE_PRIORITY_0 to NVIC_PRE_PRIORITY_7
- NVIC_IRQChannelSubPriority :
- NVIC_SUB_PRIORITY_0
- NVIC_SUB_PRIORITY_1
if NVIC_PriorityGroup is NVIC_PriorityGroup_4 :
- NVIC_IRQChannelPreemptionPriority :
- NVIC_PRE_PRIORITY_0
- NVIC_PRE_PRIORITY_1
- NVIC_PRE_PRIORITY_2
- NVIC_PRE_PRIORITY_3
- NVIC_PRE_PRIORITY_4
- NVIC_PRE_PRIORITY_5
- NVIC_PRE_PRIORITY_6
- NVIC_PRE_PRIORITY_7
- NVIC_PRE_PRIORITY_8
- NVIC_PRE_PRIORITY_9
- NVIC_PRE_PRIORITY_10
- NVIC_PRE_PRIORITY_11
- NVIC_PRE_PRIORITY_12
- NVIC_PRE_PRIORITY_13
- NVIC_PRE_PRIORITY_14
- NVIC_PRE_PRIORITY_15
- NVIC_IRQChannelSubPriority :
- NVIC_SUB_PRIORITY_0
- NVIC_IRQChannelCmd :
- ENABLE
- DISABLE
return none
void NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset)设置中断向量表的位置与偏移。
name NVIC_SetVectorTable
fun Sets the vector table location and Offset.
param NVIC_vecter_table specifies if the vector table is in RAM or FLASH memory.
This parameter can be one of the following values:
- NVIC_VectTab_RAM
- NVIC_VectTab_FLASH
param offset Vector Table base offset field. This value must be a multiple of 0x200.
return none
void NVIC_SystemLPConfig(uint8_t LowPowerMode, FunctionalState Cmd)选择系统进入低功耗模式的条件。
name NVIC_SystemLPConfig
fun Selects the condition for the system to enter low power mode.
param LowPowerMode Specifies the new mode for the system to enter low power mode.
This parameter can be one of the following values:
- NVIC_LP_SEVONPEND
- NVIC_LP_SLEEPDEEP
- NVIC_LP_SLEEPONEXIT
param - Cmd :
- ENABLE
- DISABLE
return none
void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)配置 SysTick 时钟源。
name SysTick_CLKSourceConfig
fun Configures the SysTick clock source.
param SysTick_CLKSource :
- SysTick_CLKSource_HCLK_Div8 External clock selected as SysTick clock source.
- SysTick_CLKSource_HCLK AHB clock selected as SysTick clock source.
return none
5.20.3 Demo 示例说明
本节说明 NVIC 的 3 个示例:NVIC_VectorTable_Relocation 演示把中断向量表从 FLASH 重定位到 SRAM,NVIC_IRQ_Mask 演示全局中断屏蔽对可配置优先级中断的影响,NVIC_IRQ_Priority 演示抢占优先级对中断嵌套的影响。
5.20.3.1 NVIC_VectorTable_Relocation 向量表重定位
工程目录: projects/n32h49x_EVAL/examples/NVIC/NVIC_VectorTable_Relocation/
初始化打印串口并把 PA15 配为带外部中断的输入→按键在 PA15 上产生下降沿进入 EXTI4 中断→中断中置位按键标志并清除挂起位→主循环检测到标志后把向量表从 FLASH 重定位到 SRAM→重定位后程序继续运行并打印提示
| 项目 | 源码解读 |
|---|---|
| 功能 | 演示 NVIC 中断向量表重定位:程序启动时向量表位于 FLASH(偏移 0x00000000); 按下按键后调用 NVIC_SetVectorTable 把向量表重定位到内部 SRAM 的 0x00010000,并打印提示。 |
| 源码组成 | src/main.c:main、KeyInputExtiInit 与 EXTI4_IRQHandler;inc/main.h:向量表偏移宏 VECT_TAB_SRAM_OFFSET/VECT_TAB_FLASH_OFFSET 与按键、EXTI、NVIC 宏;src/n32h49x_it.c:内核异常处理函数;readme.txt:功能、环境与使用说明;MDK-ARM/、EWARM/:Keil 与 IAR 工程。 |
| 关键配置 | PA15 配置为 GPIO_MODE_INPUT 与 GPIO_PULL_UP;外部中断线为 EXTI_LINE4,触发方式 EXTI_Trigger_Falling;优先级分组 NVIC_PriorityGroup_2,EXTI4 抢占优先级 1、子优先级 0;系统时钟 240 MHz,打印串口 USART TX-PA9、RX-PA10,115200。 |
| 程序与数据路径 | main 调用 log_init 打印 NVIC VectorTable Relocation,随后 KeyInputExtiInit 使能 GPIOA 与 AFIO 时钟、配置 PA15 输入上拉,把 EXTI_LINE4 接到 PA15 并使能下降沿中断,最后配置 NVIC 通道;主循环等待 Key_Status 置位后打印信息并执行向量表重定位。 |
| 主要 API | NVIC_SetVectorTable、NVIC_PriorityGroupConfig、NVIC_Init、GPIO_ConfigEXTILine |
| 验证方法 | 复位运行后向量表位于 FLASH,程序正常运行; 按下按键进入 EXTI4_IRQHandler,打印 EXTI4 IRQHandler is executed(FLASH),随后主循环打印重定位信息并把向量表移到 SRAM,之后程序继续运行。 |
| 使用提示 | NVIC_SetVectorTable 的偏移量必须是 0x200 的倍数;重定位后所有中断向量都从新位置取出,须先确保 SRAM 中该位置已有有效的向量表内容。 开发环境为 KEIL MDK-ARM V5.34 或 IAR EWARM 8.50.1。 |
核心代码解读与移植
示例用一个按键外部中断触发向量表重定位,把中断向量表从 FLASH 搬到内部 SRAM 并继续运行。
数据路径: inc/main.h 定义向量表偏移与按键资源宏 → KeyInputExtiInit 配置 PA15、EXTI_LINE4 与 NVIC 通道 → EXTI4_IRQHandler 置位 Key_Status 并清除挂起位 → 主循环调用 NVIC_SetVectorTable 把向量表重定位到 SRAM
1. 用宏集中定义向量表偏移与按键资源。 两个偏移宏区分 FLASH 与 SRAM 的向量表位置,按键相关的端口、引脚、EXTI 线与中断号也用宏描述。
#define VECT_TAB_FLASH_OFFSET ((uint32_t)0x00000000)
#define VECT_TAB_SRAM_OFFSET ((uint32_t)0x00010000)
#define KEY_INPUT_PORT_CLK RCC_AHB_PERIPHEN_GPIOA
#define KEY_INPUT_PORT GPIOA
#define KEY_INPUT_PIN GPIO_PIN_15
#define KEY_INPUT_EXTI_LINE EXTI_LINE4
#define KEY_INPUT_LIN_SOURCE EXTI_LINE_SOURCE4
#define KEY_INPUT_PORT_SOURCE GPIOA_PORT_SOURCE
#define KEY_INPUT_PIN_SOURCE GPIO_PIN_SOURCE15
#define KEY_INPUT_IRQn EXTI4_IRQn
VECT_TAB_SRAM_OFFSET 取 0x00010000,正好是 0x200 的倍数,满足 NVIC_SetVectorTable 对偏移的要求。
2. KeyInputExtiInit 配置 EXTI4 与 NVIC 通道。 函数先使能 GPIOA 与 AFIO 时钟,把 PA15 配为输入上拉,再把 EXTI4 接到 PA15 并配置为下降沿中断,最后设置优先级分组与 NVIC 通道。
/*Configure key EXTI Line to key input Pin*/
GPIO_ConfigEXTILine(KEY_INPUT_LIN_SOURCE, KEY_INPUT_PORT_SOURCE,KEY_INPUT_PIN_SOURCE);
/*Configure key EXTI line*/
EXTI_InitStruct(&EXTI_InitStructure);
EXTI_InitStructure.EXTI_Line = KEY_INPUT_EXTI_LINE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; // EXTI_Trigger_Rising;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_InitPeripheral(&EXTI_InitStructure);
/* Configure two bits for preemption priority */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
/*Set key input interrupt priority*/
NVIC_InitStructure.NVIC_IRQChannel = KEY_INPUT_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
NVIC_PriorityGroup_2 把 2 位分给抢占优先级、2 位分给子优先级;这里 EXTI4 的抢占优先级为 1、子优先级为 0。
3. EXTI4_IRQHandler 置位标志并清除挂起位。 中断服务函数判断 EXTI4 状态后置位 Key_Status,并把 VECT_TAB_SRAM 作为编译期分支区分两种向量表位置。
if (EXTI_GetStatusFlag(KEY_INPUT_EXTI_LINE) != RESET)
{
#ifdef VECT_TAB_SRAM
log_info("EXTI4 IRQHandler is executed(SRAM) \r\n");
#else
/* Key_Status */
Key_Status = ENABLE;
log_info("EXTI4 IRQHandler is executed(FLASH) \r\n");
#endif
/* Clears the SEL Button EXTI line pending bits */
EXTI_ClrStatusFlag(KEY_INPUT_EXTI_LINE);
}
EXTI_ClrStatusFlag 清除挂起位,避免中断反复进入;Key_Status 是主循环与中断之间的同步标志。
4. 主循环执行向量表重定位。 等待标志置位后打印提示,并调用 NVIC_SetVectorTable 把向量表搬到 SRAM 的 0x00010000,随后进入空循环。
if (Key_Status == ENABLE)
{
log_info("Sets the vector table location and Offset \r\n");
/* Vector Table Relocation in Internal SRAM. */
NVIC_SetVectorTable(NVIC_VectTab_RAM, VECT_TAB_SRAM_OFFSET);
while (1)
;
}
NVIC_VectTab_RAM 选择 RAM 作为向量表基址,偏移由 VECT_TAB_SRAM_OFFSET 给出。
对应源码: src/main.c、inc/main.h
5.20.3.2 NVIC_IRQ_Mask 全局中断屏蔽
工程目录: projects/n32h49x_EVAL/examples/NVIC/NVIC_IRQ_Mask/
初始化打印串口,配置 1 秒周期的定时器更新中断与 PA15 按键中断→定时器每秒进入一次更新中断并打印→主循环等待按键事件翻转按键标志→检测到标志后关闭全局中断并打印→轮询按键完成一次按下与释放后再打开全局中断→屏蔽期间定时器不再打印,恢复后继续
| 项目 | 源码解读 |
|---|---|
| 功能 | 演示 __disable_irq() 与 __enable_irq() 对可配置优先级中断的屏蔽效果:GTIM3 每秒产生一次更新中断并打印;按键触发后先关闭全局中断,待按键释放再重新打开。 |
| 源码组成 | src/main.c:main、KeyInputExtiInit、TIM_Config、EXTI4_IRQHandler 与 GTIM3_IRQHandler;inc/main.h:按键、EXTI、NVIC 宏与函数声明;src/n32h49x_it.c:内核异常处理函数;readme.txt:功能、环境与使用说明;MDK-ARM/、EWARM/:Keil 与 IAR 工程。 |
| 关键配置 | 优先级分组 NVIC_PriorityGroup_2;GTIM3 更新中断周期为 1 秒(Period 为 50000-1,Prescaler 为 HclkFreq/50000-1),抢占优先级 0、子优先级 0; PA15 输入上拉, EXTI_LINE4 下降沿中断,抢占优先级 1、子优先级 0。 |
| 程序与数据路径 | main 先配置 GTIM3 更新中断与 PA15 按键中断,再进入主循环;EXTI4_IRQHandler 每次翻转 Key_Status,GTIM3_IRQHandler 清除更新标志并打印 GTIM3 IRQHandler。 |
| 主要 API | NVIC_PriorityGroupConfig、NVIC_Init、GPIO_ReadInputDataBit、__disable_irq、__enable_irq |
| 验证方法 | 复位后串口每秒打印一次 GTIM3 IRQHandler;按下按键后打印 Disable irq 且 GTIM3 打印停止,松开按键后打印 enable irq 并恢复每秒打印。 |
| 使用提示 | __disable_irq() 把执行优先级提升到 0,屏蔽所有可配置优先级的中断,仅 HardFault 等仍可通过升级机制触发;关中断期间不要长时间轮询或阻塞,以免影响系统实时响应。 开发环境为 KEIL MDK-ARM V5.34 或 IAR EWARM 8.50.1。 |
核心代码解读与移植
示例用 GTIM3 的秒级更新中断显示中断是否被屏蔽,并用按键在“关闭中断—轮询按键—打开中断”之间切换。
数据路径: TIM_Config 配置 GTIM3 更新中断并使能 NVIC 通道 → GTIM3_IRQHandler 每秒清标志并打印 → EXTI4_IRQHandler 翻转 Key_Status → 主循环据 Key_Status 调用 __disable_irq、轮询按键并调用 __enable_irq
1. TIM_Config 配置秒级更新中断并使能 NVIC。 时基的 Period 与 Prescaler 由 HCLK 频率换算得到 1 秒周期,随后使能更新中断与计数。
TIM_InitTimBaseStruct(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.Period = 50000-1;
TIM_TimeBaseStructure.Prescaler = ((RCC_Clocks.HclkFreq / 50000) - 1);
TIM_TimeBaseStructure.ClkDiv = TIM_CLK_DIV1;
TIM_TimeBaseStructure.CounterMode = TIM_CNT_MODE_UP;
TIM_InitTimeBase(GTIM3, &TIM_TimeBaseStructure);
/* Immediate load of GTIM3 Precaler values */
TIM_ConfigPrescaler(GTIM3, ((RCC_Clocks.HclkFreq / 50000) - 1),TIM_PSC_RELOAD_MODE_IMMEDIATE);
/* Clear GTIM3 update pending flags */
TIM_ClearFlag(GTIM3, TIM_FLAG_UPDATE);
/* Enable the GTIM3 Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = GTIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/* Enable GTIM3 Update interrupts */
TIM_ConfigInt(GTIM3, TIM_INT_UPDATE,ENABLE);
/* GTIM3 enable counters */
TIM_Enable(GTIM3,ENABLE);
TIM_ConfigInt 打开外设端的更新中断,NVIC_Init 打开 NVIC 通道,两者缺一都不会进入 GTIM3_IRQHandler。
2. 主循环关闭与打开全局中断。 检测到按键标志后先调用 __disable_irq,再轮询按键完成一次按下与释放,最后调用 __enable_irq 恢复。
while (Key_Status == DISABLE)
{
}
log_info("Disable irq \r\n");
/* This instruction raises the execution priority to 0. This prevents all
exceptions with configurable priority from activating, other than through
the HardFault fault escalation mechanism. */
__disable_irq();
while(GPIO_ReadInputDataBit(KEY_INPUT_PORT,KEY_INPUT_PIN) == RESET)
{
}
while(GPIO_ReadInputDataBit(KEY_INPUT_PORT,KEY_INPUT_PIN) == SET)
{
}
log_info("enable irq \r\n");
__disable_irq 把执行优先级提升到 0 后,GTIM3 的更新中断被挂起而不执行;__enable_irq 恢复后中断继续按周期进入。
3. EXTI4_IRQHandler 翻转按键标志。 中断每次把 Key_Status 在 ENABLE 与 DISABLE 之间翻转,作为主循环的动作触发源。
if (EXTI_GetStatusFlag(KEY_INPUT_EXTI_LINE) != RESET)
{
if (Key_Status == ENABLE)
{
Key_Status = DISABLE;
}
else
{
Key_Status = ENABLE;
}
/* Clears the SEL Button EXTI line pending bits. */
EXTI_ClrStatusFlag(KEY_INPUT_EXTI_LINE);
}
标志翻转后主循环才退出等待,从而保证关中断发生在一次真实的按键事件之后。
4. GTIM3_IRQHandler 清标志并打印。 定时器中断只做清标志与打印,用于直观显示中断是否被屏蔽。
/* Clear GTIM3 update interrupt */
TIM_ClearFlag(GTIM3, TIM_INT_UPDATE);
/* GTIM3 IRQHandler */
log_info("GTIM3 IRQHandler \r\n");
若忘记清除更新标志,中断会立即再次进入,串口打印会连成一片。
对应源码: src/main.c、inc/main.h
5.20.3.3 NVIC_IRQ_Priority 抢占优先级
工程目录: projects/n32h49x_EVAL/examples/NVIC/NVIC_IRQ_Priority/
初始化打印串口与 PA15 按键外部中断→把 SysTick 抢占优先级设为 0 并启动周期中断→主循环每轮打印状态并延时→按下按键后在 EXTI4 中断中悬起 SysTick 异常→SysTick 抢占进入并判断是否打断了 EXTI4→主循环据状态调整 SysTick 抢占优先级并打印结束
| 项目 | 源码解读 |
|---|---|
| 功能 | 演示 NVIC 优先级对中断抢占的影响:SysTick 抢占优先级初值 0,高于 EXTI4 的 1; 按下按键后 EXTI4 中断中主动悬起 SysTick,观察 SysTick 是否在 EXTI4 执行期间抢先进入。 |
| 源码组成 | src/main.c:main、KeyInputExtiInit、EXTI4_IRQHandler 与 Delay;src/n32h49x_it.c:内核异常处理函数,含 SysTick_Handler;inc/main.h:按键、EXTI、NVIC 宏与 Status 类型声明;readme.txt:功能、环境与使用说明;MDK-ARM/、EWARM/:Keil 与 IAR 工程。 |
| 关键配置 | 优先级分组 NVIC_PriorityGroup_2;EXTI4 抢占优先级 1、子优先级 0; SysTick 用 NVIC_SetPriority 与 NVIC_EncodePriority 设为抢占优先级 0。 |
| 程序与数据路径 | main 初始化按键中断后把 SysTick 优先级设为抢占 0,主循环每轮打印 Key_Status 并调用 Delay(0x8FFFFF);EXTI4_IRQHandler 通过写 SCB->ICSR 悬起 SysTick,SysTick_Handler 依据 NVIC_GetActive(KEY_INPUT_IRQn) 把 Key_Status 置 1 或 2。 |
| 主要 API | NVIC_SetPriority、NVIC_EncodePriority、NVIC_GetPriorityGrouping、NVIC_Init |
| 验证方法 | 复位后串口打印 Key_Status = 0;按下按键到达 EXTI4,随后主循环把 SysTick 优先级改为 2 并打印 Key_Status = 1;再次触发后打印 NVIC IRQ Priority Test End。 |
| 使用提示 | 抢占优先级决定能否打断正在执行的中断,子优先级只在同时挂起时决定先后; 修改 SysTick 优先级会影响基于 SysTick 的延时,调整后须重新确认系统节拍。 开发环境为 KEIL MDK-ARM V5.34 或 IAR EWARM 8.50.1。 |
核心代码解读与移植
示例用两个中断的抢占关系说明优先级作用:EXTI4 在中断中主动悬起 SysTick,再由 SysTick 判断自己是否打断了 EXTI4。
数据路径: main 用 NVIC_SetPriority 设 SysTick 抢占优先级 → 按键进入 EXTI4_IRQHandler 写 SCB->ICSR 悬起 SysTick → SysTick_Handler 用 NVIC_GetActive 判断是否打断 EXTI4 并置位 Key_Status → 主循环据 Key_Status 再次调用 NVIC_SetPriority 调整优先级
1. 用 CMSIS 接口设置 SysTick 优先级。 初始化按键中断后,把 SysTick 的抢占优先级设为 0,使其高于 EXTI4 的 1。
KeyInputExtiInit(KEY_INPUT_PORT, KEY_INPUT_PIN);
/* Configure the SysTick Handler Priority: Preemption priority(0) and subpriority */
NVIC_SetPriority(SysTick_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), PreemptionPriorityValue, 0));
NVIC_EncodePriority 把抢占优先级与子优先级按当前分组编码成寄存器值,避免手工移位出错。
2. EXTI4_IRQHandler 主动悬起 SysTick。 中断服务函数向 SCB->ICSR 写入悬起位,请求一个 SysTick 异常,用于制造抢占场景。
log_info("EXTI4 IRQHandler Start \r\n");
if (EXTI_GetStatusFlag(KEY_INPUT_EXTI_LINE) != RESET)
{
/* Generate SysTick exception */
SCB->ICSR |= 0x04000000;
/* Clears the SEL Button EXTI line pending bits */
EXTI_ClrStatusFlag(KEY_INPUT_EXTI_LINE);
}
log_info("EXTI4 IRQHandler End \r\n");
若 SysTick 的抢占优先级高于 EXTI4,悬起后在 EXTI4 未返回时 SysTick 便会进入;否则要等 EXTI4 返回后才执行。
3. SysTick_Handler 判断是否发生抢占。 中断中用 NVIC_GetActive 读取 EXTI4 是否仍在活动,据此把 Key_Status 置 1 或 2。
void SysTick_Handler(void)
{
log_info("SysTick IRQHandler \r\n");
/* If the EXTI0 IRQ Handler was preempted by SysTick Handler */
if (NVIC_GetActive(KEY_INPUT_IRQn) != 0)
{
Key_Status = 1;
}
else
{
Key_Status = 2;
}
}
NVIC_GetActive 返回非 0 表示 EXTI4 仍处于活动状态,说明 SysTick 抢占了 EXTI4。
4. 主循环读取状态并调整优先级。 main 根据 Key_Status 打印信息,并在状态为 1 时把 SysTick 抢占优先级改为 PreemptionPriorityValue + 2。
while (1)
{
if (Key_Status == 1)
{
log_info("Key_Status = 1 \r\n");
/* Configure the SysTick Handler Priority: Preemption priority(2) and subpriority */
NVIC_SetPriority(SysTick_IRQn,
NVIC_EncodePriority(NVIC_GetPriorityGrouping(), PreemptionPriorityValue + 2, 0));
}
else if (Key_Status == 2)
{
log_info("NVIC IRQ Priority Test End \r\n");
}
else
{
log_info("Key_Status = 0 \r\n");
}
/* Insert delay Time */
Delay(0x8FFFFF);
}
把 SysTick 抢占优先级提高到 2 后,它不再高于 EXTI4 的 1;再次触发时 SysTick 只能等 EXTI4 返回后执行,Key_Status 变为 2。
对应源码: src/main.c、src/n32h49x_it.c
5.20.4 注意与排障
| 场景 | 检查与建议 | 来源 |
|---|---|---|
| 中断通道的数量与优先级位数 | N32H49x 提供 112 个可屏蔽中断通道(不含 16 个 Cortex-M4F 中断线); 优先级字段为 4 位,可编程优先级共 16 级。 |
用户手册 |
| 使能中断通道 | 使用任一外设中断前,必须在 NVIC 中使能对应的中断通道,并正确设置其优先级; 仅在外设端打开中断使能而 NVIC 通道未使能,不会进入中断服务程序。 |
用户手册 |
| 优先级分组 | 每个中断的优先级由抢占优先级与子优先级两部分组成,两者的位数由 NVIC_PriorityGroupConfig 设定;抢占优先级决定能否打断正在执行的中断,子优先级只在同时挂起时决定先后。 |
用户手册 |
| 向量表重定位 | 复位时 CPU 从 0x00000000 取出栈顶、从 0x00000004 取出复位向量; 用 NVIC_SetVectorTable 可把向量表移到 SRAM,偏移量必须是 0x200 的倍数。 |
用户手册 |
| SysTick 时钟源 | SysTick 可选用 HCLK/8 或 HCLK 作为时钟源,复位后默认使用 HCLK/8; 切换时钟源后须重新计算重装值,保证计数周期符合预期。 |
用户手册 |