文章目录
- 定时器中断
- 外部中断(EXIT)
-
- 概念与介绍
- 使用的介绍
- 使能IO时钟&基本配置
- 中断优先级(NVIC)
- 中断服务函数
- 编写中断处理的回调函数HAL_GPIO_EXTI_Callback
- 相关概念
-
- 事件、中断事件、中断
- 中断服务函数使用的注意事项
- 附录
-
- 程序代码
定时器中断
外部中断(EXIT)
概念与介绍
STM32F7 的每个 IO 都可以作为外部中断的中断输入口,STM32F7 的中断控制器支持 22 个外部中断/事件请求。每个中断设有状态位,每个中断/事件都有独立的触发和屏蔽设置。STM32F7的 23 个外部中断为:
端口 | 对应中断 |
---|---|
EXTI 线 0~15: | 对应外部 IO 口的输入中断。 |
EXTI 线 16: | 连接到 PVD 输出。 |
EXTI 线 17: | 连接到 RTC 闹钟事件。 |
EXTI 线 18: | 连接到 USB OTG FS 唤醒事件。 |
EXTI 线 19: | 连接到以太网唤醒事件。 |
EXTI 线 20: | 连接到 USB OTG HS(在 FS 中配置)唤醒事件。 |
EXTI 线 21: | 连接到 RTC 入侵和时间戳事件。 |
EXTI 线 22: | 连接到 RTC 唤醒事件。 |
EXTI 线 23: | 连接到 LPTIM1 异步事件 |
上表可知,中断线 0-15 对应外部 IO 口的输入中断,一共是 16 个外部中断线。
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 中配置的。
使用的介绍
因为STM32的中断非常多,所以必须设置中断的优先级,设置完优先级之后编写中断服务函数,进行中断服务函数的挂载。
中断的使用基本流程为
1)使能 IO 口时钟。
2)调用函数 HAL_GPIO_Init 设置 IO 口模式,触发条件,使能 SYSCFG 时钟以及设置 IO
口与中断线的映射关系。
3)配置中断优先级(NVIC),并使能中断。
4)在中断服务函数中调用外部中断共用入口函数 HAL_GPIO_EXTI_IRQHandler。
5)编写外部中断回调函数 HAL_GPIO_EXTI_Callback 实现控制逻辑。
下文为过程详细介绍
使能IO时钟&基本配置
首先,我们要使用 IO 口作为中断输入,所以我们要使能相应的 IO 口时钟,之后
设置 IO 口模式,触发条件,开启 SYSCFG 时钟,设置 IO 口与中断线的映射关系
__HAL_RCC_GPIOA_CLK_ENABLE(); //开启 GPIOA 时钟
GPIO_InitTypeDef GPIO_Initure;
GPIO_Initure.Pin=GPIO_PIN_0; //PA0
GPIO_Initure.Mode=GPIO_MODE_IT_RISING; //外部中断,上升沿触发
GPIO_Initure.Pull=GPIO_PULLDOWN; //默认下拉
HAL_GPIO_Init(GPIOA,&GPIO_Initure);
之后设置中断优先级
中断优先级(NVIC)
STM32有两个优先级概念:抢占式优先级和响应式优先级(亚优先级和副优先级)
抢占式优先级(优先级):高抢占式的优先级的中断会打断当前主程序及中断程序运行,俗称中断嵌套。
响应优先级(子优先级):在抢占式优先级相同的情况下,高响应级的中断优先被响应;在抢占式优先级不同的情况下,先判断抢占式优先级的级别,再考虑响应优先级。
如果抢占式优先级和响应优先级都相同,则按默认顺序执行。
HAL_NVIC_SetPriority(EXTI0_IRQn,2,1); //抢占优先级为 2,子优先级为 1
HAL_NVIC_EnableIRQ(EXTI0_IRQn); //使能中断线 2
中断服务函数
们配置完中断优先级之后,接着要做的就是编写中断服务函数。中断服务函数的名字是
在 HAL 库中事先有定义的。有 7 个,分别为:
void EXTI0_IRQHandler();
void EXTI1_IRQHandler();
void EXTI2_IRQHandler();
void EXTI3_IRQHandler();
void EXTI4_IRQHandler();
void EXTI9_5_IRQHandler();
void EXTI15_10_IRQHandler();
中断线 0-4 每个中断线对应一个中断函数,中断线 5-9 共用中断函数EXTI9_5_IRQHandler,中断线 10-15 共用中断函数 EXTI15_10_IRQHandler。一般情况下,我们可以把中断控制逻辑直接编写在中断服务函数中,但是 HAL 库把中断处理过程进行了简单封装,也就是中断处理的回调函数。
//使用如下
//中断服务函数
void EXTI0_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0); //调用中断处理公用函数
}
编写中断处理的回调函数HAL_GPIO_EXTI_Callback
在使用 HAL 库的时候,我们也可以跟使用标准库一样,在中断服务函数中编写控制逻辑。但 是 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) != RESET)
{
__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 口编写相应的中断服务控制逻辑。
相关概念
事件、中断事件、中断
事件、中断事件 中断三个概念或术语。这三个概念彼此关联,有时会让人有点混淆或犯迷糊。
拿一件生活中的事情打比方对上述三个概念做个基本的粗略理解,比如一老师在教室里给学生们上课。课堂上的学生可能做出各种行为动作,比方做笔记、打哈气、翻书包、讲小话等,我们把这些行为统称为事件,其中有些行为老师往往只是视而不见,继续他的上课;而有些行为可能导致老师的上课中止,比方讲小话,并对学生的相关行为予以警告、批评或纠正等,然后继续上课。我们把老师因为学生的某些行为而中止授课,并产生后续动作,之后接着上课的这个过程理解为中断或中断响应。我们把可能导致老师上课中断的学生行为理解为中断事件。
结合上面的比方,不难理解中断事件是一种可以导致中断发生的事件,中断则是因为中断事件的发生而导致的后续行为过程。事件与中断事件是包含关系,即事件可分为中断事件或非中断事件。而中断事件与中断之间属于前后关联的因果关系,虽有关联,但二者在时序上、行为上并不一样。
中断服务函数使用的注意事项
中断服务函数名是固定的,在启动代码里面已经定下来了。
书写中断服务函数的时候注意的问题:
- 中断服务函数名尽量用复制,不要自己写,因为只要你写错一个字母,这个函数就变成普通函数了。
- (如果中断服务函数是公共入口)进入到中断服务函数后先要查询是哪种中断
- 先清中断标志,然后再做中断处理,不要把清中断标志放在函数的最后。(如果把清除中断标志放在中断服务函数的最后,会出现当发出清中断标志指令后,硬件还没有把相关标志清除掉,程序就已经跳出了中断服务函数,这个时候NVIC又会识别到标志是1,出现重复中断)。—可以清除中断标志命令发出后,等待清除成功再往下执行。
- 中断服务函数应该尽量简短,一般是做一些标识,不要在中断中做延时之类的占用CPU很长时间的工作。----快进快出
- 中断服务函数不会被任何一个函数调用,当中断条件满足后(在启动文件中有中断服务函数的调用),NVIC控制把CPU拉到中断服务函数中执行。
附录
程序代码
//本程序是通过外部中断实现,四个按键按下从而控制LED灯呈现不同效果的程序
#include "exti.h"
#include "delay.h"
#include "led.h"
#include "key.h"
//中断初始化
void EXTI_Init(void)
{
GPIO_InitTypeDef GPIO_Initure;
//几个按键相关的时钟
__HAL_RCC_GPIOA_CLK_ENABLE(); //开启时钟GPIOA
__HAL_RCC_GPIOC_CLK_ENABLE(); //开启时钟GPIOC
__HAL_RCC_GPIOH_CLK_ENABLE(); //开启时钟GPIOH
GPIO_Initure.Pin=GPIO_PIN_0; //PA0
GPIO_Initure.Mode=GPIO_MODE_IT_RISING; //上升沿触发
GPIO_Initure.Pull=GPIO_PULLDOWN; //下拉
HAL_GPIO_Init(GPIOA,&GPIO_Initure);
GPIO_Initure.Pin=GPIO_PIN_13; //PC13
GPIO_Initure.Mode=GPIO_MODE_IT_FALLING; //下降沿触发
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
HAL_GPIO_Init(GPIOC,&GPIO_Initure);
GPIO_Initure.Pin=GPIO_PIN_2|GPIO_PIN_3; //PH2,3 下降沿触发,上拉
HAL_GPIO_Init(GPIOH,&GPIO_Initure);
//中断线0优先级设置
HAL_NVIC_SetPriority(EXTI0_IRQn,2,0); //优先级为2,子优先级为1
HAL_NVIC_EnableIRQ(EXTI0_IRQn); //使能中断线0
//中断线2优先级设置
HAL_NVIC_SetPriority(EXTI2_IRQn,2,1);
HAL_NVIC_EnableIRQ(EXTI2_IRQn);
//中断线3优先级设置
HAL_NVIC_SetPriority(EXTI3_IRQn,2,2);
HAL_NVIC_EnableIRQ(EXTI3_IRQn);
//中断线13优先级设置
HAL_NVIC_SetPriority(EXTI15_10_IRQn,2,3);
HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
}
//中断服务函数
void EXTI0_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0); //调用中断公用处理函数
}
void EXTI2_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_2); //调用中断公用处理函数
}
void EXTI3_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_3); //调用中断公用处理函数
}
void EXTI15_10_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13); //调用中断公用处理函数
}
//中断处理公用函数的回调函数
//中断处理公用函数在stm32f7xx_hal_gpio.c中定义,并且调用HAL_GPIO_EXTI_Callback(GPIO_Pin);
//在hal库中所有外部中断服务函数都会调用此函数
//Pram:GPIO_Pin-中断引脚
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
static u8 led0sta=1,led1sta=1;
delay_ms(50); //消抖
switch(GPIO_Pin)
{
case GPIO_PIN_0:
if(WK_UP==1) //控制LED0 LED1 互斥点亮
{
led1sta=!led1sta;
led0sta=!led1sta;
LED1(led1sta);
LED0(led0sta);
}
break;
case GPIO_PIN_2:
if(KEY1==0) //同时控制 LED1
{
led1sta=!led1sta;
LED1(led1sta);
};
break;
case GPIO_PIN_3:
if(KEY0==0) //同时控制LED0 LED1
{
led1sta=!led1sta;
led0sta=!led0sta;
LED1(led1sta);
LED0(led0sta);
}
break;
case GPIO_PIN_13:
if(KEY2==0) //LED0翻转
{
led0sta=!led0sta;
LED0(led0sta);
}
break;
}
}