OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 2704|回复: 0

《STM32F407 探索者开发指南》第十六章 外部中断实验

[复制链接]

1148

主题

1160

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
4946
金钱
4946
注册时间
2019-5-8
在线时间
1256 小时
发表于 2023-7-7 11:35:20 | 显示全部楼层 |阅读模式
本帖最后由 正点原子运营 于 2023-7-6 15:58 编辑

第十六章 外部中断实验
1)实验平台:正点原子探索者STM32F407开发板

2) 章节摘自【正点原子】STM32F407开发指南 V1.1


4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/stm32/zdyz_stm32f407_explorerV3.html

5)正点原子官方B站:https://space.bilibili.com/394620890

6)STM32技术交流QQ群:151941872

155537c2odj87vz1z9vj6l.jpg

155537nfqovl2gg9faaol9.png

在前面几章的学习中,我们掌握了STM32F407的IO口最基本的操作。本章我们将介绍如何把STM32F407的IO口作为外部中断输入来使用,在本章中,我们将以中断的方式,实现我们在第十五章所实现的功能。
本章分为如下几个小节:
16.1 STM32F407 NVIC和外部中断简介
16.2 硬件设计
16.3 程序设计
16.4 下载验证

16.1 NVIC和EXTI简介
16.1.1 NVIC简介
什么是NVIC?NVIC即嵌套向量中断控制器,全称Nested vectored interrupt controller。它是内核的器件,所以它的更多描述可以看内核有关的资料。M3/M4/M7内核都是支持256个中断,其中包含了16个系统中断和240个外部中断,并且具有256级的可编程中断设置。然而芯片厂商一般不会把内核的这些资源全部用完,如STM32F407的系统中断有10个,外部中断有82个。这个在讲解启动文件的时候有提到过,可以回顾一下。下面我们看看系统中断部分:                     
image001.png
表16.1.1.1 中断向量表-系统中断部分

关于82个外部中断部分在《STM32F4xx参考手册_V4(中文版).pdf》的10.2小节有详细的列表,这里就不列出来了。STM32F407的中断向量表在stm32f407xx.h文件中被定义。

16.1.1.1 NVIC寄存器
NVIC相关的寄存器定义了可以在core_cm4.h文件中找到。我们直接通过程序的定义来分析NVIC相关的寄存器,其定义如下:
  1. typedef struct
  2. {
  3. __IOM   uint32_t ISER[8U];             /* 中断使能寄存器 */
  4. uint32_t RESERVED0[24U];
  5. __IOM uint32_t ICER[8U];               /* 中断清除使能寄存器 */
  6. uint32_t RSERVED1[24U];
  7. __IOM uint32_t ISPR[8U];               /* 中断使能挂起寄存器 */
  8. uint32_t RESERVED2[24U];
  9. __IOM uint32_t ICPR[8U];               /* 中断解挂寄存器 */
  10. uint32_t RESERVED3[24U];
  11. __IOM uint32_t IABR[8U];               /* 中断有效位寄存器 */
  12. uint32_t RESERVED4[56U];
  13. __IOM uint8_t  IP[240U];               /* 中断优先级寄存器(8Bit 位宽) */
  14. uint32_t RESERVED5[644U];
  15. __OM  uint32_t STIR;                    /* 中断触发中断寄存器 */
  16. }  NVIC_Type;
复制代码
STM32F407的中断在这些寄存器的控制下有序的执行的。只有了解这些中断寄存器,才能方便的使用STM32F407的中断。下面重点介绍这几个寄存器:

ISER[8]:ISER全称是:Interrupt Set Enable Registers,这是一个中断使能寄存器组。上面说了CM4内核支持256个中断,这里用8个32位寄存器来控制,每个位控制一个中断。但是STM32F407的可屏蔽中断最多只有82个,所以对我们来说,有用的就是两个(ISER[0~3]),总共可以表示128个中断。而STM32F407只用了其中的82个。ISER[0]的bit0~31分别对应中断0~31;ISER[1]的bit0~31对应中断32~63; ISER[2]的bit0~16对应中断64~81,这样总共82个中断就可以分别对应上了。你要使能某个中断,必须设置相应的ISER位为1,使该中断被使能(这里仅仅是使能,还要配合中断分组、屏蔽、IO口映射等设置才算是一个完整的中断设置)。具体每一位对应哪个中断,请参考stm32f407xx.h里面的第68行。

