記一次FreeRTOS錯誤配置導致無法進入臨界區


  最近項目用到FreeRTOS,在實際調試中發現我自己的一段代碼本來好用的(在無RTOS的情況下),但是當我在帶RTOS的情況下把代碼放到一個單獨的任務中運行時我發現本來好用的代碼莫名其妙的出現問題,有一定的概率會失敗,考慮到應該是內核發生了調度導致代碼中時序比較嚴格的地方被打斷因此會出現時好時不好的現象,因此我對時序嚴格的地方調用了taskENTER_CRITICAL();和taskEXIT_CRITICAL();進行任務切換保護和中斷但是結果還是一樣,由此一來這個問題困擾了好久,我就開始懷疑freeRTOS源碼的問題,仔細看了源碼中關於進入臨界和退出臨界的函數。

進入臨界區的宏函數是:taskENTER_CRITICAL();而又一層宏是portENTER_CRITICAL()最后才是函數vPortEnterCritical()。

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 );
    }
}

注意函數中不僅僅將臨界區深度加1同時還調用了宏函數:portDISABLE_INTERRUPTS();這個宏的實體函數是vPortRaiseBASEPRI()

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
    }
}

這里參考M3權威指南中關於異常掩蔽寄存器,PRIMASK, FAULTMASK 以及 BASEPRI的功能描述。

PRIMASK 用於除能在 NMI 和硬 fault 之外的所有異常,它有效地把當前優先級改為 0(可
編程優先級中的最高優先級)。該寄存器可以通過 MRS 和 MSR 以下例方式訪問:

關中斷
MOV R0, #1
MSR PRIMASK, R0

開中斷
MOV R0, #0
MSR PRIMASK,  R0

FAULTMASK則更加絕對他可以關閉除NMI以外的所有中斷,使用其專用的指令訪問方式及功能如下:

FAULTMASK=1,關異常 
CPSID F ;

FAULTMASK=0,開異常
CPSIE F ;

最后也就是本次將說明關於FreeRTOS系統中臨界區用到的BASEPRI特殊功能寄存器,這個寄存器在內核指南中用了一個“細膩”的描述,意思這個寄存器可以更加細分的控制中斷屏蔽的問題。這個寄存器的作用就是如果你向其中寫入 A則中斷優先級數大於等於A的所有中斷將被除能。同時如果你給其中寫0則表示不屏蔽任何優先級的中斷。回到上面說的FreeRTOS的臨界區問題,在進入臨界區函數內調用了portFORCE_INLINE 函數這個函數內部是用匯編寫的但是內容比較淺顯易懂其實就是將configMAX_SYSCALL_INTERRUPT_PRIORITY 寫入BASEPRI因此這將屏蔽優先級不高於configMAX_SYSCALL_INTERRUPT_PRIORITY (中斷優先級數大於等於configMAX_SYSCALL_INTERRUPT_PRIORITY )的中斷,本次我遇到的問題比較LOW啊,我在配置文件中如下配置

#define configMAX_SYSCALL_INTERRUPT_PRIORITY     5

在實際工程中我將中斷配置為中斷優先級分組“組4”也就是全部用於搶占優先級共可以分16(0~~~15)級優先級,配置文件中我配置為5,但是在使用時發現,調用進入臨界函數后調度器雖然不會再進行調度,但是中斷還是會執行,這我就奇怪了。

繼續看內核指南,M3內核可以支持256級中斷,且BASEPRI寄存器最多可以到9位,具體幾位和芯片設計的優先級表示位數相關,這就是此次問題的原因了,STM32F1只使用了8位優先級中的高四位,低位未使用,因此實際效果如下(灰色表示未使用寫會被忽略,讀回永遠是0)

當我給BASEPRI寄存器中寫入configMAX_SYSCALL_INTERRUPT_PRIORITY = 5  時實際寫入的效果是 0000 0101因此此時對於STM32芯片來說,實際寫入完成后的寄存器實際值是0x00,因為對於低四位功能未實現當寫入時會被忽略,讀取會一直是0,所以會導致出現FreeRTOS臨界區無法屏蔽中斷的問題。同樣對於如果不使用優先級組4時,同理將搶占優先級和子優先級合並后和BASEPRI寄存器中的之比較如果大於則會被屏蔽。這里注意不管使用多少位都是MSB對齊的,查閱內核指南的一段話:通過讓優先級以 MSB 對齊,可以簡化程序的跨器件移植。比如,如果一個程序早先在支持 4 位優先級的器件上運行,在移植到只支持 3 位優先級的器件后,其功能不受影響。但若是對齊到 LSB,則會使 MSB 丟失,導致數值大於 7 的低優先級一下子升高了,甚至會反轉小於等於 7 的高優先級。如,8 號優先級因為損失了 MSB,現在反而變成 0 號了!這一點實際點也就是如圖所示的意義:

完事了。。。細節真的很重要啊!!!

 


免責聲明!

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



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