在现实的操作中,经常会遇到一个正常执行的程序或任务,需要被临时打断,腾出CPU的时间去执行另外一段事先安排好的程序,处理一些发生时间不确定的事件。注意,需要处理的事件不是意外事件,是已经预见到要发生的事件,而且也安排好了(事先安排好的程序)如何处理这样的事件,只是不知道事件究竟会在什么时候发生;例如,你上网买了一样东西,你知道快递会今天送上门,但不知道快递员具体是几点几分送到。这样的事件可以是外部通信接口接收到数据,也可以是某个按键被按动,还可以是供电电源发生故障等。
中断就是打断CPU程序顺序执行,并转去执行另一段程序的一种机制。处理中断事件的程序,叫做中断处理程序。在正常运行的程序被打断,转去执行中断处理程序之前,需要保存好程序运行的现场,在中断处理程序结束后,还需要恢复被中断的程序现场,继续原有的运行进程。
中断事件一般都是比较紧急的事件,需要尽快处理。例如,若不及时接受并存储外部通信接口到达的数据,数据就有可能丢失;如果电话铃响,你不尽快接起来,对方就会挂掉。对于中断事件的处理一般都是比较短暂的,不会占用太多的CPU时间。
对于中断的处理,需要打断正常程序的执行,所以不能过多地占用CPU时间进行处理;对于需要较长处理时间的事件,通常都是把对应的数据和状态存储下来,随后再择机处理。当然这个原则也有例外,例如一个电池供电的烟雾报警器,整个系统在正常情况下处于超低功耗的待机状态,当监测到异常时,可以直接在中断处理程序中进行甄别,并在需要时报警,当异常状态解除或被排除时,再返回待机状态。
从事件的产生至中断处理程序开始执行,这个时间间隔称为中断响应时间,这是中断机制的一个十分重要的参数指标,尤其是在实时处理的系统中,希望这个间隔是可预知的,有时需要它越短越好。设想一个电机控制系统,当发生系统过载时,需要尽可能快地采取措施,让电机停转、断电、刹车制动等,否则轻则电机损坏,重则发生人身事故。
1.1 LPC800的NVIC中断控制器特性
LPC800的核心是Cortex-M0+,她的NVIC中断控制器是ARM随核心提供的,与CPU核心一体设计的;这个中断控制器与其它Cortex-M系列的NVIC控制器功能上基本一致,但性能上略有变化,有兴趣的读者可以自己比较一下。
LPC800中集成的NVIC中断控制器,具有以下特点:
▲紧密耦合的中断控制器,提供低中断延迟。
▲同时控制系统异常和外设的中断。▲支持32个中断向量。
▲支持4级中断优先级和中断嵌套。
▲支持SVC(管理员调用) 指令和PendSV软件中断。
支持不可屏蔽中断。
▲支持用户指定中断向量表的地址。
下面分别更详细地看看这些特性:
1.1.1 低中断延迟
中断延迟是指从接收到中断信号至开始执行中断处理程序的第一条指令,在最好的情况下所需要的时间,通常用多少个CPU时钟周期来衡量中断延迟时间。
对于LPC800使用的Cortex-M0+核心来说,中断延迟的最短时间是15个CPU时钟周期;在30MHz的主频下,这只相当于0.5μs的时间。
对于MCU的应用来说,往往是需要中断的延迟越小越好;但如果具体到不同的应用场景、不同的软件架构,还要综合考虑其它部件的性能和响应速度。
下图是一个典型的中断请求、中断响应的示意图。
图1.中断请求和响应示意图
当外部设备发出中断请求,NVIC控制器检测到这个请求后,经过同步和仲裁开始进入中断响应。控制器首先是由硬件通过堆栈保护程序现场(图中的PUSH),然后调用这个中断对应的中断处理程序(ISR – Interrupt Service Routine)。ISR处理完该中断请求后,执行返回指令退出中断处理,此时NVIC控制器再把堆栈中保存的程序现场恢复到CPU中,最后回到被中断的程序继续执行。
1.1.2 系统异常和外设中断
在早期8位MCU时,基本只有外设中断的概念,还没有完整的系统异常的概念,随着MCU越来越复杂,功能不断地增强,在32位MCU中出现了一系列的系统异常中断源。
所谓系统异常,可以理解成一种特殊的中断,它的中断源来自CPU本身或系统设备,例如SysTick。
目前Cortex-M0+中定义了6个系统异常中断。
▲系统复位:这是我们通常说的Reset。除了上电复位外,还有很多条件可以产生复位,包括:复位引脚被拉低,看门狗计时器到时,检测到低电压,和ARM核心提供的软件复位请求等。
▲不可屏蔽中断:对于一般的中断请求,软件可以根据具体情况确定是否需要响应。对于不可屏蔽中断,软件没有选择的余地,必须设置相应的处理程序,在发生对应事件时进行响应。一般不可屏蔽中断都是非常紧急的事件,如果被屏蔽而得不到响应,会产生严重后果。例如系统掉电,软件必须尽快保存重要数据;再例如系统检测到失火,软件必须启动灭火流程等。
▲硬件失效(Hard Fault):所有不能恢复的系统错误,都会产生这个中断。这是很多人经常遇到的系统异常,导致硬件失效中断的原因很多,最多的状况是访问了没有存储器或外设寄存器的地址空间。其它的原因还包括,执行了非法的指令码(当程序跑乱后就会经常出现这种情况);当访问未对齐的数据时;还有就是系统受到严重干扰导致内部信号紊乱时,等等。
▲系统调用(Supervisor call- SVC):在Cortex-M的指令系统中有一条指令SVCall,执行这条指令后会引起SVC异常中断,同时CPU进入Supervisor状态。SVCall指令是用于在有操作系统并具有系统态和用户态的划分时使用,在这种环境下CPU会限制一般用户程序的某些访问权限(例如不能访问有些地址等)。当程序需要访问特权时,执行SVCall指令进入系统态并获得相应的权限。在LPC800这样的小系统中,不会用到这个异常中断。
▲预约系统服务(PendSV):和上述SVCall一样,PendSV同样是需要得到系统服务,但不同的是PendSV是通过设置一个系统状态位,当CPU处理完其它高优先级的中断,再来响应PendSV的中断请求。很多文档直译PendSV为“挂起异常”,但我觉得用“预约”更加贴切。在LPC800中,一般用户也用不到这个中断。
▲SystemTick:这是对应CPU内部的SystemTick定时器中断。SystemTick是所有Cortex-M都拥有的一个基本定时器,它为整个系统尤其是操作系统提供一个时间基准。本书将在第17章 详细介绍SysTick的用法。
在上述系统异常之外,LPC800还支持另外32个外设中断,下表列出了这些中断来源。
表1.外设中断源列表
这些中断向量只出现在LPC804中,LPC802没有这些中断向量。由于LPC84x的中断源数量多于Cortex-M0+的NVIC所支持的外部中断数目,有些中断源共用了同一个中断请求信号,例如序号11、29、30和31的中断都是复用的,软件需要通过相应的外设寄存器判断真正的中断源和原因。
1.1.3 中断向量
“中断是打断CPU程序顺序执行,并转去执行另一段程序的一种机制”,当中断发生时,CPU需要知道中断处理程序的地址并转去执行相应的处理。
在所有的CPU里面,都是为每一个中断源安排了一个固定的地址,当发生对应的中断时,CPU通过这个固定地址进入中断处理程序,进入的机制有两种。一种机制是发生中断时,CPU直接从那个地址取出中断处理程序的指令并从这个地址开始顺序执行,8051就是采用的这种进入机制。
另一种机制是,CPU从那个固定地址取出中断处理程序的开始地址,即所说的“中断向量”,然后从这个开始地址顺序取出指令并执行,LPC800内置的Cortex-M0+就是采用这种机制。所有中断源的处理程序开始地址,集中放在一个固定的区域,被称为中断向量表。
在8051里,设计者为每个中断源预留了固定8个字节的地址空间。这样设计的好处是,如果用户的中断处理程序很短,能够安排在8个字节内完成,则处理效率很高,中断响应的时间也非常短。
随着CPU的功能越来越丰富,处理的内容越来越多,几乎不会再有那种超短的中断处理程序,因此目前几乎所有的CPU都只为每个中断源预留一个中断向量的地址空间,只存放中断程序的入口地址。这样做可以最大限度地减少CPU预留的地址空间长度,同时还为重定位中断向量表提供了方便。
在LPC800中,CPU预留了地址空间0x0000 0000 ~ 0x000000BF作为异常与中断的向量表,下表是LPC82x的中断向量表的内容安排。
表2.LPC82x中断源向量表内容分配一般的用户基本不需要关心这个表的内容,因为在给出的启动文件中已经帮你填好里面的内容。
1.1.4 中断优先级和中断嵌套
当出现多个中断事件时,就需要有优先级的概念,CPU依据优先级确定如何响应多个同时出现的中断。
在LPC800中的中断优先级是指,一个高优先级的中断可以打断CPU正在处理的一个低优先级中断。这就是中断嵌套的概念,即一个高优先级的中断处理程序嵌在另一个低优先级中断处理程序中。
图2.中断嵌套示意图
从上面示意图可以看到,优先级较低的IRQ2打断了CPU当前的处理,CPU进入相应的处理程序ISR2,在ISR2处理还没有结束时,又发生了较高优先级的IRQ1中断,此时CPU转去执行IRQ1的中断处理程序ISR1,当ISR1结束返回后,CPU继续执行ISR2并最终返回到被打断的处理程序中(图中蓝色部分)。
对于多个相同优先级的中断事件,如果它们的出现有先后顺序,则CPU按照先来先到的原则顺序处理,不会发生嵌套。如果它们同时出现,则CPU按照中断编号的数值大小顺序处理,编号小的中断会先被处理,也不会发生嵌套,表3的第4列给出了每个中断的编号。
表3的第5列给出每个中断源或异常中断源的优先级,除了少数异常中断的优先级是固定的以外,其它中断源的优先级都是软件可配置的。
在LPC800中,可配置的优先级有四级,用0~3表示,0为最高优先级。
在ARM CMSIS库里,有两个函数与中断优先级相关,用户可直接调用设置中断优先级或查询某个中断源的中断优先级:
void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority);// 设置中断优先级
uint32_t NVIC_GetPriority(IRQn_Type IRQn);// 读取中断优先级
使用表1的第4列“编号”作为NVIC_SetPriority和NVIC_GetPriority函数的第一个参数,指定需要操作的中断源;NVIC_GetPriority的第二个参数是需要设定的优先级,只能取数值0~3,任何大于3的数值都可能导致不可知的结果。
1.1.5 中断优先级和嵌套的演示例程
下面以一个例程演示中断嵌套的现象和效果。
本节介绍的例程源代码,位于附属代码目录的Projects/Cookbook_NVIC_GPIO子目录下。除特别说明,所有的代码段都出自源文件Cookbook_NVIC_GPIO.c。关于附属代码目录的说明,请参见第4章 :本书中的例程和项目文件。
在这个例程中,使用了板上的两个按键S2、S4和两个LED:D7、D8。下面是此例程所用的LPC824-Lite开发板的部分线路图:
图3.中断嵌套例程部分线路图
在程序中,S4标示为KEY_USER,对应引脚中断0;S2标示为KEY_ISP,对应引脚中断1。按键对应的信号上升沿和下降沿都会产生中断:
■当进入KEY_USER的引脚中断0的处理程序时,点亮LED0(P0_7),退出处理程序时熄灭LED0。
■当进入KEY_ISP的引脚中断1的处理程序时,点亮LED1(P0_13),退出处理程序时熄灭LED1。
在例程的中断处理程序中特意设置了2秒的等待,这样可以清楚地模拟中断嵌套的过程。
代码片段1. 引脚中断0的中断处理程序
这段代码非常简单,读者可以自行完成引脚中断1的中断处理程序。
下面是主程序中的片断,包含对GPIO和中断的初始化部分。
代码片段2. 主程序中初始化GPIO和中断
关于GPIO和引脚中断的部分,将在后面章节介绍。
代码片段2初始化好各个部分后,主程序即进入一个死循环,随后每次按下按键将会进入代码片段1对应的中断处理程序。
按照下述步骤进行操作,就可以体会到中断优先级的效用:
下面这个图更加直观地展示了按键按下的顺序和中断处理的顺序。
图4.中断优先级演示程序时序图
1.2 NVIC的几个基本概念解读
在这一节介绍一些NVIC特有的概念和含义,对于不需要关心中断响应细节或没有高实时性要求的读者,可以略过此部分内容。
1.2.1中断的尾部链接(Tail-chaining)
该特性的一个形象的叫法是“咬尾中断”。这不是一种新的中断形式,所有中断都有此特性。
我们知道CPU响应中断时,在进入中断处理程序之前,需要保存当前运行任务的执行现场,即CPU的一些内部寄存器。目前绝大多数CPU(也包括ARM的所有内核)都是把执行现场保存在堆栈中,进入中断处理程序之前执行PUSH操作,然后在退出中断处理程序的时候,执行POP操作再返回被中断的任务。
从上一节的图14所展示的场景看,从步骤5.1进入步骤5.2时,即一个中断处理程序的结束紧接着另一个中断处理程序的开始。CPU需要恢复进入S4中断处理前的现场,然后紧接着再保存同一个现场并进入S2 的处理程序,S2处理结束后再次恢复原有的现场。
很显然,CPU在从S4处理程序进入S2 处理程序之间的恢复现场+保存现场的动作是多余的,这段时间被浪费了。中断的尾部链接是ARM的NVIC使用的技术,可以有效地减少这种情况的操作时间。
由于没有找到关于Cortex-M0+的时序图,下面以ARM发布的Cortex-M3时序图说明尾部链接的实现,除了显示的时间周期上可能有所出入,但原理是一样的。
图5.中断的尾部链接示意图
图5上半部分展示了ARM7TDMI的传统中断处理方式,在ISR1结束后,进入ISR2 之前,需要一组POP和另一组PUSH操作。图的下半部分是Cortex-M中NVIC的处理方式,在两个中断处理程序之间,省却了对CPU现场的处理。
途中标明“尾部链接”的这段时间,CPU需要判断是否有挂起未处理的中断请求,并根据优先级获取需要处理的中断的入口向量,再跳转到新的处理程序中。因此,这个时间是省不掉的。
1.2.2 晚到的高优先级中断处理(Late arrival preemption)
前面已经介绍了中断优先级的概念,即高优先级的中断处理程序会打断低优先级的中断处理。
当某个中断事件A来临时,CPU会保存当前执行现场后进入该事件的处理程序,如果在执行保存现场的操作时,还未进入到中断处理程序A前,恰好有另一个高优先级的中断事件B到达,此时为了节省再次保存现场的操作时间,NVIC会直接跳转到这个高优先级的中断处理程序B中,而暂时挂起早到的那个低优先级的中断A。
图6.晚到的高优先级中断处理示意图
如果中断事件B到达时,CPU已经进入了中断处理程序A,则不适用于上述操作流程。
1.2.3 终止退栈 (Pop preemption)
当CPU处理某个中断事件结束时,如果另一个中断事件正在等待处理,则3.2.1介绍的尾部链接机制可以有效地加快进入下一个中断处理程序的进程。
如果CPU结束处理某个中断事件并已经开始执行恢复现场的操作,此时另一个中断事件刚好到达,传统的处理方式是,完成恢复现场的操作后马上再执行一次新的保存现场的操作。“终止退栈处理”的机制可以有效地减少该种情形的处理时间,避免多余的恢复现场再保存现场的压栈和退栈操作,从而加快中断处理的响应速度。
图7.终止退栈处理示意图
上图中可以清楚地看出传统的处理方式,和Cortex-M改进后的处理方式的区别,结合尾部链接的机制,实现了中断的快速响应,增强了系统的实时性。
1.3. LPC800的NVIC使用
1.3.1 NVIC 控制寄存器
LPC800的NVIC(嵌套中断控制器)支持32路中断输入源。NVIC共有5个寄存器(组),每个中断输入源对应其中的一组寄存器位。
表2.NVIC控制寄存器列表
序号
|
寄存器
|
功能
|
说明
|
1
|
ISER0
|
设置中断使能
|
每个中断输入源对应1位。读出’1’表示中断已经使能。
写入’1’ 使能中断,写入’0’无效果。
|
2
|
ICER0
|
清除中断使能
|
每个中断输入源对应1位。读出’1’表示中断已经使能。
写入’1’表示清除中断使能,写入’0’无效果。
|
3
|
ISPR0
|
设置中断挂起
|
每个中断输入源对应1位。读出’1’表示中断已经挂起。
写入’1’表示设置中断的挂起状态,写入’0’无效果。
|
4
|
ICPR0
|
清除中断挂起
|
每个中断输入源对应1位。读出’1’表示中断已经挂起。
写入’1’表示清除中断的挂起状态,写入’0’无效果。
|
5
|
IPR0~7
|
设置中断优先级
|
这里总共有8个32位的寄存器,每个中断输入源对应其中的8位,但其中只有2位有效。
|
上表中前面4个寄存器都是32位寄存器,参照表2的序号,序号0的SPI0_IRQ对应这些寄存器的第0位,依次类推。
IPR0~7这8个寄存器,共有32个8位的字节,地址最小的第0个字节对应SPI0_IRQ,依次类推。
1.3.2 中断的使能和挂起
使能某个中断,是指允许某个中断源所产生的中断信号触发中断,并导致调用中断处理程序。
在LPC800中配置中断需要有至少两个部分。首先是在能够产生中断的外设中,配置相应寄存器,使其能在指定的条件下产生中断信号;例如如果希望在UART接收到数据时产生中断,则必须配置UART模块中相应的寄存器。其次是在NVIC中断控制器中打开该中断信号的传输通道,并最终触发中断,这就是常说的使能中断。
当一个使能的中断对应的中断源,产生了中断信号(例如前述的UART接收到数据),则NVIC内部的中断挂起状态位就会被置’1’,读出ISPR0或ICPR0寄存器就可以看到这一位。当CPU响应该中断请求进入中断处理程序后,NVIC会自动地清除这个中断挂起状态位。
中断挂起状态位表示某个中断请求是否已经被响应,用户程序可以利用这一位,在没有进入它的中断处理程序之前查询对应的状况。特殊情况下,也可以主动地使用ICPR0寄存器清除这个挂起状态,这样该中断的处理程序将不会被调用;例如,在一个较高优先级的中断A处理程序中,主动地判断某个较低优先级的中断B,并直接处理这个请求,实现更快地处理那个低优先级的中断B事件,当然处理完后要主动地清除这个挂起状态,否则还会再进入中断B对应的处理程序中。
1.3.3 操作NVIC 控制寄存器
用户可以直接操作以上寄存器设置NVIC,ARM在CMSIS函数包里也提供了一些简单的函数。
(1) NVIC_GetPendingIRQ和NVIC_GetPriority可以使用表3第4列“编号”中的负数作为输入参数,例如使用-1表示读取SysTick的挂起状态或中断优先级。
(2) NVIC_SetPriority可以使用表3第4列“编号”中对应SVCall、PendSV和SysTick的负数作为输入参数设置它们的中断优先级。
1.3.4 中断向量表和中断处理程序的入口
常用的IAR或Keil集成开发环境中,都提供了一个启动文件,启动文件中给出了默认的中断向量表。
在IAR集成开发环境中,启动文件的名字是:IAR_cstartup_M.s
在Keil集成开发环境中,启动文件的名字是:Keil_startup_LPC8xx.s
在中断向量表中,对应每一个中断或异常的位置都默认设置了一个向量,它们都指向同一个位置,在这个位置上只有一条自我循环的指令,形成一个死循环。用户如果使能了某个中断或异常,要在发生中断事件时跳到自己的中断处理程序,则需要替换中断向量表中向量指向自己的中断处理程序,或者简单的做法是使用与默认的向量相同的名字,编译器会帮你替换向量。
下表列出了所有默认的中断向量的名称,供读者参考。
表4.LPC82x中断源向量默认名称
当出现了你期望的中断,但没有跳转到你自己的中断处理程序时,除了检查中断源的配置以外,也需要检查中断处理程序的名称是否拼写对了,否则CPU会在发生中断时,跳到默认的死循环中,造成死机的现象。
1.3.5 HardFault的处理
HardFault是大家经常遇到的问题,所有不能恢复的系统错误,都会产生这个异常中断。
建议用户自己为HardFault写一个简单的中断处理程序,在其中通过输出某种调试信息或点亮某个LED的方式,让自己可以很清楚地知道出现了HardFault。这个处理程序结尾可以进入死循环而不必返回。
当出现Hard Fault异常中断后,用户可以在这个处理程序中设置一个断点,当程序再次运行停到这个断点时,即可通过集成开发环境(IDE)的函数调用窗口,回溯到触发这个异常中断的指令语句,可以很轻松地发现错误所在。
当然,对于复杂的应用,开发人员也可以在Hard Fault中调出堆栈中的内容,与一些重要的变量一起打包作为系统状态以日志文件的形式保存下来,供随后分析故障时使用。在PC的很多应用软件中我们也看到过类似的机制,当软件由于某种原因崩溃时,通常会弹出一个窗口询问是否需要给原厂发送诊断日志,以便日后提高软件质量。
END
更多恩智浦AI-IoT市场和产品信息,邀您同时关注“NXP客栈”微信公众号
NXP客栈
恩智浦致力于打造安全的连接和基础设施解决方案,为智慧生活保驾护航。
长按二维码,关注我们
恩智浦MCU加油站
这是由恩智浦官方运营的公众号,着重为您推荐恩智浦MCU的产品信息、开发技巧、教程文档、培训课程等内容。
长按二维码,关注我们
原文标题:LPC800前生今世 第三章-嵌套式向量中断控制器
文章出处:【微信公众号:恩智浦MCU加油站】欢迎添加关注!文章转载请注明出处。
全部0条评论
快来发表一下你的评论吧 !