ICER[8]:全称是:Interrupt Clear Enable Registers,是一个中断除能寄存器组。该寄存器组与ISER的作用恰好相反,是用来清除某个中断的使能的。其对应位的功能,也和ISER一样。这里要专门设置一个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,是一个中断优先级控制的寄存器组。这个寄存器组相当重要!STM32F407的中断分组与这个寄存器组密切相关。IP寄存器组由240个8bit的寄存器组成,每个可屏蔽中断占用8bit,这样总共可以表示240个可屏蔽中断。而STM32F407只用到了其中的82个。IP[81]~IP[0]分别对应中断81~0。而每个可屏蔽中断占用的8bit并没有全部使用,而是只用了高4位。这4位,又分为抢占优先级和子优先级。抢占优先级在前,子优先级在后。而这两个优先级各占几个位又要根据SCB->AIRCR中的中断分组设置来决定。关于中断优先级控制的寄存器组我们下面详细讲解。

16.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。

对于NVCI的中断优先级分组:STM32F407将中断分为5个组,组0~4。该分组的设置是由SCB->AIRCR寄存器的bit10~8来定义的。具体的分配关系如表16.1.1.2.1所示:
QQ截图20230706155659.png
表16.1.1.2.1 AIRCR中断分组设置表

通过这个表,我们就可以清楚的看到组0~4对应的配置关系,例如优先级分组设置为3,那么此时所有的82个中断,每个中断的中断优先寄存器的高四位中的最高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却不可以相互打断!

16.1.1.3 NVIC相关函数
ST公司把core_cm4.h文件的NVIC相关函数封装到stm32f4xx_hal_cortex.c文件中,下面列出我们较为常用的函数进行,想了解更多其他的函数请自行查阅。

1.      HAL_NVIC_SetPriorityGrouping函数
HAL_NVIC_SetPriorityGrouping是设置中断优先级分组函数。其声明如下:
  1. voidHAL_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是设置中断优先级函数。其声明如下:
  1. voidHAL_NVIC_SetPriority(IRQn_Type IRQn, uint32_t PreemptPriority,uint32_t SubPriority);
复制代码
l  函数描述:
用于设置中断的抢占优先级和响应优先级(子优先级)。

l  函数形参:
形参1是中断号,可以选择范围:IRQn_Type定义的枚举类型,定义在stm32f407xx.h。

形参2是抢占优先级,可以选择范围:0到15。

形参3是响应优先级,可以选择范围:0到15。

l  函数返回值:

3.      HAL_NVIC_EnableIRQ函数
HAL_NVIC_EnableIRQ是中断使能函数。其声明如下:
  1. voidHAL_NVIC_EnableIRQ(IRQn_Type IRQn);
复制代码
l  函数描述:
用于使能中断。

l  函数形参:
形参1是中断号,可以选择范围:IRQn_Type定义的枚举类型,定义在stm32f407xx.h。

l  函数返回值:

4.      HAL_NVIC_DisableIRQ函数
HAL_NVIC_DisableIRQ是中断失能函数。其声明如下:
  1. void HAL_NVIC_DisableIRQ(IRQn_Type IRQn);
复制代码
l  函数描述:
用于中断除能。

l  函数形参:无形参

l  函数返回值:

5.      HAL_NVIC_SystemReset函数
HAL_NVIC_SystemReset是系统复位函数。其声明如下:
  1. voidHAL_NVIC_SystemReset(void);
复制代码
l  函数描述:
用于软件复位系统。

l  函数形参:无形参

l  函数返回值:

其他的NVIC函数用得较少,我们就不一一列出来了。NVIC的介绍就到这,下面介绍外部中断。

16.1.2 EXTI简介
EXTI即是外部中断和事件控制器,它是由20个产生事件/中断请求的边沿检测器组成。每一条输入线都可以独立地配置输入类型(脉冲或挂起)和对应的触发事件(上升沿或下降沿或者双边沿都触发)。每个输入线都可以独立地被屏蔽。挂起寄存器保持着状态线的中断请求。

EXTI的功能框图是最直接把有关EXTI的知识点连接起来的图,掌握了该图的来龙去脉,就会对EXTI有了一个整体熟悉,编程时候可以得心应手。EXTI的功能框图如图16.1.2.1。
image003.png
图16.1.2.1 EXTI功能框图

