单片机和RTOS的中断系统也就这么回事

发布时间:2023-02-03  

摘要:操作系统实际上我们接触的很多,比如说windows,安卓、IOS、linux都是一种操作系统。单片机也有它自己的操作系统,叫做实时操作系统。那么这种实时操作系统和我们用的这些系统有什么区别呢?


我们经常使用的这些实际上是非实时的操作系统。为什么说它是非实时的,因为它的内核实际上是对任务进行时间片轮转的调度方式。比如说有3个任务,分别是任务A,任务B和任务C。那么在时间片轮转的调度机制里,它会让任务A运行一断时间,然后切换到任务B,然后切换到任务C,这样子不断的轮转。

两个任务间通过 Systick 轮转调度的简单模式

那么这样有一个什么缺点呢?如果有一台自动驾驶的汽车里面任务C,是用来检测障碍物和躲避障碍物的,如果任务C不能得到及时的执行的话,有可能这一台自动驾驶的汽车就会撞到障碍物上,实际上这样是非常危险。所以我们就出现了实时的操作系统,它支持抢占式调度机制,也就是说我们可以把任务C的优先级提高。这样当任务C就绪的时候,就先运行任务C,就保证了任务C的实时性。在操作系统中,最基础的功能就是实现任务调度。

接下来了解一下FreeRTOS,实时操作系统的任务调度。在了解实时操作系统之前,要先了解一下内核,这里用ARM Cortex‐M3内核作为模板。首先我们先来了解一下CPU寄存器,这个是CM3的CPU寄存器的表。CM3 拥有通用寄存器 R0‐R15 以及一些特殊功能寄存器。R0‐R12 是最“通用目的”的,但是绝大多数的 16 位指令只能使用 R0‐R7(低组寄存器),而 32 位的Thumb‐2指令则可以访问所有通用寄存器。特殊功能寄存器有预定义的功能,而且必须通过专用的指令来访问。

Cortex‐M3 的寄存器组

可以看得到,前面这里都是通用寄存器。它们分为有低位寄存器(所有指令都能访问它们)和高位寄存器(只有很少的 16 位 Thumb 指令能访问它们)。那么它们为什么要这样分呢?实际上在ARM内核的早期版本,ARM指令和Thumb指令可以访问的寄存器不一样,所以就分有低位寄存器和高位寄存器。还有后面的R13、R14和R15分别是栈指针、连接寄存器和PC程序指针寄存器。

除此之外CM3还有一些特寄存器。

大家有没有想过当CPU进入中断的时候,实际上是相当于打断了之前的任务。那么在执行完中断之后,CPU又是如何返回到原来的任务?而保证原来的任务不丢失的呢?

在进入中断之前,也就是在左半部分,我们先把CPU寄存器里面的值送入内存中,也称为压栈。然后再运行中断服务函数,在运行中断服务函数的时候,CPU寄存器会被改写。但是这并没有什么关系,因为当中断结束之后,返回到原来的任务的时候,之前CPU寄存器的值就会被从内存中取出,也叫做弹栈。那么通过这样一个机制,就保证了原来的进程的数据不丢失。

那么接下来我们来了解一下CM3的压栈顺序?

入栈顺序以及入栈后堆栈中的内容 第3列所示

上图是Cortex-M3进入中断时,硬件的压栈顺序。也就是说在它进入中断的时候,硬件会自动把这几个寄存器压栈。分别是PC指针、xPSR特殊寄存器、R0到R3通用寄存器、R12通用寄存器,还有LR连接寄存器(保存函数的返回地址)会被压入栈中。按照下面第三列的标号顺序保存到内存中。

那么在压入栈成功之后,当中断执行完成,返回到原来的进程中时,栈里面的内容就会被弹出到CPU寄存器中它的弹出顺序和压入顺序刚好是相反的。也就是说先弹出LR,然后这样依次往下这样子弹出,因为栈是先进后出,所以它是这样一个出栈顺序。

前面我们知道CPU一共有R0-R15以及几个特殊的寄存器。在中断函数到来时上面几个寄存器是硬件自动压入栈中的,那么还有几个是软件压入栈中的,这又如何理解?

举个例子:

程序在执行

if(a<=b)
 a=b;

时候,突然来了中断。任何程序,最终都会转换为机器码,上述C代码可以转换为右边的汇编指令。

对于这4条指令,它们可能随时被异常打断,怎么保证异常处理完后,被打断的程序还能正确运行?

