|
第十五章 外部中断实验
1)实验平台:正点原子Mini STM32H562开发板
2)章节摘自【正点原子】STM32H562开发指南 V1.0
3)购买链接: https://detail.tmall.com/item.htm?id=753606659612
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/stm32/zdyz_stm32h562_mini.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子STM32开发板技术交流群:414052738
在前面几章的学习中,我们掌握了STM32H5的IO口最基本的操作。本章我们将介绍如何把STM32H5的IO口作为外部中断输入来使用。在本章中,我们将以中断的方式,实现我们在第十五章所实现的功能。
本章分为如下几个小节:
15.1 STM32H5 NVIC和外部中断简介
15.2 硬件设计
15.3 程序设计
15.4 下载验证
15.1 NVIC和EXTI简介
15.1.1 NVIC简介
什么是NVIC?NVIC即嵌套向量中断控制器,全称Nested vectored interrupt controller。它是内核的器件,所以它的更多描述可以看内核有关的资料。M33内核支持不可屏蔽中断(NMI)和多达480个物理中断,具有8到256个优先级。然而芯片厂商一般不会把内核的这些资源全部用完,如STM32H562的系统中断有11个,外部中断有131个。这个在讲解启动文件的时候有提到过,可以回顾一下。下面我们看看系统中断部分:
表15.1.1.1 中断向量表-系统中断部分
关于131个外部中断部分在《STM32H5xx参考手册_V1(英文版).pdf》的17.3小节有详细的列表,这里就不列出来了。STM32H562的中断号在STM32H562xx.h文件中被定义。
15.1.1.1 NVIC寄存器
NVIC相关的寄存器定义可以在core_cm33.h文件中找到。我们直接通过程序的定义来分析NVIC相关的寄存器,其定义如下:
- typedef struct
- {
- __IOM uint32_t ISER[16U]; /* 中断使能寄存器 */
- uint32_t RESERVED0[16U];
- __IOM uint32_t ICER[16U]; /* 中断除能寄存器 */
- uint32_t RSERVED1[16U];
- __IOM uint32_t ISPR[16U]; /* 中断使能挂起寄存器 */
- uint32_t RESERVED2[16U];
- __IOM uint32_t ICPR[16U]; /* 中断解挂寄存器 */
- uint32_t RESERVED3[16U];
- __IOM uint32_t IABR[16U]; /* 中断有效位寄存器 */
- uint32_t RESERVED4[16U];
- __IOM uint32_t ITNS[16U]; /* 中断非安全状态寄存器 */
- uint32_t RESERVED5[16U];
- __IOM uint8_t IPR[496U]; /* 中断优先级寄存器(8Bit 位宽) */
- uint32_t RESERVED6[580U];
- __OM uint32_t STIR; /* 软件触发中断寄存器 */
- } NVIC_Type;
复制代码 STM32H562的中断在这些寄存器的控制下有序的执行的。只有了解这些中断寄存器,才能方便的使用STM32H562的中断。下面重点介绍这几个寄存器:
ISER[16]:ISER全称是:Interrupt Set Enable Registers,这是一个中断使能寄存器组。上面说了CM33内核支持多达480个物理中断,这里用16个32位寄存器来控制,每个位控制一个中断,所以还有一些没用到的位。STM32H562的外部中断最多只有131个,所以对我们来说,有用的就是5个(ISER[0~4]]),总共可以表示160个中断。而STM32H562只用了其中的131个。ISER[0]的bit0~31分别对应中断0~31;ISER[1]的bit0~31对应中断32~63;其他以此类推,这样总共131个中断就可以分别对应上了。你要使能某个中断,必须设置相应的ISER位为1,使该中断被使能(这里仅仅是使能,还要配合中断分组、屏蔽、IO口映射等设置才算是一个完整的中断设置)。
ICER[16]:全称是:Interrupt Clear Enable Registers,是一个中断除能寄存器组。该寄存器组与ISER的作用恰好相反,是用来清除某个中断的使能的。其对应位的功能,也和ICER一样。这里要专门设置一个ICER来清除中断位,而不是向ISER写0来清除,是因为NVIC的这些寄存器都是写1有效的,写0是无效的。
ISPR[8]:全称是:Interrupt Set Pending Registers,是一个中断使能挂起控制寄存器组。每个位对应的中断和ISER是一样的。通过置1,可以将正在进行的中断挂起,而执行同级或更高级别的中断。写0是无效的。
ICPR[16]:全称是:Interrupt Clear Pending Registers,是一个中断解挂控制寄存器组。其作用与ISPR相反,对应位也和ISER是一样的。通过设置1,可以将挂起的中断解挂。写0无效。
IABR[16]:全称是:Interrupt Active Bit Registers,是一个中断激活标志位寄存器组。对应位所代表的中断和ISER一样,如果为1,则表示该位所对应的中断正在被执行。这是一个只读寄存器,通过它可以知道当前在执行的中断是哪一个。在中断执行完了由硬件自动清零。
ITNS [16]:全称是:Interrupt Non-Secure State Register,是一个中断非安全状态寄存器组。指示该中断是否配置为非安全属性。
IP[496]:全称是:Interrupt Priority Registers,是一个中断优先级控制的寄存器组。这个寄存器组相当重要!STM32H562的中断分组与这个寄存器组密切相关。IP寄存器组由496个8bit的寄存器组成,每个可屏蔽中断占用8bit,这样总共可以表示496个可屏蔽中断。而STM32H562只用到了其中的131个。IP[130]~IP[0]分别对应中断130~0。而每个可屏蔽中断占用的8bit并没有全部使用,而是只用了高4位。这4位,又分为抢占优先级和子优先级。抢占优先级在前,子优先级在后。而这两个优先级各占几个位又要根据SCB->AIRCR中的中断分组设置来决定。关于中断优先级控制的寄存器组我们下面详细讲解。
15.1.1.2 中断优先级
STM32中的中断优先级可以分为:抢占式优先级和响应优先级,响应优先级也称子优先级,每个中断源都需要被指定这两种优先级。抢占式优先级和响应优先级的区别:
抢占优先级:抢占优先级高的中断可以打断正在执行的抢占优先级低的中断。
响应优先级:抢占优先级相同,响应优先级高的中断不能打断响应优先级低的中断。
还有一种情况就是当两个或者多个中断的抢占式优先级和响应优先级相同时,那么就遵循自然优先级,看中断向量表的中断排序,数值越小,优先级越高。
在NVIC中由寄存器NVIC_IPR0-NVIC_IPR123共124个寄存器控制中断优先级,每个寄存器的每8位又分为一组,可以分4组,所以就有了496组宽度为8bit的中断优先级控制寄存器,原则上每个外部中断可配置的优先级为0~255,数值越小,优先级越高。但是实际上M3 /M4/M33 /M7 芯片为了精简设计,只使用了高四位[7:4],低四位取零,这样以至于最多只有16级中断嵌套,即2^4=16。
对于NVCI的中断优先级分组:STM32H562将中断分为5个组,组0~4。该分组的设置是由SCB->AIRCR寄存器的bit10~8来定义的。具体的分配关系如表15.1.1.2.1所示:
表15.1.1.2.1 AIRCR中断分组设置表
通过这个表,我们就可以清楚的看到组0~4对应的配置关系,例如优先级分组设置为3,那么此时所有的131个中断,每个中断的中断优先寄存器的高四位中的最高3位是抢占优先级,低1位是响应优先级。每个中断,你可以设置抢占优先级为0~7,响应优先级为1或0。抢占优先级的级别高于响应优先级。而数值越小所代表的优先级就越高。
结合实例说明一下:假定设置中断优先级分组为2,然后设置中断3(RTC_WKUP中断)的抢占优先级为2,响应优先级为1。中断6(外部中断0)的抢占优先级为3,响应优先级为0。中断7(外部中断1)的抢占优先级为2,响应优先级为0。那么这3个中断的优先级顺序为:中断7>中断3>中断6。
上面例子中的中断3和中断7都可以打断中断6的中断。而中断7和中断3却不可以相互打断!
15.1.1.3 NVIC相关函数
ST公司把core_cm33.h文件的NVIC相关函数封装到stm32h5xx_hal_cortex.c文件中,下面列出我们较为常用的函数进行,想了解更多其他的函数请自行查阅。
1.HAL_NVIC_SetPriorityGrouping函数
HAL_NVIC_SetPriorityGrouping是设置中断优先级分组函数。其声明如下:
- void HAL_NVIC_SetPriorityGrouping(uint32_t PriorityGroup);
复制代码 函数描述:
用于设置中断优先级分组。
函数形参:
形参1是中断优先级分组号,可以选择范围:NVIC_PRIORITYGROUP_0到
NVIC_PRIORITYGROUP_4(共5组)。
函数返回值:无
注意事项:
这个函数在一个工程里基本只调用一次,而且是在程序HAL库初始化函数里面已经被调用,后续就不会再调用了。因为当后续调用设置成不同的中断优先级分组时,有可能造成前面设置好的抢占优先级和响应优先级不匹配。
2.HAL_NVIC_SetPriority函数
HAL_NVIC_SetPriority是设置中断优先级函数。其声明如下:
- void HAL_NVIC_SetPriority(IRQn_Type IRQn, uint32_t PreemptPriority,
- uint32_t SubPriority);
复制代码 函数描述:
用于设置中断的抢占优先级和响应优先级(子优先级)。
函数形参:
形参1是中断号,可以选择范围:IRQn_Type定义的枚举类型,定义在stm32h5xx.h。
形参2是抢占优先级,可以选择范围:0到15。
形参3是响应优先级,可以选择范围:0到15。
函数返回值:无
3.HAL_NVIC_EnableIRQ函数
HAL_NVIC_EnableIRQ是中断使能函数。其声明如下:
- void HAL_NVIC_EnableIRQ(IRQn_Type IRQn);
复制代码 函数描述:
用于使能中断。
函数形参:
形参1是中断号,可以选择范围:IRQn_Type定义的枚举类型,定义在stm32h5xx.h。
函数返回值:无
4.HAL_NVIC_DisableIRQ函数
HAL_NVIC_DisableIRQ是中断除能函数。其声明如下:
- void HAL_NVIC_disableIRQ(IRQn_Type IRQn);
复制代码 函数描述:
用于中断除能。
函数形参:无形参
函数返回值:无
5.HAL_NVIC_SystemReset函数
HAL_NVIC_SystemReset是系统复位函数。其声明如下:
- void HAL_NVIC_SystemReset(void);
复制代码 函数描述:
用于软件复位系统。
函数形参:无形参
函数返回值:无
其他的NVIC函数用得较少,我们就不一一列出来了。NVIC的介绍就到这,下面介绍外部中断。
15.1.2 EXTI简介
EXTI即是外部中断和事件控制器,它由四个部分组成:AHB接口访问的寄存器模块、事件输入触发模块屏蔽模块和EXTI 复用器。其中寄存器块包含了所有EXTI寄存器;事件输入触发模块提供事件输入边沿触发逻辑;屏蔽模块为不同的唤醒、中断和事件输出及其屏蔽功能提供事件分配;EXTI复用器是为了EXTI事件信号提供IO端口选择。EXTI的功能框图如下图所示:
图15.1.2.1 EXTI功能框图
由外设对EXTI产生的事件分为可配置事件和直接事件。
可配置事件是来自能够生成脉冲的 IO 或外设的信号,这类事件具有多种特性:可选择的有效触发边沿、中断挂起状态寄存器位、单独的中断和事件生成屏蔽、支持软件触发、EXTI IO端口选择、对安全输入事件的控制位和配置位的访问可以设置为安全或特权。
直接事件是来自其它外设的中断和唤醒源,需要在外设中清除,这类事件具备特性:固定上升沿有效触发、EXTI 中无中断挂起状态寄存器位、单独的中断和事件生成掩码,用于调节CPU唤醒和事件生成、不支持软件触发。
EXTI支持58个中断/事件请求,即包括可配置事件和直接事件。前25个中断/事件如下表:
表15.1.2.1 EXTI线路连接表(上部分)
由于表格太长,其余的请大家参考《STM32H5xx参考手册_V1(英文版).pdf》18.2.2小节的EXTI线路连接表格。
从上表可以看出,普通的 GPIO 口事件输入,属于可配置事件。PVD/AVD 输出也属于可配置事件,下表就请大家查看参考手册了。
事件输入0-15对应外部IO口的输入中断,一共是16个外部中断线。STM32H5供IO口使用的中断线只有16个,但是STM32H5的IO口却远远不止16个,那么STM32H5是怎么把16个中断线和IO口一一对应起来的呢?于是STM32就这样设计,GPIO的引脚GPIOx.0 ~ GPIOx.15(x=A,B,C,D,E,F,G,H,I)分别对应中断线0~15。这样每个中断线对应了最多9个IO口,以线0为例:它对应了GPIOA.0、GPIOB.0、GPIOC.0、GPIOD.0、GPIOE.0、GPIOF.0、GPIOG.0、GPIOH.0、GPIOI.0。而中断线每次只能连接到1个IO口上,这样就需要通过配置来决定对应的中断线配置到哪个GPIO上了。
GPIO和中断线映射关系是在寄存器SYSCFG_EXTICR1 ~ SYSCFG_EXTICR4中配置的。
图15.1.2.2 SYSCFG_EXTICR1寄存器
SYSCFG_EXTICR1寄存器配置EXTI0到EXTI3线,包含的外部中断的引脚包括PAx到PIx,x=0到3。SYSCFG_EXTICR2寄存器配置EXTI4到EXTI7线,包含的外部中断的引脚包括PAx到PIx,x=4到7,SYSCFG_EXTICR2寄存器请打开参考手册查看(这里没有截图出来)。SYSCFG_EXTICR3和SYSCFG_EXTICR4以此类推。
另外要注意的是,我们配置SYSCFG相关寄存器前,还需要打开SYSCFG时钟。
15.2 硬件设计
1. 例程功能
通过开发板上的三个独立按键控制LED灯或者蜂鸣器: KEY_UP控制蜂鸣器翻转、 KEY1控制LED1翻转、KEY0控制LED0/LED1同时翻转。
2. 硬件资源
1)LED灯
LED0 - PB4
LED1 - PB5
2)蜂鸣器
BEEP(PE0)
3)独立按键
KEY0 - PA1
KEY1 - PA15
WK_UP - PA0
3. 原理图
独立按键硬件部分的原理图,如图15.2.1所示:
图15.2.1 独立按键与STM32H5连接原理图
这里需要注意的是:KEY0和KEY1是低电平有效的,而KEY_UP是高电平有效的,并且外部都没有上下拉电阻,所以,需要在STM32H562内部设置上下拉,来确定设置空闲电平状态。
15.3 程序设计
15.3.1 EXTI的HAL库驱动
前面讲解HAL_GPIO_Init函数的时候有提到过:HAL库的EXTI外部中断的设置功能整合到HAL_GPIO_Init函数里面,而不是单独独立一个文件。所以我们的外部中断的初始化函数也是用HAL_GPIO_Init函数。
既然是要用到外部中断,所以我们的GPIO的模式要从下面的三个模式中选中一个:
- #define GPIO_MODE_IT_RISING (0x11110000U) /* 外部中断,上升沿触发检测 */
- #define GPIO_MODE_IT_FALLING (0x11210000U) /* 外部中断,下降沿触发检测 */
- /* 外部中断,上升和下降双沿触发检测 */
- #define GPIO_MODE_IT_RISING_FALLING (0x11310000U)
复制代码 KEY0和KEY1是低电平有效的,所以我们要选择下降沿触发检测,而KEY_UP是高电平有效的,那么就应该选择上升沿触发检测。
EXTI外部中断配置步骤
1)使能IO口时钟。
本实验用到的GPIO和按键输入实验是一样的,因此GPIO时钟使能也是一样的,请参考上一章代码。
2)设置IO口模式,触发条件,开启SYSCFG时钟,设置IO口与中断线的映射关系。
这些步骤HAL库全部封装在HAL_GPIO_Init函数里面,我们只需要设置好对应的参数,再调用HAL_GPIO_Init函数即可完成配置。
3)配置中断优先级(NVIC),并使能中断。
配置好GPIO模式以后,我们需要设置中断优先级和使能中断,中断优先级我们使用HAL_NVIC_SetPriority函数设置,中断使能我们使用HAL_NVIC_EnableIRQ函数设置。
4)编写中断服务函数。
每开启一个中断,就必须编写其对应的中断服务函数,否则将导致死机(CPU将找不到中断服务函数)。中断服务函数接口厂家已经在startup_stm32h562xx.s中做好了。STM32H5的IO口外部中断函数有16个,分别为:
- void EXTI0_IRQHandler();
- void EXTI1_IRQHandler();
- void EXTI2_IRQHandler();
- void EXTI3_IRQHandler();
- …省略中间部分…
- void EXTI14_IRQHandler();
- void EXTI15_IRQHandler();
复制代码 中断线0-15,每个中断线都有对应一个中断函数。一般情况下,我们可以把中断控制逻辑代码直接编写在中断服务函数中,但是HAL库把中断处理过程进行了简单封装,请看下面步骤5讲解。
5)编写中断处理回调函数HAL_GPIO_EXTI_Callback
HAL库为了用户使用方便,提供了一个中断通用入口函数HAL_GPIO_EXTI_IRQHandler,在该函数内部直接调用相应的中断回调函数,HAL_GPIO_EXTI_Falling_Callback()或者HAL_GPIO_EXTI_Rising_Callback()。用户只需要把上升沿触发的EXTI事件/中断的控制逻辑代码写到HAL_GPIO_EXTI_Falling_Callback()回调函数里面,把下降沿触发的EXTI事件/中断的控制逻辑代码写到HAL_GPIO_EXTI_Falling_Callback()回调函数。
我们先来看一下HAL_GPIO_EXTI_IRQHandler函数定义:
- void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
- {
- /* EXTI line interrupt detected */
- if (__HAL_GPIO_EXTI_GET_RISING_IT(GPIO_Pin) != 0U)
- {
- __HAL_GPIO_EXTI_CLEAR_RISING_IT(GPIO_Pin); /* 清下降沿触发的EXTI中断标志位 */
- HAL_GPIO_EXTI_Rising_Callback(GPIO_Pin); /* 下降沿触发的EXTI中断回调函数 */
- }
- if (__HAL_GPIO_EXTI_GET_FALLING_IT(GPIO_Pin) != 0U)
- {
- __HAL_GPIO_EXTI_CLEAR_FALLING_IT(GPIO_Pin); /* 清上升沿触发的EXTI中断标志位 */
- HAL_GPIO_EXTI_Falling_Callback(GPIO_Pin); /* 上升沿触发的EXTI中断回调函数 */
- }
- }
复制代码 该函数实现的作用非常简单,通过入口参数GPIO_Pin判断中断来自哪个IO口,然后清除相应的中断标志位,最后调用回调函数HAL_GPIO_EXTI_Falling_Callback()或者HAL_GPIO_EXTI_Rising_Callback()实现控制逻辑。因此我们可以在相应的回调函数里面实现控制逻辑编写,详见本实验源码。
15.3.2 程序流程图
图15.3.2.1外部中断实验程序流程图
15.3.3 程序解析
1. 外部中断驱动代码
下面我们先解析exti.h的程序。
由硬件设计小节,我们知道KEY0、KEY1和KEY_UP分别来连接到PA1、PA15和PA0上,我们做了下面的引脚定义。
- /*****************************************************************************/
- /* 引脚 和 中断编号 & 中断服务函数 定义 */
- #define KEY0_INT_GPIO_PORT GPIOA
- #define KEY0_INT_GPIO_PIN GPIO_PIN_1
- #define KEY0_INT_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)
- #define KEY0_INT_IRQn EXTI1_IRQn
- #define KEY0_INT_IRQHandler EXTI1_IRQHandler
- #define KEY1_INT_GPIO_PORT GPIOA
- #define KEY1_INT_GPIO_PIN GPIO_PIN_15
- #define KEY1_INT_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)
- #define KEY1_INT_IRQn EXTI15_IRQn
- #define KEY1_INT_IRQHandler EXTI15_IRQHandler
- #define WKUP_INT_GPIO_PORT GPIOA
- #define WKUP_INT_GPIO_PIN GPIO_PIN_0
- #define WKUP_INT_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)
- #define WKUP_INT_IRQn EXTI0_IRQn
- #define WKUP_INT_IRQHandler EXTI0_IRQHandler
- /*****************************************************************************/
复制代码 KEY0、KEY1和WK_UP分别连接PA1、PA15和PA0,即对应了EXTI1、EXTI15和EXTI0这三条外部中断线。
下面我们再解析exti.c的程序,先看外部中断初始化函数,其定义如下:
- /**
- * @brief 外部中断初始化程序
- * [url=home.php?mod=space&uid=271674]@param[/url] 无
- * @retval 无
- */
- void extix_init(void)
- {
- GPIO_InitTypeDef gpio_init_struct = {0};
-
- KEY0_INT_GPIO_CLK_ENABLE(); /* KEY0时钟使能 */
- KEY1_INT_GPIO_CLK_ENABLE(); /* KEY1时钟使能 */
- WKUP_INT_GPIO_CLK_ENABLE(); /* WKUP时钟使能 */
-
- gpio_init_struct.Pin = KEY0_INT_GPIO_PIN; /* KEY0引脚 */
- gpio_init_struct.Mode = GPIO_MODE_IT_FALLING; /* 下升沿触发 */
- gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
- HAL_GPIO_Init(KEY0_INT_GPIO_PORT, &gpio_init_struct); /* KEY0引脚初始化 */
-
- gpio_init_struct.Pin= KEY1_INT_GPIO_PIN; /* KEY1引脚 */
- gpio_init_struct.Mode = GPIO_MODE_IT_FALLING; /* 下升沿触发 */
- gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
- HAL_GPIO_Init(KEY1_INT_GPIO_PORT, &gpio_init_struct); /* KEY1引脚初始化 */
-
- gpio_init_struct.Pin = WKUP_INT_GPIO_PIN; /* WKUP引脚 */
- gpio_init_struct.Mode = GPIO_MODE_IT_RISING; /* 上升沿触发 */
- gpio_init_struct.Pull = GPIO_PULLDOWN; /* 下拉 */
- HAL_GPIO_Init(WKUP_INT_GPIO_PORT, &gpio_init_struct); /* WKUP引脚初始化 */
- HAL_NVIC_SetPriority(KEY0_INT_IRQn, 0, 2); /* 抢占优先级为0,子优先级为2 */
- HAL_NVIC_EnableIRQ(KEY0_INT_IRQn); /* 使能中断线1 */
- HAL_NVIC_SetPriority(KEY1_INT_IRQn, 1, 2); /* 抢占优先级为1,子优先级为2 */
- HAL_NVIC_EnableIRQ(KEY1_INT_IRQn); /* 使能中断线15 */
- HAL_NVIC_SetPriority(WKUP_INT_IRQn, 2, 2); /* 抢占优先级为2,子优先级为2 */
- HAL_NVIC_EnableIRQ(WKUP_INT_IRQn); /* 使能中断线0 */
- }
复制代码 外部中断初始化函数主要做了两件事情,先是调用IO口初始化函数HAL_GPIO_Init来初始化IO口,然后设置中断优先级并使能中断线。
3个外部中断服务函数,其定义如下:
- /**
- * @brief KEY0 外部中断服务程序
- * @param 无
- * @retval 无
- */
- void KEY0_INT_IRQHandler(void)
- {
- HAL_GPIO_EXTI_IRQHandler(KEY0_INT_GPIO_PIN); /* 调用中断处理公用函数 */
- /* HAL库默认先清中断再处理回调,退出时再清一次中断,避免按键抖动误触发 */
- __HAL_GPIO_EXTI_CLEAR_FALLING_IT(KEY0_INT_GPIO_PIN);
- }
- /**
- * @brief KEY1 外部中断服务程序
- * @param 无
- * @retval 无
- */
- void KEY1_INT_IRQHandler(void)
- {
- HAL_GPIO_EXTI_IRQHandler(KEY1_INT_GPIO_PIN); /* 调用中断处理公用函数 */
- /* HAL库默认先清中断再处理回调,退出时再清一次中断,避免按键抖动误触发 */
- __HAL_GPIO_EXTI_CLEAR_FALLING_IT(KEY1_INT_GPIO_PIN);
- }
- /**
- * @brief WK_UP 外部中断服务程序
- * @param 无
- * @retval 无
- */
- void WKUP_INT_IRQHandler(void)
- {
- HAL_GPIO_EXTI_IRQHandler(WKUP_INT_GPIO_PIN); /* 调用中断处理公用函数 */
- /* HAL库默认先清中断再处理回调,退出时再清一次中断,避免按键抖动误触发 */
- __HAL_GPIO_EXTI_CLEAR_RISING_IT(WKUP_INT_GPIO_PIN);
- }
复制代码 所有的外部中断服务函数里都只调用了同样一个函数HAL_GPIO_EXTI_IRQHandler,该函数是外部中断共用入口函数,函数内部会进行中断标志位清零, 并且调用中断处理共用回调函数HAL_GPIO_EXTI_Falling_Callback()或者HAL_GPIO_EXTI_Rising_Callback()。但是它们的形参不同,我们的回调函数也是根据形参去判断是哪个IO口的外部中断线被触发。
外部中断回调函数,其定义如下:
- /**
- * @brief EXTI线下降沿检测回调函数
- * @param GPIO_Pin: 中断引脚号
- * [url=home.php?mod=space&uid=60778]@note[/url] 在HAL库中,所有下降沿检测的外部中断发生后,都会调用此函数
- * @retval 无
- */
- void HAL_GPIO_EXTI_Falling_Callback(uint16_t GPIO_Pin)
- {
- delay_ms(20); /* 消抖 */
- switch(GPIO_Pin)
- {
- case KEY0_INT_GPIO_PIN:
- LED0_TOGGLE(); /* LED0 状态取反 */
- LED1_TOGGLE(); /* LED1 状态取反 */
- break;
-
- case KEY1_INT_GPIO_PIN:
- LED0_TOGGLE(); /* LED0 状态取反 */
- break;
- }
- }
- /**
- * @brief EXTI线上升沿检测回调函数
- * @param GPIO_Pin: 中断引脚号
- * @note 在HAL库中,所有上升沿检测的外部中断发生后,都会调用此函数
- * @retval 无
- */
- void HAL_GPIO_EXTI_Rising_Callback(uint16_t GPIO_Pin)
- {
- delay_ms(20); /* 消抖 */
- switch(GPIO_Pin)
- {
- case WKUP_INT_GPIO_PIN:
- BEEP_TOGGLE(); /* 蜂鸣器状态取反 */
- break;
- }
- }
复制代码 HAL_GPIO_EXTI_Falling_Callback()或者HAL_GPIO_EXTI_Rising_Callback()函数用来编写真正的外部中断控制逻辑代码。这两个函数都有一个形参就是IO引脚号。所以我们在该函数内部,一般通过判断IO引脚号来确定中断是来自哪个IO口,也就是哪个中断线,然后编写相应的控制逻辑代码。所以在该函数内部,我们通过switch语句判断IO口来源,例如是来自GPIO_PIN_0,那么一定是来自PA0,因为中断线一次只能连接一个IO口,而三个IO口中引脚号为0的IO口只有PA0,所以中断线0一定是连接PA0,也就是外部中断由PA0触发。其它的引脚号的逻辑类似。
2. main.c代码
在main.c里面编写如下代码:
- #include "./SYSTEM/sys/sys.h"
- #include "./SYSTEM/usart/usart.h"
- #include "./SYSTEM/delay/delay.h"
- #include "./BSP/LED/led.h"
- #include "./BSP/BEEP/beep.h"
- #include "./BSP/EXTI/exti.h"
- int main(void)
- {
- HAL_Init(); /* HAL库初始化 */
- HAL_ICACHE_Enable(); /* 使能指令缓存 */
- sys_stm32_clock_init(250, 4, 2, 2); /* 系统时钟初始化, 250Mhz */
- delay_init(250); /* 延时初始化 */
- usart_init(115200); /* 串口初始化 */
- led_init(); /* LED初始化 */
- beep_init(); /* 蜂鸣器初始化 */
- extix_init(); /* 外部中断输入初始化 */
- LED0(0); /* 先点亮LED0 */
- while(1)
- {
- printf("OK\r\n");
- delay_ms(1000);
- }
- }
复制代码 首先是调用系统初始化函数。接下来,初始化LED/蜂鸣器/EXTI。最后在无限循环里面打印OK并延时1000ms。按键操作会通过EXTI中断触发。
15.4 下载验证
在下载好程序后,我们可以按KEY0、KEY1和KEY_UP来看看LED和蜂鸣器的变化,是否和我们预期的结果一致?
至此,我们的本章的学习就结束了。本章学习了STM32H562外部中断的使用方法。 |