从EXTI功能框图可以看到有两条主线,一条是由输入线到NVIC中断控制器,一条是由输入线到脉冲发生器。这就恰恰是EXTI的两大部分功能,产生中断与产生事件,两者从硬件上就存在不同。

下面让我们看一下EXTI功能框图的产生中断的线路,最终信号是流入NVIC控制器中。输入线是线路的信息输入端,它可以通过配置寄存器设置为任何一个GPIO口,或者是一些外设的事件。输入线一般都是存在电平变化的信号。

标号①是一个边沿检测电路,包括边沿检测电路,上升沿触发选择寄存器(EXTI_RTSR)和下降沿触发选择寄存器(EXTI_FTSR)。边沿检测电路以输入线作为信号输入端,如果检测到有边沿跳变就输出有效信号‘1’,就输出有效信号‘1’到标号②部分电路,否则输入无效信号‘0’。边沿跳变的标准在于开始的时候对于上升沿触发选择寄存器或下降沿触发选择寄存器对应位的设置,对应位的设置可以参照一下表16.1.2.1。

标号②是一个或门电路,它的两个信号输入端分别是软件中断事件寄存器(EXTI_SWIER)和边沿检测电路的输入信号。或门电路只要输入端有信号‘1’,就会输出‘1’,所以就会输出‘1’到标号③电路和标号④电路。通过对软件中断事件寄存器的读写操作就可以启动中断/事件线,即相当于输出有效信号‘1’到或门电路输入端。

标号③是一个与门电路,它的两个信号输入端分别是中断屏蔽寄存器(EXTI_IMR)和标号②电路输出信号。与门电路要求输入都为‘1’才输出‘1’,这样子的情况下,如果中断屏蔽寄存器(EXTI_IMR)设置为0时,不管从标号②电路输出的信号特性如何,最终标号③电路输出的信号都是0;假如中断屏蔽寄存器(EXTI_IMR)设置为1时,最终标号③电路输出的信号才由标号②电路输出信号决定,这样子就可以简单控制EXTI_IMR来实现中断的目的。标号④电路输出‘1’就会把请求挂起寄存器(EXTI_PR)对应位置1。

最后,请求挂起寄存器(EXTI_PR)的内容就输出到NVIC内,实现系统中断事件的控制。

接下来我们看看EXTI功能框图的产生事件的线路。

产生事件线路是从标号2之后与中断线路有所不用,之前的线路都是共用的。标号④是一个与门,输入端来自标号2电路以及来自于事件屏蔽寄存器(EXTI_EMR)。如果EXTI_EMR寄存器设置为0,那不管标号2电路输出的信号是‘0’还是‘1’,最终标号4输出的是‘0’;如果EXTI_EMR寄存器设置为1,最终标号④电路输出信号就由标号③电路输出的信号决定,这样子就可以简单的控制EXTI_EMR来实现是否产生事件的目的。

标号④电路输出有效信号1就会使脉冲发生器电路产生一个脉冲,而无效信号就不会使其产生脉冲信号。脉冲信号产生可以给其他外设电路使用,例如定时器,模拟数字转换器等,这样的脉冲信号一般用来触发TIM或者ADC开始转换。

产生中断线路目的使把输入信号输入到NVIC,进一步运行中断服务函数,实现功能。而产生事件线路目的是传输一个脉冲信号给其他外设使用,属于硬件级功能。

EXTI支持23个外部中断/事件请求,这些都是信息输入端,也就是上面提及到了输入线,具体如下:
EXTI线0~15:对应外部IO口的输入中断
EXTI线16:连接到PVD输出
EXTI线17:连接到RTC闹钟事件
EXTI线18:连接到USB唤醒事件
EXTI线19:连接到以太网唤醒事件
EXTI线20:连接到USB OTG HS(在FS中配置)唤醒事件
EXTI线21:连接到RTC入侵和时间戳事件
EXTI线22:连接到RTC唤醒事件

从上面可以看出,STM32F407供给IO口使用的中断线只有16个,但是STM32F407的IO口却远远不止16个,所以STM32把GPIO管脚GPIOx.0~GPIOx.15(x=A,B,C,D,E,F,G)分别对应中断线0~15。这样子每个中断线对应了最多7个IO口,以线0为例:它对应了GPIOA.0、GPIOB.0、GPIOC.0、GPIOD.0、GPIOE.0、GPIOF.0和GPIOG.0。而中断线每次只能连接到1个IO口上,这样就需要通过配置决定对应的中断线配置到哪个GPIO上了。