这4条指令涉及R0、R1寄存器,程序被打断时、恢复运行时,R0、R1要保持不变,执行完第3条指令时,比较结果保存在程序状态寄存器PSR里,程序被打断时、恢复运行时,程序状态寄存器保持不变。这4条指令,读取a、b内存,程序被打断时、恢复运行时,a、b内存保持不变。内存保持不变,这很容易实现,程序不越界就可以。所以,关键在于R0、R1、程序状态寄存器要保持不变(当然不止这些寄存器):

  • 在处理异常前,把这些寄存器保存在栈中,这称为保存现场,也就是压栈。

  • 在处理完异常后,从栈中恢复这些寄存器,这称为恢复现场,也就是弹栈。

再举一个例子:

void A()
{
    B();
}

比如函数A调用函数B,函数A应该知道:R0-R3是用来传参数给函数B的;函数B可以肆意修改R0-R3;函数A不要指望函数B帮你保存R0-R3;保存R0-R3,是函数A的事情;对于LR、PSR也是同样的道理,保存它们是函数A的责任。由硬件帮我们完成。

对于函数B:我用到R4-R11中的某一个,我都会在函数入口保存、在函数返回前恢复,从内存中弹栈到CPU的寄存器中;保证在B函数调用前后,函数A看到的R4-R11保存不变。

假设函数B就是异常/中断处理函数,函数B本身能保证R4-R11不变,那么保存现场时,硬件只需要保存R0-R3,R12,LR,PSR和PC这8个寄存器。

那么接下来我们来了解一下CM3的两种特殊中断机制。当CM3开始响应一个中断时,会在它看不见的体内奔涌起三股暗流:

  • 入栈:把8个寄存器的值压入栈。

  • 取向量:从向量表中找出对应的服务程序入口地址。

  • 选择堆栈指针MSP/PSP,更新堆栈指针SP,更新连接寄存器LR,更新程序计数器PC。

第一种叫做咬尾中断

我们知道,在进入中断的时候需要执行入栈,而退出中断的时候需要执行出栈。那么当两个中断来临的时候,像这样在第一个中断执行完成之后,要执行第二个中断。在CM3 处理器内核中是不会再执行出栈和入栈的。也就是说这里节省了出栈和入栈的时间,实际上相当于第2个中断把第一个中断的尾巴咬掉。也就是没有让它再出栈,所以这就被称为咬尾中断。

第二种中断机制叫做晚到中断

晚到中断就是说,当有一个高优限级的任务来临时,之前低优先级的任务取向量还没有完成的时候(之前低优先级的任务还没有从向量表中找出对应的服务程序入口地址),那么这一次压栈就是为高优先级任务做的。也就是说就算高优先级的中断晚到了,它仍然可以用低优先级中断压入的栈。

CM3 处理器内核中断表

在实时操作系统中,经常用到的是这三个中断 PendSV、Systick、SVC。

那么在FreeRTOS中Systick这个中断是用来提供实时操作系统的时钟周期的。而PendSV这个是可悬挂中断,是用来切换进程的。SVC在FreeRTOS中只用了一次,也就是启动第一个进程的时候用到了它。

__asm void vPortSVCHandler( void )

{

/* *INDENT-OFF* */

    PRESERVE8


    ldr r3, = pxCurrentTCB   //取出当前的任务控制块

    ldr r1, [ r3 ] //使用 pxCurrentTCBConst 获取 pxCurrentTCB 地址

    ldr r0, [ r1 ] //pxCurrentTCB 中的第一项是栈顶任务

    ldmia r0 !, { r4 - r11 } //手动将R4-R11,R14寄存器压栈

    msr psp, r0    //恢复任务栈指针

    isb

    mov r0, # 0

    msr basepri, r0 //打开所有的中断

    orr r14, # 0xd

    bx r14

/* *INDENT-ON* */

}


系统异常清单

那么有些人可能就会问了,为什么我不直接在Systick中切换任务呢?而是要在PendSV中切换任务呢?那我们就可以来看一下:

发生 IRQ 时上下文切换的问题

如果在Systick中断到来时,前面有一个中断正在执行,也就是这里的IRQ正在执行。那它就会被打断,然后Systick执行上下文来切换,这时候切换到任务b,它要等待一断时间直到下一次上下文切换,切换回原来IRQ这个中断执行的内容。这样中断才能被执行完成,但是这样我们可以看得到,中断被严重的耽误了,所以这样做实际上是不方便。而且容易出错的。

这时候它们就想出一种办法,说我在Systick中我判断这个时候有没有中断在执行,如果有那么我们就不切换,如果没有我们就切换。这样呢实际上也会造成一个问题,就是如果这个中断函数的中断时间和Systick差不多,比如说如果这是一个定时器中断,这是Systick系统时钟中断。它们的中断周期都是1毫秒,那么它们经常就会面临着两个同时到来的情况。这样就有可能导致进程迟迟无法切换,导致了延误的产生,所以这样做也不是很好。


所以就出现了PendSV可悬挂中断

使用PendSV控制上下文切换

