一、移植準(zhǔn)備
1. 硬件準(zhǔn)備
本文中使用的開發(fā)板為小熊派IoT開發(fā)板,主控為STM32L431RCT6:
2. 下載FreeRTOS源碼
FreeRTOS源碼分為兩種,一種是FreeRTOS包,另一種是FreeRTOS LTS Release包。
2.1. FreeRTOS版本
這種版本包含F(xiàn)reeRTOS內(nèi)核的源碼和示例工程,還有FreeRTOS+的一些擴(kuò)展庫(kù)??梢酝ㄟ^下面的兩種方式下載:
- 官方下載鏈接:下載官方發(fā)布的包,截至發(fā)文時(shí)間,最新發(fā)布的版本為FreeRTOSv202012.00.zip。Github倉(cāng)庫(kù)地址:master分支為官方不斷更新修改的包。
這里我從官方下載,下載解壓之后如圖:
2.2. FreeRTOS LTS版本
LTS即long term support(長(zhǎng)期支持包),這種版本包含F(xiàn)reeRTOS LTS發(fā)布版本的源碼,包括內(nèi)核、TCP/IP、MQTT、OTA和更多支持的庫(kù)。也可以通過下面的兩種方式下載:
- 官方下載鏈接:下載官方發(fā)布的包,截至發(fā)文時(shí)間,最新發(fā)布的版本為FreeRTOSv202012.01-LTS.zip。Github倉(cāng)庫(kù)地址:master分支為官方不斷更新修改的包。
這里我從官方下載,解壓之后如圖:
3. 裸機(jī)工程準(zhǔn)備
請(qǐng)準(zhǔn)備一份可以正常使用printf串口輸出的裸機(jī)工程,本文中我使用cubemx生成。
二、添加源碼到工程中
1. 復(fù)制文件
在工程目錄下新建FreeRTOS文件夾,將FreeRTOS官方源碼復(fù)制過來,如圖:
接著將portable文件夾下面的文件夾部分刪除,只保留以下幾個(gè)文件夾,如圖。其中Gcc、IAR、RVDS(Keil)是分別適配這三種編譯器的,MemMang是FreeRTOS提供的內(nèi)存管理算法。
2. 添加文件到MDK工程
一個(gè)RTOS無(wú)非就三類文件:底層移植文件、內(nèi)核實(shí)現(xiàn)文件、配置文件,所以在MDK分組中我們按照如下來管理。
2.1. 添加底層移植文件
新建 FreeRTOS/port 分組,因?yàn)檫@里我們是MDK移植環(huán)境,STM32L431RCT6屬于帶FPU的Cortex-M4內(nèi)核,所以添加位于 FreeRTOSportableRVDSARM_CM4F 下的 port.c 文件:
再添加位于 FreeRTOSportableMemMang 下的 heap_4.c 文件,為FreeRTOS提供一種動(dòng)態(tài)內(nèi)存管理算法:
2.2. 添加FreeRTOS內(nèi)核源碼
新建 FreeRTOS/kernel 分組,添加位于 FreeRTOS 文件夾下的所有c文件:
2.3. 添加FreeRTOS配置文件
FreeRTOS的配置文件屬于和實(shí)際硬件相關(guān)的文件,在我們復(fù)制過來的文件中并沒有,所以要去FreeRTOS源碼中提供的demo工程下找份最相關(guān)的文件,復(fù)制過來:
為了便于修改,添加到MDK分組中:
3. 添加頭文件路徑
此時(shí)編譯,檢查是否有錯(cuò)誤:
可以看到編譯器提示 INCLUDE_xTaskGetCurrentTaskHandle 函數(shù)沒有實(shí)現(xiàn),全局搜索檢查一下該函數(shù)的定義:
可以看到只有定義了這兩個(gè)宏定義中的任意一個(gè),該函數(shù)才會(huì)定義,所以在配置文件中添加宏定義,開啟使用互斥鎖:
再次編譯,編譯成功。
三、修改FreeRTOS配置文件
之前我們添加的配置文件 FreeRTOSConfig.h 文件是從官方提供給STM32F103的demo中復(fù)制過來的,本實(shí)驗(yàn)中用的是STM32L431RCT6,需要進(jìn)行修改。
1. 修改內(nèi)核基本配置
因?yàn)镾TM32 HAL中定義了芯片的時(shí)鐘(SystemCoreClock),所以此處使用一個(gè)c語(yǔ)言extern聲明此變量在外部,但這是頭文件,為了不被匯編器所匯編,可以使用如下宏定義:
/* Ensure definitions are only used by the compiler, and not by the assembler. */
#if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__)
#include
extern uint32_t SystemCoreClock;
#endif
接著修改內(nèi)核基本時(shí)鐘配置:
修改一些內(nèi)核的API功能是否提供:
2. 修改中斷配置
這部分是FreeRTOS的一個(gè)特色,將中斷部分修改為如下配置:
/* Cortex-M specific definitions. */
#ifdef __NVIC_PRIO_BITS
/* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */
#define configPRIO_BITS __NVIC_PRIO_BITS
#else
#define configPRIO_BITS 4
#endif
/* The lowest interrupt priority that can be used in a call to a "set priority"
function. */
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15
/* The highest interrupt priority that can be used by any interrupt service
routine that makes calls to interrupt safe FreeRTOS API functions. DO NOT CALL
INTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHER
PRIORITY THAN THIS! (higher priorities are lower numeric values. */
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
/* Interrupt priorities used by the kernel port layer itself. These are generic
to all Cortex-M ports, and do not rely on any particular library functions. */
#define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
/* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!!
See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */
#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
/* This is the value being used as per the ST library which permits 16
priority values, 0 to 15. This must correspond to the
configKERNEL_INTERRUPT_PRIORITY setting. Here 15 corresponds to the lowest
NVIC value of 255. */
#define configLIBRARY_KERNEL_INTERRUPT_PRIORITY 15
3. 定義斷言
/* Normal assert() semantics without relying on the provision of an assert.h
header file. */
#define configASSERT( x ) if ((x) == 0) {taskDISABLE_INTERRUPTS(); for( ;; );}
4. 配置中斷接口
RTOS需要配置的中斷有兩個(gè):一個(gè)是用于任務(wù)切換的pendSV中斷(或者SVC中斷),另一個(gè)是用于提供時(shí)鐘節(jié)拍的Systick中斷。
/* Definitions that map the FreeRTOS port interrupt handlers to their CMSIS
standard names. */
#define vPortSVCHandler SVC_Handler
#define xPortPendSVHandler PendSV_Handler
/* IMPORTANT: This define is commented when used with STM32Cube firmware, when the timebase source is SysTick,
to prevent overwriting SysTick_Handler defined within STM32Cube HAL */
/* #define xPortSysTickHandler SysTick_Handler */
剛剛這兩個(gè)宏設(shè)置了pendSV和SVC中斷處理程序的名稱,將這兩個(gè)處理程序交由FreeRTOS實(shí)現(xiàn),但這會(huì)與stm32l4xx_it.c中默認(rèn)的中斷處理程序沖突,將其屏蔽:
最后處理Systick中斷函數(shù),因?yàn)镾ystick中斷處理函數(shù)中還有HAL庫(kù)的時(shí)鐘節(jié)拍處理,所以并沒有交由FreeRTOS實(shí)現(xiàn),而是選擇在Systick的中斷處理函數(shù)中調(diào)用FreeRTOS的節(jié)拍處理函數(shù)。
首先在stm32l4xx_it.c的開始包含F(xiàn)reeRTOS頭文件:
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "FreeRTOS.h"
#include "task.h"
/* USER CODE END Includes */
接著修改Systick中斷處理程序:
/**
* @brief This function handles System tick timer.
*/
void SysTick_Handler(void)
{
/* USER CODE BEGIN SysTick_IRQn 0 */
extern void xPortSysTickHandler(void);
/* USER CODE END SysTick_IRQn 0 */
HAL_IncTick();
#if (INCLUDE_xTaskGetSchedulerState == 1 )
if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
{
#endif /* INCLUDE_xTaskGetSchedulerState */
xPortSysTickHandler();
#if (INCLUDE_xTaskGetSchedulerState == 1 )
}
#endif /* INCLUDE_xTaskGetSchedulerState */
/* USER CODE BEGIN SysTick_IRQn 1 */
/* USER CODE END SysTick_IRQn 1 */
}
添加之后會(huì)發(fā)現(xiàn)INCLUDE_xTaskGetSchedulerState這個(gè)宏沒有開,導(dǎo)致在中斷處理程序中不會(huì)檢測(cè)調(diào)度器狀態(tài),所以在配置文件中配置開啟該API:
至此,移植全部完成。
四、測(cè)試內(nèi)核是否可以正常運(yùn)行
1. 開啟支持靜態(tài)內(nèi)存分配
創(chuàng)建一個(gè)靜態(tài)任務(wù)需要內(nèi)核開啟靜態(tài)內(nèi)存分配支持,在配置文件中添加如下宏定義:
#define configSUPPORT_STATIC_ALLOCATION 1
當(dāng)這個(gè)宏開啟之后,需要用戶實(shí)現(xiàn) vApplicationGetIdleTaskMemory 函數(shù),來提供一塊靜態(tài)內(nèi)存空間作為IDLE任務(wù)的內(nèi)存空間,這里我在main.c中實(shí)現(xiàn),如下:
/* GetIdleTaskMemory prototype (linked to static allocation support) */
static StaticTask_t xIdleTaskTCBBuffer;
static StackType_t xIdleStack[configMINIMAL_STACK_SIZE];
void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize )
{
*ppxIdleTaskTCBBuffer = &xIdleTaskTCBBuffer;
*ppxIdleTaskStackBuffer = &xIdleStack[0];
*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}
2. 創(chuàng)建兩個(gè)測(cè)試任務(wù)
首先在main.c中創(chuàng)建任務(wù)1和任務(wù)2的TCB控制塊內(nèi)存空間、任務(wù)??臻g,并創(chuàng)建兩個(gè)任務(wù)的任務(wù)入口函數(shù):
#define TASK1_STACK_SIZE 512
#define TASK2_STACK_SIZE 512
StaticTask_t task1_tcb_buffer;
StaticTask_t task2_tcb_buffer;
StackType_t task1_stack[TASK1_STACK_SIZE];
StackType_t task2_stack[TASK2_STACK_SIZE];
void task1_entry(void *args)
{
/* Enter into a forever loop. */
while(1)
{
printf("task 1 application running...rn");
vTaskDelay(1000);
}
}
void task2_entry(void *args)
{
/* Enter into a forever loop. */
while(1)
{
printf("task 2 application running...rn");
vTaskDelay(1000);
}
}
接下來在main函數(shù)中創(chuàng)建這兩個(gè)任務(wù),并啟動(dòng)內(nèi)核:
/* USER CODE BEGIN 2 */
printf("FreeRTOS port on BearPi board by mculover666rn");
task1_handle = xTaskCreateStatic(task1_entry,"task1", TASK1_STACK_SIZE, NULL, 1, task1_stack, &task1_tcb_buffer);
task2_handle = xTaskCreateStatic(task2_entry,"task2", TASK2_STACK_SIZE, NULL, 2, task2_stack, &task2_tcb_buffer);
vTaskStartScheduler();
/* USER CODE END 2 */
編譯、下載,在串口助手中查看結(jié)果,可以看到兩個(gè)任務(wù)交替運(yùn)行,每隔1s打印一次日志:
有意思的一點(diǎn)是,我設(shè)置的task1優(yōu)先級(jí)是1,task2優(yōu)先級(jí)是2,從日志里明顯是task2先跑,難道移植出了問題???
實(shí)則不然,F(xiàn)reeRTOS中優(yōu)先級(jí)數(shù)值越低,優(yōu)先級(jí)等級(jí)越低,空閑任務(wù)的優(yōu)先級(jí)為0,這一點(diǎn)和很多RTOS都不相同,需要特別注意!
除此之外還有:
移植uc/OS-III最新版到小熊派開發(fā)板
在小熊派上移植threadX操作系統(tǒng)
STM32標(biāo)準(zhǔn)庫(kù)工程中移植TencentOS-tiny