首页 / 资讯中心 / 文章详情

STM32之中断(以EXTI为例)

STM32之中断(以EXTI为例) ★ FEATURED ARTICLE
参考资料1-STM32F4xx中文参考手册1.pdf3-PM0214_STM32F4xx-Cortex_M4内核参考手册.pdf一、中断中断异常STM32每一个外设都可以产生中断。以F407为例系统异常有10个外部中断有82个除了个别中断的优先级是定死的其他的异常的优先级是可编程的。关于具体有哪些异常可以去stm32f4xx.h中查看IRQn_Type结构体中包含了F4系列全部的异常声明。也可以查看参考手册10.1.3查看向量表什么是NVICNVIC是嵌套中断向量控制器是一个内核外设但是各大芯片厂商设计芯片时都会对NVIC进行裁剪。在core_cm4.h中定义了NVIC的结构体有很多NVIC寄存器的映射但是F407只用了ISER使能中断、ICER失能中断、IP设置中断优先级。在固件库中提供了NVIC的一些函数但配置中断时我们一般不用。NVIC有一个专门配置中断优先级的寄存器NVIC_IPR0~59用来配置外部中断的优先级8bit原则上优先级可配置0~255(即2的8次方)数值越小优先级越高但是绝大多数芯片都会精简设计F407只用了高4bit这4位又分抢占、子优先级当多个中断同时响应时先比抢占、再比子优先级、再比硬件中断编号比谁的编号小谁优先。优先级分组怎么设置由内核外设SCB的AIRCR即应用程序中断及复位控制寄存器的PRIGROUP位配置F407分为了5组可以调用固件库函数NVIC_PriorityGroupConfig()实现。有关NVIC的初始化结构体和库函数的定义可以在misc.h和misc.c中找到这里要说明的NVIC_PriorityGroupConfig()在整个程序中只需要设计一次就可以(建议放到mian函数中)若重复调用则取最后一次赋值。在我们进行中断编程时需要遵循什么编程顺序1.使能某个外设中断2.初始化NVIC_InitTypeDef结构体配置中断优先级分组设置抢占、子优先级使能中断请求3.编写中断服务函数。其中中断服务函数在启动文件startuo.stm32f4xx.s中已经预先为每个中断写了一个中断服务函数只不过为空为的是初始化中断向量表实际的中断函数还需要我们自己去实现我们统一写在了stm32f4xx_it.c这个库文件里。二、EXTI中断讲解这里的内容我使用的是库函数。标准库函数对每一个外设都建立了一个初始化结构体比如EXTI_InitTypeDef结构体成员用于设置外设的工作参数并由外设初始化配置函数比如EXTI_Init()调用这些设定参数将会设置外设相应的寄存器达到配置外设工作环境的目的。初始化结构体和初始化库函数配合使用是标准库精髓所在理解了初始化结构体每个成员的意义也基本上就可以对外设运用自如了初始化结构体定义在stm32f4xx_exti.h中初始化库函数定义在stm32f4xx_exti.c中编程时我们可以结合这两个文件内注释使用。typedef struct{ uint32_t EXTI_LINE; //中断/事件线 EXTIMode_Typedef EXTI_Mode; //EXTI模式 EXTITrigger_TypeDef EXTI_Trigger; //触发事件 FunctionalState EXTI_LineCmd; //EXTI控制 }1.EXTI_LINEEXTI中断/事件线的选择可以选EXTI0~EXTI22如下图中断/事件线输入源EXTI0PX0X可为ABCDEFGHIEXTI1PX1X可为ABCDEFGHIEXTI2PX2X可为ABCDEFGHIEXTI3PX3X可为ABCDEFGHIEXTI4PX4X可为ABCDEFGHIEXTI5PX5X可为ABCDEFGHIEXTI6PX6X可为ABCDEFGHIEXTI7PX7X可为ABCDEFGHIEXTI8PX8X可为ABCDEFGHIEXTI9PX9X可为ABCDEFGHIEXTI10PX10X可为ABCDEFGHIEXTI11PX11X可为ABCDEFGHIEXTI12PX12X可为ABCDEFGHIEXTI13PX13X可为ABCDEFGHIEXTI14PX14X可为ABCDEFGHIEXTI15PX15X可为ABCDEFGHIEXTI16可编程电压检测器PVD输出EXTI17RTC闹钟事件EXTI18USB OTG FS唤醒事件EXTI19以太网唤醒事件EXTI20USB OTG HS在FS中配置唤醒事件EXTI21RTC入侵或事件戳事件EXTI22RTC唤醒事件也就是说EXTI有23个中断/事件线每个GPIO都可以被设置为输入线占用EXTI0~EXTI15还有另外7根用于特定的外设事件。2.EXTI_Mode:EXTI模式选择可选为产生中断EXTI_Mode_Interrupt或者产生事件EXTI_Mode_Event3.EXTI_TriggerEXTI边沿触发事件可选为上升沿触发EXTI_Trigger_Rising、下降沿触发EXTI_Trigger_Falling或者上升沿下降沿都触发EXTI_Trigger_Rising_Falling4.EXTI_LineCmd控制是否使能EXTI线可选使能ENABLE或禁用DISABLE此时我们设置一个实验这里只是为了复习流程可能会不太详细但是没有关系主要重点都会列出来如果要使用这个外部中断主要需要这3个函数的合作哪三个函数呢bsp_exti.c和bsp_exti.h以及中断服务函数stm32f4xx_it.h。这里还会用到LED的一些函数这里比较简单不列出来他主要是为了表明这个中断被触发了也就是通过触发外部中断KEY来显示现象LED。我们这里只是为了熟悉流程1.初始化LED的GPIO2.开启按键的GPIO时钟和SYSCFG时钟3.配置NVIC4.配置按键GPIO为输入模式5.将按键GPIO输入到EXTI源输入6.配置按键EXTI中断/事件线7.编写EXTI中断服务函数。这里的内容很多知识点也很多下面一一剖析怎么初始化LED的GPIO通过bsp_led.c和bsp_led.h,这两个文件.c文件主要的函数定义在这里主要是LED_GPIO_Config()函数这里需要了解的是由于我们霸天虎的板子可以配置3个LED所以开始GPIO外设时钟时可以这么设置RCC_AHB1PeriphClockCmd ( LED1_GPIO_CLK| LED2_GPIO_CLK| LED3_GPIO_CLK, ENABLE); //参数统统是宏定义该函数的整个逻辑是先定义是GPIO_InitTypeDef类型的结构体然后开启GPIO时钟通过刚刚定义的结构体设置它的每个成员包括GPIO_Pin、GPIO_Mode这里设置为GPIO_Mode_OUT、GPIO_OType这里设置GPIO_OType_PP、GPIO_PuPd这里设置GPIO_PuPd_UP、GPIO_Speed这里设置GPIO_Speed_2MHz然后调用GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)也就是GPIO的初始化函数由于这三个LED的引脚是不一样的所以分别初始化。最后使用LED的一个宏定义将这些LED关闭。完整的LED_GPIO_Config()函数如下//位于bsp_led.c中 #include ./led/bsp_led.h /** * brief 初始化控制LED的IO * param 无 * retval 无 */ void LED_GPIO_Config(void) { /*定义一个GPIO_InitTypeDef类型的结构体*/ GPIO_InitTypeDef GPIO_InitStructure; /*开启LED相关的GPIO外设时钟*/ RCC_AHB1PeriphClockCmd ( LED1_GPIO_CLK| LED2_GPIO_CLK| LED3_GPIO_CLK, ENABLE); /*选择要控制的GPIO引脚*/ GPIO_InitStructure.GPIO_Pin LED1_PIN; /*设置引脚模式为输出模式*/ GPIO_InitStructure.GPIO_Mode GPIO_Mode_OUT; /*设置引脚的输出类型为推挽输出*/ GPIO_InitStructure.GPIO_OType GPIO_OType_PP; /*设置引脚为上拉模式*/ GPIO_InitStructure.GPIO_PuPd GPIO_PuPd_UP; /*设置引脚速率为2MHz */ GPIO_InitStructure.GPIO_Speed GPIO_Speed_2MHz; /*调用库函数使用上面配置的GPIO_InitStructure初始化GPIO*/ GPIO_Init(LED1_GPIO_PORT, GPIO_InitStructure); /*选择要控制的GPIO引脚*/ GPIO_InitStructure.GPIO_Pin LED2_PIN; GPIO_Init(LED2_GPIO_PORT, GPIO_InitStructure); /*选择要控制的GPIO引脚*/ GPIO_InitStructure.GPIO_Pin LED3_PIN; GPIO_Init(LED3_GPIO_PORT, GPIO_InitStructure); /*关闭RGB灯*/ LED_RGBOFF; }这里也将用到的bsp_led.h列出来#ifndef __LED_H #define __LED_H #include stm32f4xx.h //引脚定义 /*******************************************************/ //R 红色灯 #define LED1_PIN GPIO_Pin_6 #define LED1_GPIO_PORT GPIOF #define LED1_GPIO_CLK RCC_AHB1Periph_GPIOF //G 绿色灯 #define LED2_PIN GPIO_Pin_7 #define LED2_GPIO_PORT GPIOF #define LED2_GPIO_CLK RCC_AHB1Periph_GPIOF //B 蓝色灯 #define LED3_PIN GPIO_Pin_8 #define LED3_GPIO_PORT GPIOF #define LED3_GPIO_CLK RCC_AHB1Periph_GPIOF /************************************************************/ /** 控制LED灯亮灭的宏 * LED低电平亮设置ON0OFF1 * 若LED高电平亮把宏设置成ON1 OFF0 即可 */ #define ON 0 #define OFF 1 /* 带参宏可以像内联函数一样使用 */ #define LED1(a) if (a) \ GPIO_SetBits(LED1_GPIO_PORT,LED1_PIN);\ else \ GPIO_ResetBits(LED1_GPIO_PORT,LED1_PIN) #define LED2(a) if (a) \ GPIO_SetBits(LED2_GPIO_PORT,LED2_PIN);\ else \ GPIO_ResetBits(LED2_GPIO_PORT,LED2_PIN) #define LED3(a) if (a) \ GPIO_SetBits(LED3_GPIO_PORT,LED3_PIN);\ else \ GPIO_ResetBits(LED3_GPIO_PORT,LED3_PIN) /* 直接操作寄存器的方法控制IO */ #define digitalHi(p,i) {p-BSRRLi;} //设置为高电平 #define digitalLo(p,i) {p-BSRRHi;} //输出低电平 #define digitalToggle(p,i) {p-ODR ^i;} //输出反转状态 /* 定义控制IO的宏 */ #define LED1_TOGGLE digitalToggle(LED1_GPIO_PORT,LED1_PIN) #define LED1_OFF digitalHi(LED1_GPIO_PORT,LED1_PIN) #define LED1_ON digitalLo(LED1_GPIO_PORT,LED1_PIN) #define LED2_TOGGLE digitalToggle(LED2_GPIO_PORT,LED2_PIN) #define LED2_OFF digitalHi(LED2_GPIO_PORT,LED2_PIN) #define LED2_ON digitalLo(LED2_GPIO_PORT,LED2_PIN) #define LED3_TOGGLE digitalToggle(LED3_GPIO_PORT,LED3_PIN) #define LED3_OFF digitalHi(LED3_GPIO_PORT,LED3_PIN) #define LED3_ON digitalLo(LED3_GPIO_PORT,LED3_PIN) /* 基本混色后面高级用法使用PWM可混出全彩颜色,且效果更好 */ //红 #define LED_RED \ LED1_ON;\ LED2_OFF;\ LED3_OFF //绿 #define LED_GREEN \ LED1_OFF;\ LED2_ON;\ LED3_OFF //蓝 #define LED_BLUE \ LED1_OFF;\ LED2_OFF;\ LED3_ON //黄(红绿) #define LED_YELLOW \ LED1_ON;\ LED2_ON;\ LED3_OFF //紫(红蓝) #define LED_PURPLE \ LED1_ON;\ LED2_OFF;\ LED3_ON //青(绿蓝) #define LED_CYAN \ LED1_OFF;\ LED2_ON;\ LED3_ON //白(红绿蓝) #define LED_WHITE \ LED1_ON;\ LED2_ON;\ LED3_ON //黑(全部关闭) #define LED_RGBOFF \ LED1_OFF;\ LED2_OFF;\ LED3_OFF void LED_GPIO_Config(void); #endif /* __LED_H */至此LED的一些相关函数已经理解了。由于我们会使用按键来触发外部中断这里我们要配置下按键的GPIO。与LED初始化相似但是不同的是按键 所连接的引脚 的 GPIO_Mode应设置为输入模式GPIO_Mode_INGPIO_PuPd应设置为也叫浮空既不上拉也不下拉为什么结尾会有解答最后GPIO初始化。这里有个内容就是为什么要开启SYSCFG时钟那是因为STM32外部中断EXTI的引脚选择功能是由SYSCFG外设管理的该外设有一个寄存器SYSCFG_EXTICR寄存器他是用来切换引脚多路开关的可以将KEY的引脚映射到EXTI中断线配置了之后我们就可以通过按键触发的高低电平结合EXTI的参数配置也就是触发条件将产生相关的中断这里在中断函数里面实现但是这些实现的前提使用SYSCFG_EXTICR寄存器的前提使用SYSCFG外设的前提都是要先打开SYSCFG的时钟由于加入了EXTI的中断也就是加入了中断这是就需要配置NVIC嵌套向量中断控制器那么什么是NVICNVIC是Cortex-M3/M4内核自带的外设记住可不是片上外设。他有什么作用你可以把它当做一个调度员处理外面各种外设EXTI、定时器、CAN、I2C等等的中断信号发来的请求NVIC就负责判断优先级、能不能抢占、安排先后顺序它能够决定当前正在执行的程序要不要暂停切换去执行中断服务函数。我们要理解它的主要含义一个是嵌套就是说高优先级中断可以打断正在运行的低优先级中断也叫中断抢占另一个是向量由于每个中断都有独立的入口地址中断向量表中断触发后CPU直接跳转到对应中断服务函数不需要软件逐个判断是哪个谁产生了中断。还有一点内容使用前需要配置中断优先级分组划分抢占位和子优先级位。这里我们了解下该实验EXTI中断的执行流程按键按下电平变化-EXTI 检测到边沿产生中断请求-请求发给NVIC-NVIC 裁决-CPU执行中断函数。怎么配置中断的优先级分组使用该函数NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);分组1表示1bit的抢占优先级3bit的子优先级。这里我们把它放在了NVIC_Configuration()里面。我们要注意NVIC_PriorityGroupConfig() 在整个工程只需要调用一次来配置优先级分组。也就是说如果工程里面有用到许多的外设中断那么在确定了想用哪一种优先级分组之后再给每个外设对应的中断向量配置优先级。如果重复调用了相当于对中断相关寄存器重复赋值多次就取最后一次赋值并且从代码逻辑上来看NVIC_PriorityGroupConfig()适合放在main()函数里。那么如何配置NVIC怎么配置按键的GPIO参数怎么使能按键GPIO和SYSCFG的时钟使用什么函数将EXTI中断源和引脚连接怎么配置EXTI的参数它有那些参数并且怎么将它初始化带着这些问题直接上代码//位于bsp_exti.c中 #include ./key/bsp_exti.h /** * brief 配置嵌套向量中断控制器NVIC * param 无 * retval 无 */ static void NVIC_Configuration(void) { NVIC_InitTypeDef NVIC_InitStructure; /* 配置NVIC为优先级组1 */ /* 提示 NVIC_PriorityGroupConfig() 在整个工程只需要调用一次来配置优先级分组*/ 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); /* 配置中断源按键2其他使用上面相关配置 */ NVIC_InitStructure.NVIC_IRQChannel KEY2_INT_EXTI_IRQ; NVIC_Init(NVIC_InitStructure); } /** * brief 配置 PA0 为线中断口并设置中断优先级 * param 无 * retval 无 */ void EXTI_Key_Config(void) { 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(KEY1_INT_EXTI_PORTSOURCE,KEY1_INT_EXTI_PINSOURCE); /* 选择 EXTI 中断源 */ EXTI_InitStructure.EXTI_Line KEY1_INT_EXTI_LINE; /* 中断模式 */ EXTI_InitStructure.EXTI_Mode EXTI_Mode_Interrupt; /* 上升沿触发 */ EXTI_InitStructure.EXTI_Trigger EXTI_Trigger_Rising; /* 使能中断/事件线 */ EXTI_InitStructure.EXTI_LineCmd ENABLE; EXTI_Init(EXTI_InitStructure); /* 选择按键2的引脚 */ GPIO_InitStructure.GPIO_Pin KEY2_INT_GPIO_PIN; /* 其他配置与上面相同 */ GPIO_Init(KEY2_INT_GPIO_PORT, GPIO_InitStructure); /* 连接 EXTI 中断源 到key2 引脚 */ SYSCFG_EXTILineConfig(KEY2_INT_EXTI_PORTSOURCE,KEY2_INT_EXTI_PINSOURCE); /* 选择 EXTI 中断源 */ EXTI_InitStructure.EXTI_Line KEY2_INT_EXTI_LINE; EXTI_InitStructure.EXTI_Mode EXTI_Mode_Interrupt; /* 下降沿触发 */ EXTI_InitStructure.EXTI_Trigger EXTI_Trigger_Falling; EXTI_InitStructure.EXTI_LineCmd ENABLE; EXTI_Init(EXTI_InitStructure); }下面我们也把.h文件内容列出来#ifndef __EXTI_H #define __EXTI_H #include stm32f4xx.h //引脚定义 /*******************************************************/ #define KEY1_INT_GPIO_PORT GPIOA #define KEY1_INT_GPIO_CLK RCC_AHB1Periph_GPIOA #define KEY1_INT_GPIO_PIN GPIO_Pin_0 #define KEY1_INT_EXTI_PORTSOURCE EXTI_PortSourceGPIOA #define KEY1_INT_EXTI_PINSOURCE EXTI_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_AHB1Periph_GPIOC #define KEY2_INT_GPIO_PIN GPIO_Pin_13 #define KEY2_INT_EXTI_PORTSOURCE EXTI_PortSourceGPIOC #define KEY2_INT_EXTI_PINSOURCE EXTI_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 */这里我们还有了解下EXTI_PORTSOURCEEXTI_PINSOURCEEXTI_LINE和EXTI_IRQ和IRQHandler 他们分别是什么首先EXTI_PORTSOURCEEXTI_PINSOURCE是SYSCFG_EXTILineConfig(KEY1_INT_EXTI_PORTSOURCE,KEY1_INT_EXTI_PINSOURCE);里的两个参数他们分别是一些宏定义用于在该函数中执行相关操作来配置SYSCFG-EXTICR。详细解释void SYSCFG_EXTILineConfig(uint8_t EXTI_PortSourceGPIOx, uint8_t EXTI_PinSourcex) { uint32_t tmp 0x00; /* Check the parameters */ assert_param(IS_EXTI_PORT_SOURCE(EXTI_PortSourceGPIOx)); assert_param(IS_EXTI_PIN_SOURCE(EXTI_PinSourcex)); tmp ((uint32_t)0x0F) (0x04 * (EXTI_PinSourcex (uint8_t)0x03)); SYSCFG-EXTICR[EXTI_PinSourcex 0x02] ~tmp; SYSCFG-EXTICR[EXTI_PinSourcex 0x02] | (((uint32_t)EXTI_PortSourceGPIOx) (0x04 * (EXTI_PinSourcex (uint8_t)0x03))); } /* 以上是函数的详细代码这里我们带入两个参数 #define KEY1_INT_EXTI_PORTSOURCE EXTI_PortSourceGPIOA #define KEY1_INT_EXTI_PINSOURCE EXTI_PinSource0 EXTI_PortSourceGPIOA其实是 #define EXTI_PortSourceGPIOA ((uint8_t)0x00) EXTI_PinSource0其实是 #define EXTI_PinSource0 ((uint8_t)0x00) 那么函数里面执行的是 tmp ((uint32_t)0x0F) (0x04 * ( ((uint8_t)0x00) (uint8_t)0x03)); 其实也就是tmp ((uint32_t)0x0F)进行下一步 SYSCFG-EXTICR[0] ~tmp; SYSCFG-EXTICR[0] 0xFFF0; SYSCFG-EXTICR[0] SYSCFG-EXTICR[0] 0xFFF0;其实也就是将低4位置0 SYSCFG-EXTICR[0] | ((0) (0x04 * (0 (uint8_t)0x03))); SYSCFG-EXTICR[0] | 0; SYSCFG-EXTICR[0] SYSCFG-EXTICR[0] | 0;也就是等于它本身 也就是将SYSCFG-EXTICR[0]的低四位都设为0即0000 对照寄存器说明0000代表PA[0] */根据上面的例子他设置的是SYSCFG中断寄存器的EXTI0的源输入是PA[0]。EXTI_LINE表示的是选择那一条EXTI中断线这里是EXTI0即EXTI_Line0也是一个宏定义它用来配置EXTI初始化结构体参数。EXTI_IRQ表示的是EXTI中断请求它用于NVIC的中断源参数不同的中断中断源不一样写错也不会报错只是不会导致中断。以下是STM32F407所有的中断源//位于stm32f4xx.h中 typedef enum IRQn { /****** Cortex-M4 Processor Exceptions Numbers ****************************************************************/ NonMaskableInt_IRQn -14, /*! 2 Non Maskable Interrupt */ MemoryManagement_IRQn -12, /*! 4 Cortex-M4 Memory Management Interrupt */ BusFault_IRQn -11, /*! 5 Cortex-M4 Bus Fault Interrupt */ UsageFault_IRQn -10, /*! 6 Cortex-M4 Usage Fault Interrupt */ SVCall_IRQn -5, /*! 11 Cortex-M4 SV Call Interrupt */ DebugMonitor_IRQn -4, /*! 12 Cortex-M4 Debug Monitor Interrupt */ PendSV_IRQn -2, /*! 14 Cortex-M4 Pend SV Interrupt */ SysTick_IRQn -1, /*! 15 Cortex-M4 System Tick Interrupt */ /****** STM32 specific Interrupt Numbers **********************************************************************/ WWDG_IRQn 0, /*! Window WatchDog Interrupt */ PVD_IRQn 1, /*! PVD through EXTI Line detection Interrupt */ TAMP_STAMP_IRQn 2, /*! Tamper and TimeStamp interrupts through the EXTI line */ RTC_WKUP_IRQn 3, /*! RTC Wakeup interrupt through the EXTI line */ FLASH_IRQn 4, /*! FLASH global Interrupt */ RCC_IRQn 5, /*! RCC global Interrupt */ EXTI0_IRQn 6, /*! EXTI Line0 Interrupt */ EXTI1_IRQn 7, /*! EXTI Line1 Interrupt */ EXTI2_IRQn 8, /*! EXTI Line2 Interrupt */ EXTI3_IRQn 9, /*! EXTI Line3 Interrupt */ EXTI4_IRQn 10, /*! EXTI Line4 Interrupt */ DMA1_Stream0_IRQn 11, /*! DMA1 Stream 0 global Interrupt */ DMA1_Stream1_IRQn 12, /*! DMA1 Stream 1 global Interrupt */ DMA1_Stream2_IRQn 13, /*! DMA1 Stream 2 global Interrupt */ DMA1_Stream3_IRQn 14, /*! DMA1 Stream 3 global Interrupt */ DMA1_Stream4_IRQn 15, /*! DMA1 Stream 4 global Interrupt */ DMA1_Stream5_IRQn 16, /*! DMA1 Stream 5 global Interrupt */ DMA1_Stream6_IRQn 17, /*! DMA1 Stream 6 global Interrupt */ ADC_IRQn 18, /*! ADC1, ADC2 and ADC3 global Interrupts */ #if defined(STM32F40_41xxx) CAN1_TX_IRQn 19, /*! CAN1 TX Interrupt */ CAN1_RX0_IRQn 20, /*! CAN1 RX0 Interrupt */ CAN1_RX1_IRQn 21, /*! CAN1 RX1 Interrupt */ CAN1_SCE_IRQn 22, /*! CAN1 SCE Interrupt */ EXTI9_5_IRQn 23, /*! External Line[9:5] Interrupts */ TIM1_BRK_TIM9_IRQn 24, /*! TIM1 Break interrupt and TIM9 global interrupt */ TIM1_UP_TIM10_IRQn 25, /*! TIM1 Update Interrupt and TIM10 global interrupt */ TIM1_TRG_COM_TIM11_IRQn 26, /*! TIM1 Trigger and Commutation Interrupt and TIM11 global interrupt */ 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 */ I2C1_EV_IRQn 31, /*! I2C1 Event Interrupt */ I2C1_ER_IRQn 32, /*! I2C1 Error Interrupt */ I2C2_EV_IRQn 33, /*! I2C2 Event Interrupt */ I2C2_ER_IRQn 34, /*! I2C2 Error Interrupt */ SPI1_IRQn 35, /*! SPI1 global Interrupt */ SPI2_IRQn 36, /*! SPI2 global Interrupt */ USART1_IRQn 37, /*! USART1 global Interrupt */ USART2_IRQn 38, /*! USART2 global Interrupt */ USART3_IRQn 39, /*! USART3 global Interrupt */ EXTI15_10_IRQn 40, /*! External Line[15:10] Interrupts */ RTC_Alarm_IRQn 41, /*! RTC Alarm (A and B) through EXTI Line Interrupt */ OTG_FS_WKUP_IRQn 42, /*! USB OTG FS Wakeup through EXTI line interrupt */ TIM8_BRK_TIM12_IRQn 43, /*! TIM8 Break Interrupt and TIM12 global interrupt */ TIM8_UP_TIM13_IRQn 44, /*! TIM8 Update Interrupt and TIM13 global interrupt */ TIM8_TRG_COM_TIM14_IRQn 45, /*! TIM8 Trigger and Commutation Interrupt and TIM14 global interrupt */ TIM8_CC_IRQn 46, /*! TIM8 Capture Compare Interrupt */ DMA1_Stream7_IRQn 47, /*! DMA1 Stream7 Interrupt */ FSMC_IRQn 48, /*! FSMC global Interrupt */ SDIO_IRQn 49, /*! SDIO global Interrupt */ TIM5_IRQn 50, /*! TIM5 global Interrupt */ SPI3_IRQn 51, /*! SPI3 global Interrupt */ UART4_IRQn 52, /*! UART4 global Interrupt */ UART5_IRQn 53, /*! UART5 global Interrupt */ TIM6_DAC_IRQn 54, /*! TIM6 global and DAC12 underrun error interrupts */ TIM7_IRQn 55, /*! TIM7 global interrupt */ DMA2_Stream0_IRQn 56, /*! DMA2 Stream 0 global Interrupt */ DMA2_Stream1_IRQn 57, /*! DMA2 Stream 1 global Interrupt */ DMA2_Stream2_IRQn 58, /*! DMA2 Stream 2 global Interrupt */ DMA2_Stream3_IRQn 59, /*! DMA2 Stream 3 global Interrupt */ DMA2_Stream4_IRQn 60, /*! DMA2 Stream 4 global Interrupt */ ETH_IRQn 61, /*! Ethernet global Interrupt */ ETH_WKUP_IRQn 62, /*! Ethernet Wakeup through EXTI line Interrupt */ CAN2_TX_IRQn 63, /*! CAN2 TX Interrupt */ CAN2_RX0_IRQn 64, /*! CAN2 RX0 Interrupt */ CAN2_RX1_IRQn 65, /*! CAN2 RX1 Interrupt */ CAN2_SCE_IRQn 66, /*! CAN2 SCE Interrupt */ OTG_FS_IRQn 67, /*! USB OTG FS global Interrupt */ DMA2_Stream5_IRQn 68, /*! DMA2 Stream 5 global interrupt */ DMA2_Stream6_IRQn 69, /*! DMA2 Stream 6 global interrupt */ DMA2_Stream7_IRQn 70, /*! DMA2 Stream 7 global interrupt */ USART6_IRQn 71, /*! USART6 global interrupt */ I2C3_EV_IRQn 72, /*! I2C3 event interrupt */ I2C3_ER_IRQn 73, /*! I2C3 error interrupt */ OTG_HS_EP1_OUT_IRQn 74, /*! USB OTG HS End Point 1 Out global interrupt */ OTG_HS_EP1_IN_IRQn 75, /*! USB OTG HS End Point 1 In global interrupt */ OTG_HS_WKUP_IRQn 76, /*! USB OTG HS Wakeup through EXTI interrupt */ OTG_HS_IRQn 77, /*! USB OTG HS global interrupt */ DCMI_IRQn 78, /*! DCMI global interrupt */ CRYP_IRQn 79, /*! CRYP crypto global interrupt */ HASH_RNG_IRQn 80, /*! Hash and Rng global interrupt */ FPU_IRQn 81 /*! FPU global interrupt */ #endif /* STM32F40_41xxx */从上面·代码可以看出除了前面5个线路有单独的中断服务函数后面5~9和10~15线路使用复用的思想因此EXTI9_5_IRQHandler和EXTI15_10_IRQHandler的中断函数里面使用多次EXTI_GetITStatus函数来判断出线路。这里我们NVIC的中断源选择的是EXTI0_IRQn。IRQHandler指向的中断服务函数也就是说NVIC接收到中断信号之后使CPU转向去执行对应的中断服务函数这个函数位于stm32f4xx_it.h文件中。如下//位于stm32f4xx_it.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); } }这里我们要注意的是在stm32f4xx_it.h中使用了外部变量或者函数需要提前声明该变量或者该函数所在的头文件。也就是说当中断发生时对应的中断服务函数就会被执行我们可以在中断服务函数里面实现一些控制。一般为了确保中断确实发生了我们会在中断服务函数中调用 中断标志位状态读取函数 读取外设中断标志位 并判断标志位状态。像上面的代码中EXTI_GetITStatus()用来获取EXTI的中断标志位状态如果EXTI线有中断发生 函数就返回SET否则返回RESET实际上EXTI_GetITStatus()函数是通过读取EXTI_PR挂起寄存器寄存器值来判断EXTI线状态。按键1的中断服务函数我们让LED1翻转其状态按键2的中断服务函数我们让LED2翻转其状态。执行任务后需要调用EXTI_ClearITPendingBit()函数清除EXTI线的中断标志位。问题1使用按键触发外部中断的时候GPIO的模式为什么设置为GPIO_PuPd_NOPULL答先说明一下并不是所有的按键外部中断都必须配置为浮空模式这要分硬件电路。这里有3种硬件场景对应不同的GPIO的模式1.1 按键一端接GPIO一端接GND这种最常用我们推荐配置是GPIO_PuPd_UP也就是内部上拉它的表现是按键没有按下时也就是常态时引脚高电平当按键按下引脚拉低触发中断。1.2 按键一端接GPIO一端接3.3V我们推荐配置是GPIO_PuPd_DOWN也就是内部下拉它的表现是按键没有按下时也就是常态时引脚低电平当按键按下引脚拉高触发中断。1.3 外部已经焊上了独立的上拉或者下拉电阻也就是GPIO引脚外接10k外部上拉电阻到3.3v案件接GND。此时GPIO需要配置为GPIO_PuPd_NOPULL浮空内部既不上拉也不下拉。问题2外部有独立电阻时为什么禁止开启内部上下拉问题1的延伸答2.1 分压问题。外部存在了10k的上拉电阻如果软件再次开启STM32内部的上拉电阻一般也是几十KΩ此时内外电阻并联改变了引脚电平阈值高低电平界限偏移容易出现按键不稳定、误触发中断2.2 电流异常功耗增加。内外电阻同时存在形成了持续电流通路系统静态功耗上升如果做低功耗设备非常不友好2.3 电平逻辑紊乱。如果外部上拉内部下拉也就相当于这两个电阻一头接3.3一头接GND电阻之间持续导通引脚电压被拉到中间电平这样导致的后果是不稳定、极易频繁抖动、不断触发外部中断。问题3如果板子没有外部上下拉电阻但是软件又配置了GPIO_PuPd_DOWN会出现什么问题答也就是说当按键松开时引脚没有固定的电平会受空间电磁干扰电机、电源、导线干扰电平随机漂移导致的现象也就是外部中断无缘无故频繁误触发问题4EXTI本身需要开启时钟吗答不需要EXTI属于内核外设挂载到APB总线上默认上电就工作。为一需要额外开启时钟的是引脚多路选择器如果是F103则是AFIO如果是F4则是SYSCFG。
阅读完成 · 觉得有帮助?
咨询建站