灵动微电子 MM32
直播中

zhu

8年用户 1640经验值
擅长:控制/MCU
私信 关注
[原创]

灵动微课堂 (第139讲) | 基于MM32 MCU的OS移植与应用——RT-Thread 中断管理(1)

一.学习的理论知识点中断的概念
中断向量表
中断处理过程
中断嵌套、中断栈
RT-Thread中断管理接口
二.实验Demo原理
基于MM32L073PF核心板上运行RT-Thread OS演示RT-Thread的中断管理,首先初始化一个绿色的LED灯用于指示工作状态,然后开启一个基本定时器timer14定时5ms中断一次,设置一个计时变量,每计时2s释放一个信号量;定义一个子线程,主线程调用子线程,子线程在主线程中等待获取到定时器Timer14每2s释放的信号量,当获取到Timer14释放的信号量时,LED1灯状态翻转,并通过串口UART1打印输出提示语Get the semaphore released by Timer14:LED1 TOGGLE,

三.中断的概念什么是中断?为了方便理解,中断简而言之是系统正在处理某个正常的任务事件时,忽然被另外一个需要马上处理的紧急事件打断,系统转去处理这个紧急的事件。在生活当中我们经常会遇到这个中断场景。

例如:小茗同学正在家里写作业,忽然电话铃声响了,于是标记下当前作业做到哪个题目,然后转去接电话,电话是妈妈打回来的,要求小茗同学在家用电饭煲先煮饭,等妈妈回来后再烧菜,于是小茗同学就去用电饭煲煮饭,煮饭任务执行完后小茗同学又回到书桌前继续上次标记的题目写作业。这是一个典型的中断的过程

电话是妈妈打回来的,小茗同学判断到煮饭的优先级比写作业高,于是电话挂断后小茗同学先去煮饭,煮饭任务执行完后小茗同学又回到书桌前继续写作业,这是一个典型的,在中断中进行任务调度的过程

这些场景在嵌入式系统中也很常见,当CPU正在处理某个内部数据时,外界发生了紧急情况,要求CPU暂停当前的任务转去处理这个异步事件。处理完毕后再回到原来被中断的地址,继续原来的任务,这样的过程称为中断。实现这一功能的系统称为“中断系统”,申请 CPU 中断的请求源称为“中断源”。

中断是一种异常,异常是导致处理器脱离正常运行转向执行特殊代码的任何事件,如果不及时进行处理,轻则出现系统出错,重则会导致系统毁灭性地瘫痪。所以正确地处理异常,避免错误的发生是提高软件稳定性非常重要的一环。如下图1是一个简单的中断示意图:
1.png
图1|中断示意图正常情况下CPU执行主程序任务,当CPU外部或内部触发了中断源的紧急事件时,主程序先保护当前任务的地址数据,转去执行中断服务程序的的任务,执行完中断服务程序任务后,又转回取出主程序之前保存的任务的地址数据继续执行主程序的任务。

01中断向量表
中断向量表是所有中断处理程序的入口,如下图2所示是 Cortex-M系列的中断处理过程:把一个函数(用户中断服务程序)同一个虚拟中断向量表中的中断向量联系在一起。当中断向量对应中断发生的时候,被挂接的用户中断服务程序就会被调用执行。
2.png
图2|中断向量表在 Cortex-M 内核上,所有中断都采用中断向量表的方式进行处理,即当一个中断触发时,处理器将直接判定是哪个中断源,然后直接跳转到相应的固定位置的中断源进行处理,每个中断服务程序必须排列在一起放在统一的地址上(这个地址必须要设置到 NVIC 的中断向量偏移寄存器中)。中断向量表一般由一个数组定义或在MCU起动代码中给出,默认采用起动代码给出,如下图3所示是MM32L073PF启动代码给出的中断向量表: 3.png 图3|MM32L073中断向量表请注意代码后面的 [WEAK] 标识,它是符号弱化标识,在 [WEAK] 前面的符号 (如 NMI_Handler、HardFault_Handler)将被执行弱化处理,如果整个代码在链接时遇到了名称相同的符号(例如与NMI_Handler 相同名称的函数),那么代码将使用未被弱化定义的符号(与 NMI_Handler 相同名称的函数),而与弱化符号相关的代码将被自动丢弃。以 SysTick 中断为例,在系统启动代码中,需要填上 SysTick_Handler 中断入口函数,然后实现该函数即可对 SysTick 中断进行响应,中断处理函数示例程序如下所示(对应于board.c文件里):void SysTick_Handler(void)
{
    /* enter interrupt */
    rt_interrupt_enter();

    rt_tick_increase();

    /* leave interrupt */
    rt_interrupt_leave();
}
02 中断处理过程
RT-Thread 中断管理中,将中断处理程序分为中断前导程序、用户中断服务程序、中断后续程序三部份,如下图4所示:
4.png
图4|中断处理程序2.1 中断前导程序
中断前导程序主要工作如下:
1)保存 CPU 中断现场,这部分跟 CPU 架构相关,不同 CPU 架构的实现方式有差异。
对于 Cortex-M 来说,该工作由硬件自动完成。当一个中断触发并且系统进行响应时,处理器硬件会将当前运行部分的上下文寄存器自动压入中断栈中,这部分的寄存器包括 PSR、PC、LR、R12、R3-R0 寄存器。

2)通知内核进入中断状态,调用 rt_interrupt_enter() 函数,作用是把全局变量 rt_interrupt_nest加 1,用它来记录中断嵌套的层数,代码如下所示。

