【单片机/嵌入式】【梁山派】学习日志10:外部中断

推荐学习文章:【嵌入式系统中的中断机制https://zhuanlan.zhihu.com/p/196452953

中断

一、中断基础知识

1.1什么是中断

灵活的使用系统中断可以大大提高单片机对随机事件的实时处理能力,进而提高单片机的工作效率。那什么是中断呢?这里举一个例子,比如我们正在敲代码,突然有一个电话打过来,这时我们停止敲代码转而去接电话,然后在电话聊完事情之后继续敲代码。这里面的电话就相当于一个中断,打断我们当前做的事情,接电话聊事情就相当于中断需要去执行的事情,也就是中断服务程序。

用户手册155页:中断功能描述

Arm® Cortex®-M4处理器和嵌套式矢量型中断控制器(NVIC)在处理(Handler)模式下对所有异常进行优先级区分以及处理。当异常发生时,系统自动将当前处理器工作状态压栈,在执行完中断服务子程序(ISR后自动将其出栈取向量是和当前工作态压栈并行进行的,从而提高了中断入口效率。处理器支持咬尾中断,可实现背靠背中断,大幅削减了反复切换工作态所带来的开销。表中的 NVIC 异常类型列出了所有的异常类型。

反映在软件实现就是在startup_gd32f450_470.s启动文件中定义的中断向量表,具体结构如下:

中断号为0的位置为空,设计上就用来存储堆栈指针。

在芯片在上电的过程中就是执行复位机制 根据SCB->VTOR查询向量表,找到Reset_Handler入口,并加载__initial_sp到堆栈指针R13,后续就可以正常的工作了。

在上述结构中,系统中断是在内核定义时确定的外部中断在芯片设计时被确定,将中断编号指定外设的中断触发信号绑定,就构建了完整的中断向量表。

用户手册156页:中断向量表

用户手册159页:中断/事件EXTI结构框图

 

1.2外部中断

EXTI(中断/事件控制器)包括23个相互独立的边沿检测电路并且能够向处理器内核产生中断请求或唤醒事件。EXTI有三种触发类型:上升沿触发下降沿触发任意沿触发。EXTI中的每一个边沿检测电路都可以独立配置和屏蔽。每一个中断都有4位中断优先级配置位,可提供16个中断优先等级

用户手册160页:中断/事件EXIT触发源

 

1.3中断触发源

EXTI触发源包括来自I/O管脚的16根线以及来自内部模块的7根线。(包括LVD、RTC闹钟、USB唤醒、以太网唤醒、RTC侵入和时间戳、RTC唤醒)。通过配置SYSCFG_EXTISSx寄存器,所有的GPIO管脚都可以被选作EXTI的触发源。

1.4中断事件

除了中断,EXTI还可以向处理器提供事件信号。芯片内部有一个唤醒中断控制器(WIC),用户可以让处理器和NVIC进入功耗极低的省电模式,由WIC来识别中断和事件以及判断优先级。当某些预期的事件发生时,EXTI能唤醒处理器及整个系统

1.5中断优先级

每一个中断都有4位可配置的优先级位,可以通过分组设置抢占优先级响应优先级的位数。

所有可编程的中断都需要指定抢占优先级和响应优先级抢占优先级决定是否可以产生中断嵌套响应优先级决定中断响应顺序若两种优先级一样则看中断在中断向量表中的位置,位置越靠前越先响应。抢占优先级高(值小)的中断可以中断抢占优先级低(值大)的中断处理函数。当两个中断的抢占优先级相同时,即这两个中断没有嵌套关系,当一个中断到来后,若此时CPU正在处理另一个中断,则后到来的中断就要等前一个中断处理函数处理完毕后才能被处理,当两个中断同时到达,则中断控制器会根据它们的响应优先级决定先处理哪个。

通常中断优先级分组只设置一次,它针对的是系统中所有的中断。后续设置某个中断的中断优先级时,只需要设置在这个分组内的抢占优先级和响应优先级。

二、外部中断按键点灯

2.1配置流程

一般使用GPIO的外部中断功能,都需要有以下几个步骤。

l  开启时钟(包括GPIO时钟和系统配置时钟)

l  配置GPIO的模式

l  使能NVIC中断并配置优先级【MISC

l  配置GPIO中断【SYSCFG】【EXTI

l  使能中断和清除中断标志位

l  编写中断服务函数

GD32单片机的任意引脚都可以配置为外部中断触发,开发板的按键使用的是KEY_UP,接到了单片机的PA0引脚上,因此我们可以使能PA0的外部中断功能。

2.2开启时钟

开启时钟分为两步,第一步就是先开启GPIO端口的时钟,第二步就是开启系统配置时钟,系统配置寄存器可以将外部中断线连接到我们需要的引脚

用户手册44页:系统配置寄存器

 

用户手册的第46页:系统配置寄存器的EXTI源选择寄存器

 

固件库手册625页:系统配置寄存器库函数

固件库手册628页:配置EXTI源选择

固件库手册341页:中断/事件EXTI寄存器、中断/事件EXTI库函数

 

 

按键接到了单片机的PA0引脚上,首先要使能GPIOA端口的时钟。还是用rcu_periph_clock_enable函数,只需要将GPIOA时钟当做参数传入。系统配置寄存器时钟为RCU_SYSCFG,直接作为参数传入即可。

宏定义:

#define BSP_KEY_RCU                          RCU_GPIOA

使能时钟可以写为:

/* 开启时钟 */

rcu_periph_clock_enable(BSP_KEY_RCU);rcu_periph_clock_enable(RCU_SYSCFG);  // 系统配置时钟

2.3配置GPIO模式

按键配置方式没有变化,使能按键的引脚为输入模式和下拉模式,编写代码为:

/* 配置为输入模式 下拉模式 */

gpio_mode_set(BSP_KEY_PORT, GPIO_MODE_INPUT, GPIO_PUPD_PULLDOWN, BSP_KEY_PIN); 

// 按键默认状态是低电平,配置为下拉。

2.4使能NVIC中断并配置优先级

上一章介绍了每一个中断都需要配置优先级。配置优先级之前需要先对NVIC进行分组,关于NVIC的定义在gd32f4xx_misc.h中:

MISC 是对嵌套向量中断控制器(NVIC)系统定时器(SysTick)操作的软件包。

固件库手册448页:中断控制NVIC寄存器

参考 core_cm4.h 文件中定义的结构体类型 NVIC_Type和SCB_Type

   

 

参考 core_cm4.h 文件中定义的结构体类型 SysTick _Type

 

函数void nvic_priority_group_set(uint32_t nvic_prigroup);可设置中断优先级分组,有一个参数,关于参数选项如图所示。固件库手册453页

 

 

关于优先级的选择根据系统功能的不同配置不同,选择一个合适的即可。这里就选择中间的作为讲解。

设置优先级分组代码为:

  nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); 

