两当网站建设,临沂google推广,自适应网站,网页样式与布局freertos临界段保护中断的基础知识cortex-m里面开中断、关中断指令关中断和开中断进入临界段和退出临界段中断的基础知识
嵌套#xff1a;
嵌套向量中断控制器 NVIC(Nested Vectored Interrupt Controller与内核是紧耦合的。提供如下的功能#xff1a;可嵌套中断支持、向量…
freertos临界段保护中断的基础知识cortex-m里面开中断、关中断指令关中断和开中断进入临界段和退出临界段中断的基础知识
嵌套
嵌套向量中断控制器 NVIC(Nested Vectored Interrupt Controller与内核是紧耦合的。提供如下的功能可嵌套中断支持、向量中断支持、动态优先级调整支持、中断延迟大大缩短、 中断可屏蔽。
所有的外部中断和绝大多数系统异常均支持可嵌套中断。异常都可以被赋予不同的优先级当前优先级被存储在 xPSR的专用字段中。当一个异常发生时硬件自动比较该异常的优先级和当前的异常优先级如果发现该异常的优先级更高处理器就会中断当前的中断服务例程或者是普通程序而服务新来的异常(立即抢占)。
如果优先级组设置使得中断嵌套层次很深要确认主堆栈空间足够用。 异常服务程序总是使用MSP主堆栈的容量应是嵌套最深时需要的量。 优先级
CM3 支持中断嵌套使得高优先级异常会抢占(preempt)低优先级异常。
有3个系统异常复位NMI 以及硬 fault它们有固定的优先级并且优先级号是负数高于所有其它异常。所有其它异常的优先级都是可编程的但不能编程为负数。
CM3 支持3个固定的高优先级和多达256级的可编程优先级并且支持128级抢占。但是大多数CM3芯片实际上支持的优先级数会更少如8级、16级、32级等。
裁掉表达优先级的几个低端有效位从而让优先级数减少。如果使用更多的位来表达优先级优先级数增加需要的门也更多带来更多的成本和功耗。
使用3个位来表达优先级优先级配置寄存器的结构如下图所示能够使用的8个优先级为0x00最高0x200x400x600x80 0xA00xC00xE0。 为了使抢占机能变得更可控CM3 把 256 级优先级按位分成高低两段分别是抢占优先级和亚优先级。抢占优先级决定了抢占行为。当抢占优先级相同的异常有不止一个悬起时就优先响应亚优先级最高的异常(亚优先级处理内务)。
优先级分组规定亚优先级至少是1个位。所以抢占优先级最多是7个位最多只有 128 级抢占的现象。
下图是只使用 3 个位来表达优先级从bit5处分组得到4级抢占优先级每个抢占优先级的内部有2个亚优先级。 下图是3 位优先级从比特1处分组(虽然[4:0]未使用却允许从它们中分组)。 应用程序中断及复位控制寄存器(AIRCR)地址0xE000_ED00如下。 中断的悬起与解悬
中断发生时正在处理同级或高优先级异常或者被掩蔽则中断不能立即得到响应。此时中断被悬起。
可以通过中断设置悬起寄存器(SETPEND)、中断悬起清除寄存器(CLRPEND)读取中断的悬起状态还可以写它们来手工悬起中断。 咬尾中断Tail‐Chaining
处理器在响应某异常时如果又发生其优先级高的异常当前异常被阻塞转而执行优先级高的异常。那么异常执行返回后系统处理悬起的异常时如果先POP再把POP出的再PUSH回去这就是浪费CPU时间。
所以CM3不POP这些寄存器而是继续使用上一个异常已经PUSH好的成果。如下图所示。 晚到的高优先级异常
入栈的阶段尚未执行其服务例程时如果此时收到了高优先级异常的请求入栈后将执行高优先级异常的服务例程。如果高优先级异常来得太晚以至于已经执行了前一个异常的指令则按普通的抢占处理这会需要更多的处理器时间和额外32字节的堆栈空间。高优先级异常执行完毕后以咬尾中断方式执行之前被抢占的异常。
cortex-m里面开中断、关中断指令
临界段一段在执行的时候不能被中断的代码段(必须完整运行、不能被打断的代码段)。一般是对全局变量操作时候用到临界段。当一个任务在访问某个全局变量时如果被其他中断打断改变了该全局变量再回到上个任务时全局变量已经不是当时的它了这种情况可能会导致不可意料的后果。
临界段被打断的情况系统调度(最终也是产生PendSV中断)外部中断。
freertos进入临界段代码时需要关闭中断处理完临界段代码再打开中断。
首先看下面的代码。
__asm void prvStartFirstTask( void )
{PRESERVE8/* 在Cortex-M中0xE000ED08是SCB_VTOR这个寄存器的地址里面存放的是向量表的起始地址即MSP的地址 */ldr r0, 0xE000ED08ldr r0, [r0]ldr r0, [r0]/* 设置主堆栈指针msp的值 */msr msp, r0/* 使能全局中断 */cpsie icpsie fdsbisb/* 调用SVC去启动第一个任务 */svc 0 nopnop
}
__asm void vPortSVCHandler( void )
{extern pxCurrentTCB;PRESERVE8ldr r3, pxCurrentTCB /* 加载pxCurrentTCB的地址到r3 */ldr r1, [r3] /* 加载pxCurrentTCB到r1 */ldr r0, [r1] /* 加载pxCurrentTCB指向的值到r0目前r0的值等于第一个任务堆栈的栈顶 */ldmia r0!, {r4-r11} /* 以r0为基地址将栈里面的内容加载到r4~r11寄存器同时r0会递增 */msr psp, r0 /* 将r0的值即任务的栈指针更新到psp */isbmov r0, #0 /* 设置r0的值为0 */msr basepri, r0 /* 设置basepri寄存器的值为0即所有的中断都没有被屏蔽 */orr r14, #0xd bx r14
}
__asm void xPortPendSVHandler( void )
{extern pxCurrentTCB;extern vTaskSwitchContext;PRESERVE8/* 当进入PendSVC Handler时上一个任务运行的环境即xPSRPC任务入口地址R14R12R3R2R1R0任务的形参这些CPU寄存器的值会自动保存到任务的栈中剩下的r4~r11需要手动保存 *//* 获取任务栈指针到r0 */mrs r0, pspisbldr r3, pxCurrentTCB /* 加载pxCurrentTCB的地址到r3 */ldr r2, [r3] /* 加载pxCurrentTCB到r2 */stmdb r0!, {r4-r11} /* 将CPU寄存器r4~r11的值存储到r0指向的地址 */str r0, [r2] /* 将任务栈的新的栈顶指针存储到当前任务TCB的第一个成员即栈顶指针 */ stmdb sp!, {r3, r14} /* 将R3和R14临时压入堆栈 */mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY /* 进入临界段 */msr basepri, r0dsbisbbl vTaskSwitchContext /* 调用函数vTaskSwitchContext寻找新的任务运行,通过使变量pxCurrentTCB指向新的任务来实现任务切换 */ mov r0, #0 /* 退出临界段 */msr basepri, r0ldmia sp!, {r3, r14} /* 恢复r3和r14 */ldr r1, [r3]ldr r0, [r1] /* 当前激活的任务TCB第一项保存了任务堆栈的栈顶,现在栈顶值存入R0*/ldmia r0!, {r4-r11} /* 出栈 */msr psp, r0isbbx r14 nop
}
上面这些代码可以看到有下面这些指令。
cpsie i
cpsie f
msr basepri, r0为了快速地开关中断CM3 专门设置了 CPS 指令有 4 种用法。
CPSID I ;PRIMASK1 ;关中断
CPSIE I ;PRIMASK0 ;开中断
CPSID F ;FAULTMASK1, ;关异常
CPSIE F ;FAULTMASK0 ;开异常可以看到上面指令还是控制的PRIMASK和FAULTMASK寄存器。
如下图所示可以通过CPS 指令打开全局中断或者关闭全局中断。 basepri是中断屏蔽寄存器下面这个设置优先级大于等于11的中断都将被屏蔽。相当于关中断进入临界段。
mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
msr basepri, r0
/*
#define configMAX_SYSCALL_INTERRUPT_PRIORITY 191 /* 高四位有效即等于0xb0或者是11 */
191转成二进制就是11000000高四位就是1100
*/下面这个代码优先级高于0的中断被屏蔽相当于是开中断退出临界段。
mov r0, #0 /* 退出临界段 */
msr basepri, r0关中断和开中断
下面这个代码带返回值的意思是往BASEPRI写入新的值的时候先将BASEPRI的值保存起来更新完BASEPRI的值的时候将之前保存好的BASEPRI的值返回返回的值作为形参传入开中断函数。
不带返回值的意思是在往 BASEPRI 写入新的值的时候不用先将 BASEPRI 的值保存起来 不用管当前的中断状态是怎么样的既然不用管当前的中断状态也就意味着这样的函数不能在中断里面调用。
/*portmacro.h*//*不带返回值的关中断函数不能嵌套不能在中断中使用*/
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
/*不带中断保护的开中断函数*/
#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )/*带返回值的关中断函数可以嵌套可以在中断里面使用*/
#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()
/*带中断保护的开中断函数*/
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)#define configMAX_SYSCALL_INTERRUPT_PRIORITY 191 /* 高四位有效即等于0xb0或者是11 *//*不带返回值的关中断函数*/
static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI configMAX_SYSCALL_INTERRUPT_PRIORITY;__asm{/* Set BASEPRI to the max syscall priority to effect a criticalsection. */msr basepri, ulNewBASEPRIdsbisb}
}
/*带返回值的关中断函数*/
static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
{
uint32_t ulReturn, ulNewBASEPRI configMAX_SYSCALL_INTERRUPT_PRIORITY;__asm{/* Set BASEPRI to the max syscall priority to effect a criticalsection. */mrs ulReturn, baseprimsr basepri, ulNewBASEPRIdsbisb}return ulReturn;
}
/*不带中断保护的开中断函数和带中断保护的开中断函数区别在于参数的值*/
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{__asm{/* Barrier instructions are not used as this function is only used tolower the BASEPRI value. */msr basepri, ulBASEPRI}
}进入临界段和退出临界段
对于不带中断保护情况vPortEnterCritical函数里面的uxCriticalNesting是一个全局变量记录临界段嵌套次数vPortExitCritical函数每次将uxCriticalNesting减一只有当uxCriticalNesting 0才会调用portENABLE_INTERRUPTS函数使能中断。这样的话在有多个临界段代码的时候不会因为某一个临界段代码的退出而打断其他临界段的保护只有所有的临界段代码都退出后才会使能中断。
带中断保护的主要就是往BASEPRI写入新的值的时候先将BASEPRI的值保存起来更新完BASEPRI的值的时候将之前保存好的BASEPRI的值返回返回的值作为形参传入开中断函数。
/*进入临界段不带中断保护*/
#define taskENTER_CRITICAL() portENTER_CRITICAL()
/*退出临界段不带中断保护*/
#define taskEXIT_CRITICAL() portEXIT_CRITICAL()/*进入临界段带中断保护可以嵌套*/
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
/*退出临界段带中断保护可以嵌套*/
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )/*进入临界段不带中断保护*/
#define portENTER_CRITICAL() vPortEnterCritical()
/*退出临界段不带中断保护*/
#define portEXIT_CRITICAL() vPortExitCritical()/*进入临界段带中断保护可以嵌套*/
#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()
/*退出临界段带中断保护可以嵌套*/
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)/*进入临界段不带中断保护*/
void vPortEnterCritical( void )
{/*不带返回值的关中断函数不能嵌套不能在中断中使用*/portDISABLE_INTERRUPTS();uxCriticalNesting;if( uxCriticalNesting 1 ){configASSERT( ( portNVIC_INT_CTRL_REG portVECTACTIVE_MASK ) 0 );}
}
/*退出临界段不带中断保护*/
void vPortExitCritical( void )
{configASSERT( uxCriticalNesting );uxCriticalNesting--;if( uxCriticalNesting 0 ){/*不带中断保护的开中断函数*/portENABLE_INTERRUPTS();}
}/*进入临界段带中断保护可以嵌套*/
static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
{
uint32_t ulReturn, ulNewBASEPRI configMAX_SYSCALL_INTERRUPT_PRIORITY;__asm{/* Set BASEPRI to the max syscall priority to effect a criticalsection. */mrs ulReturn, baseprimsr basepri, ulNewBASEPRIdsbisb}return ulReturn;
}
/*退出临界段带中断保护可以嵌套*/
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{__asm{/* Barrier instructions are not used as this function is only used tolower the BASEPRI value. */msr basepri, ulBASEPRI}
}/*临界段代码的应用场合*/
/* 在中断场合临界段可以嵌套 */
{uint32_t ulReturn;/* 进入临界段临界段可以嵌套 */ulReturn taskENTER_CRITICAL_FROM_ISR();/* 临界段代码 *//* 退出临界段 */taskEXIT_CRITICAL_FROM_ISR( ulReturn );
}/* 在非中断场合临界段不能嵌套 */
{/* 进入临界段 */taskENTER_CRITICAL();/* 临界段代码 *//* 退出临界段*/taskEXIT_CRITICAL();
}