FreeRTOS開關中斷 和臨界區


    FreeRTOS任務代碼中臨界段的進入和退出主要是通過操作寄存器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 );

}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM