【STM32F103笔记】4、中断之外部中断——喂~烧水啦

Vevina ·
更新时间:2024-09-20
· 609 次阅读

这一篇来说一下单片机或者说所有处理器提高运行效率的方法——中断处理,为什么这么说呢,记得我以前看到过一个十分形象的例子,这里我“修饰”一下和大家分享:

小明看着电影突然想喝水,但是水壶里没水了,要烧开一壶水(他不喝冷水的),于是把壶装满水放在炉子上(当然也可以用电热水壶嘛),然后突然精神分裂了:

一个小明每隔10秒就去揭开壶盖看看水有没有开,终于他在第100次揭盖的时候发现,水终于开了,并且也错过了电影的精彩片段,而且他还发现在这段时间他啥也没干; 另一个小明把壶放炉子上就继续看电影去了,然后发现电影不好看,又换了好几部电影,终于找到了想看的;正看着电影,炉子那边传来哨声(现在的话就是热水壶咔哒一下断电的声音),——于是小明暂停电影,赶快去关炉子。

这里的小明相当于处理器,需要不停地处理很多操作,而烧水可以类比一个事件,当事件来临时需要处理器进行处理,但又不需要处理器一直关注这个事件,因为不知道这个事件什么时候会来;

而第一个小明类比于处理器使用不断查询的方法,一旦发现事件来临就采取对应的处理方法,这样处理器大半时间都用在查询事件是否来临上,极大地降低了处理器的效率(干了很多无用功——揭盖子);

但第二个小明相当于处理器使用了中断进行处理,也就是处理器先设置好相关的中断(把炉子放水上——啊呸,把水壶放炉子上),然后就可以不再操心这个事件,放开了手去处理别的事件;等到事件来领,触发中断,这样就告诉处理器,事件来临需要处理了(水开了去关炉子);这样一来,在等到事件来临的间隙,就能处理很多事情,大大提高了处理器的效率

有人要说了,烧水不是大概10分钟嘛,到时候去看一眼就好了;好嘛,今天小明家把原来烧煤球的炉子换成了天然气的炉子,火力猛得很,5分钟不到就烧开了,等到小明10分钟去看的时候,水壶都快烧干了。

也就是说,当处理器不知道事件具体会什么时候来,那么使用定时去查询的话,就可能错过事件,或者事件还没来,需要继续查询。

因此,如果是处理器外部事件的发生,比如引脚电平的变化,或者一个触发信号等等,墙裂推荐使用中断处理。

按键触发外部中断

STM32的每个GPIO引脚都可以配置为外部中断引脚,因此,本篇用笔者的黑色最小系统板来进行说明,因为它自带一个按键,通过这个按键触发引脚的外部中断,并改变LED的亮灭状态。

电路

和上一篇中按键的电路一模一样,PA0为按键输入,按下接地为低电平;同样利用PC13控制的LED来展示按键触发外部中断的效果。
在这里插入图片描述

中断配置

STM32F103系列使用的Cortex-M3内核,有一个强大的异常(Exception)处理系统,在Cortex-M3的编程手册中(ST官网可以下载,搜索文档编号PM0056,或者名称STM32F10xxx/20xxx/21xxx/L1xxxx Cortex®-M3 programming manual)可以了解其异常处理系统,这里的异常包括复位Reset)、不可屏蔽中断NMI: NonMaskable Interrupt)、硬件错误Hard fault)、…,以及用户可以定义使用的中断Interrupt (IRQ))。

中断向量

从Cortex-M3内核的异常向量(Exception Vector)表中可以看出:
在这里插入图片描述
从0x0000地址开始,首先是Initial SP,初始化栈指针的值,然后0x0004地址为Reset,也就是第二篇说明启动方式时对应启动文件中的标号Reset,有兴趣的话可以对照启动文件中的标号去看看,了解下其他异常向量是怎么处理的;

从第16个异常向量IRQ0开始,是通常用户可以定义使用的中断请求向量,也就是说,当某个中断被触发后,程序运行指针会被导向相应的中断向量地址,比如IRQ0触发,PC指针就将指向0x0040这个地址,而这个地址一般会用来存储IRQ0的中断服务程序,这样,PC指针就能进入IRQ0的中服程序,处理中断;

什么,中断服务程序为什么不就放在这个地址?
留给每个中断请求向量的地址空间只有4个字节,写不下一个中服程序的哈哈哈。

中断优先级

那么,既然有这么多中断请求向量,而每个中断向量又将对应各种中断情况,那肯定需要有一个先后顺序了;

Cortex-M3内核的中断优先级又分为两种:

preemption priority抢占优先级设置值越小级别越高,即当两个不同的中断同时发生时,抢占优先级高的中断优先进行处理;并且,当有中断T1正在处理时,若发生了中断T2,且T2的抢占优先级高于T1的,那么T1的中断处理将被打断,转而执行T2的中断处理程序,等T2的中断处理程序执行完后,再继续执行T1的中断处理程序; subpriority响应优先级设置值越小级别越高,当两个抢占优先级相同的中断同时发生时,响应优先级高的中断先进行处理。

Cortex-M3内核通过一个叫做内嵌中断控制器(NVIC: Nested Vectored Interrupt Controller)的东西来控制中断进行控制,在库中提供了响应的操作函数与数据类型,包括NVIC初始化优先级设置等:
在这里插入图片描述
上述的异常向量、中断优先级、NVIC等都是Cortex-M3内核的东西,不要与STM32混淆,STM32是因为使用了Cortex-M3内核才具有这些。

外部中断External Interrupt(EXTI:不是exit!)

STM32的每个GPIO都可以配置为外部中断,不同引脚通过不同的中断路径进入中断处理器:
在这里插入图片描述
可以看到这里使用的PA0,位于EXTI0路径上;需要注意的是,只有EXTI0-EXTI4是单独的中断路径,而EXTI9_5和EXTI15_10都是共用一个路径。同样,在库函数中也提供了外部中断EXTI的相关操作函数。

程序

同样,和用于控制LED的引脚一样,外部中断EXTI0和NVIC控制器也需要进行初始化。

NVIC初始化

库函数中,XX初始化都伴随着XX_InitTypeDef,即提供一个初始化结构体用于设置,然后调用XX_Init()函数将初始化结构体中的设置写入寄存器,完成功能的设置。

NVIC初始化程序:

