有关具体的系统异常和外部中断可在标准库文件stm32f4xx.h这个头文件中查询到。在IRQn_Type这个结构体中包含了F4系列全部的异常声明。
1、中断
(1)中断向量表
STM32F407的嵌套向量中断控制器(NVIC)管理所有中断。
中断向量控制器NVIC的结构体定义在库头文件core_cm4.h中。该结构体定义了中断控制相关的寄存器。在stm32f407中设置中断的时候我们只需要使用到ISER/ICER/IP这三个寄存器。ISER用来使能中断,ICER用来失能中断,IP用来设置中断的优先级。
82个可屏蔽中断,13个系统中断,16个优先级。
在stm32f4xx.h文件中的IRQn_Type结构体中,定义了所有的中断源。
typedef enum IRQn
{
//此处省略
TIM1_CC_IRQn = 27, /*!< TIM1 Capture Compare Interrupt */
TIM2_IRQn = 28, /*!< TIM2 global Interrupt */
TIM3_IRQn = 29, /*!< TIM3 global Interrupt */
TIM4_IRQn = 30, /*!< TIM4 global Interrupt */ */
//此处省略
} IRQn_Type;
SYSTick定时器的定时中断,默认周期是1ms,产生周期为1ms的系统滴答信号。
(2)中断优先级
NVIC有一个专门的寄存器控制中断的优先级:NVIC_CPRx(在F407中,x = 0…981)。
中断控制器NVIC配中断的固件库函数在core_cm4.h中。这些函数在实际编程中使用较少。
void NVIC_EnableIRQ(IRQn_Type IRQn);//使能中断
void NVIC_DisableIRQ(IRQn_Type IRQn);//失能中断
uint32_t NVIC_GetPendingIRQ(IRQn_Type IRQn);//设置中断挂起位
void NVIC_SetPendingIRQ(IRQn_Type IRQn);//获取中断挂起位
void NVIC_ClearPendingIRQ(IRQn_Type IRQn);//清除中断挂起位
void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority);//设置中断优先级
uint32_t NVIC_GetPriority(IRQn_Type IRQn);//获取中断优先级
NVIC_SystemReset(void);//系统复位
NVIC_CPRx寄存器宽度为8bit,原则上每个为外部中断可配置优先级0-255,但是大多数CM4芯片都会精简设计,以致实际支持的优先级数减少。在F407中,只使用了高4位。
STM32F系列的NVIC采用4位二进制数设置中断的优先级,并且分为抢占优先级和次优先级,优先级数字越小表示优先级越高。
82个可屏蔽中断可以同时发生,也可能在执行一个中断的ISR时又发生了另一个中断。
4位二级制数可以分为两段,一段用于设置抢占优先级,另一段用于设置次优先级。例如:2位用于抢占优先级,2位用于次优先级或3位用于抢占优先级,1位用于次优先级。
-
如果两个中断的抢占优先级和次优先级相同,哪个中断先发生,就执行哪个中断的ISR。
-
高抢占优先级的中断可以打断正在执行的低抢占优先级的ISR的执行。
-
抢占优先级相同,次优先级高的中断不能打断正在执行的次优先级低的ISR的执行。
F407的优先级分组分为了5组。设置优先级分组可调用库函数NVIC_PriorityGroupConfig()实现。NVIC中断相关的库函数在库文件misc.c和misic.h中。
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);
优先级的分组策略已经在文件中定义(misic.h)
#define NVIC_PriorityGroup_0 ((uint32_t)0x700) /*!< 0 bits for pre-emption priority
4 bits for subpriority */
#define NVIC_PriorityGroup_1 ((uint32_t)0x600) /*!< 1 bits for pre-emption priority
3 bits for subpriority */
#define NVIC_PriorityGroup_2 ((uint32_t)0x500) /*!< 2 bits for pre-emption priority
2 bits for subpriority */
#define NVIC_PriorityGroup_3 ((uint32_t)0x400) /*!< 3 bits for pre-emption priority
1 bits for subpriority */
#define NVIC_PriorityGroup_4 ((uint32_t)0x300) /*!< 4 bits for pre-emption priority
0 bits for subpriority */
(3)中断编程
在配置每一个中断的时候一般有3个编程要点:
-
使能外设某个中断。具体由每个外设的相关中断使能位控制。比如串口发送完成中断,接收完成中断。
-
初始化NVIC_InitTypeDef结构体,配置中断优先级分组。设置抢占优先级和次优先级,使能中断请求。
typedef struct { uint8_t NVIC_IRQChannel; /*!< 中断源 中断源在IRQn_Type结构体中定义,前面提到*/ uint8_t NVIC_IRQChannelPreemptionPriority; /*!< 抢占优先级*/ uint8_t NVIC_IRQChannelSubPriority; /*!< 次优先级*/ FunctionalState NVIC_IRQChannelCmd; /*!< 中断使能或者失能*/ } NVIC_InitTypeDef;
-
编写中断服务函数。
在启动文件stm32f40xx.s中预先编写了一个中断服务函数,但是这个函数是空的,是为了初始化中断向量表使用的。我们需要在stm32f4xx.c文件中重新编写中断服务函数。
2、外部中断
(1)外部中断(EXTI)
外部中断的来源:
MCU上的GPIO引脚作为输入引脚,引脚上的电平变化所产生的中断。
一些内部信号作为EXTI中断线的输入。
STM32F407有23个中断/事件线。每个中断/事件线都对应一个边沿检测器,可以实现输入信号的上升沿检测和下降沿的检测。
EXTI可分为两大部分功能,一个是产生中断,另一个产生事件。这两个功能在硬件上就有所不同。
中断是将中断信号输入NVIC内,从而实现系统中断事件控制。
事件最终输出一个脉冲信号,这个脉冲信号可以给其他外设电路使用,比如定时器TIM,模拟数字转换器ADC等。
EXTI0~EXTI15这16个外部中断线以GPIO引脚作为输入线,每个GPIO引脚都可以作为某个EXTI的输入线。
EXTI0至EXTI4的每个中断有单独的ISR,EXTI线[9:5]中断共用一个中断号,也就是ISR。EXTI线[15:10]中断也共用ISR。
共用ISR需要在ISR中具体再判断是哪个EXTI线产生的中断。
EXTI线16~EXTI线22连接的不是某个实际的GPIO引脚,而是其他外设产生的事件信号。这7个EXTI线的中断有单独的ISR。
-
EXTI线16连接PCD输出。
-
EXTI线17连接RTC闹钟事件。
-
EXTI线18连接USB OTG FS唤醒事件。
-
EXTI线19连接以太网唤醒事件。
-
EXTI线20连接USB OTG HS唤醒事件。
-
EXTI线21连接RTC入侵和时间戳事件。
-
EXTI线22连接RTC唤醒事件。
(2)标准库中EXTI相关的内容
标准库函数对每个外设都建立了一个初始化结构体,结构体成员用于设置外设的工作参数,并有外设初始化函数调用。
EXTI初始化结构体定义在stm32f4xx_exti.h文件中。
typedef struct
{
uint32_t EXTI_Line; /*!< 中断/事件线*/
EXTIMode_TypeDef EXTI_Mode; /*!< EXTI模式,产生中断(EXTI_Mode_Interrupt)或产生事件 (EXTI_Mode_Event) */
EXTITrigger_TypeDef EXTI_Trigger; /*!< 触发模式,上升沿触发(EXTI_Trigger_Rising),下降沿触发 (EXTI_Trigger_Falling),上升沿和下降沿都触发 (EXTI_Trigger_Rising_Falling) */
FunctionalState EXTI_LineCmd; /*!< EXTI使能 */
}EXTI_InitTypeDef;
EXTI初始化相关的函数
void SYSCFG_EXTILineConfig(uint8_t EXTI_PortSourceGPIOx, uint8_t EXTI_PinSourcex);
//设置IO口与中断线的映射关系
example: SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE, EXTI_PinSource2);
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);
//初始化中断线:触发方式等
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);
//判断中断线中断状态,是否发生
void EXTI_ClearITPendingBit(uint32_t EXTI_Line);
//清除中断线上的中断标志位
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);//使能SYSCFG时钟
//这个函数非常重要,在使用外部中断的时候一定要先使能SYSCFG时钟
外部中断EXTI的步骤
-
初始化IO口为输入。
GPIO_Init();
-
配置中断分组(NVIC),并使能中断。
NVIC_Init();
-
使能SYSCFG时钟:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
-
设置IO口与中断线的映射关系。
void SYSCFG_EXTILineConfig();
-
初始化线上中断,设置触发条件等。
EXTI_Init();
-
编写中断服务函数。
EXTIx_IRQHandler();
-
清除中断标志位
EXTI_ClearITPendingBit();
3、外部中断使用
(1)资源
MCU:STM32F407ZGT6
外部资源:
用户标签 | 引脚名称 | 引脚功能 | GPIO模式 | 上拉或下拉 | 抢占优先级 | 次优先级 |
---|---|---|---|---|---|---|
LED1 | PF9 | GPIO_Output | 推挽输出 | 无 | —— | —— |
LED2 | PF10 | GPIO_Output | 推挽输出 | 无 | —— | —— |
Key1 | PE2 | GPIO_Input | 下降沿触发外部中断 | 上拉 | 2 | 0 |
Key2 | PE3 | GPIO_Input | 下降沿触发外部中断 | 上拉 | 1 | 2 |
Key3 | PE4 | GPIO_Input | 下降沿触发外部中断 | 上拉 | 1 | 1 |
Key4 | PA0 | GPIO_Input | 上升沿触发外部中断 | 下拉 | 1 | 0 |
(2)引脚初始化
void gpio_exti_init()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_GPIOE | RCC_AHB1Periph_GPIOF,ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStructure.GPIO_Pin = KeyDown_pin | KeyLeft_pin | KeyRight_pin;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(Key_right_left_down_port,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = KeyUp_pin;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
GPIO_Init(Key_up_port,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_Pin = Led1_pin | Led2_pin;
GPIO_Init(led_port,&GPIO_InitStructure);
}
(3)NVIC初始化
void nvic_gpio_init()
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//该函数在整个整个程序中只能出现一次
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_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);
}
(4)EXTI初始化
void exti_gpio_init()
{
EXTI_InitTypeDef EXTI_InitStructure;
//使能SYSCFG时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG,ENABLE);
//连接EXTI中断源到GPIO
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA,EXTI_PinSource0);
EXTI_InitStructure.EXTI_Line = EXTI_Line0;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE,EXTI_PinSource3);
EXTI_InitStructure.EXTI_Line = EXTI_Line3;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
}
(5)中断服务函数
void EXTI0_IRQHandler()
{
if(EXTI_GetITStatus(EXTI_Line0)!= RESET)
{
GPIO_WriteBit(led_port,Led1_pin,Bit_SET);
GPIO_WriteBit(led_port,Led2_pin,Bit_SET);
EXTI_ClearITPendingBit(EXTI_Line0);
Delay2();
}
}
void EXTI3_IRQHandler()
{
if(EXTI_GetITStatus(EXTI_Line3)!= RESET)
{
GPIO_WriteBit(led_port,Led1_pin,Bit_RESET);
GPIO_WriteBit(led_port,Led2_pin,Bit_RESET);
EXTI_ClearITPendingBit(EXTI_Line3);
Delay2();
}
}
4、库使用流程
5、注意事项
-
stm32f4xx_it.c中的中断服务函数只有写了9个特殊的中断服务函数的函数体结构。其余的在文件中没有的中断服务函数需要我们自己编写。编写是函数名必须是启动文件startup_stm32f40xx.s中已经定义好的,函数名称不对,不会有报错,但是中断会失效。
-
此在 EXTI9_5_IRQHandler 和 EXTI15_10_IRQHandler 的中断函数里面使用多次EXTI_GetITStatus 函数来判断出线路。
-
NVIC_PriorityGroupConfifig在整个程序中只需要设置一次。