STM32的每个IO都可以作为外部中断输入。以STM32F429为例,它的中断控制器支持 22个外部中断/事件请求。每个中断设有状态位,每个中断/事件都有独立的触发和屏蔽设置。通常供IO口使用的中断线只有16个:EXTI线0~15:对应外部IO口的输入中断。但是有些MCU的IO口却远远不止16个,那么MCU是怎么把16个中断线和IO口一一对应起来的呢?
在STM32上是这样设计的,GPIO的引脚 GPIOx.0-GPIOx.15(x=A,B,C,D,E,F,G,H,I)分别对应中断线0~15。这样每个中断线对应了最多9个IO口。中断线每次只能连接到1个IO口上,这样就需要通过配置来决定对应的中断线配置到哪个GPIO上。GPIO跟中断线的映射关系图如下:
除了上图看到IO与各中断线的对应关系,其它一些中断线也比较常用,如:
EXTI线16连接到PVD输出
EXTI线17连接到RTC闹钟事件
EXTI线18连接到USB唤醒事件
EXTI线19连接到以太网唤醒事件(只适用于互联型产品)等
对于不同型号的STM32来说可能会略有差异,但用法基本一致。
中断事件在不同应用场景也各不相同,比如下面一些常见的中断方式:
1.通过配置上升沿/下降沿触发选择寄存器选择边沿检测电路所要检测的边沿跳变。
2.边沿检测电路根据输入线是否有相应的边沿跳变,检测到则输出信号1,否则输出信号0。
3.通过一个或门,或门以边沿检测电路、软件中断事件寄存器(中断事件可以通过软件产生)作为输入。两者之一有一个产生信号1,或门就输出信号1。
下面来看一下在应用程序中如何来使用这些中断事件,为了更加直观明了这里使用标准库代码为例,演示如何来做一个按键。
首先,初始化一个IO口是必不可少的。
void KEY_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能PA端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入
GPIO_Init(GPIOC, &GPIO_InitStructure); //根据设定参数初始化GPIOC
}
接下来要打开IO口的复用功能:使能EXTI外设对应的时钟。注意:当使用EXTI外设时,使能的是AFIO时钟,而不是EXTI外设时钟 。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
完成上面步骤后,后面需要把中断线和对应的IO口关联上。打开库gpio.h文件,可以看到GPIO_PinSource0到GPIO_PinSource15顺序定义,可对应每一个GPIO。
利用GPIO_EXTILineConfig()将EXTI线0连接到端口GPIOA的第0个针脚上:
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0);
下面初始化EXTI。打开库exti.h文件,可以看到所定义的各中断线:
typedef enum
{
EXTI_Mode_Interrupt = 0x00, //中断触发
EXTI_Mode_Event = 0x04 //事件触发
}EXTIMode_TypeDef;
typedef enum
{
EXTI_Trigger_Rising = 0x08, //上升沿触发
EXTI_Trigger_Falling = 0x0C, //下降沿触发
EXTI_Trigger_Rising_Falling = 0x10 //高低电平触发
}EXTITrigger_TypeDef;
#define EXTI_Line0 ((uint32_t)0x00001)
#define EXTI_Line1 ((uint32_t)0x00002)
……
#define EXTI_Line18 ((uint32_t)0x40000)
#define EXTI_Line19 ((uint32_t)0x80000)
EXIT初始化代码如下:
void exti_Init(void)
{
EXTI_InitTypeDef EXTI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //外部中断,需要使能AFIO时钟
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0); //将EXTI线连接到对应的IO端口上
EXTI_InitStructure.EXTI_Line = EXTI_Line0; //常用的就是EXTI_Line0-EXTI_Line015负责gpio管脚的那几个
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE; //中断线使能
EXTI_Init(&EXTI_InitStructure); //初始化中断
}
至此外部中断就已经完成,但这还不能结束。有中断,就要有中断优先级NVIC,不然同时来几个中断那应该先执行哪个呢?我们可以看一下优先级的定义。
在NVIC有一个专门的寄存器:中断优先级寄存器NVIC_IPRx用来配置外部中断的优先级,IPR宽度为8bit,原则上每个外部中断可配置的优先级为0~255,数值越小,优先级越高。在STM32F4中,只使用了高4bit,就是每个外部中断可配置的优先级为0-15。用于表达优先级的这 4bit,又被分组成抢占优先级和子优先级(也叫响应优先级)。如果有多个中断同时响应,抢占优先级高的就会比抢占优先级低的优先得到执行,如果抢占优先级相同,就比较子优先级。如果抢占优先级和子优先级都相同的话,就比较他们的硬件中断编号,编号越小,优先级越高。
NVIC_PriorityGroupConfig有两种分组:NVIC_PriorityGroup_4和NVIC_PriorityGroup_2
如果是第一种,那系统就分配了4位抢占优先级,0位响应优先级,同等优先级的情况下先发生就先执行。
如果是第二种,系统就分配了2位抢占优先级,2位响应优先级,那么当两个中断同时发生的时候就会首先响应外部中断2,因为外部中断2子优先级高于外部中断1的子优先级,数值越小优先级越高。配置示例如下:
NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn; //使能外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //抢占优先级2 因为为分组为4 这里可以设置为0-3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //响应优先级0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
由上面我们可以看到,每个IO口与中断线要对应起来,除此之外还要对应中断函数,在库函数EXTI0_IRQn,EXTI1_IRQn,EXTI2_IRQn,EXTI3_IRQn,EXTI4_IRQn,EXTI9_5_IRQn(EXTI5-EXTI9都对应这个中断),EXTI15_10_IRQn(EXITI10-EXTI15都对应这个中断函数)。如STM32F4在stm32f4xx.h中用了一个枚举结构体包含了这些中断通道。
到此, STM32中断管理的内容也大致介绍完了,后面再完整的给出中断配置代码,这样也更为直观方便,相信大家结合代码再回想前面的内容,对外部中断有更好的理解。
void key_exti_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //外部中断,需要使能AFIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能PA端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入
GPIO_Init(GPIOA, &GPIO_InitStructure); //根据设定参数初始化GPIOA
//注意:如果配置的针脚是0号,那么参数必须是GPIO_PinSource0 如果配置的针脚是3号,那么参数必须是GPIO_PinSource3
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0);//将EXTI线连接到对应的IO端口上
//注意:如果配置的0号针脚,那么EXTI_Line0是必须的 如果配置的针脚是3号,那么参数必须是EXTI_Line3
EXTI_InitStructure.EXTI_Line = EXTI_Line0; //常用的就是EXTI_Line0-EXTI_Line015负责gpio管脚的那几个
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure); //初始化中断
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //使能外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //抢占优先级2 因为为分组为2 这里可以设置为0-3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //响应优先级0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
}
评论