16.2 硬件设计
1. 例程功能
通过外部中断的方式让开发板上的三个独立按键控制LED灯:KEY0控制LED0翻转,KEY1控制LED1翻转,KEY2控制LED1和LED2同时翻转,KEY_UP控制蜂鸣器翻转。

2. 硬件资源
1)LED灯
LED0 – PE0
LED1 – PE1
2)独立按键
KEY0   – PE2
KEY1   – PE3
KEY2   – PE4
KEY_UP – PA0
3)蜂鸣器
BEEP – PF8

3. 原理图
image005.png
图16.2.1 独立按键与STM32F4连接原理图

这里需要注意的是:KEY0、KEY1和KEY2设计为采样到按键另一端的低电平为有效,并且外部都没有上下拉电阻,所以需要在STM32F407内部设置上下拉。

16.3 程序设计
16.3.1 EXTI的HAL库驱动
前面讲解HAL_GPIO_Init函数的时候有提到过:HAL库的EXTI外部中断的设置功能整合到HAL_GPIO_Init函数里面,而不是单独独立一个文件。所以我们的外部中断的初始化函数也是用HAL_GPIO_Init函数。
既然是要用到外部中断,所以我们的GPIO的模式要从下面的三个模式中选中一个:
  1. #define GPIO_MODE_IT_RISING            (0x10110000U)  /* 外部中断,上升沿触发检测 */
  2. #define GPIO_MODE_IT_FALLING           (0x10210000U)  /* 外部中断,下降沿触发检测 */
  3. /* 外部中断,上升和下降双沿触发检测 */
  4. #define GPIO_MODE_IT_RISING_FALLING    (0x10310000U)
复制代码
程序设计按键为按下触发中断,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_stm32f407xx.s中写好了。STM32F407的IO口外部中断函数只有7个,分别为:
  1. voidEXTI0_IRQHandler();
  2. voidEXTI1_IRQHandler();
  3. voidEXTI2_IRQHandler();
  4. voidEXTI3_IRQHandler();
  5. voidEXTI4_IRQHandler();
  6. voidEXTI9_5_IRQHandler();
  7. voidEXTI15_10_IRQHandler();
复制代码
中断线0-4,每个中断线对应一个中断函数,中断线5-9共用中断函数EXTI9_5_IRQHandler,中断线10-15共用中断函数EXTI15_10_IRQHandler。一般情况下,我们可以把中断控制逻辑直接编写在中断服务函数中,但是HAL库把中断处理过程进行了简单封装,请看下面步骤5讲解。

5)编写中断处理回调函数HAL_GPIO_EXTI_Callback
HAL库为了用户使用方便,提供了一个中断通用入口函数HAL_GPIO_EXTI_IRQHandler,在该函数内部直接调用回调函数HAL_GPIO_EXTI_Callback。

我们先来看一下HAL_GPIO_EXTI_IRQHandler函数定义:
  1. voidHAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
  2. {
  3.   if(__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != 0x00U)
  4.   {
  5.    __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);     /* 清中断标志位 */
  6.     HAL_GPIO_EXTI_Callback(GPIO_Pin);       /* 外部中断回调函数 */
  7.   }
  8. }
复制代码
该函数实现的作用非常简单,通过入口参数GPIO_Pin判断中断来自哪个IO口,然后清除相应的中断标志位,最后调用回调函数HAL_GPIO_EXTI_Callback()实现控制逻辑。在所有的外部中断服务函数中直接调用外部中断共用处理函数HAL_GPIO_EXTI_IRQHandler,然后在回调函数HAL_GPIO_EXTI_Callback中通过判断中断是来自哪个IO口编写相应的中断服务控制逻辑。

因此我们可以在HAL_GPIO_EXTI_Callback里面实现控制逻辑编写,详见本实验源码

16.3.2 程序流程图
QQ截图20230706155814.png
图16.3.2.1外部中断实验程序流程图

主程序初始外设,在按键初始化时初始化按键的采样边缘。

16.3.3 程序解析
1. 外部中断驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。外部中断(EXTI)驱动源码包括两个文件:exti.c和exti.h。

下面我们先解析exti.h的程序。

