NVIC 是嵌套向量中断控制器,控制着整个芯片中断相关的功能,它跟内核紧密耦合,是内核里面的一个外设。配置中断的时候我们一般只用 ISER、ICER 和 IP 这三个寄存器,ISER 用来使能中断,ICER 用来失能中断,IP用来设置中断优先级。NVIC 结构体定义如下。
typedef struct { __IO uint32_t ISER[8]; /*!< Offset: 0x000 Interrupt Set Enable Register */ uint32_t RESERVED0[24]; __IO uint32_t ICER[8]; /*!< Offset: 0x080 Interrupt Clear Enable Register */ uint32_t RSERVED1[24]; __IO uint32_t ISPR[8]; /*!< Offset: 0x100 Interrupt Set Pending Register */ uint32_t RESERVED2[24]; __IO uint32_t ICPR[8]; /*!< Offset: 0x180 Interrupt Clear Pending Register */ uint32_t RESERVED3[24]; __IO uint32_t IABR[8]; /*!< Offset: 0x200 Interrupt Active bit Register */ uint32_t RESERVED4[56]; __IO uint8_t IP[240]; /*!< Offset: 0x300 Interrupt Priority Register (8Bit wide) */ uint32_t RESERVED5[644]; __O uint32_t STIR; /*!< Offset: 0xE00 Software Trigger Interrupt Register */ } NVIC_Type;
中断编程:
1、使能外设某个中断,具体哪个中断,由每个外设的相关中断使能位控制。比如串口有发送完成中断,接收完成中断,这两个中断都由串口控制寄存器的相关中断使能位控制。
2、初始化 NVIC_InitTypeDef 结构体,配置中断优先级分组,设置抢占优先级和子优先级,使能中断请求。NVIC_InitTypeDef 结构体在固件库头文件 misc.h 中定义,如下。
typedef struct { uint8_t NVIC_IRQChannel; //中断源 uint8_t NVIC_IRQChannelPreemptionPriority; //抢占优先级 uint8_t NVIC_IRQChannelSubPriority; //子优先级 FunctionalState NVIC_IRQChannelCmd; //中断使能或失能 } NVIC_InitTypeDef;
中断源:stm32f10x.h 头文件里面的 IRQn_Type 结构体包含了所有的中断源。
抢占优先级和子优先级:具体值要根据优先级分组设定。
中断使能或失能:操作的是 NVIC_ISER 和 NVIC_ICER 这两个寄存器。
3、编写中断服务函数
在启动文件 startup_stm32f10x_hd.s 中预先为每个中断都写了一个中断服务函数, 只是这些中断函数都是为空,为的只是初始化中断向量表。
实际的中断服务函数都需要重新编写,这里把中断服务函数统一写在 stm32f10x_it.c 文件中。
关于中断服务函数的函数名必须跟启动文件里面预先设置的一样,如果写错,系统就在中断向量表中找不到中断服务函数的入口,直接跳转到启动文件里面预先写好的空函数, 并且在里面无限循环,实现不了中断。
EXTI:外部中断/事件控制器
代码编写流程:初始化用来产生中断的 GPIO;初始化 EXTI; 配置 NVIC; 编写中断服务函数;
效果就是按下按键1,灯亮,因为是上升沿触发。按下按键2,灯不亮,按键松开,灯亮,因为是下降沿触发。
#include "stm32f10x.h" #include "bsp_led.h" #include "bsp_exti.h" /** * @brief 主函数 * @param 无 * @retval 无 */ int main(void) { /* LED GPIO初始化 */ LED_GPIO_Config(); /* 初始化EXTI中断,按下按键会触发中断, * 触发中断会进入stm32f4xx_it.c文件中的函数 * KEY1_IRQHandler和KEY2_IRQHandler,处理中断,反转LED灯。 */ EXTI_Key_Config(); /*进行成两个按键的 GPIO 和 EXTI配置。 /* 等待中断,由于使用中断方式,CPU不用轮询按键 */ while(1) { } }
里面是按键和 EXTI 的宏定义。
#ifndef __EXTI_H #define __EXTI_H #include "stm32f10x.h" //引脚定义 #define KEY1_INT_GPIO_PORT GPIOA #define KEY1_INT_GPIO_CLK (RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO) #define KEY1_INT_GPIO_PIN GPIO_Pin_0 #define KEY1_INT_EXTI_PORTSOURCE GPIO_PortSourceGPIOA #define KEY1_INT_EXTI_PINSOURCE GPIO_PinSource0 #define KEY1_INT_EXTI_LINE EXTI_Line0 #define KEY1_INT_EXTI_IRQ EXTI0_IRQn #define KEY1_IRQHandler EXTI0_IRQHandler #define KEY2_INT_GPIO_PORT GPIOC #define KEY2_INT_GPIO_CLK (RCC_APB2Periph_GPIOC|RCC_APB2Periph_AFIO) #define KEY2_INT_GPIO_PIN GPIO_Pin_13 #define KEY2_INT_EXTI_PORTSOURCE GPIO_PortSourceGPIOC #define KEY2_INT_EXTI_PINSOURCE GPIO_PinSource13 #define KEY2_INT_EXTI_LINE EXTI_Line13 #define KEY2_INT_EXTI_IRQ EXTI15_10_IRQn #define KEY2_IRQHandler EXTI15_10_IRQHandler void EXTI_Key_Config(void); #endif /* __EXTI_H */
里面有配置嵌套向量中断控制器NVIC的函数、配置EXTI中断的函数。
#include "bsp_exti.h" /** * @brief 配置嵌套向量中断控制器NVIC * @param 无 * @retval 无 */ 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; /* 配置抢占优先级 */ NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; /* 配置子优先级 */ NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; /* 使能中断通道 */ NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); /* 配置中断源:按键2,其他使用上面相关配置 */ NVIC_InitStructure.NVIC_IRQChannel = KEY2_INT_EXTI_IRQ; NVIC_Init(&NVIC_InitStructure); } /** * @brief 配置 IO为EXTI中断口,并设置中断优先级 * @param 无 * @retval 无 */ void EXTI_Key_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; EXTI_InitTypeDef EXTI_InitStructure; /*开启按键GPIO口的时钟*/ RCC_APB2PeriphClockCmd(KEY1_INT_GPIO_CLK,ENABLE); RCC_APB2PeriphClockCmd(KEY2_INT_GPIO_CLK,ENABLE); /* 配置 NVIC 中断*/ NVIC_Configuration(); /*--------------------------KEY1配置-----------------------------*/ /* 选择按键用到的GPIO */ GPIO_InitStructure.GPIO_Pin = KEY1_INT_GPIO_PIN; /* 配置为浮空输入 */ GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(KEY1_INT_GPIO_PORT, &GPIO_InitStructure); /* 选择EXTI的信号源 */ GPIO_EXTILineConfig(KEY1_INT_EXTI_PORTSOURCE, KEY1_INT_EXTI_PINSOURCE); EXTI_InitStructure.EXTI_Line = KEY1_INT_EXTI_LINE; /* EXTI为中断模式 */ EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; /* 上升沿中断 */ EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; /* 使能中断 */ EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure); /*--------------------------KEY2配置-----------------------------*/ /* 选择按键用到的GPIO */ GPIO_InitStructure.GPIO_Pin = KEY2_INT_GPIO_PIN; /* 配置为浮空输入 */ GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(KEY2_INT_GPIO_PORT, &GPIO_InitStructure); /* 选择EXTI的信号源 */ GPIO_EXTILineConfig(KEY2_INT_EXTI_PORTSOURCE, KEY2_INT_EXTI_PINSOURCE); EXTI_InitStructure.EXTI_Line = KEY2_INT_EXTI_LINE; /* EXTI为中断模式 */ EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; /* 下降沿中断 */ EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; /* 使能中断 */ EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure); } /*********************************************END OF FILE**********************/
里面配置KEY1是根据EXTI_InitTypeDef这个结构体来的,在stm32f10x_exti.h 文件中。
typedef struct { uint32_t EXTI_Line; EXTIMode_TypeDef EXTI_Mode; EXTITrigger_TypeDef EXTI_Trigger; FunctionalState EXTI_LineCmd; }EXTI_InitTypeDef;
里面是EXTI 中断服务函数,中断发生时,对应的中断服务函数就会被执行。
#include "stm32f10x_it.h" #include "bsp_led.h" #include "bsp_exti.h" void KEY1_IRQHandler(void) { //确保是否产生了EXTI Line中断 if(EXTI_GetITStatus(KEY1_INT_EXTI_LINE) != RESET) { // LED1 取反 LED1_TOGGLE; //清除中断标志位 EXTI_ClearITPendingBit(KEY1_INT_EXTI_LINE); } } void KEY2_IRQHandler(void) { //确保是否产生了EXTI Line中断 if(EXTI_GetITStatus(KEY2_INT_EXTI_LINE) != RESET) { // LED2 取反 LED2_TOGGLE; //清除中断标志位 EXTI_ClearITPendingBit(KEY2_INT_EXTI_LINE); } }