购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

第7章
提升运行效率的中断编程

虽然已经实现了按键控制LED状态的功能,但在实际的项目开发中,单片机通常需要实现很多功能,不可能将所有时间都用在按键状态检测上,如果其他功能花费的时间比较长,很有可能当用户按下按键时,单片机却还没有执行到读取按键状态的代码,从而出现LED状态不会随按键状态 实时改变 的情况(有些按键状态被忽略掉了),这肯定不是我们希望看到的。该怎么办呢?比较好的解决方案就是使用 中断(Interrupt)编程

什么是中断呢?举个例子,假如我在教室讲课,门外有人敲门说:外面有陌生人找。我回复道:你让他在会议室等一下,下课后我再过去。然后继续上课。其间,有一个学生好像生病了,需要去医务室,于是我急匆匆地对学生们说:你们先自己预习一下,送他去医务室后我再过来。随即带生病的学生去医务室。

在这个教学的过程中,“我在教室讲课”相当于单片机在顺序执行语句,当陌生人到来时,我对此处理的方式是押后(先将手头的事情干完再处理),在单片机编程中称为 顺序编程 。当有学生生病需要去医务室时,我马上应答,在单片机编程中称为 中断编程 。也就是说,“带生病的学生去医务室”相当于一个中断请求(Interrupt Request, IRQ)信号(以下简称“中断信号”),它打断了我正在进行的讲课动作。很明显,中断编程一般应用在 对实时性要求比较高 的场合,如果不马上处理则后果不堪设想。

在按键控制LED状态的简单功能中,我们可以将按键按下与松开作为中断信号,这样无论与其他功能相关的代码是否正在执行,单片机都可以实时响应。我们先来看看相关的代码,如清单7.1所示。

清单7.1 中断编程

STM32单片机的中断配置主要包括两个方面:其一, 中断信号是如何产生的。 例如,需要使用中断吗?使用哪个引脚作为中断输入?触发中断的方式是电平还是边沿?如果是边沿触发,是上升沿、下降沿还是两者都可以触发中断呢?其二, 系统是如何响应中断信号的。 它决定当多个中断信号同时产生时,系统按照什么优先级来处理。

STM32F103x系列的单片机使用 外部中断/事件控制器 (External Interrupt/Event Controller, EXTI)管理最多19个外部中断,具体如表7.1所示。

表7.1 STM32F103x系列单片机的外部中断

STM32单片机的每个引脚都可以作为中断输入,但是从表7.1中可以看到,能够分配给GPIO使用的外部中断线的数量只有16个,而通常单片机可用的GPIO数量远大于此值,肯定是不够用的。该怎么办呢?STM32单片机将所有GPIO按组分别对应中断线0~15,这样每个中断线对应最多7个GPIO,如图7.1所示。

以中断线0(EXTI0)为例,我们可以将其分配给PA0、PB0、PC0、PD0、PE0、PF0、PG0任意引脚(只能由寄存器EXTIx[3:0]选择其中之一)。例如,将中断线0分配给PA0,这意味着PB0、PC0、PD0、PE0、PF0、PG0无法作为外部中断输入引脚。

在STM32单片机开发平台中,GPIO与中断线的映射关系配置是由GPIO_EXTILineConfig函数完成的,清单7.1中将按键对应的引脚PB8与中断线8进行了映射。

图7.1 GPIO外部中断线

接下来需要对外部中断进行初始化,为此声明了EXTI_InitTypeDef结构体变量,该结构体的类型定义在stm32f10x_exti.h头文件中,如清单7.2所示。

清单7.2 stm32f10x_exti.h头文件(部分)

EXTI_InitTypeDef结构体中的成员变量EXTI_Line表示需要初始化的中断线。EXTI_Mode表示中断线的模式,可以设置为中断(EXTI_Mode_Interrupt)或事件(EXTI_Mode_Event),两者有细节上的差异,我们无须理会。EXTI_Trigger表示中断的触发方式,可以是上升沿(EXTI_Trigger_Rising)、下降沿(EXTI_Trigger_Falling)或双边沿(EXTI_Trigger_Rising_Falling)。EXTI_LineCmd用来设置中断线的新状态(使能或禁止)。在清单7.1中,我们调用EXTI_InitStructure函数进行了中断的初始化,由于LED状态需要实时跟随按键状态变化,因此设置为双边沿触发模式。

