STM32F4_外部中断详解(EXTI)
目录
1. EXTI 是什么?
2. EXTI主要特性
3. EXTI框图
3.1 外部中断/事件线映射
4. EXTI寄存器
4.1 中断屏蔽寄存器 EXTI_IMR
4.2 事件屏蔽寄存器 EXTI_EMR
4.3 上升沿触发选择寄存器 EXTI_RTSR
4.4 下降沿触发选择寄存器 EXTI_FTSR
4.5 软件中断事件寄存器 EXTI_SWIER
4.6 挂起寄存器 EXTI_PR
5. 库函数配置外部中断的步骤
6. STM32外部中断程序
6.1 main.c
6.2 exti.c
6.3 exti.h
1. EXTI 是什么?
EXTI:外部中断/事件控制器包含多达23个用于产生事件/中断请求的边沿检测器。每根输入线都可以单独进行配置,以选择类型 (中断或事件) 和相应的触发事件(上升沿触发、下降沿触发或边沿触发)。每根输入线还可以单独屏蔽。挂起寄存器用于保持中断请求的状态线。是用来专门的管理所有的GPIO用来处理中断和事件的。
中断的意思就是程序在执行过程中,中断就如同一个小插曲,中断或者事件一来,优先执行中断事件,完成中断以后,再执行未完成的程序。(举个简单的例子就是:打比方我们正在家里学习,学习的同时可能你在开水,这个时候,突然门铃响了,门铃响就是一个中断,这个时候就打断了你正在执行的事(学习),你需要先去开门,看一下是否有重要的事;倘若没有很重要的事(处理完中断事件了),你回到座位上重新开始学习;这个时候突然水壶响了,同样水壶响了也是一个中断,需要再一次打断你正在学习的状态,去关一下水壶(再次去处理中断事件);)
2. EXTI主要特性
1. 每个中断/事件线上都具有独立的触发和屏蔽。
2. 每个中断线都具有专用的状态位。
3. 支持多达23个软件事件/中断请求。
4. 检测脉冲宽度低于APB2时钟宽度的外部信号。
3. EXTI框图
要产生中断,必须先配置好并使能中断线。根据需要的边沿检测设置2个触发寄存器,同时在中断屏蔽寄存器的相应位写 1 使能中断请求。当外部中断线上出现选定信号沿时,便会产生中断请求,对应的挂起位会置1。在挂起寄存器的对应位写 1 ,将清除该中断请求。
要产生事件,必须先配置好并使能事件线。根据需要的边沿检测设置2个触发寄存器,同时在事件屏蔽寄存器的相应位写 1 允许事件请求。当事件线上出现选定信号沿时,便会产生事件脉冲,对应的挂起位不会置 1。
通过在软件中对软件中断/事件寄存器写 1 ,也可以产生中断/事件请求。
3.1 外部中断/事件线映射
注意:一个中断线可以控制8个IO口的输入
void EXTI0_IRQHandler(void)是外部中断0的服务函数,负责WK_UP按键的中断检测; 因为原理图上KEY_UP接引脚PA0
void EXTI2_IRQHandler(void)是外部中断2的服务函数,负责KEY2按键的中断检测; 因为原理图上KEY2接引脚PE2
void EXTI3_IRQHandler(void)是外部中断3的服务函数,负责KEY1按键的中断检测; 因为原理图上KEY1接引脚PE3
void EXTI4_IRQHandler(void)是外部中断4的服务函数,负责KEY0按键的中断检测; 因为原理图上KEY0接引脚PE4
在对上述的理解上需要基于上述图,外部EXTI0是控制PA0/PB0/PC0/PD0/PE0/PF0…… ;简单理解就是EXTI1控制PA1/PB1/PC1/PD1/PE1/PF1…… ; 因此,KEY_UP负责外部中断0的中断服务函数的检测,KEY2负责外部中断2的服务函数的中断检测,KEY1负责外部中断3的服务函数的中断检测,KEY0负责外部中断4的服务函数的中断检测;
4. EXTI寄存器
4.1 中断屏蔽寄存器 EXTI_IMR
中断屏蔽寄存器:EXTI_IMR(Interrupt mask register) 32位寄存器
位31:23 保留,必须保持复位值
位22:0 MRx :x线上的中断屏蔽
0:屏蔽来自于x线上的中断请求
1:开放来自于x线上的中断请求
4.2 事件屏蔽寄存器 EXTI_EMR
事件屏蔽寄存器 EXTI_EMR(Event mask register) 32位寄存器
位31:23 保留,必须保持复位值
位22:0 MRx:x线上的事件屏蔽
0:屏蔽来自x线的事件请求
1:开放来自x线的事件请求
4.3 上升沿触发选择寄存器 EXTI_RTSR
上升沿触发选择寄存器 EXIT_RTSR(Rising trigger selection register) 32位寄存器
位31:23 保留,必须保持复位值
位22:0 TRx:线x的上升沿触发事件配置位
0:禁止输入线上升沿触发
1:允许输入线上升沿触发
4.4 下降沿触发选择寄存器 EXTI_FTSR
下降沿触发选择寄存器 EXIT_RTSR(Falling trigger selection register) 32位寄存器
位31:23 保留,必须保持复位值
位22:0 TRx:线x的下降沿触发事件配置位
0:禁止输入线下降沿触发
1:允许输入线下降沿触发
4.5 软件中断事件寄存器 EXTI_SWIER
软件中断事件寄存器 EXTI_SWIER(Software interrupt event register)
位 31:23 保留,必须保持复位值。
位 22:0 SWIERx:线 x 上的软件中断 (Software Interrupt on line x)
当该位为“0”时,写“1”将设置 EXTI_PR 中相应的挂起位。如果在 EXTI_IMR 和 EXTI_EMR 中允许产生该中断,则产生中断请求。
通过清除 EXTI_PR 的对应位(写入“1”),可以清除该位为“0”。
4.6 挂起寄存器 EXTI_PR
挂起寄存器 EXTI_PR(Pending register)
位 31:23 保留,必须保持复位值。
位 22:0 PRx:挂起位 (Pending bit)
0:没有发生触发请求
1:发生了选择的触发请求 当在外部中断线上发生了选择的边沿事件,该位被置“1”。
在此位中写入“1”可以清除它,也可以通过改变边沿检测的极性清除。
5. 库函数配置外部中断的步骤
STM32F4的每个IO口都可以作为外部中断的中断输入口,这点也是STM32F4的强大之处(相比于51只有5个中断,2个外部中断、2个定时器中断、1个串口中断)STM32F4的中断控制器支持22个外部中断/事件请求。每个中断都有状态位,每个中断/事件都有独立的触发和屏蔽设置。
STM32F4的22个外部中断为:
EXTI线0-15:对应外部IO口的输入中断。
EXTI线16:连接到PVD输出。
EXTI线17:连接到RTC闹钟事件。
EXTI线18:连接到USB OTG FS唤醒事件。
EXTI线19:连接到以太网唤醒事件。
EXTI线20:连接到USB OTG HS(在FS中配置)唤醒事件。
EXTI线21:连接到RTC入侵和时间戳事件。
EXTI线22:连接到RTC唤醒事件。
库函数配置外部中断的步骤:
1. 使能IO口时钟,初始化IO口为输入;
2. 开启SYSCFG时钟,设置IO口与中断线的映射关系;
注意:只要使用到外部中断,必须使能SYSCFG时钟。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE); //使能SYSCFG时钟
配置GPIO与中断线的映射关系函数:
void SYSCFG_EXTILineConfig(uint8_t EXTI_PortSourceGPIOx, uint8_t EXTI_PinSourcex);
ag. SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0); 将中断线0 与GPIOA映射起来,那么GPIOA.0与EXTI 1中断线连接了。
3. 初始化线上中断,设置触发条件
初始化是通过EXTI_Init();来实现的;中断线4上的下降沿触发配置如下:
EXTI_InitTypeDef EXTI_InitStructure;//设置EXTI结构体结构体变量
EXTI_InitStructure.EXTI_Line=EXTI_Line4; //中断线标号,配置某个中断线上的中断函数,取值范围:EXTI_Line0~EXTI_Line15
EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt; //中断模式,选值:中断EXTI_Mode_Interrupt和事件EXTI_Mode_Event
EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;//触发方式,可以是下降沿触发EXTI_Trigger_Falling,上升沿触发 EXTI_Trigger_Rising,或者任意电平(上升沿和下降沿)触发EXTI_Trigger_Rising_Falling
EXTI_InitStructure.EXTI_LineCmd=ENABLE; //中断使能
EXTI_Init(&EXTI_InitStructure); //初始化外设EXTI寄存器
4. 配置好中断分组NVIC,并且使能中断
设置中断线2的中断优先级如下:
NVIC_InitTypeDef NVIC_InitStructure; //设置NVIC结构体变量
NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn; //使能按键外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02; //响应优先级2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure); //中断优先级分组初始化
5. 编写中断服务函数
STM32F4的IO口外部中断函数只有7个:
EXPORT EXTI0_IRQHandler // 中断线0-4每个中断线对应一个中断函数
EXPORT EXTI1_IRQHandler
EXPORT EXTI2_IRQHandler
EXPORT EXTI3_IRQHandler
EXPORT EXTI4_IRQHandler
EXPORT EXTI9_5_IRQHandler // 中断线5-9共用中断函数EXPORT EXTI9_5_IRQHandler
EXPORT EXTI15_10_IRQHandler // 中断线10-15共用EXPORT EXTI15_10_IRQHandler
注意:一个中断线可以控制8个IO口的输入
void EXTI0_IRQHandler(void)是外部中断0的服务函数,负责WK_UP按键的中断检测; 因为原理图上KEY_UP接引脚PA0
void EXTI2_IRQHandler(void)是外部中断2的服务函数,负责KEY2按键的中断检测; 因为原理图上KEY2接引脚PE2
void EXTI3_IRQHandler(void)是外部中断3的服务函数,负责KEY1按键的中断检测; 因为原理图上KEY1接引脚PE3
void EXTI4_IRQHandler(void)是外部中断4的服务函数,负责KEY0按键的中断检测; 因为原理图上KEY0接引脚PE4
编写中断服务函数时经常使用到的函数:
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);// 判断某个中断是否发生(标志位是否置位)
void EXTI_ClearITPendingBit(uint32_t EXTI_Line);// 清除中断标志位
void EXTI3_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line3)!=RESET)//判断某个线上的中断是否发生
{ …中断逻辑…
EXTI_ClearITPendingBit(EXTI_Line3); //清除LINE上的中断标志位
}
}
6. STM32外部中断程序
本程序主要实现:通过中断来检测按键:
按下KEY_UP控制蜂鸣器,一叫一停;KEY2控制DS0,一亮一灭;KEY1控制DS1,一亮一灭;KEY0同时控制DS0和DS1,状态翻转;
6.1 main.c
#include "stm32f4xx.h"
#include "delay.h"
#include "LED.h"
#include "BEEP.h"
#include "Key.h"
#include "usart.h"
#include "exti.h"
int main()
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置中断优先级分组
delay_init(168); //初始化延迟函数
uart_init(115200); //初始化串口,设置波特率115200
LED_Init(); //初始化LED
BEEP_Init(); //初始化蜂鸣器
EXTIX_Init(); //初始化外部中断
LED0=0; //LED0点亮
while(1)
{
printf("OK\r\n");//通过串口发送ok 这里需要注意想要通过串口打印必须引用头文件#include "usart.h"
//更加简单干脆的说:想要使用printf函数,就必须引用该头文件,否则就会报错
delay_ms(1000);//1秒发送一次
}
}
6.2 exti.c
#include "delay.h"
#include "Key.h"
#include "BEEP.h"
#include "LED.h"
#include "exti.h"
void EXTI0_IRQHandler(void) //外部中断线0中断服务函数
{
delay_ms(10); //消抖
if(KEY_UP==1) //KEY_UP=1 表示按下
{
BEEP=!BEEP; //蜂鸣器翻转
}
EXTI_ClearITPendingBit(EXTI_Line0); //清除LINE0上的中断标志位
}
void EXTI2_IRQHandler(void)//中断线2的外部中断服务函数
{
delay_ms(10); //按键消抖
if(KEY2==0)//KEY2按下
{
LED0=!LED0;//LED0翻转
}
EXTI_ClearITPendingBit(EXTI_Line2);//消除中断线2上的中断标志位
}
void EXTI3_IRQHandler(void)//中断线3的外部中断服务函数
{
delay_ms(10);//按键消抖
if(KEY1==0)//KEY1按下
{
LED1=!LED1;//LED1翻转
}
EXTI_ClearITPendingBit(EXTI_Line3);//消除中断线3上的中断标志位
}
void EXTI4_IRQHandler(void)//中断线4的外部中断服务函数
{
delay_ms(10);//按键消抖
if(KEY0==0)//KEY0按下
{
LED0=!LED0;//LED0翻转
LED1=!LED1;//LED1翻转
}
EXTI_ClearITPendingBit(EXTI_Line4);//消除中断线4上的中断标志位
}
void EXTIX_Init(void)
{
NVIC_InitTypeDef NVIC_InitStructure;// 设置中断优先级结构体变量
EXTI_InitTypeDef EXTI_InitStructure;//设置外部中断结构体结构体变量
Key_Init(); //按键对应的IO口初始化
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG,ENABLE); //使能SYSCFG时钟
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA,EXTI_PinSource0); //PA0连接中断线0,映射按键KEY_UP 简单来说 接下来四条语句实现把引脚接在对应的中断线上,类似于复用功能
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE,EXTI_PinSource2); //PE2连接中断线2,映射按键KEY2
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE,EXTI_PinSource3); //PE3连接中断线3,映射按键KEY1
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE,EXTI_PinSource4); //PE4连接中断线4,映射按键KEY0
//以下之所以要分开设置EXTI初始化函数,是因为KEY_UP是高电平1有效,KEY0/KEY1/KEY2是低电平0有效,
//所以上升沿和下降沿需要分开设置
EXTI_InitStructure.EXTI_Line=EXTI_Line0;//中断线0
EXTI_InitStructure.EXTI_LineCmd=ENABLE;//中断使能
EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;//模式中断
EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising;//上升沿有效 通过原理图可以发现KEY_UP按键左侧接V3.3,右侧接引脚,按键没有按下时,引脚呈现低电平,按键一旦按下,引脚呈现高电平,按键按下也就表示着上升沿有效
EXTI_Init(&EXTI_InitStructure);//初始化外部中断
EXTI_InitStructure.EXTI_Line=EXTI_Line2 | EXTI_Line3 | EXTI_Line4;//中断线2/3/4
EXTI_InitStructure.EXTI_LineCmd=ENABLE;//中断使能
EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;//模式中断
EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;//下降沿 原理图上KEY0/KEY1/KEY2左侧接地,按键一旦按下,表示引脚低电平,也就是下降沿有效
EXTI_Init(&EXTI_InitStructure);//初始化外部中断
NVIC_InitStructure.NVIC_IRQChannel=EXTI0_IRQn;//外部中断0
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;//使能外部中断0
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x00;//抢占优先级0
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x02;//响应优先级2
NVIC_Init(&NVIC_InitStructure);//初始化中断优先级
NVIC_InitStructure.NVIC_IRQChannel=EXTI2_IRQn;//外部中断2
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;//使能外部中断2
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x03;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x02;//响应优先级2
NVIC_Init(&NVIC_InitStructure);//初始化中断优先级
NVIC_InitStructure.NVIC_IRQChannel=EXTI3_IRQn;//外部中断3
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;//使能外部中断3
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x02;//抢占优先级2
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x02;//响应优先级2
NVIC_Init(&NVIC_InitStructure);//初始化中断优先级
NVIC_InitStructure.NVIC_IRQChannel=EXTI4_IRQn;//外部中断4
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;//使能外部中断4
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x01;//抢占优先级1
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x02;//响应优先级2
NVIC_Init(&NVIC_InitStructure);//初始化中断优先级
}
// 这里和51的外部中断的优先级顺序是相同的,抢占优先级等级越低
6.3 exti.h
#ifndef _EXTI__H_
#define _EXTI__H_
void EXTIX_Init(void);
#endif