freertos实时操作系统临界段保护开关中断及进入退出的方法(FreeRTOS,开发技术)

时间:2024-05-01 20:24:02 作者 : 石家庄SEO 分类 : 开发技术
  • TAG :

中断的基础知识

嵌套:

嵌套向量中断控制器 NVIC(Nested Vectored Interrupt Controller与内核是紧耦合的。提供如下的功能:可嵌套中断支持、向量中断支持、动态优先级调整支持、中断延迟大大缩短、 中断可屏蔽。

所有的外部中断和绝大多数系统异常均支持可嵌套中断。异常都可以被赋予不同的优先级,当前优先级被存储在 xPSR的专用字段中。当一个异常发生时,硬件自动比较该异常的优先级和当前的异常优先级,如果发现该异常的优先级更高,处理器就会中断当前的中断服务例程(或者是普通程序),而服务新来的异常(立即抢占)。

如果优先级组设置使得中断嵌套层次很深,要确认主堆栈空间足够用。 异常服务程序总是使用MSP,主堆栈的容量应是嵌套最深时需要的量。

优先级:

CM3 支持中断嵌套,使得高优先级异常会抢占(preempt)低优先级异常。

有3个系统异常:复位,NMI 以及硬 fault,它们有固定的优先级,并且优先级号是负数,高于所有其它异常。所有其它异常的优先级都是可编程的(但不能编程为负数)。

CM3 支持3个固定的高优先级和多达256级的可编程优先级,并且支持128级抢占。但是大多数CM3芯片实际上支持的优先级数会更少如8级、16级、32级等。

裁掉表达优先级的几个低端有效位,从而让优先级数减少。如果使用更多的位来表达优先级,优先级数增加,需要的门也更多,带来更多的成本和功耗。

使用3个位来表达优先级,优先级配置寄存器的结构如下图所示,能够使用的8个优先级为:0x00(最高),0x20,0x40,0x60,0x80, 0xA0,0xC0,0xE0。

freertos实时操作系统临界段保护开关中断及进入退出的方法

为了使抢占机能变得更可控,CM3 把 256 级优先级按位分成高低两段,分别是抢占优先级和亚优先级。抢占优先级决定了抢占行为。当抢占优先级相同的异常有不止一个悬起时,就优先响应亚优先级最高的异常(亚优先级处理内务)。

优先级分组规定:亚优先级至少是1个位。所以抢占优先级最多是7个位,最多只有 128 级抢占的现象。

下图是只使用 3 个位来表达优先级,从bit5处分组,得到4级抢占优先级,每个抢占优先级的内部有2个亚优先级。

freertos实时操作系统临界段保护开关中断及进入退出的方法

下图是3 位优先级,从比特1处分组,(虽然[4:0]未使用,却允许从它们中分组)。

freertos实时操作系统临界段保护开关中断及进入退出的方法

应用程序中断及复位控制寄存器(AIRCR)(地址:0xE000_ED00)如下。

freertos实时操作系统临界段保护开关中断及进入退出的方法

中断的悬起与解悬:

中断发生时,正在处理同级或高优先级异常,或者被掩蔽,则中断不能立即得到响应。此时中断被悬起。

可以通过中断设置悬起寄存器(SETPEND)、中断悬起清除寄存器(CLRPEND)读取中断的悬起状态,还可以写它们来手工悬起中断。

咬尾中断Tail‐Chaining:

处理器在响应某异常时,如果又发生其优先级高的异常,当前异常被阻塞,转而执行优先级高的异常。那么异常执行返回后,系统处理悬起的异常时,如果先POP再把POP出的再PUSH回去,这就是浪费CPU时间。

所以CM3不POP这些寄存器,而是继续使用上一个异常已经PUSH好的成果。如下图所示。

freertos实时操作系统临界段保护开关中断及进入退出的方法

晚到的高优先级异常:

入栈的阶段,尚未执行其服务例程时,如果此时收到了高优先级异常的请求,入栈后,将执行高优先级异常的服务例程。如果高优先级异常来得太晚,以至于已经执行了前一个异常的指令,则按普通的抢占处理,这会需要更多的处理器时间和额外32字节的堆栈空间。高优先级异常执行完毕后,以咬尾中断方式执行之前被抢占的异常。

cortex-m里面开中断、关中断指令

临界段:一段在执行的时候不能被中断的代码段(必须完整运行、不能被打断的代码段)。一般是对全局变量操作时候,用到临界段。当一个任务在访问某个全局变量时,如果被其他中断打断,改变了该全局变量,再回到上个任务时,全局变量已经不是当时的它了,这种情况可能会导致不可意料的后果。

临界段被打断的情况:系统调度(最终也是产生PendSV中断);外部中断。

freertos进入临界段代码时需要关闭中断,处理完临界段代码再打开中断。

首先看下面的代码。

__asmvoidprvStartFirstTask(void){PRESERVE8/*在Cortex-M中,0xE000ED08是SCB_VTOR这个寄存器的地址,里面存放的是向量表的起始地址,即MSP的地址*/ldrr0,=0xE000ED08ldrr0,[r0]ldrr0,[r0]/*设置主堆栈指针msp的值*/msrmsp,r0/*使能全局中断*/cpsieicpsiefdsbisb/*调用SVC去启动第一个任务*/svc0nopnop}__asmvoidvPortSVCHandler(void){externpxCurrentTCB;PRESERVE8ldr r3,=pxCurrentTCB /*加载pxCurrentTCB的地址到r3*/ldrr1,[r3] /*加载pxCurrentTCB到r1*/ldrr0,[r1] /*加载pxCurrentTCB指向的值到r0,目前r0的值等于第一个任务堆栈的栈顶*/ldmiar0!,{r4-r11} /*以r0为基地址,将栈里面的内容加载到r4~r11寄存器,同时r0会递增*/msrpsp,r0 /*将r0的值,即任务的栈指针更新到psp*/isbmovr0,#0/*设置r0的值为0*/msr basepri,r0/*设置basepri寄存器的值为0,即所有的中断都没有被屏蔽*/orrr14,#0xdbxr14}__asmvoidxPortPendSVHandler(void){externpxCurrentTCB;externvTaskSwitchContext;PRESERVE8/*当进入PendSVCHandler时,上一个任务运行的环境即:xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0(任务的形参)这些CPU寄存器的值会自动保存到任务的栈中,剩下的r4~r11需要手动保存*//*获取任务栈指针到r0*/mrsr0,pspisbldr r3,=pxCurrentTCB /*加载pxCurrentTCB的地址到r3*/ldr r2,[r3]/*加载pxCurrentTCB到r2*/stmdbr0!,{r4-r11} /*将CPU寄存器r4~r11的值存储到r0指向的地址*/strr0,[r2]/*将任务栈的新的栈顶指针存储到当前任务TCB的第一个成员,即栈顶指针*/stmdbsp!,{r3,r14}/*将R3和R14临时压入堆栈*/movr0,#configMAX_SYSCALL_INTERRUPT_PRIORITY/*进入临界段*/msrbasepri,r0dsbisbblvTaskSwitchContext/*调用函数vTaskSwitchContext,寻找新的任务运行,通过使变量pxCurrentTCB指向新的任务来实现任务切换*/movr0,#0/*退出临界段*/msrbasepri,r0ldmiasp!,{r3,r14}/*恢复r3和r14*/ldrr1,[r3]ldrr0,[r1] /*当前激活的任务TCB第一项保存了任务堆栈的栈顶,现在栈顶值存入R0*/ldmiar0!,{r4-r11} /*出栈*/msrpsp,r0isbbxr14nop}
cpsieicpsiefmsr basepri,r0
cpsieicpsiefmsrbasepri,r0

为了快速地开关中断,CM3 专门设置了 CPS 指令,有 4 种用法。

CPSIDI;PRIMASK=1,;关中断CPSIEI;PRIMASK=0,;开中断CPSIDF;FAULTMASK=1,;关异常CPSIEF;FAULTMASK=0;开异常

可以看到,上面指令还是控制的PRIMASK和FAULTMASK寄存器。

如下图所示,可以通过CPS 指令打开全局中断或者关闭全局中断。

freertos实时操作系统临界段保护开关中断及进入退出的方法

basepri是中断屏蔽寄存器,下面这个设置,优先级大于等于11的中断都将被屏蔽。相当于关中断进入临界段。

movr0,#configMAX_SYSCALL_INTERRUPT_PRIORITYmsrbasepri,r0/*#defineconfigMAX_SYSCALL_INTERRUPT_PRIORITY 191/*高四位有效,即等于0xb0,或者是11*/191转成二进制就是11000000,高四位就是1100*/

下面这个代码:优先级高于0的中断被屏蔽,相当于是开中断退出临界段。

movr0,#0/*退出临界段*/msrbasepri,r0

关中断和开中断

下面这个代码,带返回值的意思是:往BASEPRI写入新的值的时候,先将BASEPRI的值保存起来,更新完BASEPRI的值的时候,将之前保存好的BASEPRI的值返回,返回的值作为形参传入开中断函数。

不带返回值的意思是:在往 BASEPRI 写入新的值的时候,不用先将 BASEPRI 的值保存起来, 不用管当前的中断状态是怎么样的,既然不用管当前的中断状态,也就意味着这样的函数不能在中断里面调用。

/*portmacro.h*//*不带返回值的关中断函数,不能嵌套,不能在中断中使用*/#defineportDISABLE_INTERRUPTS() vPortRaiseBASEPRI()/*不带中断保护的开中断函数*/#defineportENABLE_INTERRUPTS() vPortSetBASEPRI(0)/*带返回值的关中断函数,可以嵌套,可以在中断里面使用*/#defineportSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()/*带中断保护的开中断函数*/#defineportCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)#defineconfigMAX_SYSCALL_INTERRUPT_PRIORITY 191/*高四位有效,即等于0xb0,或者是11*//*不带返回值的关中断函数*/staticportFORCE_INLINEvoidvPortRaiseBASEPRI(void){uint32_tulNewBASEPRI=configMAX_SYSCALL_INTERRUPT_PRIORITY;/*不带返回值的关中断函数*/staticportFORCE_INLINEvoidvPortRaiseBASEPRI(void){uint32_tulNewBASEPRI=configMAX_SYSCALL_INTERRUPT_PRIORITY; __asm { /*SetBASEPRItothemaxsyscallprioritytoeffectacritical section.*/ msrbasepri,ulNewBASEPRI dsb isb }}/*带返回值的关中断函数*/staticportFORCE_INLINEuint32_tulPortRaiseBASEPRI(void){uint32_tulReturn,ulNewBASEPRI=configMAX_SYSCALL_INTERRUPT_PRIORITY; __asm { /*SetBASEPRItothemaxsyscallprioritytoeffectacritical section.*/ mrsulReturn,basepri msrbasepri,ulNewBASEPRI dsb isb } returnulReturn;}/*不带中断保护的开中断函数和带中断保护的开中断函数,区别在于参数的值*/staticportFORCE_INLINEvoidvPortSetBASEPRI(uint32_tulBASEPRI){ __asm { /*Barrierinstructionsarenotusedasthisfunctionisonlyusedto lowertheBASEPRIvalue.*/ msrbasepri,ulBASEPRI }}

进入临界段和退出临界段

对于不带中断保护情况,vPortEnterCritical函数里面的uxCriticalNesting是一个全局变量,记录临界段嵌套次数,vPortExitCritical函数每次将uxCriticalNesting减一,只有当uxCriticalNesting = 0才会调用portENABLE_INTERRUPTS函数使能中断。这样的话,在有多个临界段代码的时候,不会因为某一个临界段代码的退出而打断其他临界段的保护,只有所有的临界段代码都退出后,才会使能中断。

带中断保护的,主要就是往BASEPRI写入新的值的时候,先将BASEPRI的值保存起来,更新完BASEPRI的值的时候,将之前保存好的BASEPRI的值返回,返回的值作为形参传入开中断函数。

/*进入临界段,不带中断保护*/#definetaskENTER_CRITICAL() portENTER_CRITICAL()/*退出临界段,不带中断保护*/#definetaskEXIT_CRITICAL() portEXIT_CRITICAL()/*进入临界段,带中断保护,可以嵌套*/#definetaskENTER_CRITICAL_FROM_ISR()portSET_INTERRUPT_MASK_FROM_ISR()/*退出临界段,带中断保护,可以嵌套*/#definetaskEXIT_CRITICAL_FROM_ISR(x)portCLEAR_INTERRUPT_MASK_FROM_ISR(x)/*进入临界段,不带中断保护*/#defineportENTER_CRITICAL() vPortEnterCritical()/*退出临界段,不带中断保护*/#defineportEXIT_CRITICAL() vPortExitCritical()/*进入临界段,带中断保护,可以嵌套*/#defineportSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()/*退出临界段,带中断保护,可以嵌套*/#defineportCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)/*进入临界段,不带中断保护*/voidvPortEnterCritical(void){/*不带返回值的关中断函数,不能嵌套,不能在中断中使用*/ portDISABLE_INTERRUPTS(); uxCriticalNesting++; if(uxCriticalNesting==1) { configASSERT((portNVIC_INT_CTRL_REG&portVECTACTIVE_MASK)==0); }}/*退出临界段,不带中断保护*/voidvPortExitCritical(void){ configASSERT(uxCriticalNesting); uxCriticalNesting--; if(uxCriticalNesting==0) {/*不带中断保护的开中断函数*/ portENABLE_INTERRUPTS(); }}/*进入临界段,带中断保护,可以嵌套*/staticportFORCE_INLINEuint32_tulPortRaiseBASEPRI(void){uint32_tulReturn,ulNewBASEPRI=configMAX_SYSCALL_INTERRUPT_PRIORITY; __asm { /*SetBASEPRItothemaxsyscallprioritytoeffectacritical section.*/ mrsulReturn,basepri msrbasepri,ulNewBASEPRI dsb isb } returnulReturn;}/*退出临界段,带中断保护,可以嵌套*/staticportFORCE_INLINEvoidvPortSetBASEPRI(uint32_tulBASEPRI){ __asm { /*Barrierinstructionsarenotusedasthisfunctionisonlyusedto lowertheBASEPRIvalue.*/ msrbasepri,ulBASEPRI }}
/*临界段代码的应用场合*//*在中断场合,临界段可以嵌套*/{uint32_tulReturn;/*进入临界段,临界段可以嵌套*/ulReturn=taskENTER_CRITICAL_FROM_ISR();/*临界段代码*//*退出临界段*/taskEXIT_CRITICAL_FROM_ISR(ulReturn);}/*在非中断场合,临界段不能嵌套*/{/*进入临界段*/taskENTER_CRITICAL();/*临界段代码*//*退出临界段*/taskEXIT_CRITICAL();}
 </div> <div class="zixun-tj-product adv-bottom"></div> </div> </div> <div class="prve-next-news">
本文:freertos实时操作系统临界段保护开关中断及进入退出的方法的详细内容,希望对您有所帮助,信息来源于网络。
上一篇:FreeRTOS实时操作系统多任务管理基础知识有哪些下一篇:

7 人围观 / 0 条评论 ↓快速评论↓

(必须)

(必须,保密)

阿狸1 阿狸2 阿狸3 阿狸4 阿狸5 阿狸6 阿狸7 阿狸8 阿狸9 阿狸10 阿狸11 阿狸12 阿狸13 阿狸14 阿狸15 阿狸16 阿狸17 阿狸18