GD32的中断系统就像单片机界的"紧急呼叫系统",能让CPU放下手头活儿去处理更紧急的事。
先看下面这个表格,快速了解GD32中断的"家庭成员":
中断类型 | 来源 | 特点 |
---|---|---|
内部中断 | 处理器内部事件 | 如定时器溢出、串口收到数据等 |
外部中断 (EXTI) | 外部硬件或设备触发 | 如按键按下、传感器信号变化,通过GPIO引脚输入 |
异常 | 内核级事件 | 如系统复位、硬件错误等 |
🧠 中断处理核心:NVIC
NVIC(Nested Vectored Interrupt Controller),即"嵌套向量中断控制器",是Cortex-M内核的"中断大管家"。它强大且方便,统一管理着芯片的所有中断。
-
中断优先级 :NVIC为每个中断源分配优先级。GD32的中断优先级分为抢占优先级 和子优先级(响应优先级)。
- 抢占优先级:高抢占优先级的中断可以打断低抢占优先级的中断正在执行的服务函数。
- 子优先级:当多个相同抢占优先级的中断同时待处理时,子优先级高的先执行。
- 重要规则 :优先级数值越小,优先级越高。这一点和一些操作系统(如FreeRTOS)的优先级定义相反。
-
优先级分组 :抢占优先级和子优先级的位数可通过优先级分组设置。注意:一个程序中通常只设置一种优先级分组方式。
📍 中断向量表
中断向量表可以看作一个函数指针数组,每个元素对应一个特定中断的服务函数入口地址。当特定中断发生时,CPU会自动查找这个表并跳转到对应的函数执行。
🔌 外部中断EXTI
EXTI(External Interrupt/Event Controller)是GD32处理外部中断的重要模块。
-
EXTI线与GPIO的映射 :EXTI有20个独立的边沿检测电路。GPIO引脚号相同的引脚共享一根EXTI线(如PA0、PB0、PC0都共享EXTI0线),但同一时间只能配置一个引脚连接到对应的EXTI线。
-
中断服务函数(ISR):
- EXTI0 - EXTI4:每条线有自己独立的中断服务函数(如
EXTI0_IRQHandler
)。 - EXTI5 - EXTI9:这5条线共享一个中断服务函数
EXTI9_5_IRQHandler
。 - EXTI10 - EXTI15:这6条线共享一个中断服务函数
EXTI15_10_IRQHandler
。 - 对于共享中断服务函数的情况,需要在函数内部检查具体是哪条线产生了中断。
- EXTI0 - EXTI4:每条线有自己独立的中断服务函数(如
⚙️ 配置EXTI的步骤
配置外部中断通常遵循以下步骤:
-
使能时钟:使能所用GPIO端口的时钟和AFIO(复用功能)时钟。
crcu_periph_clock_enable(RCU_GPIOA); // 使能GPIOA时钟 rcu_periph_clock_enable(RCU_AF); // 使能AFIO时钟
-
配置GPIO为输入模式:将GPIO引脚初始化为输入模式,并根据需要设置上拉或下拉电阻。
cgpio_init(GPIOA, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, GPIO_PIN_0); // 上拉输入
-
映射GPIO引脚到EXTI线:将具体的GPIO引脚连接到对应的EXTI线。
cgpio_exti_source_select(GPIO_PORT_SOURCE_GPIOA, GPIO_PIN_SOURCE_0); // 连接PA0到EXTI0
-
初始化EXTI线:设置EXTI线的模式(中断或事件)和触发方式(上升沿、下降沿或双边沿)。
cexti_init(EXTI_0, EXTI_INTERRUPT, EXTI_TRIG_RISING); // 上升沿触发中断
-
配置NVIC:设置EXTI中断的优先级并使能该中断通道。
cnvic_irq_enable(EXTI0_IRQn, 2, 0); // 使能EXTI0中断,抢占优先级2,子优先级0
📝 编写中断服务函数(ISR)
在中断服务函数中,必须检查并清除相应的中断标志位,否则会导致中断持续触发。
独立中断线(如EXTI0)的服务函数示例:
c
void EXTI0_IRQHandler(void) {
if (exti_interrupt_flag_get(EXTI_0) != RESET) { // 检查标志位
// 用户处理逻辑
led_toggle(); // 例如切换LED状态
exti_interrupt_flag_clear(EXTI_0); // 清除中断标志位
}
}
共享中断线(如EXTI5-9)的服务函数示例:
c
void EXTI9_5_IRQHandler(void) {
if (exti_interrupt_flag_get(EXTI_5) != RESET) {
// 处理EXTI5的中断
exti_interrupt_flag_clear(EXTI_5);
}
if (exti_interrupt_flag_get(EXTI_6) != RESET) {
// 处理EXTI6的中断
exti_interrupt_flag_clear(EXTI_6);
}
// ... 检查EXTI7, EXTI8, EXTI9
}
💡 重要注意事项
-
中断效率 :中断服务函数(ISR)应尽可能简短,只执行最紧急的任务(如设置标志位、清除中断等)。耗时的操作应放到主循环中处理。
-
清除中断标志 :务必在退出ISR前清除对应的中断标志位,否则会导致中断不断重复触发。
-
SysTick中断优先级 :SysTick定时器中断(通常用于提供系统时基)的优先级一般设置较高。注意 :如果用户中断的抢占优先级与SysTick相同,并在其ISR中调用了类似
HAL_Delay
的函数(该函数依赖SysTick中断),可能会导致死机,因为SysTick中断无法得到及时响应。 -
硬件消抖:机械开关(如按键)在闭合或断开时会产生抖动,可能导致多次触发中断。常用的消抖方法包括:
- 硬件消抖:在开关两端并联一个小电容(如0.1μF)。
- 软件消抖:在中断服务函数中延时一段时间(如10-20ms)再判断引脚电平状态。
🧪 实践实验
实验目标:按键(接PA0,EXTI0)按下时,LED(接PB4)状态翻转。
主要代码思路:
c
#include "gd32f10x.h"
#include "systick.h" // 假设有延时函数
uint8_t int_flag = 0; // 中断标志
void EXTI0_IRQHandler(void) {
if(exti_interrupt_flag_get(EXTI_0) != RESET) {
exti_interrupt_flag_clear(EXTI_0);
int_flag = 0x55; // 设置标志位,主循环中处理
}
}
int main(void) {
// 初始化系统时钟和SysTick
// ...
// 使能时钟
rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_GPIOB);
rcu_periph_clock_enable(RCU_AF);
// 配置PB4为输出(LED)
gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_4);
// 配置PA0为上拉输入(按键)
gpio_init(GPIOA, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, GPIO_PIN_0);
// 连接PA0到EXTI0线
gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOA, GPIO_PIN_SOURCE_0);
// 配置EXTI0为下降沿触发中断
exti_init(EXTI_0, EXTI_INTERRUPT, EXTI_TRIG_FALLING);
// 配置NVIC,使能EXTI0中断
nvic_irq_enable(EXTI0_IRQn, 2, 0);
while(1) {
if(int_flag == 0x55) {
int_flag = 0; // 清除标志
// 执行耗时操作,例如翻转LED
gpio_bit_write(GPIOB, GPIO_PIN_4, (bit_status)(1 - gpio_input_bit_get(GPIOB, GPIO_PIN_4)));
}
// 主循环其他任务
}
}
注意:实际代码需根据所用GD32型号和库版本调整。
🤔 总结
理解GD32的中断,关键是掌握NVIC如何管理优先级、EXTI如何检测外部信号、以及如何高效安全地编写ISR。切记:中断服务函数要"快进快出",清除标志位,并合理分配优先级。