介紹freertos在stm32上的移植說明,每一步都很詳細,讓你學會在stm32+freeRTOS的平臺上開發(fā)應用程序
一、各文件關鍵部分的實現(xiàn):
1、PORTMACRO.H 宏定義部分1)定義編譯器相關的各種數(shù)據類型
#define portCHAR char
#define portFLOAT float
#define portDOUBLE double
#define portLONG long
#define portSHORT short
#define portSTACK_TYPE unsigned portLONG
#define portBASE_TYPE long
2)架構相關的定義
Cortex-M3的堆棧增長方向為高地址向低地址增長
#define portSTACK_GROWTH ( -1 )
每毫秒的心跳次數(shù)
#define portTICK_RATE_MS ( ( portTickType ) 1000 / configTICK_RATE_HZ )
訪問SRAM的字節(jié)對齊
#define portBYTE_ALIGNMENT 8
3)定義用戶主動引起內核調度的2個函數(shù)
強制上下文切換,用在任務環(huán)境中調用
#define portYIELD() vPortYieldFromISR()
強制上下文切換,用在中斷處理環(huán)境中調用
#define portEND_SWITCHING_ISR( xSwitchRequired ) if( xSwitchRequired ) vPortYieldFromISR()
4)定義臨界區(qū)的管理函數(shù)
中斷允許和關閉
#define portDISABLE_INTERRUPTS() vPortSetInterruptMask()
#define portENABLE_INTERRUPTS() vPortClearInterruptMask()
臨界區(qū)進入和退出
#define portENTER_CRITICAL() vPortEnterCritical()
#define portEXIT_CRITICAL() vPortExitCritical()
用于在中斷環(huá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; /* 任務的入口點 */
pxTopOfStack--;
*pxTopOfStack = 0; /* LR */
pxTopOfStack -= 5; /* R12, R3, R2 and R1. */
*pxTopOfStack = ( portSTACK_TYPE ) pvParameters; /* 任務的參數(shù) */
pxTopOfStack -= 8; /* R11, R10, R9, R8, R7, R6, R5 and R4. */
return pxTopOfStack;
}
2)啟動任務調度
portBASE_TYPE xPortStartScheduler( void )
{
讓任務切換中斷和心跳中斷位于最低的優(yōu)先級,使更高優(yōu)先級可以搶占mcu
*(portNVIC_SYSPRI2) |= portNVIC_PENDSV_PRI;
*(portNVIC_SYSPRI2) |= portNVIC_SYSTICK_PRI;
設置并啟動系統(tǒng)的心跳時鐘
prvSetupTimerInterrupt();
初始化臨界區(qū)的嵌套的個數(shù)
uxCriticalNesting = 0;
啟動第一個任務
vPortStartFirstTask();
執(zhí)行到vPortStartFirstTask函數(shù),內核已經開始正常的調度
return 0;
}
3)主動釋放mcu使用權
void vPortYieldFromISR( void )
{
觸發(fā)PendSV系統(tǒng)服務中斷,中斷到來時由匯編函數(shù)xPortPendSVHandler()處理
*(portNVIC_INT_CTRL) = portNVIC_PENDSVSET;
}
進入臨界區(qū)時,首先關閉中斷;當退出所以嵌套的臨界區(qū)后再使能中斷
void vPortEnterCritical( void )
{
portDISABLE_INTERRUPTS();
uxCriticalNesting++;
}
void vPortExitCritical( void )
{
uxCriticalNesting--;
if( uxCriticalNesting == 0 )
{
portENABLE_INTERRUPTS();
}
}
4)心跳時鐘處理函數(shù)
void xPortSysTickHandler( void )
{
unsigned portLONG ulDummy;
如果是搶占式調度,首先看一下有沒有需要調度的任務
#if configUSE_PREEMPTION == 1
*(portNVIC_INT_CTRL) = portNVIC_PENDSVSET;
#endif
ulDummy = portSET_INTERRUPT_MASK_FROM_ISR();
{ 通過task.c的心跳處理函數(shù)vTaskIncrementTick(),進行時鐘計數(shù)和延時任務的處理
vTaskIncrementTick();
}
portCLEAR_INTERRUPT_MASK_FROM_ISR( ulDummy );
}
3、PORTASM.S 匯編處理部分
1)請求切換任務
xPortPendSVHandler:
保存當前任務的上下文到其任務控制塊
mrs r0, psp
ldr r3, =pxCurrentTCB 獲取當前任務的任務控制塊指針
ldr r2, [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.)中斷允許和關閉的實現(xiàn),通過BASEPRI屏蔽相應優(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)直接切換任務,用于vPortStartFirstTask第一次啟動任務時初始化堆棧和各寄存器
vPortSVCHandler;
ldr r3, =pxCurrentTCB
ldr r1, [r3]
ldr r0, [r1]
ldmia r0!, {r4-r11}
msr psp, r0
mov r0, #0
msr basepri, r0
orr r14, r14, #13
bx r14
4)啟動第一個任務的匯編實現(xiàn)
vPortStartFirstTask
通過中斷向量表的定位堆棧的地址
ldr r0, =0xE000ED08 向量表偏移量寄存器 (VTOR)
ldr r0, [r0]
ldr r0, [r0]
msr msp, r0 將堆棧地址保存到主堆棧指針msp中
觸發(fā)SVC軟中斷,由vPortSVCHandler()完成第一個任務的具體切換工作
svc 0
FreeRTOS內核調度器啟動的流程如下:
以上3個文件實現(xiàn)了FreeRTOS內核調度所需的底層接口,相關代碼十分精簡。
二、創(chuàng)建測試任務:
下面創(chuàng)建第一個測試任務,LED測試int main( void )
{
設置系統(tǒng)時鐘,中斷向量表和LED使用的GPIO
使用stm32的固件包提供的初始化函數(shù),具體說明見相關手冊
prvSetupHardware();
通過xTaskCreate()創(chuàng)建4個LED任務vLEDFlashTask(),
每個任務根據各自的頻率閃爍,分別對應開發(fā)板上的4個LED
vStartLEDFlashTasks( mainFLASH_TASK_PRIORITY );
? 創(chuàng)建一個IDLE任務后,通過xPortStartScheduler啟動調度器
vTaskStartScheduler();
調度器工作不正常時返回
return 0;
}
portTASK_FUNCTION()是FreeRTOS定義的函數(shù)聲明,沒特殊作用
static portTASK_FUNCTION( vLEDFlashTask, pvParameters )
{
……省略……,具體為計算各LED的閃爍頻率
for(;;)
{
vTaskDelayUntil( &xLastFlashTime, xFlashRate );
vParTestToggleLED( uxLED );
vTaskDelayUntil()的延時時間xFlashRate,是從上一次的延時時間xLastFlashTime算起的,
相對vTaskDelay()的直接延時更為精準。
vTaskDelayUntil( &xLastFlashTime, xFlashRate );
vParTestToggleLED( uxLED );
}
}
FreeRTOS的任務創(chuàng)建與UC/OSII差異不大,主要參數(shù)為任務函數(shù),堆棧大小和任務的優(yōu)先級。如:
xTaskCreate( vLEDFlashTask, ( signed portCHAR * ) "LEDx", ledSTACK_SIZE, NULL, uxPriority, ( xTaskHandle * ) NULL );
下面再創(chuàng)建一個LCD顯示任務,以最低優(yōu)先級運行:
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上刷新一個數(shù)字。
至此,F(xiàn)reeRTOS在STM32上的移植基本完成。與UC/OSII相比,F(xiàn)reeRTOS精簡的實現(xiàn)更適合用來學習實時操作系統(tǒng)的工作原理,對其進行剖析也相對容易。
接下來,我們將會移植CAN,RS485,SD卡和USB等接口到FreeRTOS,使其在STM32平臺上更加完善。