NVIC是嵌套向量中断控制器,主要负责整个芯片有关中断的相关功能,与内核紧密耦合,是内核的一个外设。
但是各个芯片厂商在设计芯片的时候会对Cortex-M4里面的NVIC进行裁剪。
NVIC中有多个寄存器都是为中断服务的。
一般我们使用的有ISER、ICER、IP。ISER是用来使能中断。ICER是用来失能中断。IP用来设置中断优先级。
固件库文件core_cm4.h提供了NVIC的一些函数。
比如使能中断 void NVIC_EnableIRQ(IQRn_Type IRQn);
这些库函数我们在编程的时候用的比较少。
在NVIC中有一个中断优先级寄存器NVIC_IPRx,用于配置外部中断的优先级。IPR宽度为8位,原则上每个外部中断可配置优先级为0~255,数值越小,优先级越高。但是F4中只使用了高四位。
用于表达优先级的这四位,有被分组为抢占优先级和子优先级。如果有多个中断同时响应,则会先判断抢占优先级,再判断子优先级。如果这两者都相同,则会判断硬件中断编号,编号越小,优先级越高。
优先级分组由外设SCB的应用程序中断以及复位控制寄存器AIRCR的PRIGROUP[10:8]位决定,共分为5组,主优先级就是抢占优先级。
有关NVIC中断有关的库函数都在misc.c和misc.h中
设置优先级分组可调用库函数
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroupx)
设置每个中断时一般有3个要点
1、使能某个外设中断,这个具体由每个外设的相关中断使能位控制。
2、初始化NVIC_InitTypeDef结构体,设置中断优先级分组,设置抢占优先级和子优先级,使能中断请求。
typedef struct
{
uint8_t NVIC_IRQChannel; //中断源
//具体成员配置可参考stm32f4xx.h头文件里面的IRQn_Type结构体定义
uint8_t NVIC_IRQChannelPreemptionPriority; //抢占优先级
uint8_t NVIC_IRQChannelSubPriority; //子优先级
FunctionalState NVIC_IRQChannelCmd; //中断使能或使能
} NVIC_InitTypeDef;
static void NVIC_Configuration(void) {
NVIC_InitTypeDef NVIC_InitStructure;
/* 配置 NVIC 为优先级组 1 */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
/* 配置中断源:按键 1 */
NVIC_InitStructure.NVIC_IRQChannel = KEY1_INT_EXTI_IRQ;
/* 配置抢占优先级: 1 */
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
/* 配置子优先级: 1 */
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
/* 使能中断通道 */
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
3、编写中断服务函数
中断服务函数统一写在stm32f4xx_it.c这个库文件。
中断服务函数的函数名必须和启动文件startup_stm32f40xx.s里面预先设置的一样。
EXTI管理了控制器的23个中断/事件线,每个中断/事件线都有一个边沿检测器,实现上升沿或者下降沿检测,EXTI可以对它们进行单独控制,配置为中断或者事件,以及触发的属性。
EXTI共有23个中断/事件输入线,它们可以设置成为任意一个GPIO或者一些外设的事件。
边沿检测电路与上升/下降沿触发选择寄存器电路配合来控制信号触发,通过设置,可以实现上升沿触发、下降沿触发以及上升下降沿都触发。
边沿检测电路接下来进入到一个或电路。软件中断寄存器允许我们通过程序控制启动中断/事件线。或门有一则一,这两个输入源有一个输出1,则会有信号通过或门。
1、出了或门,往上方走是产生中断线路,则先是一个与门,与门另外一个输入是中断屏蔽寄存器,其主要作用就是屏蔽或开放外部中断请求。与门有0则0,所以,只有中断屏蔽寄存器置一时,也就是开放中断请求时,通过或门输入来的信号才会起到作用。然后输出信号会被保存到挂起寄存器中,与门输出为一就会把对应位置1.最终将挂起寄存器的内容输出到NVIC中,从而实现系统中断事件配置。
2、出了或门,往下方走是产生事件线路,先是一个与门,与门的另外一个输入时事件屏蔽寄存器,只有其对应位置1时,输出信号才由或门出来的信号控制,接下来到达脉冲发生器电路,当其输入信号为1时,就会产生一个脉冲。产生事件的最终的脉冲信号可以提供给其他外设电路使用。
EXTI共有23个中断/事件线,每个GPIO都可以被设置成输入线,占用EXTI 0~EXTI15,另外7根用于特定的外设信号。
typedef struct {
uint32_t EXTI_Line; // 中断/事件线 可选择EXTI0~EXIT22
EXTIMode_TypeDef EXTI_Mode; // EXTI 模式 选择为产生中断/事件
EXTITrigger_TypeDef EXTI_Trigger; // 触发事件 上升沿触发、下降沿触发,上升下降都触发
FunctionalState EXTI_LineCmd; // EXTI 控制 是否使能EXTI线 ENABALE/DISABLE
} EXTI_InitTypeDef;
关于外部中断的实际使用主要是有以下几个核心点。
1、开启GPIO时钟和SYSCFG时钟
2、配置GPIO
3、配置NVIC
4、配置EXTI将GPIO连接到EXTI源输入
5、编写EXTI中断服务函数
static void NVIC_Configuration(void) {
NVIC_InitTypeDef NVIC_InitStructure;
/* 配置 NVIC 为优先级组 1 */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
/* 配置中断源:按键 1 */
NVIC_InitStructure.NVIC_IRQChannel = KEY1_INT_EXTI_IRQ;
/* 配置抢占优先级: 1 */
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
/* 配置子优先级: 1 */
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
/* 使能中断通道 */
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void EXTI_Key_Config(void){
/*
1、定义两个初始化结构体
2、开启GPIO以及SYSCFG时钟
3、配置NVIC
4、配置GPIO结构体并初始化
5、利用SYSCFG_EXYILineConfig连接中断源
6、配置EXTI结构体并初始化
*/
/*定义GPIO以及EXTI初始化结构体*/
GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
/*开启按键 GPIO 口的时钟*/
RCC_AHB1PeriphClockCmd(KEY1_INT_GPIO_CLK|KEY2_INT_GPIO_CLK ,ENABLE);
/* 使能 SYSCFG 时钟 ,使用 GPIO 外部中断时必须使能 SYSCFG 时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
/* 配置 NVIC */
NVIC_Configuration();
/* 选择按键 1 的引脚 */
GPIO_InitStructure.GPIO_Pin = KEY1_INT_GPIO_PIN;
/* 设置引脚为输入模式 */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
/* 设置引脚不上拉也不下拉 */
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
/* 使用上面的结构体初始化按键 */
GPIO_Init(KEY1_INT_GPIO_PORT, &GPIO_InitStructure);
/* 连接 EXTI 中断源 到 key1 引脚 */
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA,EXTI_PinSource0);
/* 选择 EXTI 中断源 */
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);
}
void KEY1_IRQHandler(void)
{
//确保是否产生了 EXTI Line 中断
if (EXTI_GetITStatus(KEY1_INT_EXTI_LINE) != RESET) {
//清除中断标志位
EXTI_ClearITPendingBit(KEY1_INT_EXTI_LINE);
}
}
中断服务函数写在stm32fxx_it.h文件中