虽然完成了外部中断线的配置,但是系统如何响应我们配置的中断呢?这就涉及 中断优先级 的配置与STM32单片机的嵌套向量中断控制器(Nested Vectored Interrupt Controller, NVIC)。一个复杂的系统可能会配置多个中断,有些中断可能非常重要,而另一些却可能并非如此。例如,在多个中断请求中,某个中断请求非常急,那你就应该将其配置为高优先级。STM32单片机的 中断优先级 抢占 响应 两种优先级共同决定,前者占主导地位, 高抢占优先级 的中断会优先打断主程序或另外一个中断程序,如果两个中断同时发生,且 抢占优先级 相同,系统会优先处理 高响应优先级 的中断。但是要注意, 高响应优先级 的中断不会打断 低响应优先级 的中断,如果正在处理 低响应优先级 的中断, 高响应优先级 的中断并不会打断它,而是等待其执行完成后再执行。

STM32单片机使用4个寄存器位调整 中断优先级 ,这意味着我们能够设置16种优先级,你可以根据项目的实际需求灵活配置抢占或响应优先级的数量(以应对复杂的情况),具体有5种组合使用方式,如表7.2所示。

表7.2 中断优先级组合使用方式

例如,项目中需要及时处理的重要中断数量比较多,那就可以将抢占优先级调高一些,并按照中断的重要程度分配 抢占优先级 。如果重要中断的数量并不多,就可以将 抢占优先级 调低一些。实际应用时,我们只需要使用NVIC_PriorityGroupConfig函数选择优先级组别即可,它被声明在misc.h头文件中,如清单7.3所示。

清单7.3 misc.h头文件(部分)

在清单7.1中,我们选择了第2种中断优先级组别(NVIC_PriorityGroup_2),这也就意味着可供分配的抢占与响应优先级数量均为4。通常在系统代码执行过程中,仅需进行一次中断优先级组别的配置操作,随意改变组别会导致中断管理混乱,可能导致意想不到的程序执行结果。

中断优先级组别只是针对系统中断的整体设置,它确定了抢占与响应优先级的数量,而具体每个中断对应的抢占与响应优先级也需要一一确定,为此我们声明了一个NVIC_InitStructure结构体变量,其成员变量NVIC_IRQChannel指出按键引脚对应的中断通道(也就是清单5.4中定义的中断号),引脚PB8对应为EXTI9_5_IRQn。NVIC_IRQChannelPreemptionPriority与NVIC_IRQChannelSubPriority分别表示中断抢占与响应优先级(此例均设置为2)。NVIC_IRQChannelCmd表示使能或禁止中断嵌套(此例为使能)。最后调用NVIC_Init函数即可实现嵌套向量中断控制器的初始化。

当中断信号产生时,系统就会进入相应的中断服务函数,它们的命名格式都是固定的,引脚PB8对应中断服务函数的名称为EXTI9_5_IRQHandler。由于我们配置的外部中断的触发模式为双边沿,这意味着按键的按下与松开都会产生中断,中断服务函数中只需要根据按键状态设置相应LED的状态即可。最后请务必记住清除中断线上的中断标志位,表示我们已经处理过了该中断请求。

有心的读者可能会想:为什么只有特定名称的函数才可以作为中断服务函数呢?其实,中断服务函数的名称在启动文件startup_stm32f10x_md.s中已经定义好了,如清单7.4所示。

清单7.4 startup_stm32f10x_md.s文件(部分)

前面我们提过“中断向量”的概念,其实它就是“中断服务函数的入口地址”,系统会将所有中断向量按一定规律分配在一片区域内,我们称该区域为中断向量表。在清单7.4中,__Vectors与__Vectors_End分别表示向量表的起始与结束地址,而DCD分配了一些内存,并以中断服务函数的入口地址进行初始化。入口地址中通常有一条跳转到中断服务函数的指令。也就是说,当中断请求产生时,它会从中断向量表中先找到对应的入口地址,然后再跳转到相应的中断服务函数,EXPORT声明了一些可被外部文件引用的全局属性标号,由于 函数的名称代表函数的入口地址 ,如果按照预定义的名称进行函数实现,中断请求产生时就会跳入对应的中断服务函数,其中的USB_HP_CAN1_TX_IRQHandler与USB_LP_CAN1_RX0_IRQHandler分别为USB控制器会使用到的高与低优先级中断服务函数。 55+JM3+QogHfClMyWktB7nRtjG0b5o3ZmsvmumXd+G70Z2fmE84kET1H3a56Ezxx

点击中间区域
呼出菜单
上一章
目录
下一章
×