FreeRTOS 在STM32上的移植 V1.0
FreeRTOS作為開源的輕量級實時性操作系統(tǒng),不僅實現(xiàn)了基本的實時調(diào)度、信號量、隊列和存儲管理,而且在商業(yè)應(yīng)用上不需要授權(quán)費。
本文引用地址:http://m.butianyuan.cn/article/201611/315439.htmFreeRTOS的實現(xiàn)主要由list.c、queue.c、croutine.c和tasks.c 4個文件組成。list.c 是一個鏈表的實現(xiàn),主要供給內(nèi)核調(diào)度器使用;queue.c 是一個隊列的實現(xiàn),支持中斷環(huán)境和信號量控制;croutine.c 和task.c是兩種任務(wù)的組織實現(xiàn)。對于croutine,各任務(wù)共享同一個堆棧,使RAM的需求進一步縮小,但也正因如此,他的使用受到相對嚴格的限制。而task則是傳統(tǒng)的實現(xiàn),各任務(wù)使用各自的堆棧,支持完全的搶占式調(diào)度。
FreeRTOS的主要功能可以歸結(jié)為以下幾點:
1)優(yōu)先級調(diào)度、相同優(yōu)先級任務(wù)的輪轉(zhuǎn)調(diào)度,同時可設(shè)成可剝奪內(nèi)核或不可剝奪內(nèi)核
2)任務(wù)可選擇是否共享堆棧(co-routines & tasks),并且沒有任務(wù)數(shù)限制
3)消息隊列,二值信號量,計數(shù)信號量,遞歸互斥體
4)時間管理
5)內(nèi)存管理
與UC/OSII一樣,F(xiàn)reeRTOS在STM32的移植大致由3個文件實現(xiàn),一個.h文件定義編譯器相關(guān)的數(shù)據(jù)類型和中斷處理的宏定義;一個.c文件實現(xiàn)任務(wù)的堆棧初始化、系統(tǒng)心跳的管理和任務(wù)切換的請求;一個.s文件實現(xiàn)具體的任務(wù)切換。
在本次移植中,使用的編譯軟件為IAR EWARM 5.2。
一、各文件關(guān)鍵部分的實現(xiàn):
1、PORTMACRO.H 宏定義部分
1)定義編譯器相關(guān)的各種數(shù)據(jù)類型
#define portCHARchar
#define portFLOATfloat
#define portDOUBLEdouble
#define portLONGlong
#define portSHORTshort
#define portSTACK_TYPEunsigned portLONG
#define portBASE_TYPElong
2)架構(gòu)相關(guān)的定義
Cortex-M3的堆棧增長方向為高地址向低地址增長
#define portSTACK_GROWTH( -1 )
每毫秒的心跳次數(shù)
#define portTICK_RATE_MS( ( portTickType ) 1000 / configTICK_RATE_HZ )
訪問SRAM的字節(jié)對齊
#define portBYTE_ALIGNMENT8
3)定義用戶主動引起內(nèi)核調(diào)度的2個函數(shù)
強制上下文切換,用在任務(wù)環(huán)境中調(diào)用
#define portYIELD()vPortYieldFromISR()
強制上下文切換,用在中斷處理環(huán)境中調(diào)用
#define portEND_SWITCHING_ISR( xSwitchRequired ) if( xSwitchRequired ) vPortYieldFromISR()
4)定義臨界區(qū)的管理函數(shù)
中斷允許和關(guān)閉
#define portDISABLE_INTERRUPTS()vPortSetInterruptMask()
#define portENABLE_INTERRUPTS()vPortClearInterruptMask()
臨界區(qū)進入和退出
#define portENTER_CRITICAL()vPortEnterCritical()
#define portEXIT_CRITICAL()vPortExitCritical()
用于在中斷環(huán)境的中斷允許和關(guān)閉
#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;/* 程序狀態(tài)寄存器 */
pxTopOfStack--;
*pxTopOfStack = ( portSTACK_TYPE ) pxCode;/* 任務(wù)的入口點 */
pxTopOfStack--;
*pxTopOfStack = 0;/* LR */
pxTopOfStack -= 5;/* R12, R3, R2 and R1. */
*pxTopOfStack = ( portSTACK_TYPE ) pvParameters;/* 任務(wù)的參數(shù) */
pxTopOfStack -= 8;/* R11, R10, R9, R8, R7, R6, R5 and R4. */
return pxTopOfStack;
}
2)啟動任務(wù)調(diào)度
portBASE_TYPE xPortStartScheduler( void )
{
讓任務(wù)切換中斷和心跳中斷位于最低的優(yōu)先級,使更高優(yōu)先級可以搶占mcu
*(portNVIC_SYSPRI2) |= portNVIC_PENDSV_PRI;
*(portNVIC_SYSPRI2) |= portNVIC_SYSTICK_PRI;
設(shè)置并啟動系統(tǒng)的心跳時鐘
prvSetupTimerInterrupt();
初始化臨界區(qū)的嵌套的個數(shù)
uxCriticalNesting = 0;
啟動第一個任務(wù)
vPortStartFirstTask();
執(zhí)行到vPortStartFirstTask函數(shù),內(nèi)核已經(jīng)開始正常的調(diào)度
return 0;
}
3)主動釋放mcu使用權(quán)
void vPortYieldFromISR( void )
{
觸發(fā)PendSV系統(tǒng)服務(wù)中斷,中斷到來時由匯編函數(shù)xPortPendSVHandler()處理
*(portNVIC_INT_CTRL) = portNVIC_PENDSVSET;
}
進入臨界區(qū)時,首先關(guān)閉中斷;當(dāng)退出所以嵌套的臨界區(qū)后再使能中斷
void vPortEnterCritical( void )
{
portDISABLE_INTERRUPTS();
uxCriticalNesting++;
}
void vPortExitCritical( void )
{
uxCriticalNesting--;
if( uxCriticalNesting == 0 )
{
portENABLE_INTERRUPTS();
}
}
4)心跳時鐘處理函數(shù)
void xPortSysTickHandler( void )
{
unsigned portLONG ulDummy;
如果是搶占式調(diào)度,首先看一下有沒有需要調(diào)度的任務(wù)
#if configUSE_PREEMPTION == 1
*(portNVIC_INT_CTRL) = portNVIC_PENDSVSET;
#endif
ulDummy = portSET_INTERRUPT_MASK_FROM_ISR();
{ 通過task.c的心跳處理函數(shù)vTaskIncrementTick(),進行時鐘計數(shù)和延時任務(wù)的處理
vTaskIncrementTick();
}
portCLEAR_INTERRUPT_MASK_FROM_ISR( ulDummy );
}
3、PORTASM.S 匯編處理部分
1)請求切換任務(wù)
xPortPendSVHandler:
保存當(dāng)前任務(wù)的上下文到其任務(wù)控制塊
mrs r0, psp
ldrr3, =pxCurrentTCB獲取當(dāng)前任務(wù)的任務(wù)控制塊指針
ldrr2, [r3]
stmdb r0!, {r4-r11}保存R4-R11到該任務(wù)的堆棧
str r0, [r2]將最后的堆棧指針保存到任務(wù)控制塊的pxTopOfStack
stmdb sp!, {r3, r14}
關(guān)閉中斷
mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
msr basepri, r0
切換任務(wù)的上下文,pxCurrentTCB已指向新的任務(wù)
bl vTaskSwitchContext
mov r0, #0
msr basepri, r0
ldmia sp!, {r3, r14}
恢復(fù)新任務(wù)的上下文到各寄存器
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
任務(wù)切換的示意圖如下:
2.)中斷允許和關(guān)閉的實現(xiàn),通過BASEPRI屏蔽相應(yīng)優(yōu)先級的中斷源
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)直接切換任務(wù),用于vPortStartFirstTask第一次啟動任務(wù)時初始化堆棧和各寄存器
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)啟動第一個任務(wù)的匯編實現(xiàn)
vPortStartFirstTask
通過中斷向量表的定位堆棧的地址
ldr r0, =0xE000ED08向量表偏移量寄存器 (VTOR)
ldr r0, [r0]
ldr r0, [r0]
msr msp, r0將堆棧地址保存到主堆棧指針msp中
觸發(fā)SVC軟中斷,由vPortSVCHandler()完成第一個任務(wù)的具體切換工作
svc 0
FreeRTOS內(nèi)核調(diào)度器啟動的流程如下:
以上3個文件實現(xiàn)了FreeRTOS內(nèi)核調(diào)度所需的底層接口,相關(guān)代碼十分精簡。
二、創(chuàng)建測試任務(wù):
下面創(chuàng)建第一個測試任務(wù),LED測試
int main( void )
{
設(shè)置系統(tǒng)時鐘,中斷向量表和LED使用的GPIO
使用stm32的固件包提供的初始化函數(shù),具體說明見相關(guān)手冊
prvSetupHardware();
通過xTaskCreate()創(chuàng)建4個LED任務(wù)vLEDFlashTask(),
每個任務(wù)根據(jù)各自的頻率閃爍,分別對應(yīng)開發(fā)板上的4個LED
vStartLEDFlashTasks( mainFLASH_TASK_PRIORITY );
•創(chuàng)建一個IDLE任務(wù)后,通過xPortStartScheduler啟動調(diào)度器
vTaskStartScheduler();
調(diào)度器工作不正常時返回
return 0;
}
portTASK_FUNCTION()是FreeRTOS定義的函數(shù)聲明,沒特殊作用
static portTASK_FUNCTION( vLEDFlashTask, pvParameters )
{
……省略……,具體為計算各LED的閃爍頻率
for(;;)
{
vTaskDelayUntil( &xLastFlashTime, xFlashRate );
vParTestToggleLED( uxLED );
vTaskDelayUntil()的延時時間xFlashRate,是從上一次的延時時間xLastFlashTime算起的,
相對vTaskDelay()的直接延時更為精準(zhǔn)。
vTaskDelayUntil( &xLastFlashTime, xFlashRate );
vParTestToggleLED( uxLED );
}
}
FreeRTOS的任務(wù)創(chuàng)建與UC/OSII差異不大,主要參數(shù)為任務(wù)函數(shù),堆棧大小和任務(wù)的優(yōu)先級。如:
xTaskCreate( vLEDFlashTask, ( signed portCHAR * ) "LEDx", ledSTACK_SIZE, NULL, uxPriority, ( xTaskHandle * ) NULL );
下面再創(chuàng)建一個LCD顯示任務(wù),以最低優(yōu)先級運行:
xTaskCreate( vLCDTask, ( signed portCHAR * ) "LCD", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY, NULL );
void vLCDTask( void *pvParameters )
{
……省略……
for( ;; )
{
vTaskDelay(1000);
printf("%c ", usDisplayChar);
}
}
該任務(wù)很簡單,每隔1000個ticks(就是1000ms),從LCD上刷新一個數(shù)字。
評論