basepri实现的。进入临界段前操作寄存器basepri关闭了所有小于等于宏定义configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY所定义的中断优先级,这样临界段代码就不会被中断干扰到,而且实现任务切换功能的PendSV中断和滴答定时器中断是最低优先级中断,所以此任务在执行临界段代码期间是不会被其它高优先级任务打断的。退出临界段时重新操作basepri寄存器,即打开被关闭的中断(这里我们不考虑不受FreeRTOS管理的更高优先级中断)

FreeRTOS开关中断

 

FreeRTOS开关中断函数为portENABLE_INTERRUPTS()和portDISABLE_INTERRUPTS(),位于portmacro.h中:

#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )

 

 
可以看出开关中断实际上是通过函数vPortRaiseBASEPRI()和vPortSetBASEPRI(0)来实现的:

 

static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
    __asm
    {
        /* Barrier instructions are not used as this function is only used to lower the BASEPRI value. */
        msr basepri, ulBASEPRI
    }
}

static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
    uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
    __asm
    {
        /* Set BASEPRI to the max syscall priority to effect a critical section. */
        msr basepri, ulNewBASEPRI
        dsb
        isb
    }
}

 

 
函数vPortSetBASEPRI()是向寄存器BASEPRI写入一个值,portENABLE_INTERRUPTS()是开中断,它传递一个0值给vPortSetBASEPRI(),即开中断。
函数vPortRaiseBASEPRI()是向寄存器BASEPRI写入宏configMAX_SYSCALL_INTERRUPT_PRIORITY,那么优先级低于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断就会被屏蔽。
 

临界段代码

 

临界段代码也称临界区,指那些必须完整运行,不能被打断的代码段。FreeRTOS在进入临界段代码的时候需要关闭中断,当处理完临界段代码以后再打开中断。FreeRTOS系统本身就有很多临界段代码,这些代码都加了临界段代码保护。
FreeRTOS与临界段代码保护有关的函数有4个:taskENTER_CRITICAL()、taskEXIT_CRITICAL()、taskENTER_CRITICAL_FROM_ISR()和taskEXIT_CRITICAL_FROM_ISR(),这四个函数是宏定义,在task.h文件中。前两个是任务级的临界段代码保护,后两个是中断级的临界段代码保护。 

 

任务级临界段代码保护

 

taskENTER_CRITICAL()和taskEXIT_CRITICAL()是任务级的临界代码保护,一个是进入临界段,一个是退出临界段,这两个是成对使用的,在task.h文件里如下:

#define taskENTER_CRITICAL()        portENTER_CRITICAL()
#define taskEXIT_CRITICAL()            portEXIT_CRITICAL()

portENTER_CRITICAL()和portSET_INTERRUPT_MASK_FROM_ISR()也是宏定义,在portmacro.h文件里:

#define portENTER_CRITICAL()                    vPortEnterCritical()
#define portEXIT_CRITICAL()                        vPortExitCritical()

函数vPortEnterCritical()和vPortExitCritical()位于port.c文件中:

 

临界区uxCriticalNesting

void vPortEnterCritical( void )
{
    portDISABLE_INTERRUPTS(); //关中断
    uxCriticalNesting++;
    /* This is not the interrupt safe version of the enter critical function so assert() if it is being called from an interrupt context.  Only API functions that end in "FromISR" can be used in an interrupt.  Only assert if the critical nesting count is 1 to protect against recursive calls if the assert function also uses a critical section. */
    if( uxCriticalNesting == 1 )
    { configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 ); }
}

void vPortExitCritical( void )
{
    configASSERT( uxCriticalNesting );
    uxCriticalNesting--;
    if( uxCriticalNesting == 0 )
    { 
        portENABLE_INTERRUPTS();
    }  //uxCriticalNesting等于0,开中断
}

 