// 设置中断优先级组  2位用于抢占优先级,2位用于响应优先级

中断分组设置完成还需要配置中断的抢占优先级响应优先级

void nvic_irq_enable(uint8_t nvic_irq, uint8_t nvic_irq_pre_priority, uint8_t nvic_irq_sub_priority);

这个函数配置中断的优先级。有三个参数,第一个参数就是要配置的中断类型,第二个参数是抢占优先级,第三个参数是响应优先级。固件库手册454页

 

 

关于中断类型在gd32f4xx.h中有定义,如图所示(部分截取)。

固件库手册450页

 

需要注意的是,16个IO中断的中断类型为0-4引脚配置为EXTIx_IRQn(x=可取1-4)5-9引脚配置为 EXTI5_9_IRQn10-15引脚配置为EXTI10_15_IRQn。原因如下:

根据枚举类型 IRQn_Type可知(用户手册450-451页

根据EXTI触发源可知(固件库手册160页

 

所以,枚举类型IRQn_Type是和触发源的EXTI线编号对应的

配置PA0引脚中断类型宏定义如下:

#define BSP_KEY_EXTI_IRQn       EXTI0_IRQn  // 中断类型

上一步中断分组设置了2位抢占优先级,2位响应优先级,那对应的抢占优先级等级为0-3,响应优先级等级为0-3。按键检测事件属于不是很紧急的任务,我们配置为最低优先级。配置代码为:

/* 使能NVIC中断 中断分组为2位抢占优先级,2位响应优先级

*/nvic_irq_enable(BSP_KEY_EXTI_IRQn,3U,3U);  // 抢占、响应优先级3

2.5配置GPIO中断

在配置中断优先级之后,需要将中断线和gpio进行连接。在gd32f4xx_syscfg.h中有定义

void syscfg_exti_line_config(uint8_t exti_port, uint8_t exti_pin);

这个函数配置GPIO作为中断使用(将GPIO作为中断源),即中断与GPIO的连接。函数有两个参数,第一个参数就是对应的中断引脚资源端口,第二个参数就是对应的中断引脚。

固件库手册628页:配置EXTI源选择

 

关于中断资源引脚定义在gd32f4xx_syscfg.h中可以查看,如图所示。

中断引脚资源定义如下:

#define BSP_KEY_EXTI_PORT_SOURCE   EXTI_SOURCE_GPIOA

#define BSP_KEY_EXTI_PIN_SOURCE    EXTI_SOURCE_PIN0

配置中断到GPIO可写为:

/* 连接中断线到GPIO */

syscfg_exti_line_config(BSP_KEY_EXTI_PORT_SOURCE,BSP_KEY_EXTI_PIN_SOURCE);

配置好中断和GPIO连接之后,还需要对中断进行初始化,配置一些参数。在gd32f4xx_exti.h中有

void exti_init(exti_line_enum linex, exti_mode_enum mode, exti_trig_type_enum trig_type);

这个函数初始化中断配置。有三个参数,第一个参数是中断线,第二个参数是中断模式,第三个参数是触发类型。固件库手册342页

 

 

 

关于中断线一共有23个,在gd32f4xx_exti.h中有定义,如图所示。

关于每个中断线对应的功能在固件库手册的第160页,如所示。

 

PA0的中断线就是EXTI_0。关于中断线宏定义如下:

#define BSP_KEY_EXTI_LINE          EXTI_0 // 中断线

关于中断模式的可选选项如图所示。

用按键来触发选择中断模式即可。

关于触发类型的可选选项如图所示。

开发板的按键默认接到了地,也就是低电平,当按键按下的时候为高电平,这样一个从低到高变化的过程就是一个上升沿,即选择上升沿触发。当按键松开的时候,将会从高电平变为低电平,变化的过程是一个下降沿。如果要同时检测按键按下和松开就需要两者都配置,选择上升沿和下降沿均触发。

最终初始化中断线配置为中断模式,上升沿和下降沿均触发。

/* 初始化中断线 */

exti_init(BSP_KEY_EXTI_LINE,EXTI_INTERRUPT,EXTI_TRIG_BOTH);

2.6使能中断和清除中断标志位

配置好中断之后,就可以开启中断了。

void exti_interrupt_enable(exti_line_enum linex);

固件库手册343页

 

 

这个函数使能中断,有一个参数就是中断线。

配置如下:

/* 使能中断 */

exti_interrupt_enable(BSP_KEY_EXTI_LINE);

在使用中断的时候先清一下中断标志位,确保中断是有效的。

void exti_interrupt_flag_clear(exti_line_enum linex);

固件库手册349页

 

这个函数清中断标志位,有一个参数就是中断线。

配置如下:

/* 清除中断标志位 */

exti_interrupt_flag_clear(BSP_KEY_EXTI_LINE);

2.7编写中断服务函数

使能中断之后,如果有中断触发,就会跳转到中断处理函数里面执行。需要编写中断处理函数。首先是中断函数名, 这个是固定的,在startup_gd32f450_470.s启动文件中有定义,如图所示。(部分截取)

 

在中断处理函数里需要检测中断标志位是否被置位。

FlagStatus exti_interrupt_flag_get(exti_line_enum linex);

固件库手册348页

 

 

这个函数是获取中断标志位。只有一个参数就是中断线。有一个返回值FlagStatus,返回值的状态为SET和RESET。需要注意的是每次中断执行完毕之后都需要清除一下中断标志位,等待下一次中断发生

宏定义中断服务函数名:

#define BSP_KEY_EXTI_IRQHandler    EXTI0_IRQHandler  // 中断服务函数名

中断服务函数编写代码如下:

void BSP_KEY_EXTI_IRQHandler(void){ 

    if(exti_interrupt_flag_get(BSP_KEY_EXTI_LINE) == SET)  // 中断标志位为1,按键按下           

    {                   

        if(gpio_input_bit_get(BSP_KEY_PORT,BSP_KEY_PIN) == SET) // 按键按下                   

        {                       

            /* 执行功能 */                       

            printf("key press!\r\n");                       

            gpio_bit_toggle(BSP_LED2_PORT,BSP_LED2_PIN); // led电平状态翻转                   

        }else{                                                                                      

            // 按键释放                        

            printf("key release!\r\n");                   

        }   

        exti_interrupt_flag_clear(BSP_KEY_EXTI_LINE); // 清中断标志位           

    }

}

2.8实验

按键按下后,触发中断;按下时,串口传回字符串press,小灯亮起;抬起时,串口传回字符串release,小灯熄灭。

【exti.h】

【exti.c】

【main.c】

解决了一个问题

【Keil5警告】Duplicate input file ...【文件重复包含】https://www.cnblogs.com/steven913/p/16871079.html

posted @ 2022-11-08 21:10  U羊U  阅读(523)  评论(2编辑  收藏  举报