|
第十五章 外部中断实验
在前面几章的学习中,我们掌握了STM32H7R7的IO口最基本的操作。本章我们将介绍如何把STM32H7R7的IO口作为外部中断输入来使用,在本章中,我们将以中断的方式,实现我们在本章所实现的功能。 本章分为如下几个小节: 15.1 STM32H7R7 NVIC和外部中断简介 15.2 硬件设计 15.3 程序设计 15.4 下载验证
15.1 NVIC和EXTI简介15.1.1 NVIC简介什么是NVIC?NVIC即嵌套向量中断控制器,全称Nested vectored interrupt controller。它是内核的器件,所以它的更多描述可以看内核有关的资料。M3/M4/M7内核都是支持256个中断,其中包含了16个系统中断和240个外部中断,并且具有256级的可编程中断设置。然而芯片厂商一般不会把内核的这些资源全部用完,如STM32H7R7的系统中断有10个,外部中断有140个。这个在讲解启动文件的时候有提到过,可以回顾一下。下面我们看看系统中断部分: 表15.1.1.1 中断向量表-系统中断部分 关于140个外部中断部分在《STM32H7Rx参考手册_V6(英文版).pdf》的19.1.2小节有详细的列表,这里就不列出来了。STM32H7R7的中断向量表在stm32h7r7xx.h文件中被定义。 15.1.1.1 NVIC寄存器NVIC相关的寄存器定义了可以在core_cm7.h文件中找到。我们直接通过程序的定义来分析NVIC相关的寄存器,其定义如下: typedef struct { __IOM uint32_t ISER[8U]; /* 中断使能寄存器 */ uint32_t RESERVED0[24U]; __IOM uint32_t ICER[8U]; /* 中断除能寄存器 */ uint32_t RSERVED1[24U]; __IOM uint32_t ISPR[8U]; /* 中断使能挂起寄存器 */ uint32_t RESERVED2[24U]; __IOM uint32_t ICPR[8U]; /* 中断解挂寄存器 */ uint32_t RESERVED3[24U]; __IOM uint32_t IABR[8U]; /* 中断有效位寄存器 */ uint32_t RESERVED4[56U]; __IOM uint8_t IP[240U]; /* 中断优先级寄存器(8Bit 位宽) */ uint32_t RESERVED5[644U]; __OM uint32_t STIR; /* 软件触发中断寄存器 */ } NVIC_Type; STM32H7R7的中断在这些寄存器的控制下有序的执行的。只有了解这些中断寄存器,才能方便的使用STM32H7R7的中断。下面重点介绍这几个寄存器: ISER[8]:ISER全称是:Interrupt Set Enable Registers,这是一个中断使能寄存器组。上面说了CM7内核支持256个中断,这里用8个32位寄存器来控制,每个位控制一个中断。但是STM32H7R7的可屏蔽中断最多只有150个,所以对我们来说,有用的就是5个(ISER[0~4),总共可以表示155个中断。而STM32H7R7只用了其中的150个。ISER[0]的bit0~31分别对应中断0~31;ISER[1]的bit0~31对应中断32~63;其他以此类推,这样总共150个中断就可以分别对应上了。你要使能某个中断,必须设置相应的ISER位为1,使该中断被使能(这里仅仅是使能,还要配合中断分组、屏蔽、IO口映射等设置才算是一个完整的中断设置)。具体每一位对应哪个中断,请参考stm32h7r7xx.h里面的第54行到209行,共155个。 ICER[8]:全称是:Interrupt Clear Enable Registers,是一个中断除能寄存器组。该寄存器组与ISER的作用恰好相反,是用来清除某个中断的使能的。其对应位的功能,也和ICER一样。这里要专门设置一个ICER来清除中断位,而不是向ISER写0来清除,是因为NVIC的这些寄存器都是写1有效的,写0是无效的。 ISPR[8]:全称是:Interrupt Set Pending Registers,是一个中断使能挂起控制寄存器组。每个位对应的中断和ISER是一样的。通过置1,可以将正在进行的中断挂起,而执行同级或更高级别的中断。写0是无效的。 ICPR[8]:全称是:Interrupt Clear Pending Registers,是一个中断解挂控制寄存器组。其作用与ISPR相反,对应位也和ISER是一样的。通过设置1,可以将挂起的中断解挂。写0无效。 IABR[8]:全称是:Interrupt Active Bit Registers,是一个中断激活标志位寄存器组。对应位所代表的中断和ISER一样,如果为1,则表示该位所对应的中断正在被执行。这是一个只读寄存器,通过它可以知道当前在执行的中断是哪一个。在中断执行完了由硬件自动清零。 IP[240]:全称是:Interrupt Priority Registers,是一个中断优先级控制的寄存器组。这个寄存器组相当重要!STM32H7R7的中断分组与这个寄存器组密切相关。IP寄存器组由240个8bit的寄存器组成,每个可屏蔽中断占用8bit,这样总共可以表示240个可屏蔽中断。而STM32H7R7只用到了其中的150个。IP[149]~IP[0]分别对应中断149~0。而每个可屏蔽中断占用的8bit并没有全部使用,而是只用了高4位。这4位,又分为抢占优先级和子优先级。抢占优先级在前,子优先级在后。而这两个优先级各占几个位又要根据SCB->AIRCR中的中断分组设置来决定。关于中断优先级控制的寄存器组我们下面详细讲解。 15.1.1.2 中断优先级STM32中的中断优先级可以分为:抢占式优先级和响应优先级,响应优先级也称子优先级,每个中断源都需要被指定这两种优先级。抢占式优先级和响应优先级的区别: 抢占优先级:抢占优先级高的中断可以打断正在执行的抢占优先级低的中断。 响应优先级:抢占优先级相同,响应优先级高的中断不能打断响应优先级低的中断。 还有一种情况就是当两个或者多个中断的抢占式优先级和响应优先级相同时,那么就遵循自然优先级,看中断向量表的中断排序,数值越小,优先级越高。 在NVIC中由寄存器NVIC_IPR0-NVIC_IPR59共60个寄存器控制中断优先级,每个寄存器的每8位又分为一组,可以分4组,所以就有了240组宽度为8bit的中断优先级控制寄存器,原则上每个外部中断可配置的优先级为0~255,数值越小,优先级越高。但是实际上M3 /M4 /M7 芯片为了精简设计,只使用了高四位[7:4],低四位取零,这样以至于最多只有16级中断嵌套,即2^4=16。 对于NVIC的中断优先级分组:STM32H7R7将中断分为5个组,组0~4。该分组的设置是由SCB->AIRCR寄存器的bit10~8来定义的。具体的分配关系如表15.1.1.2.1所示: 表15.1.1.2.1 AIRCR中断分组设置表 通过这个表,我们就可以清楚的看到组0~4对应的配置关系,例如优先级分组设置为3,那么此时所有的150个中断,每个中断的中断优先寄存器的高四位中的最高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_cm7.h文件的NVIC相关函数封装到stm32h7rsxx_hal_cortex.c文件中,下面列出我们较为常用的函数进行,想了解更多其他的函数请自行查阅。 1. HAL_NVIC_SetPriorityGrouping函数 HAL_NVIC_SetPriorityGrouping是设置中断优先级分组函数。其声明如下: void HAL_NVIC_SetPriorityGrouping(uint32_t PriorityGroup); l 函数描述: 用于设置中断优先级分组。 l 函数形参: 形参1是中断优先级分组号,可以选择范围:NVIC_PRIORITYGROUP_0到 NVIC_PRIORITYGROUP_4(共5组)。 l 函数返回值:无 l 注意事项: 这个函数在一个工程里基本只调用一次,而且是在程序HAL库初始化函数里面已经被调用,后续就不会再调用了。因为当后续调用设置成不同的中断优先级分组时,有可能造成前面设置好的抢占优先级和响应优先级不匹配。 2. HAL_NVIC_SetPriority函数 HAL_NVIC_SetPriority是设置中断优先级函数。其声明如下: void HAL_NVIC_SetPriority(IRQn_Type IRQn, uint32_t PreemptPriority, uint32_t SubPriority); l 函数描述: 用于设置中断的抢占优先级和响应优先级(子优先级)。 l 函数形参: 形参1是中断号,可以选择范围:IRQn_Type定义的枚举类型,定义在stm32h7r7xx.h。 形参2是抢占优先级,可以选择范围:0到15。 形参3是响应优先级,可以选择范围:0到15。 l 函数返回值:无 3. HAL_NVIC_EnableIRQ函数 HAL_NVIC_EnableIRQ是中断使能函数。其声明如下: void HAL_NVIC_EnableIRQ(IRQn_Type IRQn); l 函数描述: 用于使能中断。 l 函数形参: 形参1是中断号,可以选择范围:IRQn_Type定义的枚举类型,定义在stm32h7r7xx.h。 l 函数返回值:无 4. HAL_NVIC_DisableIRQ函数 HAL_NVIC_DisableIRQ是中断除能函数。其声明如下: void HAL_NVIC_disableIRQ(IRQn_Type IRQn); l 函数描述: 用于中断除能。 函数形参: 形参1是中断号,可以选择范围:IRQn_Type定义的枚举类型,定义在stm32h7r7xx.h。 l 函数返回值:无 5. HAL_NVIC_SystemReset函数 HAL_NVIC_SystemReset是系统复位函数。其声明如下: void HAL_NVIC_SystemReset(void); l 函数描述: 用于软件复位系统。 l 函数形参:无形参 l 函数返回值:无 其他的NVIC函数用得较少,我们就不一一列出来了。NVIC的介绍就到这,下面介绍外部中断。 15.1.2 EXTI简介EXTI即是外部中断和事件控制器,它由三个部分组成:APB接口访问的寄存器模块、事件输入触发模块和屏蔽模块。其中寄存器块包含了所有EXTI寄存器,事件输入触发模块提供事件输入边沿触发逻辑,屏蔽模块为不同的唤醒、中断和事件输出及其屏蔽功能提供事件分配。EXTI的功能框图如下图所示: 图15.1.2.1 EXTI功能框图 由外设对EXTI产生的事件分为可配置事件和直接事件。 可配置事件是来自能够生成脉冲的 IO 或外设的信号,这类事件具有多种特性:可选择的有效触发边沿、中断挂起状态寄存器位、单独的中断和事件生成屏蔽、支持软件触发、可配置唤醒事件。 直接事件是来自其它外设的中断和唤醒源,需要在外设中清除,这类事件具备特性:固定上升沿有效触发、EXTI 中无中断挂起状态寄存器位、单独的中断和实际生产屏蔽、不支持软件触发、直接系统唤醒事件包含一个挂起屏蔽和状态寄存器。 EXTI支持80个中断/事件请求,即包括可配置事件和直接事件。每个中断设有状态位,每个中断/事件都有独立的屏蔽设置,前21个中断/事件如下表 表15.1.2.1 EXTI事件输入映射 由于表格太长,其余的请大家参考《STM32H7Rx参考手册_V6(英文版).pdf》20.4小节的EXTI事件输入映射表格。 从上表可以看出,普通的 GPIO 口事件输入,属于可配置事件,唤醒目标为任意,比如 CPU NVIC 中断控制器,或者连接至 D3 低功耗域,产生唤醒中断。 事件输入0-15对应外部IO口的输入中断,一共是16个外部中断线。STM32H7R7供IO口使用的中断线只有16个,但是STM32H7R7的IO口却远远不止16个,那么STM32H7R7是怎么把16个中断线和IO口一一对应起来的呢?于是STM32就这样设计,GPIO的引脚GPIOx.0 ~ GPIOx.15(x=A,B,C,D,E,F,G,M,N,O,P)分别对应中断线0~15。这样每个中断线对应了最多11个IO口,以线0为例:它对应了GPIOA.0、GPIOB.0、GPIOC.0、GPIOD.0、GPIOE.0、GPIOF.0、GPIOG.0、GPIOM.0、GPION.0、GPIOO.0、GPIOP.0。而中断线每次只能连接到1个IO口上,这样就需要通过配置来决定对应的中断线配置到哪个GPIO上了。 GPIO和中断线映射关系是在寄存器SBS_EXTICR1 ~ SBS_EXTICR4中配置的。 图15.1.2.2 SBS_EXTICR1寄存器 SBS_EXTICR1寄存器配置EXTI0到EXTI3线,包含的外部中断的引脚包括PAx到PKx,x=0到3。SBS_EXTICR2寄存器配置EXTI4到EXTI7线,包含的外部中断的引脚包括PAx到PGx,x=4到7,SBS_EXTICR2寄存器请打开参考手册查看(这里没有截图出来)。SBS_EXTICR3和SBS_EXTICR4以此类推。 另外要注意的是,我们配置SBS相关寄存器前,还需要打开SBS时钟。 15.2 硬件设计1. 例程功能通过外部中断的方式让开发板上的四个独立按键控制LED灯:KEY0控制LED0翻转,KEY1控制LED1翻转,KEY2控制LED0/LED1同时翻转,KEY_UP控制LED0/LED1点亮。 2. 硬件资源 1)LED灯 LED0 – PD14 LED1 – PC0 2)独立按键 KEY0 – PE9 KEY1 – PE8 KEY2 – PE7 KEY_UP – PC13 3. 原理图独立按键硬件部分的原理图,如图15.2.1所示: 图15.2.1 独立按键与STM32H7R7连接原理图 这里需要注意的是:KEY0、KEY1和KEY2设计为采样到按键另一端的低电平为有效,并且外部都没有上下拉电阻,所以需要在STM32H7R7内部设置上下拉以设置空闲电平。 15.3 程序设计 15.3.1 EXTI的HAL库驱动前面讲解HAL_GPIO_Init函数的时候有提到过:HAL库的EXTI外部中断的设置功能整合到HAL_GPIO_Init函数里面,而不是单独独立一个文件。所以我们的外部中断的初始化函数也是用HAL_GPIO_Init函数。 既然是要用到外部中断,所以我们的GPIO的模式要从下面的三个模式中选中一个: #define GPIO_MODE_IT_RISING (MODE_INPUT | EXTI_IT | TRIGGER_RISING) /* 外部中断,上升沿触发检测 */ #define GPIO_MODE_IT_FALLING (MODE_INPUT | EXTI_IT | TRIGGER_FALLING) /* 外部中断,下降沿触发检测 */ #define GPIO_MODE_IT_RISING_FALLING (MODE_INPUT | EXTI_IT | TRIGGER_RISING | TRIGGER_FALLING) /* 外部中断,上升和下降双沿触发检测 */ 程序设计按键为按下触发中断,KEY0、KEY1和KEY2是低电平有效,所以我们要选择下降沿触发检测,而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_stm32h7r7xx.s中做好了。STM32H7R7的IO口外部中断函数只有16个,分别为: void EXTI0_IRQHandler(); void EXTI1_IRQHandler(); void EXTI2_IRQHandler(); void EXTI3_IRQHandler(); void EXTI4_IRQHandler(); void EXTI5_IRQHandler(); void EXTI6_IRQHandler(); void EXTI7_IRQHandler(); void EXTI8_IRQHandler(); void EXTI9_IRQHandler(); void EXTI10_IRQHandler(); void EXTI11_IRQHandler(); void EXTI12_IRQHandler(); void EXTI13_IRQHandler(); void EXTI14_IRQHandler(); void EXTI15_IRQHandler(); 中断线0-15,每个中断线对应一个中断函数。一般情况下,我们可以把中断控制逻辑直接编写在中断服务函数中,但是HAL库把中断处理过程进行了简单封装,请看下面步骤5讲解。 5)编写中断处理回调函数HAL_GPIO_EXTI_Callback HAL库为了用户使用方便,提供了一个中断通用入口函数HAL_GPIO_EXTI_IRQHandler,在该函数内部直接调用回调函数HAL_GPIO_EXTI_Callback。 我们先来看一下HAL_GPIO_EXTI_IRQHandler函数定义: void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin) { if(__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != 0x00U) { __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin); /* 清中断标志位 */ HAL_GPIO_EXTI_Callback(GPIO_Pin); /* 外部中断回调函数 */ } } 该函数实现的作用非常简单,通过入口参数GPIO_Pin判断中断来自哪个IO口,然后清除相应的中断标志位,最后调用回调函数HAL_GPIO_EXTI_Callback()实现控制逻辑。在所有的外部中断服务函数中直接调用外部中断共用处理函数HAL_GPIO_EXTI_IRQHandler,然后在回调函数HAL_GPIO_EXTI_Callback中通过判断中断是来自哪个IO口编写相应的中断服务控制逻辑。 因此我们可以在HAL_GPIO_EXTI_Callback里面实现控制逻辑编写,详见本实验源码。 15.3.2 程序解析1. 外部中断驱动代码下面我们先解析exti.h的程序。 l 外部中断引脚定义 由硬件设计小节,我们知道KEY0、KEY1、KEY2和KEY_UP分别来连接到PE9、PE8、PE7和PC13上,我们做了下面的引脚定义: /* 引脚、中断编号和中断服务函数定义 */ #define WKUP_INT_GPIO_PORT GPIOC #define WKUP_INT_GPIO_PIN GPIO_PIN_13 #define WKUP_INT_GPIO_CLK_ENABLE() do{__HAL_RCC_GPIOC_CLK_ENABLE();}while (0) #define WKUP_INT_IRQn EXTI13_IRQn #define WKUP_INT_IRQHandler EXTI13_IRQHandler #define KEY0_INT_GPIO_PORT GPIOE #define KEY0_INT_GPIO_PIN GPIO_PIN_9 #define KEY0_INT_GPIO_CLK_ENABLE() do{__HAL_RCC_GPIOE_CLK_ENABLE();}while (0) #define KEY0_INT_IRQn EXTI9_IRQn #define KEY0_INT_IRQHandler EXTI9_IRQHandler #define KEY1_INT_GPIO_PORT GPIOE #define KEY1_INT_GPIO_PIN GPIO_PIN_8 #define KEY1_INT_GPIO_CLK_ENABLE() do{__HAL_RCC_GPIOE_CLK_ENABLE();}while (0) #define KEY1_INT_IRQn EXTI8_IRQn #define KEY1_INT_IRQHandler EXTI8_IRQHandler #define KEY2_INT_GPIO_PORT GPIOE #define KEY2_INT_GPIO_PIN GPIO_PIN_7 #define KEY2_INT_GPIO_CLK_ENABLE() do{__HAL_RCC_GPIOE_CLK_ENABLE();}while (0) #define KEY2_INT_IRQn EXTI7_IRQn #define KEY2_INT_IRQHandler EXTI7_IRQHandler KEY0、KEY1、KEY2和KEY_UP分别来连接到PE9、PE8、PE7和PC13,即对应了EXTI9、EXTI8、EXTI7和EXTI13这四条外部中断线。 下面我们再解析exti.c的程序,先看外部中断初始化函数,其定义如下: /** * @param 无 * @retval 无 */ void exti_init(void) { GPIO_InitTypeDef gpio_init_struct = {0};
/* 使能GPIO端口时钟 */ WKUP_INT_GPIO_CLK_ENABLE(); KEY0_INT_GPIO_CLK_ENABLE(); KEY1_INT_GPIO_CLK_ENABLE(); KEY2_INT_GPIO_CLK_ENABLE();
/* 配置WKUP控制引脚 */ gpio_init_struct.Pin = WKUP_INT_GPIO_PIN; gpio_init_struct.Mode = GPIO_MODE_IT_RISING; gpio_init_struct.Pull = GPIO_PULLDOWN; gpio_init_struct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(WKUP_INT_GPIO_PORT, &gpio_init_struct);
/* 配置KEY0控制引脚 */ gpio_init_struct.Pin = KEY0_INT_GPIO_PIN; gpio_init_struct.Mode = GPIO_MODE_IT_FALLING; gpio_init_struct.Pull = GPIO_PULLUP; gpio_init_struct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(KEY0_INT_GPIO_PORT, &gpio_init_struct);
/* 配置KEY1控制引脚 */ gpio_init_struct.Pin = KEY1_INT_GPIO_PIN; gpio_init_struct.Mode = GPIO_MODE_IT_FALLING; gpio_init_struct.Pull = GPIO_PULLUP; gpio_init_struct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(KEY1_INT_GPIO_PORT, &gpio_init_struct);
/* 配置KEY2控制引脚 */ gpio_init_struct.Pin = KEY2_INT_GPIO_PIN; gpio_init_struct.Mode = GPIO_MODE_IT_FALLING; gpio_init_struct.Pull = GPIO_PULLUP; gpio_init_struct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(KEY2_INT_GPIO_PORT, &gpio_init_struct);
/* 配置中断优先级并使能中断 */ HAL_NVIC_SetPriority(WKUP_INT_IRQn, 0, 0); HAL_NVIC_EnableIRQ(WKUP_INT_IRQn); HAL_NVIC_SetPriority(KEY0_INT_IRQn, 0, 0); HAL_NVIC_EnableIRQ(KEY0_INT_IRQn); HAL_NVIC_SetPriority(KEY1_INT_IRQn, 0, 0); HAL_NVIC_EnableIRQ(KEY1_INT_IRQn); HAL_NVIC_SetPriority(KEY2_INT_IRQn, 0, 0); HAL_NVIC_EnableIRQ(KEY2_INT_IRQn); } 外部中断初始化函数主要做了两件事情,先是调用IO口初始化函数HAL_GPIO_Init来初始化IO口,然后设置中断优先级并使能中断线。 4个外部中断服务函数,其定义如下: /** * @brief WKUP按键外部中断服务函数 * @param 无 * @retval 无 */ void WKUP_INT_IRQHandler(void) { HAL_GPIO_EXTI_IRQHandler(WKUP_INT_GPIO_PIN); }
/** * @brief KEY0按键外部中断服务函数 * @param 无 * @retval 无 */ void KEY0_INT_IRQHandler(void) { HAL_GPIO_EXTI_IRQHandler(KEY0_INT_GPIO_PIN); }
/** * @brief KEY1按键外部中断服务函数 * @param 无 * @retval 无 */ void KEY1_INT_IRQHandler(void) { HAL_GPIO_EXTI_IRQHandler(KEY1_INT_GPIO_PIN); }
/** * @brief KEY2按键外部中断服务函数 * @param 无 * @retval 无 */ void KEY2_INT_IRQHandler(void) { HAL_GPIO_EXTI_IRQHandler(KEY2_INT_GPIO_PIN); } 所有的外部中断服务函数里都只调用了同样一个函数HAL_GPIO_EXTI_IRQHandler,该函数是外部中断共用入口函数,函数内部会进行中断标志位清零, 并且调用中断处理共用回调函数HAL_GPIO_EXTI_Callback。但是它们的形参不同,我们的回调函数也是根据形参去判断是哪个IO口的外部中断线被触发。 外部中断回调函数,其定义如下: /** * @brief 中断服务程序中需要做的事情 在HAL库中所有的外部中断服务函数都会调用此函数 * @param GPIO_Pin:中断引脚号 * @retval 无 */ void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { delay_ms(20); /* 机械按键消抖(仅演示,切勿在实际工程的中断服务函数种进行阻塞延时) */
switch (GPIO_Pin) { case WKUP_INT_GPIO_PIN: /* WKUP按键对应引脚发生中断 */ { LED0(0); /* 开启LED0 */ LED1(0); /* 开启LED1 */ break; } case KEY0_INT_GPIO_PIN: /* KEY0按键对应引脚发生中断 */ { LED0_TOGGLE(); /* 翻转LED0状态 */ break; } case KEY1_INT_GPIO_PIN: /* KEY1按键对应引脚发生中断 */ { LED1_TOGGLE(); /* 翻转LED1状态 */ break; } case KEY2_INT_GPIO_PIN: /* KEY2按键对应引脚发生中断 */ { LED0_TOGGLE(); /* 翻转LED0状态 */ LED1_TOGGLE(); /* 翻转LED1状态 */ break; } } } 外部中断回调函数HAL_GPIO_EXTI_Callback是用来编写真正的外部中断控制逻辑。该函数有一个形参就是IO引脚号。所以我们在该函数内部,一般通过判断IO引脚号来确定中断是来自哪个IO口,也就是哪个中断线,然后编写相应的控制逻辑。所以在该函数内部,我们通过switch语句判断IO口来源,例如是来自GPIO_PIN_0,那么一定是来自PA0,因为中断线一次只能连接一个IO口,而四个IO口中引脚号为0的IO口只有PA0,所以中断线0一定是连接PA0,也就是外部中断由PA0触发。其他的引脚号的逻辑类似。 2. main.c代码在main.c里面编写如下代码: int main(void) { sys_mpu_config(); /* 配置MPU */ sys_cache_enable(); /* 使能Cache */ HAL_Init(); /* 初始化HAL库 */ sys_stm32_clock_init(300, 6, 2); /* 配置时钟,600MHz */ delay_init(600); /* 初始化延时 */ usart_init(115200); /* 初始化串口 */ led_init(); /* 初始化LED */ exti_init(); /* 初始化外部中断 */
while (1) {
} } 首先是调用系统级别的初始化:初始化 HAL库、系统时钟和延时函数。接下来,调用led_init来初始化LED灯,调用exti_init函数初始化外部中断。最后逻辑控制代码都在中断回调函数中完成。 15.4 下载验证在下载好程序后,我们可以按KEY0、KEY1、KEY2和KEY_UP来看看LED灯的变化,是否和我们预期的结果一致? 至此,我们的本章的学习就结束了。本章学习了STM32H7R7外部中断的使用方法。
|