l  外部中断引脚定义
由硬件设计小节,我们知道KEY0、KEY1、KEY2和KEY_UP分别来连接到PE4、PE3、PE2和PA0上,我们做了下面的引脚定义。
  1. /* 引脚 和 中断编号 & 中断服务函数 定义 */
  2. #define KEY0_INT_GPIO_PORT          GPIOE
  3. #define KEY0_INT_GPIO_PIN           GPIO_PIN_4
  4. /* PE口时钟使能 */
  5. #define KEY0_INT_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0)
  6. #define KEY0_INT_IRQn                 EXTI4_IRQn
  7. #define KEY0_INT_IRQHandler          EXTI4_IRQHandler
  8. #define KEY1_INT_GPIO_PORT           GPIOE
  9. #define KEY1_INT_GPIO_PIN            GPIO_PIN_3
  10. /* PE口时钟使能 */
  11. #define KEY1_INT_GPIO_CLK_ENABLE() do{__HAL_RCC_GPIOE_CLK_ENABLE(); }while(0)
  12. #define KEY1_INT_IRQn                EXTI3_IRQn
  13. #define KEY1_INT_IRQHandler         EXTI3_IRQHandler
  14. #define KEY2_INT_GPIO_PORT           GPIOE
  15. #define KEY2_INT_GPIO_PIN            GPIO_PIN_2
  16. /* PE口时钟使能 */
  17. #define KEY2_INT_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0)
  18. #define KEY2_INT_IRQn                 EXTI2_IRQn
  19. #define KEY2_INT_IRQHandler         EXTI2_IRQHandler
  20. #define WKUP_INT_GPIO_PORT           GPIOA
  21. #define WKUP_INT_GPIO_PIN            GPIO_PIN_0
  22. /* PE口时钟使能 */
  23. #define WKUP_INT_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)
  24. #define WKUP_INT_IRQn                 EXTI0_IRQn
  25. #define WKUP_INT_IRQHandler         EXTI0_IRQHandler
复制代码
KEY0、KEY1、KEY2和KEY_UP分别连接PE4、PE3、PE2和PA0,即对应了EXTI4、EXTI3、EXTI2和EXTI0这四条外部中断线。这里需要注意的是EXTI0到EXTI4都是有单独的中断向量,EXTI5到EXTI9是公用EXTI9_5_IRQn,EXTI10到EXTI15是公用EXTI15_10_IRQn。

下面我们再解析exti.c的程序,先看外部中断初始化函数,其定义如下:
  1. /**
  2. * @brief       外部中断初始化程序
  3. * @param       无
  4. * @retval      无
  5. */
  6. void extix_init(void)
  7. {
  8.    GPIO_InitTypeDef gpio_init_struct;
  9.    
  10. key_init();
  11.    gpio_init_struct.Pin = KEY0_INT_GPIO_PIN;
  12.    gpio_init_struct.Mode = GPIO_MODE_IT_FALLING;           /* 下升沿触发 */
  13.    gpio_init_struct.Pull = GPIO_PULLUP;
  14.     /* KEY0配置为下降沿触发中断 */
  15.     HAL_GPIO_Init(KEY0_INT_GPIO_PORT, &gpio_init_struct);
  16.    gpio_init_struct.Pin = KEY1_INT_GPIO_PIN;
  17.    gpio_init_struct.Mode = GPIO_MODE_IT_FALLING;          /* 下降沿触发 */
  18.    gpio_init_struct.Pull = GPIO_PULLUP;
  19.     /* KEY1配置为下降沿触发中断 */
  20.     HAL_GPIO_Init(KEY1_INT_GPIO_PORT, &gpio_init_struct);
  21.    gpio_init_struct.Pin = KEY2_INT_GPIO_PIN;
  22.    gpio_init_struct.Mode = GPIO_MODE_IT_FALLING;           /* 下降沿触发 */
  23.    gpio_init_struct.Pull = GPIO_PULLUP;
  24.     /* KEY2配置为下降沿触发中断 */
  25.     HAL_GPIO_Init(KEY2_INT_GPIO_PORT, &gpio_init_struct);
  26.    gpio_init_struct.Pin = WKUP_INT_GPIO_PIN;
  27.    gpio_init_struct.Mode = GPIO_MODE_IT_RISING;            /* 下降沿触发 */
  28.    gpio_init_struct.Pull = GPIO_PULLDOWN;
  29.     /* KEY_UP配置为下降沿触发中断 */
  30.     HAL_GPIO_Init(WKUP_INT_GPIO_PORT, &gpio_init_struct);
  31.    HAL_NVIC_SetPriority(KEY0_INT_IRQn, 0, 2);               /* 抢占0,子优先级2 */
  32.    HAL_NVIC_EnableIRQ(KEY0_INT_IRQn);                        /* 使能中断线4 */
  33.    HAL_NVIC_SetPriority(KEY1_INT_IRQn, 1, 2);               /* 抢占1,子优先级2 */
  34.    HAL_NVIC_EnableIRQ(KEY1_INT_IRQn);                       /* 使能中断线3 */
  35.    
  36.    HAL_NVIC_SetPriority(KEY2_INT_IRQn, 2, 2);               /* 抢占2,子优先级2 */
  37.     HAL_NVIC_EnableIRQ(KEY2_INT_IRQn);                         /* 使能中断线2 */
  38.    
  39.    HAL_NVIC_SetPriority(WKUP_INT_IRQn, 3, 2);               /* 抢占3,子优先级2 */
  40.    HAL_NVIC_EnableIRQ(WKUP_INT_IRQn);                        /* 使能中断线0 */
  41. }