上述代码可知,在进入函数vPortEnterCritical()以后会首先关闭中断,给变量uxCriticalNesting加1,uxCriticalNesting是全局变量,记录临界段嵌套次数。函数vPortExitCritical()是退出临界段调用的,将uxCriticalNesting减1,只有当uxCriticalNesting为0时才会调用函数portENABLE_INTERRUPTS()使能中断。这样,只有所有临界段代码都退出后才会使能中断!

 

    细心的读者还会发现上面的这两个函数都对变量uxCriticalNesting进行了操作。这个变量比较重要,用于临界段的嵌套计数。初学的同学也许会问这里直接的开关中断不就可以了吗,为什么还要做一个嵌套计数呢?主要是因为直接的开关中断方式不支持在开关中断之间的代码里再次执行开关中断的嵌套处理,假如当前我们的代码是关闭中断的,嵌套了一个含有开关中断的临界区代码后,退出时中断就成开的了,这样就出问题了。通过嵌套计数就有效地防止了用户嵌套调用函数taskENTER_CRITICAL和taskEXIT_CRITICAL时出错。
 比如下面的例子:

 

void FunctionA()

{

taskDISABLE_INTERRUPTS();  关闭中断

FunctionB(); 调用函数B

FunctionC(); 调用函数C

taskENABLE_INTERRUPTS();  打开中断

}

 

void FunctionB()

{

taskDISABLE_INTERRUPTS();  关闭中断


taskENABLE_INTERRUPTS();  打开中断

}

 

工程中调用了FunctionA就会出现执行完FunctionB后中断被打开的情况,此时FunctionC将

不被保护了。

 

 

任务级临界段代码保护案例:

void taskcritical_test(void)
{
    while(1)
    {
        taskENTER_CRITICAL();   // 进入临界区
        total_num+=0.01f;
        printf("total_num的值为:%.4f\r\n",total_num);
        taskEXIT_CRITICAL();    // 退出临界区
        vTaskDelay(1000);
    }
}

中断级临界段代码保护

 

函数 taskENTER_CRITICAL_FROM_ISR()和taskEXIT_CRITICAL_FROM_ISR()中断级别临界段代码保护,是用在中断服务程序中的,且这个中断的优先级一定要低于configMAX_SYSCALL_INTERRUPT_PRIORITY!
这两个函数位于task.h文件中: 

#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )

portSET_INTERRUPT_MASK_FROM_ISR()和portCLEAR_INTERRUPT_MASK_FROM_ISR( x )位于portmacro.h文件中:

#define portSET_INTERRUPT_MASK_FROM_ISR()        ulPortRaiseBASEPRI()
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x)    vPortSetBASEPRI(x)

 

vPortSetBASEPRI(x)前面已经说明,ulPortRaiseBASEPRI()位于也portmacro.h文件中:

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 critical section. */
        mrs ulReturn, basepri       // 读出BASEPRI的值,保存在ulReturn中
        msr basepri, ulNewBASEPRI   // 将configMAX_SYSCALL_INTERRUPT_PRIORITY写入到寄存器BASEPRI中。
        dsb
        isb
    }
    return ulReturn;                // 返回ulReturn,退出临界区代码保护时要用此值!
}

 

static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )

{

     __asm

     {

        

         msr basepri, ulBASEPRI

     }

}

 

通过上面的源码可以看出,中断服务程序里面的临界段代码的开关中断也是通过寄存器basepri实现的。

初学的同学也许会问,这里怎么没有中断嵌套计数了呢?是的,这里换了另外一种实现方法,通过保存和恢复寄存器basepri的数值就可以实现嵌套使用。如果大家研究过uCOS-II或者III的源码,跟这里的实现方式是一样的,具体看下面的使用举例

 

中断级临界代码保护案例:

使用的时候一定要保证成对使用

void TIM3_IRQHandler(void)
{
    if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) // 溢出中断
    {    
        status_value=taskENTER_CRITICAL_FROM_ISR();    // 进入临界区
        total_num+=1;
        printf("float_num的值为:%d\r\n",total_num);
        taskEXIT_CRITICAL_FROM_ISR(status_value);      // 退出临界区
    }
    TIM_ClearITPendingBit(TIM3,TIM_IT_Update);
}

 嵌套使用举例:

void FunctionB()

{

UBaseType_t uxSavedInterruptStatus;

 

uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();

     临界区代码

portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );

}

 

 

 

void TIM6_DAC_IRQHandler( void )

{

UBaseType_t uxSavedInterruptStatus;

 

uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();

FunctionB(); 

FunctionC();     

portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );

}