参考资料:《STM32F4xx中文参考手册》系统配置控制器以及中断和事件章节。
EXTI( External interrupt /evet controller)
之前接触过51单片机的都了解到51单片机有两个外部中断 ,分别为外部中断0、1。用来实时地处理外部事件的一种内部机制。当某种外部事件发生时,单片机的中断系统将迫使CPU暂停正在执行的程序,转而去进行中断事件的处理;中断处理完毕后.又返回被中断的程序处,继续执行下去。而STM32的则有与之功能相同的外部中断事件控制器。外部中断/事件控制器(EXTI)管理了控制器的 23个中断/事件线。每个中断/事件线都对应有一个边沿检测器,可以实现输入信号的上升沿检测和下降沿的检测。EXTI可以实现对每个中断/事件线进行单独配置,可以单独配置为中断或者事件,以及触发事件的属性。
23个中断事件线包含了16个与I/O有关的外部中断/事件线和其余七根 EXTI 线
由上图可以看出PA-PI口的各个位对应EXT0~EXTI15 共16个外部中断/事件线。
可以通过配置SYSCFG_EXTICR1~CR4来选择用于外部中断事件的I/O口。
其余7个如下图所示
下表即为所列的外部中断事件线
下图即为 外部中断/ 事件控制器框图
图中左下角的输入线即I/O口的输入状态,通过配置上升沿触发或下降沿触发选择寄存器来选择输入线的触发状态(可以是只有上升沿触发、只有下降沿触发或者上升沿和下降沿都触发),如果检测到有边沿跳变就输出有效信号 1,否则就输出无线信号0,出来的信号作为一路或门电路的输入,另外一输入来自软件中断事件寄存器(EXTI_SWIER)。EXTI_SWIER 允许我们通过程序控制就可以启动中断/事件线,当或门电路输入中有一路信号为1,那么就会输出信号1,或门电路输出的信号出来分两路,下面路作为与门的输入,通过配置事件屏蔽寄存器,两个都为1则输出为1,脉冲发生器则输出脉冲,这个脉冲信号,就是产生事件的线路最终的产物,可以给其他外设电路使用,比如定时器 TIM、模拟数字转换器 ADC等等。或门电路出来的上面一路通过配置中断屏蔽寄存器,然后同样经过一个与门电路后输出,作为挂起请求寄存寄存器的输入,如果中断屏蔽寄存器和挂起请求寄存器同时为1,那么就可以输出作为NVIC中断控制器的输入了,即可以实现系统中断事件控制。产生中断线路目的是把输入信号输入到NVIC,进一步会运行中断服务函数,实现功能,这样是软件级的。而产生事件线路目的就是传输一个脉冲信号给其他外设使用,并且是电路级别的信号传输,属于硬件级的。中断在嵌入式应用中占有非常重要的地位,几乎每个控制器都有中断功能。中断对保证紧急事件得到第一时间处理是非常重要的,接下来通过一个小实验要操作一下外部中断
硬件电路图如下所示
我要实现的操作是当我每按下一次开关,LED灯改变一次状态(可以参考固件库点亮LED灯和输入按键检测以及NVIC概述)。
要利用外部中断使LED灯改变状态,首先就是需要检测I/O口的状态,当I/O口满足触发条件,就进入中断,执行中断服务程序。
在进行编程之前我们先理一理编程的思路
1、首先要初始化按键和LED相关的GPIO
2、接下来要初始化EXTI相关寄存器用来产生中断
3、初始化NVIC,用于处理中断。
4、编写中断服务函数
5、主函数
1、所以我们第一步就应该配置KEY1,KEY2的端口,那么应该配置KEY1、KEY2的GPIO为哪种状态才能与中断产生联系呢,
通过查阅手册了解到上图所表达的意思就是要使用外部中断线的话,端口必须被配置成输入模式,所以我们就要开始对GPIO进行初始化初始化包括按键GPIO端口的初始化与LED灯GPIO的初始化。首先我先把与硬件相关的宏定义放在定义好的bsp_exti.h与bsp_led.h文件中,方便程序的移植与可读性,代码如下
//引脚定义
/*******************************************************/
#define KEY1_INT_GPIO_PORT GPIOA //按键1端口宏定义
#define KEY1_INT_GPIO_CLK RCC_AHB1Periph_GPIOA //按键1端口总线时钟宏定义
#define KEY1_INT_GPIO_PIN GPIO_Pin_0 //按键1端口引脚宏定义
#define KEY1_INT_EXTI_PORTSOURCE EXTI_PortSourceGPIOA //按键1中断端口宏定义
#define KEY1_INT_EXTI_PINSOURCE EXTI_PinSource0 //按键1中断端口引脚宏定义
#define KEY1_INT_EXTI_LINE EXTI_Line0 //按键1外部中断线宏定义
#define KEY1_INT_EXTI_IRQ EXTI0_IRQn //按键1的中断请求类型宏定义
#define KEY1_IRQHandler EXTI0_IRQHandler //按键1的中断服务函数名宏定义
#define KEY2_INT_GPIO_PORT GPIOC
#define KEY2_INT_GPIO_CLK RCC_AHB1Periph_GPIOC
#define KEY2_INT_GPIO_PIN GPIO_Pin_13
#define KEY2_INT_EXTI_PORTSOURCE EXTI_PortSourceGPIOC
#define KEY2_INT_EXTI_PINSOURCE EXTI_PinSource13
#define KEY2_INT_EXTI_LINE EXTI_Line13
#define KEY2_INT_EXTI_IRQ EXTI15_10_IRQn
#define KEY2_IRQHandler EXTI15_10_IRQHandler
#include "stm32f4xx_gpio.h"
#define GPIO_R_Pin GPIO_Pin_10
#define GPIO_R_Port GPIOH
#define GPIO_G_Pin GPIO_Pin_11
#define GPIO_G_Port GPIOH
#define GPIO_B_Pin GPIO_Pin_12
#define GPIO_B_Port GPIOH
/************************控制LED灯亮灭的宏***************/
/*直接操作寄存器的方法控制IO*/
#define LED_PORT_OUT_HI(p,i) { p->BSRRL = i ;} //输出为高电平
#define LED_PORT_OUT_LO(p,i) { p->BSRRH = i ;} //输出为低电平
#define LED_PORT_OUT_Toggle(p,i) { p->BSRRL ^= i ;} //输出为反状态
/*定义控制IO的宏*/
#define LED_R_Toggle LED_PORT_OUT_Toggle(GPIO_R_Port,GPIO_R_Pin)
#define LED_R_ON LED_PORT_OUT_LO(GPIO_R_Port,GPIO_R_Pin)
#define LED_R_OFF LED_PORT_OUT_HI(GPIO_R_Port,GPIO_R_Pin)
#define LED_G_Toggle LED_PORT_OUT_Toggle(GPIO_G_Port,GPIO_G_Pin)
#define LED_G_ON LED_PORT_OUT_LO(GPIO_G_Port,GPIO_G_Pin)
#define LED_G_OFF LED_PORT_OUT_HI(GPIO_G_Port,GPIO_G_Pin)
#define LED_B_Toggle LED_PORT_OUT_Toggle(GPIO_B_Port,GPIO_B_Pin)
#define LED_B_ON LED_PORT_OUT_LO(GPIO_B_Port,GPIO_B_Pin)
#define LED_B_OFF LED_PORT_OUT_HI(GPIO_B_Port,GPIO_B_Pin)
#define LED_RGBOFF LED_R_OFF;\
LED_G_OFF;\
LED_B_OFF
接下来开始初始化按键的GPIO与LED的端口引脚,在初始化按键的GPIO引脚时因为要与外部中断产生联系,所以通过查阅数据手册,所以要将按键初始化为输入模式,代码分别如下。
void bsp_exti_key_gpio_config()
{
GPIO_InitTypeDef GPIO_InitStructure;
/*开启按键GPIO口的时钟*/
RCC_AHB1PeriphClockCmd(KEY1_INT_GPIO_CLK|KEY2_INT_GPIO_CLK ,ENABLE);
/* 选择按键1的引脚 */
GPIO_InitStructure.GPIO_Pin = KEY1_INT_GPIO_PIN;
/* 设置引脚为输入模式 */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
/* 设置引脚不上拉也不下拉 */
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
/* 使用上面的结构体初始化按键 */
GPIO_Init(KEY1_INT_GPIO_PORT, &GPIO_InitStructure);
/* 选择按键2的引脚 */
GPIO_InitStructure.GPIO_Pin = KEY2_INT_GPIO_PIN;
/* 其他配置与上面相同 */
GPIO_Init(KEY2_INT_GPIO_PORT, &GPIO_InitStructure);
}
void LED_Config(void)
{
GPIO_InitTypeDef GPIO_LED_Struct;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOH , ENABLE);
GPIO_LED_Struct.GPIO_Mode = GPIO_Mode_OUT;
GPIO_LED_Struct.GPIO_Speed = GPIO_Fast_Speed;
GPIO_LED_Struct.GPIO_OType = GPIO_OType_PP;
GPIO_LED_Struct.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_LED_Struct.GPIO_Pin = GPIO_R_Pin;
GPIO_Init(GPIO_R_Port, &GPIO_LED_Struct);
GPIO_LED_Struct.GPIO_Pin = GPIO_B_Pin;
GPIO_Init(GPIO_B_Port, &GPIO_LED_Struct);
GPIO_LED_Struct.GPIO_Pin = GPIO_G_Pin;
GPIO_Init(GPIO_G_Port, &GPIO_LED_Struct);
LED_RGBOFF;
}
关于按键及LED的GPIO配置可以参考STM32按键输入检测和STM32固件库点亮LED灯
2、当我们配置好按键的GPIO后,接下来我们要进行EXTI的有关配置,由于23个外部中断事件线相关的外设都是挂载在APB2总线上的,所以们要线打开APB2总线上的时钟,因为STM32的外设设计的都是很棒的,当外设不工作时,都处于休眠状态,要想配置使用相关的外设,首先要打开对应的时钟(关于时钟配置这块可以参考STM32时钟树介绍)
/* 使能 SYSCFG 时钟 ,使用GPIO外部中断时必须使能SYSCFG时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
打开时钟后,首先要做的是什么?,肯定是要先先配置EXTI的输入线,我们要把按键的GPIO端口与EXTI联系起来,这里我们使用了STM32自带的库函数SYSCFG_EXTILineConfig函数,这个函数用来选择GPIO的引脚作为EXTI的中断事件线,函数有两个参数,一个是GPIO的端口(A~I)另一个是端口的哪个引脚(0~15),无返回值。函数说明大致意思就是这样。
/* 连接 EXTI 中断源 到key1引脚 */
SYSCFG_EXTILineConfig(KEY1_INT_EXTI_PORTSOURCE,KEY1_INT_EXTI_PINSOURCE);
/* 连接 EXTI 中断源 到key2 引脚 */
SYSCFG_EXTILineConfig(KEY2_INT_EXTI_PORTSOURCE,KEY2_INT_EXTI_PINSOURCE);
配置完输入后,接下来就要开始对EXTI进行配置,在官方的固件库exti.h文件中可以找到EXTI_InitTypeDef,进行配置,STM32的固件库非常的好用,齐全,每一种外设在对应的.h文件中都可以找到xx_InitTypeDef这个初始化结构体和xx_Init初始化配置函数,非常棒。
EXTI_InitTypeDef EXTI_InitStructure;
/* 选择 EXTI 中断源 */
EXTI_InitStructure.EXTI_Line = KEY1_INT_EXTI_LINE;
/* 中断模式 */
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
/* 下降沿触发 */
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
/* 使能中断/事件线 */
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
/* 选择 EXTI 中断源 */
EXTI_InitStructure.EXTI_Line = KEY2_INT_EXTI_LINE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
/* 上升沿触发 */
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
好,上面的就已经把EXTI前面的准备(红色方框里的)工作都已经做好了。
3、当中断发生后,EXTI会产生一个中断请求,接来下就要配置NVIC的中断优先级等来处理中断信号,在讲配置NVIC时说,首先要使能中断源(就上是我上面配置EXTI的操作)已经完成,接下来要进行初始化 NVIC_InitTypeDef结构体。
void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* 配置NVIC为优先级组1 */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
/* 配置中断源:按键1 */
NVIC_InitStructure.NVIC_IRQChannel = KEY1_INT_EXTI_IRQ;
/* 配置抢占优先级:1 */
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
/* 配置子优先级:1 */
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
/* 使能中断通道 */
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/* 配置中断源:按键2,其他使用上面相关配置 */
NVIC_InitStructure.NVIC_IRQChannel = KEY2_INT_EXTI_IRQ;
NVIC_Init(&NVIC_InitStructure);
}
4、配置完NVIC后我们接下来就要进行第四步编写中断服务函数,中断服务函数的函数名在启动文件中都已经定义好了,注意引用的时候中断服务函数的名字不要打错,否则编译器不会报错,然后进入一个死循环当中,有关中断服务函数我建议以后所有的中断服务函数都放在一个文件中,避免出错。在编写中断服务函数时,我们首先要检查中断是否真的发生,然后执行中断程序,执行完程序后记得消除中断标志位为了下次进入中断。程序如下。
void KEY1_IRQHandler(void)
{
//确保是否产生了EXTI Line中断
if(EXTI_GetITStatus(KEY1_INT_EXTI_LINE) != RESET)
{
// LED红色取反
LED_R_Toggle;
//清除中断标志位
EXTI_ClearITPendingBit(KEY1_INT_EXTI_LINE);
}
}
void KEY2_IRQHandler(void)
{
//确保是否产生了EXTI Line中断
if(EXTI_GetITStatus(KEY2_INT_EXTI_LINE) != RESET)
{
// LED蓝色取反
LED_B_Toggle;
//清除中断标志位
EXTI_ClearITPendingBit(KEY2_INT_EXTI_LINE);
}
}
5、接下来我们就编写主函数来实现实验最终现象
int main(void)
{
/* LED 端口初始化 */
LED_GPIO_Config();
/* 初始化EXTI中断,按下按键会触发中断,
* 触发中断会进入stm32f4xx_it.c文件中的函数
* KEY1_IRQHandler和KEY2_IRQHandler,处理中断,反转LED灯。
*/
bsp_exti_key_gpio_config();
EXTI_Key_Config();
NVIC_Configuration() ;
/* 等待中断,由于使用中断方式,CPU不用轮询按键 */
while(1)
{
}
}
好了,以上就是我在学习过程中的感悟与总结,写的不好敬请见谅。
上一篇:SysTick_系统定时器实现流水灯
下一篇:STM32中断及NVIC概述
推荐阅读最新更新时间:2024-03-16 16:24