概述
中断指的是 CPU 来处理和响应外部发生的异常,中断也就意味着打断,比如打断正在做的事,然后去处理一个紧急的事,处理完成后在继续做刚才没做完的事。
ARM Cortex M4 中断机制
当中断控制器通知 cpu,产生 xx 中断,这个时候,cpu 会停止正在做的事件,转到中断处理上来。而且,M4 给不同的事件(中断),一个唯一的中断编号(为了区分不同的中断事件)。当不同的中断事件产生时,cpu 作不同的处理。
任何中断产生到 cpu 响应,都要经过两个大的阶段:
- 中断源的控制
- 中断控制器的控制 NVIC
中断源的分析
中断源指的是中断发生的源头,中断源在内核中已经定义好了,也称为向量表
中断向量表:一个函数指针数组,保存不同中断事件处理函数的地址的数组 P234
NVIC 的概述
NVIC 指的是嵌套向量中断控制器,属于内核中的外设,作用是管理所有的中断,比如中断的使能或失能、中断的优先级.....。
-
中断的使能与失能
NVIC 管理中断通道的打开与关闭,可以把 NVIC 理解为所有中断的开关,想要使用中断发送中断请求,就必须提前打开中断的通道。关于 NVIC 的使用都存储在一个结构体中,这个结构体和 NVIC 的函数接口都定义在 misc.c 和 misc.h 中。
/** * @brief NVIC Init Structure definition */ typedef struct { uint8_t NVIC_IRQChannel; /*!< Specifies the IRQ channel to be enabled or disabled. This parameter can be an enumerator of @ref IRQn_Type enumeration (For the complete STM32 Devices IRQ Channels list, please refer to stm32f4xx.h file) */ uint8_t NVIC_IRQChannelPreemptionPriority; /*!< Specifies the pre-emption priority for the IRQ channel specified in NVIC_IRQChannel. This parameter can be a value between 0 and 15 as described in the table @ref MISC_NVIC_Priority_Table A lower priority value indicates a higher priority */ uint8_t NVIC_IRQChannelSubPriority; /*!< Specifies the subpriority level for the IRQ channel specified in NVIC_IRQChannel. This parameter can be a value between 0 and 15 as described in the table @ref MISC_NVIC_Priority_Table A lower priority value indicates a higher priority */ FunctionalState NVIC_IRQChannelCmd; /*!< Specifies whether the IRQ channel defined in NVIC_IRQChannel will be enabled or disabled. This parameter can be set either to ENABLE or DISABLE */ } NVIC_InitTypeDef;
-
中断的优先级设置
NVIC 利用 4bit 的优先级来管理所有的中断通道
STM32 中断的优先级分为两种:抢占式优先级(主优先级) + 响应式优先级(次优先级),每种都有 16 个优先级(0~15),数字越小,优先级越高。
意义:如果同时发生多个中断请求,但是又不能同时处理,就根据中断请求的优先级来处理和响应中断。
抢占优先级(主优先级):抢占优先级高的中断可以打断抢占优先级低的中断的执行。
响应优先级(次优先级):在同时发生多个中断的情况下,响应优先级高的先执行。
(1) 抢占优先级高的中断可以打断抢占优先级低的中断的执行
(2) 抢占优先级一样高的中断,响应优先级高的中断不可以打断响应优先级低的中
(3) 抢占优先级一样高的中断,如果同时发生的情况下响应优先级高的先执行
(4) 抢占优先级和响应优先级一样高的中断同时发生,则按照向量表中的优先级执行
-
为了方便用户管理和响应中断,NVIC 提供一个函数接口可以对中断优先级进行分组
/** * @brief Configures the priority grouping: pre-emption priority and subpriority. * @param NVIC_PriorityGroup: specifies the priority grouping bits length. * This parameter can be one of the following values: * @arg NVIC_PriorityGroup_0: 0 bits for pre-emption priority * 4 bits for subpriority * @arg NVIC_PriorityGroup_1: 1 bits for pre-emption priority * 3 bits for subpriority * @arg NVIC_PriorityGroup_2: 2 bits for pre-emption priority * 2 bits for subpriority * @arg NVIC_PriorityGroup_3: 3 bits for pre-emption priority * 1 bits for subpriority * @arg NVIC_PriorityGroup_4: 4 bits for pre-emption priority * 0 bits for subpriority * @note When the NVIC_PriorityGroup_0 is selected, IRQ pre-emption is no more possible. * The pending IRQ priority will be managed only by the subpriority. * @retval None */ void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup); 函数原型 void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup) 函数参数 参数一:NVIC_PriorityGroup 打算设置的NVIC优先级分组 一般为NVIC_PriorityGroup_2 返回值 None
注意:设置中断优先级分组应该在主程序运行的开头部分进行,并且不能随意修改分组,否则会出现中断管理混乱,导致程序出现未知问题。
int main() { //设置优先级分组 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //对硬件进行初始化 ....... }
EXTI 的概述
EXTI 指的是外部中断/事件控制器,一共有 23 个,每个都有一个内部的边沿检测器,可以检测上升沿或者下降沿,每根线都可以产生事件或者中断。
上升沿:指的是电平信号由低变高的那一刻
下降沿:指的是电平信号由高变低的那一刻
EXTI 使用流程
注意:每个 GPIO 引脚都可以配置为外部中断,但是和 GPIO 相关的外部中断线一共只有 16 根,分别为 EXTI0~EXTI15
STM32F407 系列有 114 个 GPIO 口,通过映射的方式与外部中断线关联
-
对应的函数接口
/** * @brief Selects the GPIO pin used as EXTI Line. * @param EXTI_PortSourceGPIOx : selects the GPIO port to be used as source for * EXTI lines where x can be (A..K) for STM32F42xxx/43xxx devices, (A..I) * for STM32F405xx/407xx and STM32F415xx/417xx devices or (A, B, C, D and H) * for STM32401xx devices. * * @param EXTI_PinSourcex: specifies the EXTI line to be configured. * This parameter can be EXTI_PinSourcex where x can be (0..15, except * for EXTI_PortSourceGPIOI x can be (0..11) for STM32F405xx/407xx * and STM32F405xx/407xx devices and for EXTI_PortSourceGPIOK x can * be (0..7) for STM32F42xxx/43xxx devices. * * @retval None */ void SYSCFG_EXTILineConfig(uint8_t EXTI_PortSourceGPIOx, uint8_t EXTI_PinSourcex); 函数原型 void SYSCFG_EXTILineConfig(uint8_t EXTI_PortSourceGPIOx, uint8_t EXTI_PinSourcex) 函数参数 参数一:EXTI_PortSourceGPIOx 想要映射的GPIO端口 如 EXTI_PortSourceGPIOA 参数二:EXTI_PinSourcex 想要映射的GPIO引脚 如 EXTI_PinSource0
因为建立映射的时候需要使用系统配置控制器(SYSCFG),所以在编写代码的时候必须打开 SYSCFG 外设的时钟,挂载在 APB2 总线下 调用 RCC_APB2PeriphClockCmd()函数
外部中断的代码编写
注意:所有端口都具有外部中断功能。要使用外部中断线,必须将端口配置为输入模式
##### How to use this driver #####
===============================================================================
[..] In order to use an I/O pin as an external interrupt source, follow steps
below:
(#) Configure the I/O in input mode using GPIO_Init()
(#) Select the input source pin for the EXTI line using SYSCFG_EXTILineConfig()
(#) Select the mode(interrupt, event) and configure the trigger
selection (Rising, falling or both) using EXTI_Init()
(#) Configure NVIC IRQ channel mapped to the EXTI line using NVIC_Init()
[..]
(@) SYSCFG APB clock must be enabled to get write access to SYSCFG_EXTICRx
registers using RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
-
工程中添加 stm32f4xx_exti.c 以及 stm32f4xx_syscfg.c 两个源文件
-
打开 GPIO 时钟外设(挂载 AHB1 总线) + SYSCFG 外设时钟(挂载 APB2 总线)
-
定义 GPIO 初始化结构体,将引脚模式配置为输入模式 + 初始化
-
调用 SYSCFG_EXTILineConfig()函数来对外部中断线和 GPIO 引脚建立映射关系
-
定义 EXTI 初始化结构体,需要配置外部中断线(模式、触发方式.....) + 初始化
/** * @brief EXTI Init Structure definition */ typedef struct { uint32_t EXTI_Line; /*!< Specifies the EXTI lines to be enabled or disabled. This parameter can be any combination value of @ref EXTI_Lines */ EXTIMode_TypeDef EXTI_Mode; /*!< Specifies the mode for the EXTI lines. This parameter can be a value of @ref EXTIMode_TypeDef */ EXTITrigger_TypeDef EXTI_Trigger; /*!< Specifies the trigger signal active edge for the EXTI lines. This parameter can be a value of @ref EXTITrigger_TypeDef */ FunctionalState EXTI_LineCmd; /*!< Specifies the new state of the selected EXTI lines. This parameter can be set either to ENABLE or DISABLE */ }EXTI_InitTypeDef;
-
EXTI_Line 需要使用的外部中断线
/** @defgroup EXTI_Lines * @{ */ #define EXTI_Line0 ((uint32_t)0x00001) /*!< External interrupt line 0 */ #define EXTI_Line1 ((uint32_t)0x00002) /*!< External interrupt line 1 */ #define EXTI_Line2 ((uint32_t)0x00004) /*!< External interrupt line 2 */ #define EXTI_Line3 ((uint32_t)0x00008) /*!< External interrupt line 3 */ #define EXTI_Line4 ((uint32_t)0x00010) /*!< External interrupt line 4 */ #define EXTI_Line5 ((uint32_t)0x00020) /*!< External interrupt line 5 */ #define EXTI_Line6 ((uint32_t)0x00040) /*!< External interrupt line 6 */ #define EXTI_Line7 ((uint32_t)0x00080) /*!< External interrupt line 7 */ #define EXTI_Line8 ((uint32_t)0x00100) /*!< External interrupt line 8 */ #define EXTI_Line9 ((uint32_t)0x00200) /*!< External interrupt line 9 */ #define EXTI_Line10 ((uint32_t)0x00400) /*!< External interrupt line 10 */ #define EXTI_Line11 ((uint32_t)0x00800) /*!< External interrupt line 11 */ #define EXTI_Line12 ((uint32_t)0x01000) /*!< External interrupt line 12 */ #define EXTI_Line13 ((uint32_t)0x02000) /*!< External interrupt line 13 */ #define EXTI_Line14 ((uint32_t)0x04000) /*!< External interrupt line 14 */ #define EXTI_Line15 ((uint32_t)0x08000) /*!< External interrupt line 15 */ #define EXTI_Line16 ((uint32_t)0x10000) /*!< External interrupt line 16 Connected to the PVD Output */ #define EXTI_Line17 ((uint32_t)0x20000) /*!< External interrupt line 17 Connected to the RTC Alarm event */ #define EXTI_Line18 ((uint32_t)0x40000) /*!< External interrupt line 18 Connected to the USB OTG FS Wakeup from suspend event */ #define EXTI_Line19 ((uint32_t)0x80000) /*!< External interrupt line 19 Connected to the Ethernet Wakeup event */ #define EXTI_Line20 ((uint32_t)0x00100000) /*!< External interrupt line 20 Connected to the USB OTG HS (configured in FS) Wakeup event */ #define EXTI_Line21 ((uint32_t)0x00200000) /*!< External interrupt line 21 Connected to the RTC Tamper and Time Stamp events */ #define EXTI_Line22 ((uint32_t)0x00400000) /*!< External interrupt line 22 Connected to the RTC Wakeup event */ #define EXTI_Line23 ((uint32_t)0x00800000) /*!< External interrupt line 23 Connected to the LPTIM Wakeup event */
-
EXTI_Mode 外部中断线的模式 (中断 or 事件)
/** * @brief EXTI mode enumeration */ typedef enum { EXTI_Mode_Interrupt = 0x00, EXTI_Mode_Event = 0x04 }EXTIMode_TypeDef;
-
EXTI_Trigger 边沿检测方式(上升沿、下降沿、边沿)
/** * @brief EXTI Trigger enumeration */ typedef enum { EXTI_Trigger_Rising = 0x08, EXTI_Trigger_Falling = 0x0C, EXTI_Trigger_Rising_Falling = 0x10 }EXTITrigger_TypeDef;
-
EXTI_LineCmd 外部中断线使能 ENABLE or DISABLE
-
初始化 EXTI
/** * @brief Initializes the EXTI peripheral according to the specified * parameters in the EXTI_InitStruct. * @param EXTI_InitStruct: pointer to a EXTI_InitTypeDef structure * that contains the configuration information for the EXTI peripheral. * @retval None */ void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);
-
-
定义 NVIC 的初始化结构体,并进行赋值(中断通道....) + 初始化
-
NVIC_IRQChannel 需要打开的中断通道
-
NVIC_IRQChannelPreemptionPriority 抢占优先级 需要根据分组进行填写
-
l NVIC_IRQChannelSubPriority 响应优先级 需要根据分组进行填写
-
NVIC_IRQChannelCmd 中断通道的使能 ENABLE or DISABLE
-
初始化 NVIC
/** * @brief Initializes the NVIC peripheral according to the specified * parameters in the NVIC_InitStruct. * @note To configure interrupts priority correctly, the NVIC_PriorityGroupConfig() * function should be called before. * @param NVIC_InitStruct: pointer to a NVIC_InitTypeDef structure that contains * the configuration information for the specified NVIC peripheral. * @retval None */ void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
-
-
编写中断服务函数 中断服务函数的名字必须从启动文件中进行拷贝
注意:中断服务函数是不需要手动调用的,并且不要再中断中添加过长的延时时间,会导致系统的响应能力降低,所以如果打算处理比较复杂的事件,可以在中断中定义一个标志位,然后回到主程序中进行处理。
实例
/* main.c */
#include "stm32f4xx.h"
void LED_Init(void)
{
// LED 0 | 1
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //输出模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //输出速率
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //无上下拉
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9|GPIO_Pin_10; //引脚编号
GPIO_Init(GPIOF, &GPIO_InitStructure);
GPIO_SetBits(GPIOF,GPIO_Pin_9|GPIO_Pin_10); //默认不亮
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //输出模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //输出速率
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //无上下拉
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13|GPIO_Pin_14; //引脚编号
GPIO_Init(GPIOE, &GPIO_InitStructure);
GPIO_SetBits(GPIOE,GPIO_Pin_13|GPIO_Pin_14); //默认不亮
}
void KEY_Init(void)
{
// init key 0 | 1
GPIO_InitTypeDef key0,key1;
/*打开GPIOA外设时钟*/
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
/*打开GPIOE外设时钟*/
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);
/*配置PA0引脚*/
key0.GPIO_Mode = GPIO_Mode_IN; //输入模式
key0.GPIO_PuPd = GPIO_PuPd_NOPULL; //无上下拉
key0.GPIO_Pin = GPIO_Pin_0; //引脚编号
GPIO_Init(GPIOA, &key0);
/*配置PE2 PE3 PE4引脚*/
key1.GPIO_Mode = GPIO_Mode_IN; //输入模式
key1.GPIO_PuPd = GPIO_PuPd_NOPULL; //无上下拉
key1.GPIO_Pin = GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4; //引脚编号
GPIO_Init(GPIOE, &key1);
}
void EXTI0_1_Init(void)
{
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
/*打开SYSCFG外设时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
/*------------------------------------------------配置PA中断------------------------------------------------------------------------*/
/*引脚和外部中断线建立映射关系*/
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0);
/*配置外部中断线*/
EXTI_InitStructure.EXTI_Line = EXTI_Line0; //外部中断线0
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //中断模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE; //使能外部中断线0
EXTI_Init(&EXTI_InitStructure);
/*配置中断优先级*/
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //响应优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能通道
NVIC_Init(&NVIC_InitStructure);
/*------------------------------------------------配置PE2中断------------------------------------------------------------------------*/
/*GPIOE引脚和外部中断线建立映射关系*/
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE, EXTI_PinSource2);
/* 配置外部中断线*/
EXTI_InitStructure.EXTI_Line = EXTI_Line2; //外部中断线1
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //中断模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE; //使能外部中断线1
EXTI_Init(&EXTI_InitStructure);
/*配置中断优先级*/
NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn; //中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //响应优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能通道
NVIC_Init(&NVIC_InitStructure);
/*------------------------------------------------配置PE3中断------------------------------------------------------------------------*/
/*GPIOE引脚和外部中断线建立映射关系*/
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE, EXTI_PinSource3);
/* 配置外部中断线*/
EXTI_InitStructure.EXTI_Line = EXTI_Line3; //外部中断线1
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //中断模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE; //使能外部中断线1
EXTI_Init(&EXTI_InitStructure);
/*配置中断优先级*/
NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn; //中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //响应优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能通道
NVIC_Init(&NVIC_InitStructure);
/*------------------------------------------------配置PE4中断------------------------------------------------------------------------*/
/*GPIOE引脚和外部中断线建立映射关系*/
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE, EXTI_PinSource4);
/* 配置外部中断线*/
EXTI_InitStructure.EXTI_Line = EXTI_Line4; //外部中断线1
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //中断模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE; //使能外部中断线1
EXTI_Init(&EXTI_InitStructure);
/*配置中断优先级*/
NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn; //中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 4;//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 4; //响应优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能通道
NVIC_Init(&NVIC_InitStructure);
}
int main()
{
//1.硬件初始化
LED_Init();
KEY_Init();
EXTI0_1_Init();
//2.进入死循环
while(1)
{
// if( GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2) == RESET ) //说明被按下
// {
// GPIO_ResetBits(GPIOF,GPIO_Pin_10); //设置低电平 LED亮
// }
// else
// {
// GPIO_SetBits(GPIOF,GPIO_Pin_10); //设置高电平 LED灭
// }
}
}
//中断服务函数 不需要手动调用 尽量精简(尽量不要添加很长的延时)
void EXTI2_IRQHandler(void)
{
//检测中断线的标志
if( EXTI_GetITStatus(EXTI_Line2) != RESET )
{
GPIO_ToggleBits(GPIOF,GPIO_Pin_10); //LED电平翻转
//清除中断标志位
EXTI_ClearITPendingBit(EXTI_Line2);
}
}
//中断服务函数 不需要手动调用 尽量精简(尽量不要添加很长的延时)
void EXTI3_IRQHandler(void)
{
//检测中断线的标志
if( EXTI_GetITStatus(EXTI_Line3) != RESET )
{
GPIO_ToggleBits(GPIOE,GPIO_Pin_13); //LED电平翻转
//清除中断标志位
EXTI_ClearITPendingBit(EXTI_Line3);
}
}
//中断服务函数 不需要手动调用 尽量精简(尽量不要添加很长的延时)
void EXTI4_IRQHandler(void)
{
//检测中断线的标志
if( EXTI_GetITStatus(EXTI_Line4) != RESET )
{
GPIO_ToggleBits(GPIOE,GPIO_Pin_14); //LED电平翻转
//清除中断标志位
EXTI_ClearITPendingBit(EXTI_Line4);
}
}
//中断服务函数 不需要手动调用 尽量精简(尽量不要添加很长的延时)
void EXTI0_IRQHandler(void)
{
//检测中断线的标志
if( EXTI_GetITStatus(EXTI_Line0) != RESET )
{
GPIO_ToggleBits(GPIOF,GPIO_Pin_9); //LED电平翻转
//清除中断标志位
EXTI_ClearITPendingBit(EXTI_Line0);
}
}
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于