新聞中心

        EEPW首頁 > 嵌入式系統 > 設計應用 > FreeRTOS 在STM32上的移植 V1.0

        FreeRTOS 在STM32上的移植 V1.0

        作者: 時間:2016-11-17 來源:網絡 收藏

        FreeRTOS作為開源的輕量級實時性操作系統,不僅實現了基本的實時調度、信號量、隊列和存儲管理,而且在商業應用上不需要授權費。

        本文引用地址:http://www.104case.com/article/201611/315439.htm

        FreeRTOS的實現主要由list.c、queue.c、croutine.c和tasks.c 4個文件組成。list.c 是一個鏈表的實現,主要供給內核調度器使用;queue.c 是一個隊列的實現,支持中斷環境和信號量控制;croutine.c 和task.c是兩種任務的組織實現。對于croutine,各任務共享同一個堆棧,使RAM的需求進一步縮小,但也正因如此,他的使用受到相對嚴格的限制。而task則是傳統的實現,各任務使用各自的堆棧,支持完全的搶占式調度。

        FreeRTOS的主要功能可以歸結為以下幾點:
        1)優先級調度、相同優先級任務的輪轉調度,同時可設成可剝奪內核或不可剝奪內核
        2)任務可選擇是否共享堆棧(co-routines & tasks),并且沒有任務數限制
        3)消息隊列,二值信號量,計數信號量,遞歸互斥體
        4)時間管理
        5)內存管理

        與UC/OSII一樣,FreeRTOS在STM32移植大致由3個文件實現,一個.h文件定義編譯器相關的數據類型和中斷處理的宏定義;一個.c文件實現任務的堆棧初始化、系統心跳的管理和任務切換的請求;一個.s文件實現具體的任務切換。

        在本次移植中,使用的編譯軟件為IAR EWARM 5.2。
        一、各文件關鍵部分的實現:

        1、PORTMACRO.H 宏定義部分
        1)定義編譯器相關的各種數據類型
        #define portCHARchar
        #define portFLOATfloat
        #define portDOUBLEdouble
        #define portLONGlong
        #define portSHORTshort
        #define portSTACK_TYPEunsigned portLONG
        #define portBASE_TYPElong

        2)架構相關的定義
        Cortex-M3的堆棧增長方向為高地址向低地址增長
        #define portSTACK_GROWTH( -1 )
        每毫秒的心跳次數
        #define portTICK_RATE_MS( ( portTickType ) 1000 / configTICK_RATE_HZ )
        訪問SRAM的字節對齊
        #define portBYTE_ALIGNMENT8

        3)定義用戶主動引起內核調度的2個函數
        強制上下文切換,用在任務環境中調用
        #define portYIELD()vPortYieldFromISR()
        強制上下文切換,用在中斷處理環境中調用
        #define portEND_SWITCHING_ISR( xSwitchRequired ) if( xSwitchRequired ) vPortYieldFromISR()

        4)定義臨界區的管理函數
        中斷允許和關閉
        #define portDISABLE_INTERRUPTS()vPortSetInterruptMask()
        #define portENABLE_INTERRUPTS()vPortClearInterruptMask()
        臨界區進入和退出
        #define portENTER_CRITICAL()vPortEnterCritical()
        #define portEXIT_CRITICAL()vPortExitCritical()
        用于在中斷環境的中斷允許和關閉
        #define portSET_INTERRUPT_MASK_FROM_ISR()0;vPortSetInterruptMask()
        #define portCLEAR_INTERRUPT_MASK_FROM_ISR(x)vPortClearInterruptMask();(void)x

        2、PORT.C C接口部分
        1)堆棧初始化
        portSTACK_TYPE *pxPortInitialiseStack( portSTACK_TYPE *pxTopOfStack, pdTASK_CODE pxCode, void *pvParameters )
        {
        *pxTopOfStack = portINITIAL_XPSR;/* 程序狀態寄存器 */
        pxTopOfStack--;
        *pxTopOfStack = ( portSTACK_TYPE ) pxCode;/* 任務的入口點 */
        pxTopOfStack--;
        *pxTopOfStack = 0;/* LR */
        pxTopOfStack -= 5;/* R12, R3, R2 and R1. */
        *pxTopOfStack = ( portSTACK_TYPE ) pvParameters;/* 任務的參數 */
        pxTopOfStack -= 8;/* R11, R10, R9, R8, R7, R6, R5 and R4. */
        return pxTopOfStack;
        }

        2)啟動任務調度
        portBASE_TYPE xPortStartScheduler( void )
        {
        讓任務切換中斷和心跳中斷位于最低的優先級,使更高優先級可以搶占mcu
        *(portNVIC_SYSPRI2) |= portNVIC_PENDSV_PRI;
        *(portNVIC_SYSPRI2) |= portNVIC_SYSTICK_PRI;

        設置并啟動系統的心跳時鐘
        prvSetupTimerInterrupt();

        初始化臨界區的嵌套的個數
        uxCriticalNesting = 0;

        啟動第一個任務
        vPortStartFirstTask();

        執行到vPortStartFirstTask函數,內核已經開始正常的調度
        return 0;
        }

        3)主動釋放mcu使用權
        void vPortYieldFromISR( void )
        {
        觸發PendSV系統服務中斷,中斷到來時由匯編函數xPortPendSVHandler()處理
        *(portNVIC_INT_CTRL) = portNVIC_PENDSVSET;
        }
        進入臨界區時,首先關閉中斷;當退出所以嵌套的臨界區后再使能中斷
        void vPortEnterCritical( void )
        {
        portDISABLE_INTERRUPTS();
        uxCriticalNesting++;
        }
        void vPortExitCritical( void )
        {
        uxCriticalNesting--;
        if( uxCriticalNesting == 0 )
        {
        portENABLE_INTERRUPTS();
        }
        }
        4)心跳時鐘處理函數
        void xPortSysTickHandler( void )
        {
        unsigned portLONG ulDummy;

        如果是搶占式調度,首先看一下有沒有需要調度的任務
        #if configUSE_PREEMPTION == 1
        *(portNVIC_INT_CTRL) = portNVIC_PENDSVSET;
        #endif

        ulDummy = portSET_INTERRUPT_MASK_FROM_ISR();
        { 通過task.c的心跳處理函數vTaskIncrementTick(),進行時鐘計數和延時任務的處理
        vTaskIncrementTick();
        }
        portCLEAR_INTERRUPT_MASK_FROM_ISR( ulDummy );
        }

        3、PORTASM.S 匯編處理部分
        1)請求切換任務
        xPortPendSVHandler:
        保存當前任務的上下文到其任務控制塊
        mrs r0, psp
        ldrr3, =pxCurrentTCB獲取當前任務的任務控制塊指針
        ldrr2, [r3]

        stmdb r0!, {r4-r11}保存R4-R11到該任務的堆棧
        str r0, [r2]將最后的堆棧指針保存到任務控制塊的pxTopOfStack

        stmdb sp!, {r3, r14}
        關閉中斷
        mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
        msr basepri, r0
        切換任務的上下文,pxCurrentTCB已指向新的任務

        bl vTaskSwitchContext
        mov r0, #0
        msr basepri, r0
        ldmia sp!, {r3, r14}
        恢復新任務的上下文到各寄存器
        ldr r1, [r3]
        ldr r0, [r1]/* The first item in pxCurrentTCB is the task top of stack. */
        ldmia r0!, {r4-r11}/* Pop the registers. */
        msr psp, r0
        bx r14
        任務切換的示意圖如下:

        2.)中斷允許和關閉的實現,通過BASEPRI屏蔽相應優先級的中斷源
        vPortSetInterruptMask:
        push { r0 }
        mov R0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
        msr BASEPRI, R0
        pop { R0 }

        bx r14

        vPortClearInterruptMask:
        PUSH { r0 }
        MOV R0, #0
        MSR BASEPRI, R0
        POP { R0 }

        bx r14

        3)直接切換任務,用于vPortStartFirstTask第一次啟動任務時初始化堆棧和各寄存器
        vPortSVCHandler;
        ldrr3, =pxCurrentTCB
        ldr r1, [r3]
        ldr r0, [r1]
        ldmia r0!, {r4-r11}
        msr psp, r0
        mov r0, #0
        msrbasepri, r0
        orr r14, r14, #13
        bx r14

        4)啟動第一個任務的匯編實現
        vPortStartFirstTask
        通過中斷向量表的定位堆棧的地址
        ldr r0, =0xE000ED08向量表偏移量寄存器 (VTOR)
        ldr r0, [r0]
        ldr r0, [r0]
        msr msp, r0將堆棧地址保存到主堆棧指針msp中
        觸發SVC軟中斷,由vPortSVCHandler()完成第一個任務的具體切換工作
        svc 0

        FreeRTOS內核調度器啟動的流程如下:

        以上3個文件實現了FreeRTOS內核調度所需的底層接口,相關代碼十分精簡。

        二、創建測試任務:
        下面創建第一個測試任務,LED測試
        int main( void )
        {
        設置系統時鐘,中斷向量表和LED使用的GPIO
        使用stm32的固件包提供的初始化函數,具體說明見相關手冊
        prvSetupHardware();

        通過xTaskCreate()創建4個LED任務vLEDFlashTask(),
        每個任務根據各自的頻率閃爍,分別對應開發板上的4個LED
        vStartLEDFlashTasks( mainFLASH_TASK_PRIORITY );

        •創建一個IDLE任務后,通過xPortStartScheduler啟動調度器
        vTaskStartScheduler();

        調度器工作不正常時返回
        return 0;
        }

        portTASK_FUNCTION()是FreeRTOS定義的函數聲明,沒特殊作用
        static portTASK_FUNCTION( vLEDFlashTask, pvParameters )
        {
        ……省略……,具體為計算各LED的閃爍頻率
        for(;;)
        {
        vTaskDelayUntil( &xLastFlashTime, xFlashRate );
        vParTestToggleLED( uxLED );

        vTaskDelayUntil()的延時時間xFlashRate,是從上一次的延時時間xLastFlashTime算起的,
        相對vTaskDelay()的直接延時更為精準。
        vTaskDelayUntil( &xLastFlashTime, xFlashRate );
        vParTestToggleLED( uxLED );
        }
        }

        FreeRTOS的任務創建與UC/OSII差異不大,主要參數為任務函數,堆棧大小和任務的優先級。如:
        xTaskCreate( vLEDFlashTask, ( signed portCHAR * ) "LEDx", ledSTACK_SIZE, NULL, uxPriority, ( xTaskHandle * ) NULL );

        下面再創建一個LCD顯示任務,以最低優先級運行:
        xTaskCreate( vLCDTask, ( signed portCHAR * ) "LCD", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY, NULL );

        void vLCDTask( void *pvParameters )
        {
        ……省略……
        for( ;; )
        {
        vTaskDelay(1000);
        printf("%c ", usDisplayChar);
        }
        }
        該任務很簡單,每隔1000個ticks(就是1000ms),從LCD上刷新一個數字。



        評論


        技術專區

        關閉
        主站蜘蛛池模板: 万安县| 安义县| 杭锦旗| 大名县| 巩义市| 卢湾区| 阳春市| 永昌县| 高青县| 东安县| 会泽县| 敦煌市| 文水县| 新巴尔虎左旗| 满城县| 静安区| 吴桥县| 历史| 承德县| 涞水县| 织金县| 汶上县| 灌阳县| 建平县| 堆龙德庆县| 米易县| 治县。| 翁牛特旗| 中卫市| 平邑县| 大同县| 邓州市| 北川| 石门县| 贵德县| 集贤县| 周口市| 确山县| 田林县| 轮台县| 时尚|