void rt_interrupt_enter(void)
{
    rt_base_t level;
    level = rt_hw_interrupt_disable();
    rt_interrupt_nest ++;
    rt_hw_interrupt_enable(level);
}
2.2 用户中断服务程序
在用户中断服务程序(ISR)中,分为两种情况,第一种情况是不进行线程切换,这种情况下用户中断服务程序和中断后续程序运行完毕后退出中断模式,返回被中断的线程。

另一种情况是,在中断处理过程中需要进行线程切换,这种情况会调用rt_hw_context_switch_interrupt()函数进行上下文切换,该函数跟 CPU 架构相关,不同 CPU 架构的实现方式有差异。

在Cortex-M架构中,rt_hw_context_switch_interrupt()的函数实现流程如下图所示,它将设置需要切换的线程 rt_interrupt_to_thread 变量,然后触发 PendSV 异常(PendSV 异常是专门用来辅助上下文切换的,且被初始化为最低优先级的异常)。PendSV 异常被触发后,不会立即进行 PendSV 异常中断处理程序,因为此时还在中断处理中,只有当中断后续程序运行完毕,真正退出中断处理后,才进入PendSV异常中断处理程序。
5.png
图5|用户中断处理流程2.3 中断后续程序
中断后续程序主要完成的工作是:
1)通知内核离开中断状态,通过调用 rt_interrupt_leave() 函数,将全局变量 rt_interrupt_nest 减 1,代码如下所示:

void rt_interrupt_leave(void)
{
    rt_base_t level;
    level = rt_hw_interrupt_disable();
    rt_interrupt_nest --;
    rt_hw_interrupt_enable(level);
}
2)恢复中断前的CPU上下文,如果在中断处理过程中未进行线程切换,那么恢复from线程的CPU上下文,如果在中断中进行了线程切换,那么恢复 to 线程的 CPU 上下文。这部分实现跟 CPU 架构相关,不同 CPU 架构的实现方式有差异,在 Cortex-M 架构中实现流程如下图所示:
6.png
图6|CPU处理中断流程2.4 中断嵌套
在允许中断嵌套的情况下,在执行中断服务程序的过程中,如果出现高优先级的中断,当前中断服务程序的执行将被打断,执行高优先级中断的中断服务程序,当高优先级中断的程序处理完成后,被打断的中断服务程序才又得到继续执行,如果需要进行线程调度,线程的上下文切换将在所有中断处理程序都运行结束时才发生,如下图7所示:
7.png
图7|中断嵌套2.5 中断栈
在中断处理过程中,在系统响应中断前,软件代码(或处理器)需要把当前线程的上下文保存下来(通常保存在当前线程的线程栈中),再调用中断服务程序进行中断响应、处理。在进行中断处理时(实质是调用用户的中断服务程序函数),中断处理函数中很可能会有自己的局部变量,这些都需要相应的栈空间来保存,所以中断响应依然需要一个栈空间来做为上下文,运行中断处理函数。中断栈可以保存在打断线程的栈中,当从中断中退出时,返回相应的线程继续执行。

RT-Thread 采用的方式是提供独立的中断栈,即中断发生时,中断的前期处理程序会将用户的栈指针更换到系统事先留出的中断栈空间中,等中断退出时再恢复用户的栈指针。这样中断就不会占用线程的栈空间,从而提高了内存空间的利用率,且随着线程的增加,这种减少内存占用的效果也越明显。

在 Cortex-M 处理器内核里有两个堆栈指针,一个是主堆栈指针(MSP),是默认的堆栈指针,在运行第一个线程之前和在中断和异常服务程序里使用;另一个是线程堆栈指针(PSP),在线程里使用。在中断和异常服务程序退出时,修改 LR 寄存器的第 2 位的值为 1,线程的 SP 就由 MSP 切换到 PSP。

2.6 中断的底半处理
RT-Thread 不对中断服务程序所需要的处理时间做任何假设、限制,但如同其他实时操作系统或非实时操作系统一样,用户需要保证所有的中断服务程序在尽可能短的时间内完成(中断服务程序在系统中相当于拥有最高的优先级,会抢占所有线程优先执行)。这样在发生中断嵌套,或屏蔽了相应中断源的过程中,不会耽误嵌套的其他中断处理过程,或自身中断源的下一次中断信号。

当一个中断发生时,中断服务程序需要取得相应的硬件状态或者数据。如果中断服务程序接下来要对状态或者数据进行简单处理,比如 CPU 时钟中断,中断服务程序只需对一个系统时钟变量进行加以操作,然后就结束中断服务程序。这类中断需要的运行时间往往都比较短。

但对于另外一些中断,中断服务程序在取得硬件状态或数据以后,还需要进行一系列更耗时的处理过程,通常需要将该中断分割为两部分,即上半部分(Top Half)和底半部分(Bottom Half)。在上半部分中,取得硬件状态和数据后,打开被屏蔽的中断,给相关线程发送一条通知(可以是 RT-Thread 所提供的信号量、事件、邮箱或消息队列等方式),然后结束中断服务程序;而接下来,相关的线程在接收到通知后,接着对状态或数据进行进一步的处理,这一过程称之为底半处理。

在下一章节我们将根据上面的内容在MM32L073核心板上实验演练基于RT-ThreadOS演示RT-Thread的中断管理,尽请期待!

更多回帖

发帖
×
20
完善资料,
赚取积分