目录
2.2.4 配置NVIC(中断分组NVIC_PriorityGroupConfig以及初始化NVIC_Init)
1.中断分组函数NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
通过按键来触发外部中断,然后控制LED灯的亮灭。
一、原理部分
1.1 按键原理
在STM32F103 上的按键 KEY0 连接在 PE4 上、KEY1 连接在 PE3 上、KEY_UP 连接在 PA0 上,而且可以看到KEY0 和 KEY1 是低电平有效的,而 KEY_UP 是高电平有效的。
要注意的是这三个按键外部都没有上下拉电阻,这会导致按键在悬空的时候,对应引脚的电压不确定。例如,在KEY0被按下时,引脚被接地,为低电平;但是在KEY0没被按下的时候,引脚悬空会导致电压的值不确定,所以这种情况下,必须设置为上拉输入模式,否则按键的判定会不准确。相反,KEY__UP这种接法就要求设置为下拉输入模式。
1.2 外部中断
下图为EXTI的基本结构
外部中断EXTI是由中断控制器NVIC控制的,从上图我们可以看到,要想让GPIO连通到NVIC,还需要先经过AFIO和EXTI这两个部分。其中AFIO寄存器的主要功能是选择中断引脚(GPIOA,GPIOB...等等);EXTI用来配置中断的触发方式(上升沿、下降沿、双边沿以及软件触发)和相应中断的方式(中断响应和事件相应);NVIC用来配置中断的优先级(抢占优先级和中断优先级)。
所以开启外部中断的要依次配置好GPIO、AFIO、EXTI和NVIC,这样才能使用外部中断。
对应的程序框图:
二、工程部分
2.1 按键初始化
这里我们使用KEY0和KEY1来分别控制两个LED的亮灭。
配置它们的GPIO和之前LED的GPIO配置类似,先RCC开启对应GPIO时钟,然后GPIO_Init(),不过要记得配置GPIO为输入模式,并且是上拉输入模式。
void KEY_GPIO_Init(void)
{
//使能KEY引脚对应IO端口时钟
KEY0_RCC_CLOCKCMD(KEY0_RCC_CLOCKGPIO, ENABLE);
KEY1_RCC_CLOCKCMD(KEY1_RCC_CLOCKGPIO, ENABLE);
//定义IO硬件初始化结构体变量
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = KEY1_GPIO_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //设置为上拉输入模式
//初始化KEY1对应引脚IO
GPIO_Init(KEY0_GPIO, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = KEY1_GPIO_PIN;
//初始化KEY1对应引脚IO
GPIO_Init(KEY1_GPIO, &GPIO_InitStructure);
}
2.2 配置KEY引脚为外部中断
我们上面的代码只是简单地配置了两个按键KEY0和KEY1的GPIO,而我们需要实现通过按键来触发外部中断,因此还要对中断选择器AFIO、EXTI和中断处理器NVIC进行相应的配置。
新建两个"KEY0_EXIT_Config"和"KEY1_EXIT_Config"函数,来为两个按键配置外部中断。
2.2.1开启时钟
第一步还是先开启时钟,不过这次除了开启GPIO的时钟,还要多开启一个中断选择器AFIO,并且因为EXTI和NVIC的时钟是一直打开的,所以我们不用开启它们的时钟。
//启动KEY按键对应的GPIO和AFIO时钟
KEY0_RCC_CLOCKCMD(KEY0_RCC_CLOCKGPIO | KEY0_RCC_CLOCKAFIO,ENABLE);
其中KEY0_RCC_CLOCKCMD =RCC_APB2PeriphClockCmd,GPIO和AFIO都在总线APB2上。
2.2.2 GPIO_Init和AFIO中断引脚选择
GPIO配置还是使用结构体来配置,而AFIO需要用到GPIO_EXTILineConfig函数:
GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource)
其中,GPIO_PortSource是选择的GPIO中断源(GPIOA~GPIOG);
GPIO_PinSource是要配置的外部中断线,可以是GPIO_Pin_0到GPIO _Pin_15 。
两个按键分别对应PE4和PE3,所以GPIO_PortSource为GPIOE,GPIO_PinSource分别为4和3。
//启动KEY按键对应的GPIO和AFIO时钟
KEY0_RCC_CLOCKCMD(KEY0_RCC_CLOCKGPIO | KEY0_RCC_CLOCKAFIO,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;//IO硬件初始化结构体变量
NVIC_InitTypeDef NVIC_InitStructure;//嵌套向量中断控制器初始化结构体变量
//启动KEY按键对应的GPIO和AFIO时钟
KEY0_RCC_CLOCKCMD(KEY0_RCC_CLOCKGPIO | KEY0_RCC_CLOCKAFIO,ENABLE);
GPIO_InitStructure.GPIO_Pin = KEY1_GPIO_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入模式
GPIO_Init(KEY1_GPIO, &GPIO_InitStructure);//GPIO初始化
//AFIO配置
GPIO_EXTILineConfig(KEY0_GPIO_PORTSOURCE,KEY0_GPIO_PINSOURCE);
2.2.3 外部中断EXTI初始化(EXTI_Init)
也是用结构体来初始化EXTI。
对应的结构体EXTI_InitTypeDef:
typedef struct
{
uint32_t EXTI_Line;
EXTIMode_TypeDef EXTI_Mode;
EXTITrigger_TypeDef EXTI_Trigger;
FunctionalState EXTI_LineCmd;
}EXTI_InitTypeDef;
EXTI_Line,要配置的中断线,KEY0的EXITLINE为EXTI_Line4。
EXTI_Mode,指定外部中断线的模式,选用中断模式EXTI_Mode_Interrupt。
EXTI_Trigger,中断触发方式,选择下降沿触发方式EXTI_Trigger_Falling,因为开关的按下会导致对应的I/O口接地,从而导致电压下降。
EXTI_LineCmd,指定中断线的状态,选为ENABLE。
2.2.4 配置NVIC(中断分组NVIC_PriorityGroupConfig以及初始化NVIC_Init)
1.中断分组函数NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
NVIC_PriorityGroup中断优先级分组,对应两个中断优先级:
抢占优先级:是指打断其它中断,会出现嵌套中断;
子优先级(响应优先级):先处理响应优先级高的中断;
中断不多的情况下,两个优先级的设定比较随意,这里选择NVIC_PriorityGroup_2。
2.初始化NVIC
ypedef struct
{
uint8_t NVIC_IRQChannel;
uint8_t NVIC_IRQChannelPreemptionPriority;
uint8_t NVIC_IRQChannelSubPriority;
FunctionalState NVIC_IRQChannelCmd;
} NVIC_InitTypeDef;
NVIC_IRQChannel,指定的中断通道,所以除了103系列公用的中断通道可以使用,STM32F103ZET6还有更多的一些中断通道可以使用,可以在"stm32f10x.h"中找到它们。
这里KEY0选择了公有的中断通道EXTI4_IRQn,KEY1选择了公有的中断通道的EXTI3_IRQn。
NVIC_IRQChannelCmd,指定中断通道使能或使能,选使能(ENABLE)。
NVIC_IRQChannelPreemptionPriority,抢占优先级;
NVIC_IRQChannelSubPriority,响应优先级(子优先级)。
我们之前设定的优先级分组为2,可以参照下表来取值。这里选择了设置抢占优先级为2(0x02),
响应优先级为3(0x03)。
通过以上步骤,我们就完成通过按键触发外部中断的配置了,接下来需要写处理中断的函数。
2.3 中断函数
在之前选择中断通道的时候,KEY0选择的是公有通道EXTI4_IRQn,KEY1选择的是EXTI3_IRQn,它们对应的中断函数分别为 void EXTI4_IRQHandler(void)以及
void EXTI3_IRQHandler(void)。
进入中断函数第一步要先判断是不是我们选择的EXTI_Line4(对应KEY0)进来的。
用EXTI_GetITStatus(EXTI_Line4)来检查,返回值是SET或RESET,如果确实是KEY0触发的外部中断,那么函数就会返回SET,之后我们再翻转LED灯的状态便可实现按键控制LED灯了,最后要记得中断标志位复位。
//外部中断4对应Key0
void EXTI4_IRQHandler(void)
{
Delay();//消抖
if(KEY0==0) //按键KEY0
{
LED1_TOGGLE;
}
EXTI_ClearITPendingBit(EXTI_Line4); //清除LINE4上的中断标志位
}
Key1同理。
将代码烧录进去,查看效果。