在这种中断中有什么好处呢?我们可以看得到,在Systick中它只将PendSV的中断位挂起,也就是说,它不执行经常切换的这个操作。而是等到后面,当所有的中断执行完成之后在PendSV中执行上下文切换,这样既保证了任务的及时切换,也保证了中断的及时执行。PendSV异常会自动延迟上下文切换的请求,直到其它的ISR都完成了处理后才放行。为实现这个机制,需要把PendSV编程为最低优先级的异常。如果 OS 检测到某 IRQ正在活动并且被Systick抢占,它将悬起一个PendSV异常,以便缓期执行上下文切换。


那么在PendSV中到底是怎么样进行进程切换?在这里用的是汇编语言写的。


__asm void xPortPendSVHandler( void )

{

    extern uxCriticalNesting;

    extern pxCurrentTCB;

    extern vTaskSwitchContext;


    PRESERVE8


    mrs r0, psp//将当前进程栈指针保存在R0寄存器中

    isb


    ldr r3, =pxCurrentTCB //取出当前的任务控制块

    ldr r2, [ r3 ] //将任务控制块地址保存在R2寄存器中


    stmdb r0 !, { r4 - r11 } //手动将R4-R11,R14寄存器压栈

    str r0, [ r2 ] //将当前的栈顶地址写入控制块


    stmdb sp !, { r3, r14 }

    mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY//将这个宏所代表的立即数写入R0寄存器,而这个宏是用户想要屏蔽的最高优先级中断

    msr basepri, r0 //将刚刚R0寄存器的值写入特殊寄存器basepriority中,这个寄存器可以对中断进行细腻的控制它可以将高于这个优先级的中断不屏蔽,而低于这个优先级的中断屏蔽

    dsb

    isb

    bl vTaskSwitchContext

    mov r0, #0

    msr basepri, r0 //取消中断屏蔽

    ldmia sp !, { r3, r14 } //将当前的栈指针从R3寄存器中恢复,这个时候R3寄存器存的值是刚刚从下一任务控制块取


    ldr r1, [ r3 ]

    ldr r0, [ r1 ] //将新任务的栈顶保存到R0寄存器中

    ldmia r0 !, { r4 - r11 } //手动将R4-R11以及R14寄存器弹栈

    msr psp, r0

    isb

    bx r14  //异常返回,返回后硬件将自动恢复其余寄存器,并且使用进程栈指针。

    nop

/* *INDENT-ON* */

}

那么我们刚刚已经了解到了,FreeRTOS实时操作系统的最基本的功能任务切换。但是如果想做一个完善的实时操作系统,还需要非常多的其它的东西,比如说列表和列表项、任务通知、低功耗模式任务控制块及对堆栈处理内存管理、空闲任务、对信号量、软件定时器、事件标志组等等这些内容。


参考内容:


《FreeRTOS源码详解与应用开发》

《ARM Cortex-M3权威指南》

看看程序中具体是怎么实现中断的

下面这张表来自《ARM Cortex-M3权威指南》

在Cortex-M3中有15个异常中断,对应在stm32中如下图

在启动文件中不仅有异常,还有中断,其实中断也是属于一种异常。我们说中断的时候,更多的说的是某一种设备发出的信号比如GPIO模块:发信号给CPU比如12C控制器发送完数据,发出信号给CPU比如UART接收到一个数据之后也会产生中断注意了:中断属于异常。除了中断外其他异常一般有哪些呢:复位:也是一种异常,发生了各种错误:属于异常。

当我们板子复位的时候CPU会执行中断向量表中的Reset_Handler执行这个函数。

当我们板子看门狗中断时的时候CPU会执行中断向量表中的WWDG_IRQHandler执行这个函数。

你肯定有这样一个疑问?CPU怎么知道跳转到中断向量表中的执行哪一个函数呢?

这肯定是硬件确定,因为这时候软件还没有开始执行,硬件确定当前发生的是哪一个异常,哪一个中断,当恢复的时候由软件触发、硬件恢复。


/**

  * @brief  This function handles NMI exception.

  * @param  None

  * @retval None

  */

void NMI_Handler(void)

{

}


/**

  * @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)

  {

  }

}


/**

  * @brief  This function handles Memory Manage exception.

  * @param  None

  * @retval None

  */

void MemManage_Handler(void)

{

  /* Go to infinite loop when Memory Manage exception occurs */

  while (1)

  {

  }

}


/**

  * @brief  This function handles Bus Fault exception.

  * @param  None

  * @retval None

  */

void BusFault_Handler(void)

{

  /* Go to infinite loop when Bus Fault exception occurs */

  while (1)

  {

  }

}


/**

  * @brief  This function handles Usage Fault exception.

  * @param  None

  * @retval None

  */

void UsageFault_Handler(void)

{

  /* Go to infinite loop when Usage Fault exception occurs */

  while (1)

  {

  }

}