复制代码
外部中断初始化函数主要做了两件事情,先是调用IO口初始化函数HAL_GPIO_Init来初始化IO口,然后设置中断优先级并使能中断线。

3个外部中断服务函数,其定义如下:
  1. /**
  2. * @brief       KEY0 外部中断服务程序
  3. * @param       无
  4. * @retval      无
  5. */
  6. voidKEY0_INT_IRQHandler(void)
  7. {
  8.     /* 调用中断处理公用函数 清除KEY0所在中断线 的中断标志位 */
  9.     HAL_GPIO_EXTI_IRQHandler(KEY0_INT_GPIO_PIN);
  10.     /* HAL库默认先清中断再处理回调,退出时再清一次中断,避免按键抖动误触发 */
  11.     __HAL_GPIO_EXTI_CLEAR_IT(KEY0_INT_GPIO_PIN);
  12. }
  13. /**
  14. * @brief       KEY1 外部中断服务程序
  15. * @param       无
  16. * @retval      无
  17. */
  18. voidKEY1_INT_IRQHandler(void)
  19. {
  20.     /* 调用中断处理公用函数 清除KEY1所在中断线 的中断标志位 */
  21.     HAL_GPIO_EXTI_IRQHandler(KEY1_INT_GPIO_PIN);
  22.     /* HAL库默认先清中断再处理回调,退出时再清一次中断,避免按键抖动误触发 */
  23.     __HAL_GPIO_EXTI_CLEAR_IT(KEY1_INT_GPIO_PIN);
  24. }
  25. /**
  26. * @brief       KEY2 外部中断服务程序
  27. * @param       无
  28. * @retval      无
  29. */
  30. voidKEY2_INT_IRQHandler(void)
  31. {
  32.     /* 调用中断处理公用函数 清除KEY2所在中断线 的中断标志位 */
  33.     HAL_GPIO_EXTI_IRQHandler(KEY2_INT_GPIO_PIN);
  34.     /* HAL库默认先清中断再处理回调,退出时再清一次中断,避免按键抖动误触发 */
  35.     __HAL_GPIO_EXTI_CLEAR_IT(KEY2_INT_GPIO_PIN);
  36. }
  37. /**
  38. * @brief       WKUP 外部中断服务程序
  39. * @param       无
  40. * @retval      无
  41. */
  42. void WKUP_INT_IRQHandler(void)
  43. {
  44.     /* 调用中断处理公用函数 清除WKUP所在中断线 的中断标志位 */
  45.     HAL_GPIO_EXTI_IRQHandler(WKUP_INT_GPIO_PIN);
  46.     /* HAL库默认先清中断再处理回调,退出时再清一次中断,避免按键抖动误触发 */
  47.     __HAL_GPIO_EXTI_CLEAR_IT(WKUP_INT_GPIO_PIN);
  48. }
复制代码
所有的外部中断服务函数里都只调用了同样一个函数HAL_GPIO_EXTI_IRQHandler,该函数是外部中断共用入口函数,函数内部会进行中断标志位清零,并且调用中断处理共用回调函数HAL_GPIO_EXTI_Callback。但是它们的形参不同,我们的回调函数也是根据形参去判断是哪个IO口的外部中断线被触发。

