简介:
最近在学习stm32外设的过程中,学到EXTI这个外设的时候,感觉有点复杂,虽然是hal库开发,但是不明白所以,所以跟着也野火的教程,一遍看寄存器,一边看hal库的例子,写一篇记录在此,以备查阅。以下叙述思路:
①EXTI功能框图是最重要的,因为不仅仅可以大致看出配置流程,还能看出寄存器的控制过程。
②GPIO的初始化
③AFIO和CR1/2/3/4寄存器和EXIT0-15的映射关系,这个可以在参考手册AFIO章节中看
④NVIC的初始化
实验目的:通过按下按键1产生中断,然后中断服务函数执行让PB0的电平灯翻转一次,中断服务函数自己随便写,也可以发送一串字符到UART1上。目的是知道按键按下后,单片机收到按键按下的信号后到执行中断服务函数这中间的逻辑过程。
平台:野火的指南者开发板
实验原理图如下:按下KEY1,LED1亮一次,再按一次LED1灭。
一、EXTI功能框图
这张图在参考手册的EXTI章节,此处用野火的图便于叙述。看图顺序从右到左。
首先可以看到红色的线和绿色的两条线,下面简单记录一下处理逻辑和目的。
红色线:①处引脚被外部控制产生电平变化,从而EXTI通知NVIC执行中断服务函数。
绿色线:①处引脚被外部控制产生电平变化,从而EXTI通知⑦脉冲发生器,产生高低脉冲,通知ADC、TIM等外设动作。
中断和事件区别:产生中断是为了执行中断服务函数,产生事件是为了通知其他外设动作。
此次实验配置红色线,PA0连接的Key1,故将PA0配置成EXTI的输入源,产生中断,通知NVIC,然后执行中断服务函数,翻转PB0上的电平从而控制LED灯的亮灭。
EXTI的初始化代码如下:
EXTI_InitStruct.EXTI_Line = EXTI_Line0; EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising; EXTI_InitStruct.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStruct);
二、AFIO的CRx寄存器与GPIO引脚的对应关系
如上图复用IO口AFIO的4个控制寄存器CR可以将GPIO的16个引脚配置成EXTI的16个输入源。即GPIOA的pin0对应EXTI0,pin15对应EXTI15。初始化代码如下。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
三、NVIC的初始化
NVIC配置寄存器的工作已经由NVIC_Init函数做了,我们只需要传入可变的参数的即可,主要是配置中断源和中断源的抢断优先级、子优先级。初始化代码如下。
NVIC_InitTypeDef NVIC_InitStruct; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); NVIC_InitStruct.NVIC_IRQChannel = EXTI0_IRQn; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStruct);
四、实验
实验目的和拓扑图如下:按下KEY1,LED1亮一次,再按一次LED1灭。
实验原理:配置PA0为EXTI的中断源,按下KEY1,PA0上电平被拉高,此时产生一个上升沿中断信号,EXTI通知NVIC执行中断服务函数,服务函数中就将PB0的电平拉低,则LED亮,再按一次,第二次中断来了,中断服务函数将PB0电平拉高,LED灯灭。
代码逻辑:
①先将PB0初始化为普通的IO口,即推挽输出模式,由单片机控制PB0的电平变化。
②将PA0初始化为浮空输入模式,由外部控制PA0的电平。
③将配置AFIO将PA0初始化为EXTI的中断源EXTI0
④将EXTI初始化为中断模式,而非事件模式,来源LINE_0。
⑤将NVIC的中断源配置为EXTI0。
⑥写EXTIO的中断服务函数,将PB0的电平反转一次。
完整的代码如下,写在一个C文件里面方便大家阅读。
#include "stm32f10x.h" void LED_GPIO_Config(void); void GPIOA_Init(void); void AFIO_Init(void); void __EXIT_Init(void); static void __NVIC_Init(void); void EXTI0_IRQHandler (void); int main(void) { LED_GPIO_Config();//第一步初始化PB0为推挽输出模式 GPIOA_Init();//第二步初始化PA0为浮空输入模式 AFIO_Init();//第三步通过AFIO将PA0初始化为EXTI的中断源EXTI0 __EXIT_Init();//第四步将EXTI的中断源配置为EXIT0和中断模式 __NVIC_Init();//第四步将EXTI的中断源配置为EXIT0和中断模式 while(1) { } } //第一步初始化PB0为推挽输出模式 void LED_GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStruct); } //第二步初始化PA0为浮空输入模式 void GPIOA_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStruct); } //第三步通过AFIO将PA0初始化为EXTI的中断源EXTI0 void AFIO_Init(void) { // 初始化EXTI RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0); } //第四步将EXTI的中断源配置为EXIT0和中断模式 void __EXIT_Init(void) { EXTI_InitTypeDef EXTI_InitStruct; EXTI_InitStruct.EXTI_Line = EXTI_Line0; EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising; EXTI_InitStruct.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStruct); } //第五步将NVIC的中断源配置为EXTI0,和中断优先级 static void __NVIC_Init(void) { NVIC_InitTypeDef NVIC_InitStruct; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); NVIC_InitStruct.NVIC_IRQChannel = EXTI0_IRQn; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStruct); } //第六步将PB0的电平取反,来一次中断取反一次。 void EXTI0_IRQHandler (void) { if(EXTI_GetITStatus(EXTI_Line0) != RESET) { GPIOB->ODR ^= GPIO_Pin_0; } EXTI_ClearITPendingBit(EXTI_Line0); }
代码中的宏,只要包含了stm32f10x.h头文件均可以使用,都定义在了hal库的各个文件中。
编译下载之后,代码实现了按下key1灯亮,再按灯灭的功能,达到实验目的。