本文章基于兆易创新GD32 MCU所提供的2.2.4版本库函数开发
向上代码兼容GD32F450ZGT6中使用
后续项目主要在下面该专栏中发布:
https://blog.youkuaiyun.com/qq_62316532/category_12608431.html?spm=1001.2014.3001.5482
感兴趣的点个关注收藏一下吧!
电机驱动开发可以跳转:
GD32F103RCT6/GD32F303RCT6-实战项目-无刷电机驱动(1)_gd32f103rct6例程-优快云博客
BMS电源系统开发可以跳转:
暂未放链接
DCDC-双向BUCK-BOOST实战链接:
向上代码兼容GD32F303RCT6中使用
本项目配套开发板:
基于GD32F103RCT6国产GD32平台,以下教程编写基于该开发板
中断介绍
EXTI(中断/事件控制器)包括20个相互独立的边沿检测电路并且能够向处理器内核产生中断 请求或唤醒事件。EXTI有三种触发类型:上升沿触发、下降沿触发和任意沿触发。EXTI中的每 一个边沿检测电路都可以独立配置和屏蔽。
主要特征
Cortex-M3 系统异常;
多达 68 种可屏蔽的外设中断;
4 位中断优先级配置位——16 个中断优先等级;
高效的中断处理;
支持异常抢占和咬尾中断;
将系统从省电模式唤醒;
EXTI 中有多达 20 个相互独立的边沿检测电路;
3 种触发类型:上升沿触发,下降沿触发和任意沿触发;
软件中断或事件触发;
可配置的触发源。
外部中断及事件(EXTI) 框图
EXTI包含多达20个相互独立的边沿检测电路并且可以向处理器产生中断请求或事件唤醒。 EXTI提供3种触发类型:上升沿触发,下降沿触发和任意沿触发。EXTI中每个边沿检测电路都 可以分别予以配置或屏蔽。
EXTI触发源包括来自I/O管脚的16根线以及来自内部模块的4根线。(包括LVD、RTC闹钟、USB 唤醒、以太网唤醒)。通过配置GPIO模块的AFIO_EXTISSx寄存器,所有的GPIO管脚都可以被 选作EXTI的触发源,具体细节请参考GPIO和AFIO章节。
EXTI触发源
EXTI 线编号 | 触发源 |
0 | PA0/PB0/PC0/PD0/PE0/PF0/PG0 |
1 | PA1/PB1/PC1/PD1/PE1/PF1/PG1 |
2 | PA2/PB2/PC2/PD2/PE2/PF2/PG2 |
3 | PA3/PB3/PC3/PD3/PE3/PF3/PG3 |
4 | PA4/PB4/PC4/PD4/PE4/PF4/PG4 |
5 | PA5/PB5/PC5/PD5/PE5/PF5/PG5 |
6 | PA6/PB6/PC6/PD6/PE6/PF6/PG6 |
7 | PA7/PB7/PC7/PD7/PE7/PF7/PG7 |
8 | PA8/PB8/PC8/PD8/PE8/PF8/PG8 |
9 | PA9/PB9/PC9/PD9/PE9/PF9/PG9 |
10 | PA10/PB10/PC10/PD10/PE10/PF10/PG10 |
11 | PA11/PB11/PC11/PD11/PE11/PF11/PG11 |
12 | PA12/PB12/PC12/PD12/PE12/PF12/PG12 |
13 | PA13/PB13/PC13/PD13/PE13/PF13/PG13 |
14 | PA14/PB14/PC14/PD14/PE14/PF14/PG14 |
15 | PA15/PB15/PC15/PD15/PE15/PF15/PG15 |
16 | LVD |
17 | RTC 闹钟 |
18 | USB 唤醒 |
注意:EXTI线19的以太网唤醒只可用于GD32F107xx设备中,故此103设备不写本通道。
EXTI 寄存器
虽然我知道大家对应寄存器这种生涩难懂的东西不是很想接触,但是毕竟是出一个教程,加上很多时候找问题的时候我们都会返回寻找寄存器,所以我这里还是需要讲一下各个寄存器
如果对寄存器不感兴趣的同学,可以跳过本小结!
EXTI基地址:0x4001 0400
中断使能寄存器(EXTI_INTEN)
作用:使能或使能对应GPIO口的中断,可写可读。
地址偏移:0x00
复位值:0x0000 0000
该寄存器只能按字(32 位)访问
事件使能寄存器(EXTI_EVEN)
作用:使能或使能对应端口的事件,可写可读。
地址偏移:0x04
复位值:0x0000 0000 该寄存器只能按字(32 位)访问
上升沿触发使能寄存器(EXTI_RTEN)
作用:使能或使能对应端口的上升沿触发选项,可写可读。
地址偏移:0x08
复位值:0x0000 0000 该寄存器只能按字(32位)访问
下降沿触发使能寄存器(EXTI_FTEN)
作用:使能或使能对应端口的下降沿触发选项,可写可读。
地址偏移:0x0C
复位值:0x0000 0000 该寄存器只能按字(32位)访问
软件中断事件寄存器(EXTI_SWIEV)
作用:激活或禁用对应中断线的软件软件/事件触发选项,可写可读。
地址偏移:0x10
复位值:0x0000 0000 该寄存器只能按字(32位)访问
挂起寄存器(EXTI_PD)
作用:读取对应的中断/事件线的状态,并且支持清除该位,软件可以读此位,也可以通过写’1’清除此位。
EXTI库函数
官方资料:
GD32F10x_Firmware_Library_V2.2.4\Firmware\GD32F10x_standard_peripheral\Include\gd32f10x_misc.h
GD32F10x_Firmware_Library_V2.2.4\Firmware\GD32F10x_standard_peripheral\Include\gd32f10x_gpio.h
以下函数位于:
GD32F10x_Firmware_Library_V2.2.4\Firmware\GD32F10x_standard_peripheral\Include\gd32f10x_misc.h
设置优先级分组
函数声明如下:
void nvic_priority_group_set(uint32_t nvic_prigroup)
参数:
gpio_periph | 参数1 | GPIO端口x(x = A,B,C,D,E,F,G) |
返回 | 无 | 无 |
参数1:
值 | 含义 |
NVIC_PRIGROUP_PRE0_SUB4 | 0位用于抢占优先级,4位用于响应优先级 |
NVIC_PRIGROUP_PRE1_SUB3 | 1位用于抢占优先级,3位用于响应优先级 |
NVIC_PRIGROUP_PRE2_SUB2 | 2位用于抢占优先级,2位用于响应优先级 |
NVIC_PRIGROUP_PRE3_SUB1 | 3位用于抢占优先级,1位用于响应优先级 |
NVIC_PRIGROUP_PRE4_SUB0 | 4位用于抢占优先级,0位用于响应优先级 |
使能NVIC中断
函数声明如下:
void nvic_irq_enable(uint8_t nvic_irq, uint8_t nvic_irq_pre_priority, uint8_t nvic_irq_sub_priority)
参数:
nvic_irq | 参数1 | NVIC中断请求,详见下文 |
nvic_irq_pre_priority | 参数2 | 需要设置的抢占优先级 |
nvic_irq_sub_priority | 参数3 | 需要设置的子优先级 |
返回 | 无 | 无 |
参数1:
名称 | 功能 |
EXTI0_IRQn | EXTI线0中断 |
EXTI1_IRQn | EXTI线1中断 |
EXTI2_IRQn | EXTI线2中断 |
EXTI3_IRQn | EXTI线3中断 |
EXTI4_IRQn | EXTI线4中断 |
EXTI5_9_IRQn | EXTI线[9:5]中断 |
EXTI10_15_IRQn | EXTI线[15:10]中断 |
参数2:
根据前面选择的优先级分组填入主优先级
参数3:
根据前面选择的优先级分组填入子优先级
选择哪个引脚作为EXTI源
函数声明如下:
void gpio_exti_source_select(uint8_t output_port, uint8_t output_pin)
参数:
output_port | 参数1 | EXTI源端口x(x = A,B,C,D,E,F,G) |
output_pin | 参数2 | 源端口引脚x(x=0…15) |
返回 | 无 | 无 |
初始化EXTI线x
函数声明如下:
void exti_init(exti_line_enum linex, exti_mode_enum mode, exti_trig_type_enum trig_type)
参数:
linex | 参数1 | EXTI线x(x=0,1,2…19) |
mode | 参数2 | EXTI模式 |
trig_type | 参数3 | 触发类型 |
返回 | 无 | 无 |
参数1:
EXTI线x(x=0,1,2…19)
参数2:
名称 | 功能 |
EXTI_INTERRUPT | 中断模式 |
EXTI_EVENT | 事件模式 |
参数3:
名称 | 功能 |
EXTI_TRIG_RISING | 上升沿触发 |
EXTI_TRIG_FALLING | 下降沿触发 |
EXTI_TRIG_BOTH | 上升沿和下降沿均触发 |
清除EXTI线x中断标志位
函数声明如下:
void exti_interrupt_flag_clear(exti_line_enum linex)
参数:
linex | 参数1 | EXTI线x(x=0,1,2…19) |
返回 | 无 | 无 |
获取EXTI行x中断挂起标志
函数声明如下:
FlagStatus exti_flag_get(exti_line_enum linex)
FlagStatus exti_interrupt_flag_get(exti_line_enum linex)
参数:
linex | 参数1 | EXTI线x(x=0,1,2…19) |
返回 | retval FlagStatus: | 标志的状态(RESET或SET) |
清除EXTI行x中断挂起标志
函数声明如下:
void exti_flag_clear(exti_line_enum linex)
void exti_interrupt_flag_clear(exti_line_enum linex)
参数:
linex | 参数1 | EXTI线x(x=0,1,2…19) |
返回 | 无 | 无 |
外部中断处理函数
一旦中断发生,其所对应的中断服务函数就会被执行,那么我们就可以在中断服务函数中现实一些控制
为确定中断是否确定发生,我们会在中断服务函数中调用中断标志位状态读取函数读取外设中断标志位并判断标志位状态。
exti_interrupt_flag_get() 函数用来获取 EXTI 的中断标志位状态,如果 EXTI 线有中断发生函数返回“SET”否则返回“RESET”。
而exti_interrupt_flag_get() 函数是通过读取 EXTI_PD 寄存器值来判断 EXTI 线状态的。
void EXTI1_IRQHandler(void)
{
//判定是否是外部中断线1
if (RESET != exti_interrupt_flag_get(EXTI_1))
{
//在此加入自己的控制,一般为置标志等短暂非阻塞操作
//清除中断标记
exti_interrupt_flag_clear(TAMPER_KEY_EXTI_LINE);
}
}
外部中断处理函数名称要与EXTI线相对应,不然就无法在中断发生时,进入到我们编写的处理函数中,具体的表可在 startup_gd32f10x_hd.s 启动文件中查看:
代码编写:
接下来我们开始代码部分的编写
按键输入实验
使用开发板上三个按键采用中断控制三个LED灯亮灭。
按键硬件电路如下:
如果硬件上已外部上拉或下拉,则选择浮空输入模式。
如果硬件外部没有上拉,则选择内部上拉模式。
LED硬件电路如下:
//管脚复用时钟使能
rcu_periph_clock_enable(RCU_AF);
//PB4和PB3管脚默认是NJTRST,要当GPIO,需要重映射
gpio_pin_remap_config(GPIO_SWJ_SWDPENABLE_REMAP, ENABLE)
不要忘记重映射
需要按步骤进行以下操作:
1.使能 GPIO 端口时钟
2.初始化 EXTI
3.配置 NVIC
4.编写中断服务函数
配置 EXTI 信号源的时候需要用到 AFIO 的外部中断控制寄存器 AFIO_EXTISSx,所以用到 EXTI 必须开启 AFIO 时钟
抢占优先级,数字越小,优先级越高
若抢占优先级相同,判断子优先级,同样,数字越小,优先级越高
当中断发生时,对应的中断服务函数就会被执行,我们可以在中断服务函数实现一些控制。
一般为确保中断确实发生,我们会在中断服务函数中调用中断标志位状态读取函数读取外设中断标志位并判断标志位状态。
exti_interrupt_flag_get() 函数用来获取 EXTI 的中断标志位状态,如果 EXTI 线有中断发生函数返回“SET”否则返回“RESET”。
这里因为中断5、6、7都是属于5-9中断源中的,我们要进行区分,所以我定义了一个state_choose_flag变量去对当前中断进行区分,同时
在最后清楚这个标志位,读者可以自行去编写这部分处理代码,我的源代码会贴在例程中
按键初始化:
//GPIOB时钟使能
rcu_periph_clock_enable(RCU_GPIOB);
//PB5配置成上拉输入
gpio_init(GPIOB, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, GPIO_PIN_5);
//PB6配置成上拉输入
gpio_init(GPIOB, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, GPIO_PIN_6);
//PB7配置成上拉输入
gpio_init(GPIOB, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, GPIO_PIN_7);
中断初始化:
// AFIO时钟使能
rcu_periph_clock_enable(RCU_AF);
// 设置优先级分组
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2);
// 设置优先级
nvic_irq_enable(EXTI5_9_IRQn, 2U, 2U);
// 设置EXTI触发源
gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOB, GPIO_PIN_SOURCE_5);
gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOB, GPIO_PIN_SOURCE_6);
gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOB, GPIO_PIN_SOURCE_7);
// 下降沿中断
exti_init(EXTI_5, EXTI_INTERRUPT, EXTI_TRIG_FALLING);
exti_init(EXTI_6, EXTI_INTERRUPT, EXTI_TRIG_FALLING);
exti_init(EXTI_7, EXTI_INTERRUPT, EXTI_TRIG_FALLING);
// 清中断标志
exti_interrupt_flag_clear(EXTI_5);
exti_interrupt_flag_clear(EXTI_6);
exti_interrupt_flag_clear(EXTI_7);
编写中断服务函数:
//先是函数弱定义
__attribute__((weak)) void Key_GPIO_IrqCallback(void)
{
}
//定义中断线判断标志位
int state_choose_flag = 0 ;
void EXTI5_9_IRQHandler(void)
{
//判定是否是外部中断线5
if (RESET != exti_interrupt_flag_get(EXTI_5))
{
state_choose_flag=5;
Key_GPIO_IrqCallback();
//清除中断标记
state_choose_flag=0;
exti_interrupt_flag_clear(EXTI_5);
}
//判定是否是外部中断线6
if (RESET != exti_interrupt_flag_get(EXTI_6))
{
state_choose_flag=6;
Key_GPIO_IrqCallback();
//清除中断标记
state_choose_flag=0;
exti_interrupt_flag_clear(EXTI_6);
}
//判定是否是外部中断线7
if (RESET != exti_interrupt_flag_get(EXTI_7))
{
state_choose_flag=7;
Key_GPIO_IrqCallback();
//清除中断标记
state_choose_flag=0;
exti_interrupt_flag_clear(EXTI_7);
}
}
在外部重写中断处理函数:
void Key_GPIO_IrqCallback(void)
{
LED1_TOG;
}
以上为代码逻辑
接下来让我们新建中断外设文件,具体步骤可以参考上一章bsp_key.c以及bsp_key.h的建立
然后在bsp_exti.c文件中编写中断初始化代码以及放置中断处理函数
整体代码如下:
#include "gd32f10x.h"
#include "bsp_exti.h"
/*********************************************************************
*
*/
/**
@brief 按键外部中断初始化
@param 无
@return 无
*/
void Key_EXTI_Init(void)
{
// AFIO时钟使能
rcu_periph_clock_enable(RCU_AF);
// 设置优先级分组
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2);
// 设置优先级
nvic_irq_enable(EXTI5_9_IRQn, 2U, 2U);
// 设置EXTI触发源
gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOB, GPIO_PIN_SOURCE_5);
gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOB, GPIO_PIN_SOURCE_6);
gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOB, GPIO_PIN_SOURCE_7);
// 下降沿中断
exti_init(EXTI_5, EXTI_INTERRUPT, EXTI_TRIG_FALLING);
exti_init(EXTI_6, EXTI_INTERRUPT, EXTI_TRIG_FALLING);
exti_init(EXTI_7, EXTI_INTERRUPT, EXTI_TRIG_FALLING);
// 清中断标志
exti_interrupt_flag_clear(EXTI_5);
exti_interrupt_flag_clear(EXTI_6);
exti_interrupt_flag_clear(EXTI_7);
}
extern int state_choose_flag;
/**
@brief 按键中断回调函数
@param 无
@return 无
*/
void Key_GPIO_IrqCallback(void)
{
//如果来自中断线5
if(state_choose_flag==5)
{
LED1_TOG;
}
//如果来自中断线6
else if(state_choose_flag==6)
{
LED2_TOG;
}
//如果来自中断线7
else if(state_choose_flag==7)
{
LED3_TOG;
}
}
然后在bsp_exti.c文件中放置声明文件
#ifndef _BSP_EXTI_H_
#define _BSP_EXTI_H_
#include "gd32f10x_exti.h"
#include "bsp_led.h"
/**函数声明
*
*
*/
void Key_EXTI_Init(void);
#endif
代码完成编写,现在烧录!
与实验预期相符合,完成!
具体代码请参考例程
群号:621154399
有问题欢迎大家加入我们一起交流,这个群是开源性技术交流群。