STM32-一文读懂EXTI外部中断系统
STM32的外部中断系统简介
注意这里的外部中断指的是EXTI,是芯片的外部中断,主要是由芯片外部事件触发的中断,不是内核的外部中断!在《STM32-异常与中断》中提到的外部中断均是相对于内核而言的,比如串口中断、定时器中断等等都是(内核的)外部中断。
大部分的STM32外部中断/事件控制器由19个产生事件/中断请求的边沿检测器组成,支持 19 个外部中断/事件请求,这19 个外部分别中断为: (互联型[STM32F105和STM32F107])产品有20个)
线 0~15:对应外部 IO 口的输入中断。
线 16:连接到 PVD 输出。
线 17:连接到 RTC 闹钟事件。
线 18:连接到 USB 唤醒事件。
每个中断设有状态位,每个中断/事件都有独立的触发和屏蔽设置。
EXTI外部中断系统详解
下图是一个EXTI外部中断系统的框图,斜杠20表示20根这样的连接线。黄色部分是EXTI的寄存器,绿色和蓝色分别是外部事件、和软件事件触发中断的流程。先看第一个或门,他的输入有两个,一个是中断线的输入信号(边沿信号)、一个是软件中断信号,他们都可以触发中断请求,被挂起寄存器挂起,然后通过一个被中断屏蔽寄存器控制的与门后到中断控制器进行中断处理。
另外我们注意到,最下角有一个事件屏蔽寄存器控制的与门,它可以将软件中断事件和中断线事件送到脉冲发生器进而向外发出一个事件信号!它主要实现一个功能:告诉其他功能模块“外部中断线上有边沿来到”。当然我们也可以在中断函数里告诉其他模块有中断事件来临,只是效率相对低一些。
比如,假设我们要使用外部I/O触发AD转换。如果使用传统的中断通道,需要I/O触发产生外部中断,外部中断服务程序启动AD转换,AD转换完成中断服务程序提交最后结果。要是使用事件通道,I/O触发产生事件,然后联动触发AD转换,AD转换完成中断服务程序提交最后结果。相比之下,后者不要软件参与AD触发,并且响应速度也更块。要是使用事件触发DMA操作,就完全不用软件参与就可以完成某些联动任务了。
我写了一个关于EXTI中断的形象的例子的文章《STM32-一个例子搞懂EXTI外部中断系统》,可以从宏观上辅助理解!另外,这里有一篇写的非常好的文章参考!
线0~15外部中断
STM32 的每个 IO 都可以作为外部中断的中断输入口,每个中 断线对应了7 个 IO 口,线x对应了 GPIOA.x、GPIOB.x、GPIOC.x、GPIOD.x、 GPIOE.x、GPIOF.x、GPIOG.x,但中断线每次只能连接到 1 个 IO 口上,需要通过配置AFIO_EXTICRx寄存器的EXTIx[3:0]来决定中断线n配置到哪个 GPIO引脚 上。(x:0~15)
EXTI寄存器
中断屏蔽寄存器 EXTI_IMR
MRx: 线x上的中断屏蔽,为0时屏蔽对应中断线上的中断请求。(MR19只对互联型产品)
事件屏蔽寄存器 EXTI_EMR
MRx: 线x上的事件屏蔽 ,为0时屏蔽对应中断先上的事件请求。
上升/下降沿触发选择寄存器 EXTI_RTSR/EXTI_FTSR
这两个寄存器格式一模一样。
TRx: 线x上的上升/下降沿触发事件配置位,为0时禁止,为1时允许。
挂起寄存器 EXTI_PR
这是一个外部中断事件请求标志位(中断请求挂起)。当在外部中断线x上发生了(EXTI_RTSR/EXTI_FTSR里)选择的边沿事件,该位被置’1’(该中断请求被挂起),如果在EXTI_IMR和EXTI_EMR中允许产生该线中断,则此时将产生一个中断。在该位中写入’1’可以清除它,也可以通过改变边沿检测的极性清除。
软件中断事件寄存器 EXTI_SWIER
该寄存器的复位值为0,写’1’将设置EXTI_PR中相应的挂起位,即以软件的方式发起相应中断线上的中断请求。通过清除EXTI_PR的对应位(写入’1’),可以清除该位为’0’。
代码实现
配置过程
- 初始化IO口为输入状态
- void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
- 开启IO口复用时钟
- void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState)
- 设置IO口与中断线的映射关系
- void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource)
- 初始化线上中断,设置触发条件等
- void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct)
- 配置中断NVIC分组,使能中断。
- void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct)
- 编写中断服务函数(并中断函数中清除中断标志位)
需要注意的是,虽然STM32有15个中断线对应到IO上,但是STM32 的 IO 口外部中断函数只有 6 个!中断线 0-4 每个中断线对应一个中断函数,中断线 5-9 共用中断函数 EXTI9_5_IRQHandler,中断线 10-15 共用中断函数 EXTI15_10_IRQHandler。
void EXTI0_IRQHandler(void)
void EXTI2_IRQHandler(void)
void EXTI3_IRQHandler(void)
void EXTI4_IRQHandler(void)
void EXTI9_5_IRQHandler(void)
void EXTI15_10_IRQHandler(void)
判断某个中断线上的中断标志位和清除标志位(软件中断事件寄存器 EXTI_SWIER标志)
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);
void EXTI_ClearITPendingBit(uint32_t EXTI_Line);
判断中断线的挂起状态和清除状态(挂起寄存器 EXTI_PR标志)
FlagStatus EXTI_GetFlagStatus(uint32_t EXTI_Line);
void EXTI_ClearFlag(uint32_t EXTI_Line);
中断服务函数模板
void EXTI3_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line3)!=RESET)//判断某个线上的中断是否发生
{
//中断服务内容
EXTI_ClearITPendingBit(EXTI_Line3); //清除 LINE 上的中断标志位
}
}
代码范例
通过外部中断的方式读取按键,控制LED灯翻转。
exti.c
void Extix_Init()
{
EXTI_InitTypeDef EXTI_Initstructure;
NVIC_InitTypeDef NVIC_Initstructure;
/**************************外部引脚配置**************************************/
Key_Init();//初始化按键相关IO(输入和上下拉)
/**************************时钟源配置**************************************/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//使能 引脚复用
/**************************外部中断基本配置**************************************/
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource4);//PE4/3 将PE4/3设置为外部中断引脚
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource3);//PE4/3 将PE4/3设置为外部中断引脚
EXTI_Initstructure.EXTI_Line = EXTI_Line4|EXTI_Line3;
EXTI_Initstructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_Initstructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_Initstructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_Initstructure);
/**************************中断号、优先级配置**************************************/
NVIC_Initstructure.NVIC_IRQChannel = EXTI4_IRQn; //选择中断通道EXTI4_IRQn
NVIC_Initstructure.NVIC_IRQChannelPreemptionPriority = 1;//抢占优先级
NVIC_Initstructure.NVIC_IRQChannelSubPriority = 1;//子(响应)优先级
NVIC_Initstructure.NVIC_IRQChannelCmd = ENABLE; //使能
NVIC_Init(&NVIC_Initstructure);
NVIC_Initstructure.NVIC_IRQChannel = EXTI3_IRQn;
NVIC_Init(&NVIC_Initstructure);
}
void EXTI4_IRQHandler(void)
{
delay_ms(10);
if(KEY0==0)
{
LED0=!LED0;
}
EXTI_ClearITPendingBit(EXTI_Line4);
}
void EXTI3_IRQHandler(void)
{
delay_ms(10);
if(KEY1==0)
{
LED1=!LED1;
}
EXTI_ClearITPendingBit(EXTI_Line3);
}
main.c
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
LED_Init(); //初始化LED
EXTIX_Init(); //外部中断初始化
while(1)
{
//无
}
}
推荐阅读