十四、外部中断按键点灯
1.1.配置流程
一般使用 GPIO 的外部中断功能,都需要有以下几个步骤。
- 开启时钟(包括 GPIO 时钟和系统配置时钟)
- 配置 GPIO 的模式
- 使能 NVIC 中断并配置优先级
- 配置 GPIO 中断
- 使能中断和清除中断标志位
- 编写中断服务函数
GD32 单片机的任意引脚都可以配置为外部中断触发,开发板的按键使用的是 KEY_UP,接到了单片机的 PA0 引脚上,因此我们可以使能 PA0 的外部中断功能。
1.1.1.开启时钟
开启时钟分为两步,第一步就是先开启 GPIO 端口的时钟,第二步就是开启系统配置时钟,系统配置寄存器可以将外部中断线连接到我们需要的引脚。关于系统配置寄存器的 EXTI 源选择寄存器可以查看用户手册的第 45 页。
按键接到了单片机的 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
1.1.2.配置 GPIO 模式
按键配置方式没有变化,使能按键的引脚为输入模式和下拉模式,编写代码为:
/* 配置为输入模式 下拉模式 */
gpio_mode_set(BSP_KEY_PORT, GPIO_MODE_INPUT, GPIO_PUPD_PULLDOWN, BSP_KEY_PIN);
// 按键默认状态是低电平,配置为下拉
2
3
1.1.3.使能 NVIC 中断并配置优先级
上一章介绍了每一个中断都需要配置优先级。配置优先级之前需要先对 NVIC 进行分组,关于 NVIC 的定义在 gd32f4xx_misc.h 中,
void nvic_priority_group_set(uint32_t nvic_prigroup);
这个函数设置中断优先级分组,有一个参数,关于参数选项如图 1-3-1 所示。
关于优先级的选择根据系统功能的不同配置不同,选择一个合适的即可。这里就选择中间的作为讲解。
设置优先级分组代码为:
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2);
// 设置中断优先级组 2位用于抢占优先级,2位用于响应优先级
2
中断分组设置完成还需要配置中断的抢占优先级和响应优先级。
void nvic_irq_enable(uint8_t nvic_irq, uint8_t nvic_irq_pre_priority, uint8_t nvic_irq_sub_priority);
这个函数配置中断的优先级。有三个参数,第一个参数就是要配置的中断类型,第二个参数是抢占优先级,第三个参数是响应优先级。
关于中断类型在 gd32f4xx.h 中有定义,如图 1-3-2 所示(部分截取)。
这里需要注意一下,关于 16 个 IO 中断的中断类型为 0-4 引脚配置为 EXTIx_IRQn(x=可取 1-4),
5-9 引脚配置为 EXTI5_9_IRQn,10-15 引脚配置为 EXTI10_15_IRQn 。
配置 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,子优先级3
2
1.1.4.配置 GPIO 中断
在配置中断优先级之后,需要将中断线和 gpio 进行连接。在 gd32f4xx_syscfg.h 中有定义
void syscfg_exti_line_config(uint8_t exti_port, uint8_t exti_pin);
这个函数配置 GPIO 作为中断使用,有两个参数,第一个参数就是对应的中断引脚资源端口,第二个参数就是对应的中断引脚。关于中断资源引脚定义在 gd32f4xx_syscfg.h 中可以查看,如图 1-4-1 所示。
中断引脚资源定义如下:
#define BSP_KEY_EXTI_PORT_SOURCE EXTI_SOURCE_GPIOA
#define BSP_KEY_EXTI_PIN_SOURCE EXTI_SOURCE_PIN0
2
配置中断到 GPIO 可写为:
/* 连接中断线到GPIO */
syscfg_exti_line_config(BSP_KEY_EXTI_PORT_SOURCE,BSP_KEY_EXTI_PIN_SOURCE);
2
配置好中断和 GPIO 连接之后,还需要对中断进行初始化,配置一些参数。在 gd32f4xx_exti.h 中有
void exti_init(exti_line_enum linex, exti_mode_enum mode, exti_trig_type_enum trig_type);
这个函数初始化中断配置。有三个参数,第一个参数是中断线,第二个参数是中断模式,第三个参数是触发类型。
关于中断线一共有 23 个,在 gd32f4xx_exti.h 中有定义,如图 1-4-2 所示。
关于每个中断线对应的功能在用户手册的第 160 页,如图 1-4-3 所示。
PA0 的中断线就是 EXTI_0。关于中断线宏定义如下:
#define BSP_KEY_EXTI_LINE EXTI_0 // 中断线
关于中断模式的可选选项如图 1-4-4 所示。
用按键来触发选择中断模式即可。
关于触发类型的可选选项如图 1-4-5 所示。
开发板的按键引脚默认接到了地,也就是低电平,当按键按下的时候为高电平,这样一个从低到高变化的过程就是一个上升沿,即选择上升沿触发。当按键松开的时候,将会从高电平变为低电平,变化的过程是一个下降沿。如果要同时检测按键按下和松开就需要两者都配置,选择上升沿和下降沿均触发。
最终初始化中断线配置为中断模式,上升沿和下降沿均触发。
/* 初始化中断线 */
exti_init(BSP_KEY_EXTI_LINE,EXTI_INTERRUPT,EXTI_TRIG_BOTH);
2
1.1.5.使能中断和清除中断标志位
配置好中断之后,就可以开启中断了。
void exti_interrupt_enable(exti_line_enum linex);
这个函数使能中断,有一个参数就是中断线。
配置如下:
/* 使能中断 */
exti_interrupt_enable(BSP_KEY_EXTI_LINE);
2
在使用中断的时候先清一下中断标志位,确保中断是有效的。
void exti_interrupt_flag_clear(exti_line_enum linex);
这个函数是清除中断标志位,有一个参数就是中断线。
配置如下:
/* 清除中断标志位 */
exti_interrupt_flag_clear(BSP_KEY_EXTI_LINE);
2
1.1.6.编写中断服务函数
使能中断之后,如果有中断触发,就会跳转到中断处理函数里面执行。需要编写中断处理函数。首先是中断函数名, 这个是固定的,在 startup_gd32f450_470.s 启动文件中有定义,如图 1-6-1 所示。(部分截取)
在中断处理函数里需要检测中断标志位是否被置位。
FlagStatus exti_interrupt_flag_get(exti_line_enum linex);
这个函数是获取中断标志位。只有一个参数就是中断线。有一个返回值 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
3
4
5
6
7
8
9
10
11
12
13
14
15
1.2.实验现象
关于这一章节的代码,在资源包/04 软件资料/代码例程/里面的 007 外部中断按键点灯。
烧写我们的代码之后,按下一次按键,开启 LED2,再按下一次按键,关闭 LED2,以此类推。每次按下按键都会打印 key press!,松开按键都会打印 key release!。如图 2-1-1 所示。