中断系统与外部中断EXTI
1、中断的概念
中断:在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行。它基于ARM Cortex-M内核的NVIC(嵌套向量中断控制器),支持复杂的中断嵌套和灵活的优先级管理。
中断优先级:当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源。
中断嵌套:当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回。
中断的作用:提高系统运行的实时性。
2、中断的执行流程![]()
3、NVIC中断优先级分组
GD32F303支持配置抢占优先级和子优先级,各中断都有自己的优先级IP寄存器(CUP内部,共240个),4位5组优先级,只使用了优先级寄存器(IP)的高四位(4 ~ 7位),由两部分构成,分别对应高位的抢占优先级和低位的子优先级。
Bit7 | Bit6 | Bit5 | Bit4 | Bit3 | Bit2 | Bit1 | Bit0 |
使用 | 未使用 | ||||||
两部分各占几位,统一由CPU里的AIRCR寄存器来设置:
优先级分组 | 抢占优先级 | 子优先级 | 高四位描述 |
0(7) | 0级 | 0 ~ 15级 | 0bit用于抢占优先级 |
4bit用于子优先级 | |||
1(6) | 0 ~ 1级 | 0 ~ 7级 | 1bit用于抢占优先级 |
3bit用于子优先级 | |||
2(5) | 0 ~ 3级 | 0 ~ 3级 | 2bit用于抢占优先级 |
2bit用于子优先级 | |||
3(4) | 0 ~ 7级 | 0 ~ 1级 | 3bit用于抢占优先级 |
1bit用于子优先级 | |||
4(3) | 0 ~ 15级 | 0级 | 4bit用于抢占优先级 |
0bit用于子优先级 |
寄存器名称 | 寄存器描述 |
ISER | 中断使能寄存器,每一位对应一个中断 |
ICER | 中断禁能寄存器,每一位对应一个中断 |
ISPR | 中断挂起寄存器,每一位对应一个中断 |
ICPR | 中断清除寄存器,每一位对应一个中断 |
IABR | 中断活动状态寄存器 |
IP | 中断优先级寄存器(8位宽),240个 |
STIR | 软触发中断寄存器 |
CPUID | CPUID寄存器 |
ICSR | 中断控制及状态寄存器 |
VTOR | 向量表偏移量寄存器 |
AIRCR | 应用程序中断及复位控制寄存器 |
... | ... |
CPACR | 协处理器访问控制寄存器 |
NVIC硬件结构:
抢占优先级高的可以中断嵌套,子优先级高的可以优先排队,抢占优先级和子优先级均相同的按中断号排队。数值越小,优先级越高。
设计抢占优先级目的是,当前中断服务函数还未执行完,又产生了新的中断,需要判断是否暂停当前中断去执行新的中断服务函数。
设计子优先级目的是,①只有在两个中断同时发生,抢占优先级相同时才起作用,数值越小,优先执行。②如果两个中断的抢占优先级相同,子优先级高的中断不能打断子优先级低的中断。
编号是硬件定义好的,不可改变。
4、EXTI外部中断
①EXTI简介
EXTI可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序。EXTI提供3种触发类型:上升沿触发,下降沿触发和任意沿触发。所有GPIO口都可以,但相同的Pin不能同时触发中断。
②EXTI硬件结构
③EXTI触发源
④硬件触发
硬件触发被用来检测外部或内部信号的电压变化。
软件需要按如下步骤配置来使用这项功能:
1. 根据应用需要配置 AFIO模块中的EXTI 触发源;
2. 配置EXTI_RTEN寄存器和EXTI_FTEN寄存器以使能相应引脚的上升沿或下降沿检测(软件应当同时配置引脚对应的 RTENx和FTENx位以检测该引脚上升沿和下降沿的变
化);
3. 通过配置引脚对应的 EXTI_INTEN 或 EXTI_EVEN位,使能中断或事件;
4. EXTI 开始检测被配置的引脚上的电平变化,当这些引脚上期望的变化被检测到时,使能
的中断或事件将被触发。如果为中断触发,则对应的PD位将立刻被置1;如果为事件触发,则对应的 PD位不被置1。软件需要响应该中断或事件并清除相应PDx位。
⑤软件触发
按照如下步骤软件也可以触发 EXTI中断或事件:
1. 配置对应的 EXTI_INTEN 或 EXTI_EVEN位使能中断或事件;
配置EXTI_SWIEV寄存器的对应SWIEVx位,使能的中断或事件将被立即触发。如果为中断触
发,则对应的PD位将立刻被置1;如果为事件触发,则对应的PD位不被置1。软件需要响应该
中断或事件并清除相应PDx位。
⑥代码编写
1.初始化GPIO:
①使能GPIO时钟;
②输入模式;
2.初始化EXTI:
①使能EXTI时钟;
②配置I/O连接到EXTI线;
③配置上升/下降沿;
④清除标志;
⑤使能中断。
#include <stdint.h> #include "gd32f30x.h" #include "led_driver.h" #include "key_exti.h" static void GpioInit(void) { /*使能GPIO的时钟*/ rcu_periph_clock_enable(RCU_GPIOA); rcu_periph_clock_enable(RCU_GPIOG); /*配置按键的IO为浮空输入模式*/ gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_2MHZ, GPIO_PIN_0); //KEY1 gpio_init(GPIOG, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_2MHZ, GPIO_PIN_13); //KEY2 gpio_init(GPIOG, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_2MHZ, GPIO_PIN_14); //KEY3 gpio_init(GPIOG, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_2MHZ, GPIO_PIN_15); //KEY4 } static void ExtiInit(void) { /* 使能EXTI时钟 */ rcu_periph_clock_enable(RCU_AF); /* 配置EXTI0 */ /* 配置I/O连接到EXTI0线 */ gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOA, GPIO_PIN_SOURCE_0); /* 配置上升/下降沿 */ exti_init(EXTI_0, EXTI_INTERRUPT, EXTI_TRIG_FALLING); /* 清除标志 */ exti_interrupt_flag_clear(EXTI_0); /* 使能中断 */ nvic_irq_enable(EXTI0_IRQn, 1, 1); /* 配置EXTI13 - EXTI15 */ /* 配置I/O连接到EXTI13 - EXTI15线 */ gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOG, GPIO_PIN_SOURCE_13); gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOG, GPIO_PIN_SOURCE_14); gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOG, GPIO_PIN_SOURCE_15); /* 配置上升/下降沿 */ exti_init(EXTI_13, EXTI_INTERRUPT, EXTI_TRIG_FALLING); exti_init(EXTI_14, EXTI_INTERRUPT, EXTI_TRIG_FALLING); exti_init(EXTI_15, EXTI_INTERRUPT, EXTI_TRIG_FALLING); /* 清除标志 */ exti_interrupt_flag_clear(EXTI_13); exti_interrupt_flag_clear(EXTI_14); exti_interrupt_flag_clear(EXTI_15); /* 使能中断 */ nvic_irq_enable(EXTI10_15_IRQn, 1, 1); } void KeyExtiInit(void) { GpioInit(); ExtiInit(); } void EXTI0_IRQHandler(void) { /* 清除标志 */ exti_interrupt_flag_clear(EXTI_0); TurnOnLed(LED1); } void EXTI10_15_IRQHandler(void) { if(SET == exti_interrupt_flag_get(EXTI_13)) { /* 清除标志 */ exti_interrupt_flag_clear(EXTI_13); TurnOnLed(LED2); } if(SET == exti_interrupt_flag_get(EXTI_14)) { /* 清除标志 */ exti_interrupt_flag_clear(EXTI_14); TurnOnLed(LED3); } if(SET == exti_interrupt_flag_get(EXTI_15)) { /* 清除标志 */ exti_interrupt_flag_clear(EXTI_15); TurnOffLed(LED1); TurnOffLed(LED2); TurnOffLed(LED3); } } #ifndef _KEY_EXTI_H_ #define _KEY_EXTI_H_ void KeyExtiInit(void); #endif /* __KEY_EXTI_H__ */5、启动流程
按键如何触发进入到中断中?
单片机上电以后,首先从Reset_Handler开始执行,Reset_Handler也是一种中断,属于内核的一种异常。
CPU执行时,会用到一些内部寄存器,特别重要的一个寄存器就是R15,又叫PC寄存器,在CPU执行过程中,该32位寄存器始终保存着下一条要执行机器码的存储地址。
使用查看编译生成的.map文件,该文件内有很多编译信息,在编译信息中,可以找到Reset_Handler的起始地址。
该部分代码,作用是分配中断服务函数的起始地址,DCD是告诉编译器,编译时预留四字节的固定空间,用于存放中断函数的起始地址。
每一个中断,都有一个唯一中断号。
6、bin文件
bin文件是最终烧录进单片机的程序。
①生成bin文件
设置输出文件的名字为ARM。
设置编译后生成bin文件
fromelf --bin --output .\Objects\ARM.bin .\Objects\ARM.axf
·生成的文件可以在Objects文件夹中
②解析bin文件
找到JLINK安装路径,打开J-Flash V7.58a
打开后点击叉号
生成的bin文件拖进J-Flash V7.58a中,起始地址设置为0x08000000
设置4字节对齐
可以看到Reset_Handler的起始地址
0x0800015f是B.对应的起始地址
结合.map文件,同样可以找到其他中断函数的入口地址,比如EXTI0_IRQHandler
中断向量表可查看数据手册160页,有详细的向量编号、向量地址说明
7、中断常见BUG
①使能了中断,未实现中断服务函数会怎么样?
所有的中断,如果没有实现函数代码,都会在发生中断时进入执行到.s文件的335行 B .代码,其实这个代码也是中断服务函数,上面的就是函数的名字,所有中断函数代码都是B .,这些函数是弱函数,如果我们自己没有实现,就会使用自带的这些函数。
②中断标志不清除会怎么样?
如果不清除中断标志位,就会一直触发中断,执行这个中断服务函数。
③中断抢占
void EXTI0_IRQHandler(void) { /* 清除标志 */ exti_interrupt_flag_clear(EXTI_0); ToggleLed(LED1); //while (1); // 如果此时KEY1又再次按下,是不会再次进入这个函数的,因为自己不能抢占自己,优先级是一样的 DelayNms(5000); // 如果在此期间KEY1又再次按下,会等延时函数执行完,会再次进入到本函数执行第二次对应的硬件中断 }