/**

  * @brief  This function handles SVCall exception.

  * @param  None

  * @retval None

  */

void SVC_Handler(void)

{

}


/**

  * @brief  This function handles Debug Monitor exception.

  * @param  None

  * @retval None

  */

void DebugMon_Handler(void)

{

}


/**

  * @brief  This function handles PendSVC exception.

  * @param  None

  * @retval None

  */

void PendSV_Handler(void)

{

}


/**

  * @brief  This function handles SysTick Handler.

  * @param  None

  * @retval None

  */

void SysTick_Handler(void)

{

}


好了,现在你知道MCU的中断流程和RTOS的的基本原理了吧?


文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。

相关文章

    。 进入临界就是关闭单片机总中断,退出临界就是恢复单片机中断,记住最好是恢复,不是打开,因为进入临界之前单片机总中断未必是开着的。 那为什么要进入临界?有什么作用? 我们假设一种场景: 我们......
    单片机中断系统介绍_51单片机中断系统结构;什么是中断?就是打断当前要做的事,转而去执行别的事情。单片机中断就是当单片机正在执行程序的时候,突然某个按键按下了(产生外部中断),单片机......
    通信是指使用一条数据线依次逐位传输数据,每一位数据占据固定长度的时间。可以看一下简单的串行通信示意图。 串口通信示意图 二、串口通信有什么用 这里简单列举一下串口通信的用途 • 下载程序 • 外设与单片机通信 单片机......
    单片机volatile关键字的作用;最近有些想伙伴问在单片机c语言编程时volatile这个关键词怎么理解?有什么作用? Volatile是C语言的一个关键字,在stm8和stm32的固......
    什么是模拟看门狗?模拟看门狗有什么用处?;看门狗大家应该再熟悉不过了,一旦忘记喂狗就会导致单片机复位,而在大部分stm32中都存在两种类型的看门狗,Independent watchdog......
    有兴趣,我相信一个小学生都能玩的很好。 单片机初学者怎么看懂代码? 在看代码之前,我们首先要背下C语言的一些关键词和语句都有什么作用,这是基础,这一步没做到,不要想着能看懂代码。 那是不是知道C语言......
    面向对象思想编写单片机程序其实很简单!;摘要:在看别人单片机程序时,你也许是崩溃的,因为全局变量满天飞,不知道哪个在哪用了,哪个表示什么,而且编写极其不规范。自己写单片机程序时,也许......
    51单片机中断基本概念;问题引入 在了解基本概念之前,先看三个问题: 1.你想使用的中断是哪个? 2.你所希望的触发条件是什么? 3.你希望在中断之后做什么? 可以边看边思考,文章......
    51单片机和52单片机有什么区别;  本文主要是关于51单片机和52单片机的相关介绍,并着重对51单片机和52单片机进行了相近的对比分析。   51单片机   51单片机是对所有兼容Intel......
    51单片机中断用法实例解析;51单片机共有5个中断源,分别为: 外部中断0 定时器0中断 外部中断1 定时器1中断 串口中断 每一个中断都对应一个中断向量,中断向量表如下所示: 2什么是中断......

我们与500+贴片厂合作,完美满足客户的定制需求。为品牌提供定制化的推广方案、专属产品特色页,多渠道推广,SEM/SEO精准营销以及与公众号的联合推广...详细>>

利用葫芦芯平台的卓越技术服务和新产品推广能力,原厂代理能轻松打入消费物联网(IOT)、信息与通信(ICT)、汽车及新能源汽车、工业自动化及工业物联网、装备及功率电子...详细>>

充分利用其强大的电子元器件采购流量,创新性地为这些物料提供了一个全新的窗口。我们的高效数字营销技术,不仅可以助你轻松识别与连接到需求方,更能够极大地提高“闲置物料”的处理能力,通过葫芦芯平台...详细>>

我们的目标很明确:构建一个全方位的半导体产业生态系统。成为一家全球领先的半导体互联网生态公司。目前,我们已成功打造了智能汽车、智能家居、大健康医疗、机器人和材料等五大生态领域。更为重要的是...详细>>

我们深知加工与定制类服务商的价值和重要性,因此,我们倾力为您提供最顶尖的营销资源。在我们的平台上,您可以直接接触到100万的研发工程师和采购工程师,以及10万的活跃客户群体...详细>>

凭借我们强大的专业流量和尖端的互联网数字营销技术,我们承诺为原厂提供免费的产品资料推广服务。无论是最新的资讯、技术动态还是创新产品,都可以通过我们的平台迅速传达给目标客户...详细>>

我们不止于将线索转化为潜在客户。葫芦芯平台致力于形成业务闭环,从引流、宣传到最终销售,全程跟进,确保每一个potential lead都得到妥善处理,从而大幅提高转化率。不仅如此...详细>>