STM32第四章-外部中断管理 - 知乎 (zhihu.com)(这篇文章觉得写的很好)
GPIOx.0映射到EXTI0
GPIOx.1映射到EXTI1
…
GPIOx.15映射到EXTI15
外部中断常用库函数
①void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
//设置IO口与中断线的映射关系
exp: GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource2);
②void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);
//初始化中断线:触发方式等
③ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);
//判断中断线中断状态,是否发生
④void EXTI_ClearITPendingBit(uint32_t EXTI_Line);
//清除中断线上的中断标志位
外部中断的一般配置步骤:
GPIO_Init();
② 开启IO口复用时钟。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
void GPIO_EXTILineConfig();
EXTI_Init();
NVIC_Init();
⑥ 编写中断服务函数。
EXTIx_IRQHandler();
EXTI_ClearITPendingBit();
代码:
exit.c
#include "exti.h"
#include "led.h"
#include "key.h"
#include "delay.h"
#include "usart.h"
#include "beep.h"
void EXTIX_Init(void)
{
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
KEY_Init(); // 按键端口初始化
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //使能复用功能时钟
//GPIOE.3 中断线以及中断初始化配置 下降沿触发 //KEY1
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource3);
EXTI_InitStructure.EXTI_Line=EXTI_Line3;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStructure); //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器
//GPIOE.4 中断线以及中断初始化配置 下降沿触发 //KEY0
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource4);
EXTI_InitStructure.EXTI_Line=EXTI_Line4;
EXTI_Init(&EXTI_InitStructure); //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器
//GPIOA.0 中断线以及中断初始化配置 上升沿触发 PA0 WK_UP
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0);
EXTI_InitStructure.EXTI_Line=EXTI_Line0;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_Init(&EXTI_InitStructure); //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //使能按键WK_UP所在的外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级2,
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x03; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn; //使能按键KEY1所在的外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01; //子优先级1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn; //使能按键KEY0所在的外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00; //子优先级0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
}
//外部中断0服务程序
void EXTI0_IRQHandler(void)
{
delay_ms(10);//消抖
if(WK_UP==1) //WK_UP按键
{
BEEP=!BEEP;
}
EXTI_ClearITPendingBit(EXTI_Line0); //清除LINE0上的中断标志位
}
//外部中断3服务程序
void EXTI3_IRQHandler(void)
{
delay_ms(10);//消抖
if(KEY1==0) //按键KEY1
{
LED1=!LED1;
}
EXTI_ClearITPendingBit(EXTI_Line3); //清除LINE3上的中断标志位
}
void EXTI4_IRQHandler(void)
{
delay_ms(10);//消抖
if(KEY0==0) //按键KEY0
{
LED0=!LED0;
LED1=!LED1;
}
EXTI_ClearITPendingBit(EXTI_Line4); //清除LINE4上的中断标志位
}
使能AFIO时钟
要设置AFIO时钟使能,有三个先决条件:
- 使用了AFIO的事件控制寄存器。
- 使用了AFIO的重映射功能。
- 使用了外部中断(EXTI)控制寄存器。
因为在配置外部中断时,要对AFIO_EXTICRX进行读写操作,所以要打开RCC_APB2Periph_AFIO时钟
以中断线 0 为例:它对应了 GPIOA.0、 GPIOB.0、 GPIOC.0、 GPIOD.0、GPIOE.0、 GPIOF.0、 GPIOG.0。
而中断线每次只能连接到 1 个 IO 口上,这样就需要通过配置来决定对应的中断线配置到哪个 GPIO 上了。
在库函数中,配置 GPIO 与中断线的映射关系的函数 GPIO_EXTILineConfig()来实现的:void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource)
该函数将 GPIO 端口与中断线映射起来,例如将中断线 9 与 GPIOB 映射起来:
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource9);
//GPIOE.3 中断线以及中断初始化配置 下降沿触发 //KEY1
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource3);
EXTI_InitStructure.EXTI_Line=EXTI_Line3;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStructure); //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器
以此为例
KEY1对应-------PE3 因此使用的是 GPIO_PortSourceGPIOE, GPIO_PinSource3
对应的EXTI_Line3; 以此类推配置其他两个按键中断
再配置中断优先级 子优先级即响应优先级
当两个中断向量的抢占优先级相同时,如果两个中断同时到达, 则先处理响应优先级高的中断
再配置外部中断服务程序
//外部中断0服务程序
void EXTI0_IRQHandler(void)
{
delay_ms(10);//消抖
if(WK_UP==1) //WK_UP按键
{
BEEP=!BEEP;
}
EXTI_ClearITPendingBit(EXTI_Line0); //清除LINE0上的中断标志位
}
以此为例,即按键WK_UP执行的功能是蜂鸣器的开关
为什么要设置清除中断标志位:
因为你要把中断标志位清除,才能开始接收下一次的中断事件不清除这个标志位的话CPU认为这个终端还没有准备好,也就是说CPU这个时候是不会理会这个中断的。更通俗的说法是,不清除中断的情况下,这个中断会反复触发,导致系统卡死在中断里面。
main.c
#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "usart.h"
#include "exti.h"
#include "beep.h"
int main(void)
{
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
uart_init(115200); //串口初始化为115200
LED_Init(); //初始化与LED连接的硬件接口
BEEP_Init(); //初始化蜂鸣器IO
EXTIX_Init(); //初始化外部中断输入
LED0=0; //先点亮红灯
while(1)
{
printf("OK\r\n");
delay_ms(1000);
}
}
为什么要设置串口初始化?
串口是由于主函数里有一句打印
延时函数是为了?
我把延时函数删去,功能没有变化