外部中断回调函数,其定义如下:
  1. /**
  2. *@brief       外部中断回调函数
  3. * @param       GPIO_Pin: 中断引脚号
  4. *@note        在HAL库中所有的外部中断服务函数都会调用此函数
  5. *@retval      无
  6. */
  7. voidHAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
  8. {
  9. /* 消抖,此处为了方便使用了延时函数,实际代码中禁止在中断服务函数中调用
  10. 任何delay之类的延时函数!!! */
  11.    delay_ms(20);     
  12.     switch (GPIO_Pin)
  13.     {
  14.        caseKEY0_INT_GPIO_PIN:
  15.            if (KEY0 == 0)         /* KEY0U中断 */
  16.            {
  17.                 LED0_TOGGLE();     /* LED0 状态取反 */   
  18.            }
  19.            break;
  20.        caseKEY1_INT_GPIO_PIN:
  21.            if (KEY1 == 0)         /* KEY1中断 */
  22.            {
  23.                 LED1_TOGGLE();     /* LED1 状态取反 */
  24.            }
  25.            break;
  26.        case KEY2_INT_GPIO_PIN:
  27.            if (KEY2 == 0)         /* KEY2中断 */
  28.            {
  29.                 LED0_TOGGLE();     /* LED0 状态取反 */   
  30.                 LED1_TOGGLE();     /* LED1 状态取反 */
  31.            }
  32.            break;
  33.        case WKUP_INT_GPIO_PIN:
  34.            if (WKUP == 1)         /* KEY_UP中断 */
  35.            {
  36.                 BEEP_TOGGLE();     /* 蜂鸣器状态取反 */   
  37.            }
  38.            break;
  39.        default : break;
  40.     }
  41. }
复制代码
外部中断回调函数HAL_GPIO_EXTI_Callback是用来编写真正的外部中断控制逻辑。该函数有一个形参就是IO引脚号。所以我们在该函数内部,一般通过判断IO引脚号来确定中断是来自哪个IO口,也就是哪个中断线,然后编写相应的控制逻辑。所以在该函数内部,我们通过switch语句判断IO口来源,例如是来自GPIO_PIN_2,那么一定是来自PE2,因为中断线一次只能连接一个IO口,而四个IO口中引脚号为2的IO口只有PE2,所以中断线2一定是连接PE2,也就是外部中断由PE2触发。其他的引脚号的逻辑类似。

2. main.c代码
在main.c里面编写如下代码:
  1. #include "./SYSTEM/sys/sys.h"
  2. #include "./SYSTEM/usart/usart.h"
  3. #include "./SYSTEM/delay/delay.h"
  4. #include "./BSP/LED/led.h"
  5. #include "./BSP/BEEP/beep.h"
  6. #include "./BSP/EXTI/exti.h"
  7. int main(void)
  8. {
  9.    HAL_Init();                                 /* 初始化HAL库 */
  10.    sys_stm32_clock_init(336, 8, 2, 7);     /* 设置时钟,168Mhz */
  11.    delay_init(168);                            /* 延时初始化 */
  12.    usart_init(115200);                        /* 串口初始化为115200 */
  13.    led_init();                                 /* 初始化LED */
  14.    beep_init();                                /* 初始化蜂鸣器 */
  15.    extix_init();                               /* 初始化外部中断输入 */
  16. LED0(0);                                     /* 先点亮红灯 */
  17.     while (1)
  18.     {
  19.        delay_ms(1000);
  20.     }
  21. }
  22. }
复制代码
首先是调用系统级别的初始化:初始化 HAL库、系统时钟和延时函数。接下来,调用led_init来初始化LED灯,调用beep_init函数初始化蜂鸣器,调用extix_init函数初始化外部中断,点亮红灯。最后在无限循环里面执行延时1000ms的重复动作。逻辑控制代码都在中断回调函数中完成。

16.4 下载验证
在下载好程序后,我们可以按KEY0、KEY1、KEY2来看看LED灯的变或者按KEY_UP看看蜂鸣器的变化,是否和我们预期的结果一致?

至此,我们的本章的学习就结束了。本章学习了STM32F407外部中断的使用方法。
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-4-18 06:38

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表