/** * @brief Configure EXTI0 and set priority * @param None * @retval None */ void NVICConfig(void) { NVIC_InitTypeDef NVICInitStruct; // 设置优先级配置方法 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); // 中断通道选择EXTI0,EXTI0_IRQn定义在stm32f10x.h文件中 NVICInitStruct.NVIC_IRQChannel = EXTI0_IRQn; // 定义抢占优先级为最高 NVICInitStruct.NVIC_IRQChannelPreemptionPriority = 0; // 定义响应优先级为最高 NVICInitStruct.NVIC_IRQChannelSubPriority = 0; // 使能 NVICInitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVICInitStruct); } 外部中断初始化

外部中断初始化包括GPIOA0引脚的初始化以及EXTI0的初始化:

/** * @brief Configure EXTI0 and set priority * @param None * @retval None */ void PA0ExtConfig(void) { GPIO_InitTypeDef GPIOInitStruct; EXTI_InitTypeDef EXTIInitStruct; // 同时开启GPIOA和AFIO的外设时钟 // GPIO用作外部中断或者重映射时需开启AFIO时钟,复用功能时则不用 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); GPIOInitStruct.GPIO_Pin = GPIO_Pin_0; // 不需要设置复用功能,直接设置为上拉输入即可,按键接低电平所以设置为上拉 // 如果需要使用GPIO的复用功能,则相应的设置GPIO_Mode_AF_xx GPIOInitStruct.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(GPIOA, &GPIOInitStruct); // 设置外部中断源为GPIOA的Pin_0引脚 GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0); // PA0的外部中断为EXTI0 EXTIInitStruct.EXTI_Line = EXTI_Line0; // 模式为外部中断 EXTIInitStruct.EXTI_Mode = EXTI_Mode_Interrupt; // 设置中断触发方式为下降沿触发 EXTIInitStruct.EXTI_Trigger = EXTI_Trigger_Falling; // 使能 EXTIInitStruct.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTIInitStruct); } 中断服务程序(中断处理程序)

在USER文件夹下有stm32f10x_it.c文件,结尾的it即为interrupt的缩写,这个文件存放中断服务程序,打开文件可以看到里面已经有了一些默认的空函数,当需要用到这些中断功能时,就可以在相应的函数中写入对应的中断处理程序。

在文件结尾,有:

/** * @brief This function handles PPP interrupt request. * @param None * @retval None */ /*void PPP_IRQHandler(void) { }*/

这个被注释的函数PPP_IRQHandler(void)是提供给用户编写外设中断函数用的,比如我们接下来要写的外部中断函数,其中PPP并不能随意填写,这里的名称是和startup_stm32f10x_hd.s文件中设置好的异常向量标号一致的,了解汇编和单片机地址的朋友应该明白为什么,这里可以简要说明一下:

单片机地址一般指的是其内部RAM的地址分配,就像上面的vector table图里一样,地址从0开始递增,在启动文件startup_stm32f10x_hd.s中,按照vector table中的顺序,将内部RAM前面一小部分的地址从0开始分别命名,这样标号__initial_sp就对应了RAM中的0x0000地址,Reset_Handler标号对应0x0004地址,依次类推; 在汇编程序中,可以认为标号代表的就是其所在的地址; 单片机或是处理器内核上电或者复位时,其程序运行指针PC会跳转指向一个固定的地址,一般为0x0000或者RAM开头的某个地址,然后从这个地址开始运行; 而当进入中断(或者异常)时,PC又会跳转到固定的地址,也就是vector table中对应中断的地址,比如STM32某个中断通过它的中断路径,最后触发NVIC控制器的Hard Fault中断,这是PC指针会跳转到Hard Fault对应的地址,也就是图中的0x000C开始运行; 而在启动文件startup_stm32f10x_hd.s中,将这个地址命名为了HardFault_Handler,这样,PC指针就会startup_stm32f10x_hd.s文件中标号为HardFault_Handler的后续程序: HardFault_Handler\ PROC EXPORT HardFault_Handler [WEAK] B . ENDP 从而跳转到stm32f10x_it.c文件中的HardFault_Handler()函数,进入死循环(进入死循环是因为函数中默认的处理就是while(1)): /** * @brief This function handles Hard Fault exception. * @param None * @retval None */ void HardFault_Handler(void) { /* Go to infinite loop when Hard Fault exception occurs */ while (1) { } }

因此,在startup_stm32f10x_hd.s文件中找到EXTI0对应的标号EXTI0_IRQHandler,在stm32f10x_it.c文件中写入中断处理函数,函数名即为标号,和上一篇一样,将LED取反就可以:

/** * @brief This function handles EXTI0(PA0) interrupt request. * @param None * @retval None */ void EXTI0_IRQHandler(void) { BitAction status; if (EXTI_GetITStatus(EXTI_Line0) == SET) { status = (BitAction)(1-GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_13)); GPIO_WriteBit(GPIOC, GPIO_Pin_13, status); EXTI_ClearITPendingBit(EXTI_Line0); } } 完整程序

main.c:

/* Includes ------------------------------------------------------------------*/ #include "stm32f10x.h" /* Private functions ---------------------------------------------------------*/ void NVICConfig(void); void PA0ExtConfig(void); void PC13LEDConfig(void); /** * @brief Main body program * @param None * @retval None */ int main(void) { PC13LEDConfig(); PA0ExtConfig(); NVICConfig(); while(1); } /** * @brief Configure EXTI0 and set priority * @param None * @retval None */ void NVICConfig(void) { NVIC_InitTypeDef NVICInitStruct; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); NVICInitStruct.NVIC_IRQChannel = EXTI0_IRQn; NVICInitStruct.NVIC_IRQChannelPreemptionPriority = 0; NVICInitStruct.NVIC_IRQChannelSubPriority = 0; NVICInitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVICInitStruct); } /** * @brief Configure EXTI0 and set priority * @param None * @retval None */ void PA0ExtConfig(void) { GPIO_InitTypeDef GPIOInitStruct; EXTI_InitTypeDef EXTIInitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); GPIOInitStruct.GPIO_Pin = GPIO_Pin_0; // no need AF mode and dont have AF mode GPIOInitStruct.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(GPIOA, &GPIOInitStruct); GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0); EXTIInitStruct.EXTI_Line = EXTI_Line0; EXTIInitStruct.EXTI_Mode = EXTI_Mode_Interrupt; EXTIInitStruct.EXTI_Trigger = EXTI_Trigger_Falling; EXTIInitStruct.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTIInitStruct); } void PC13LEDConfig(void) { GPIO_InitTypeDef GPIOInitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); GPIOInitStruct.GPIO_Pin = GPIO_Pin_13; GPIOInitStruct.GPIO_Mode = GPIO_Mode_Out_PP; GPIOInitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOC, &GPIOInitStruct); }

stm32f10x_it.c中的中断服务程序,添加在PPP_IRQHandler后面就行:

/** * @brief This function handles PPP interrupt request. * @param None * @retval None */ /*void PPP_IRQHandler(void) { }*/ /** * @brief This function handles EXTI0(PA0) interrupt request. * @param None * @retval None */ void EXTI0_IRQHandler(void) { BitAction status; if (EXTI_GetITStatus(EXTI_Line0) == SET) { status = (BitAction)(1-GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_13)); GPIO_WriteBit(GPIOC, GPIO_Pin_13, status); EXTI_ClearITPendingBit(EXTI_Line0); } } 运行结果

和上一篇中的按键一模一样,只不过处理器通过中断来处理按键,其它时间什么也不用做,而上一篇中需要不停的循环查询按键对应的引脚:
在这里插入图片描述

完结撒花✿✿ヽ(°▽°)ノ✿
作者:Keep_moving_tzw



f10 外部中断 中断 stm32f103 f1

需要 登录 后方可回复, 如果你还没有账号请 注册新账号
相关文章