一、前言
下載平臺:STM32F407VGT6
代碼使用平臺:VSCode
編譯器:arm-none-aebi-gcc
程序下載工具:STlink
批處理工具:make
移植的FreeRTOS版本:V11.2.0
其實此方法并不局限在arm-none-aebi-gcc中,此方法對于Keil5也是可以使用的,
只不過復制的一些文件不同而已。
歡迎來我的倉庫下載我的代碼,FreeRTOS項目在freertos分支內:
https://gitee.com/timing_matlab/stm32-f407-vgt6-project.git
這里分享一下在移植過程中需要注意的點!
二、移植FreeRTOS的文件
1、不管三七二十一,先拿源碼
來到FreeRTOS的github代碼倉庫,下載源碼或者直接克隆倉庫
https://github.com/FreeRTOS/FreeRTOS-Kernel/archive/refs/tags/V11.2.0.tar.gz
或者
克隆的話一定要加–recurse-submodules,不然得到的源碼不完整!
git clone https://github.com/FreeRTOS/FreeRTOS.git --recurse-submodules
2、創建一個文件夾保存移植過來的源碼
創建一個文件夾ThirdParty,表示這是第三方庫,然后再里面創建FreeRTOS文件夾,然后創建inc與src,保存頭文件與源文件。
然后我們來到FreeRTOS的源文件開始移植!
第一步來到源碼的./FreeRTOS/FreeRTOS/Source
復制當前目錄下的全部的c語言源文件,到我們創建的目錄下的src里面了
復制完成之后的src文件夾下
第二步來到源碼的./FreeRTOS/FreeRTOS/Source/include
復制當前目錄下全部的頭文件,到我們創建目錄的inc里面
復制完成之后的inc文件夾下
第三步來到源碼的./FreeRTOS/FreeRTOS/Source/portable
此處需要移植兩處地方的源文件與頭文件,這里與前兩步不太一樣。前兩步無論在什么平臺都是通用的,但是這一步不同平臺有不同平臺的移植方式。
1、移植平臺文件夾–gcc或者RVDS
這里我使用的是STMF40732VGT6,這個芯片使用的是M4架構的,所以要選擇CM4結尾或者特征的目錄文件,并且如果不使用浮點運算直接使用CM3也是可以的。
我這里因為使用的是arm-none-aebi-gcc,所以我選擇gcc。如果是keil,可以去RVDS找芯片對應的文件!
這里很有趣的點是Keil MDK移植居然不是在keil,而是在RVDS中,這里簡單提一嘴:
[!提一嘴] 提一嘴
Keil-MDK 的 編譯器 就是 ARM 當年 RVDS 套裝里的 RVCT(RealView Compiler Toolchain),
只是 ARM 在 2005 年收購 Keil 后,把:
RVCT 編譯器 uVision IDE(原來 Keil 的)
重新打包成 “Keil-MDK” 并沿用 “RealView” 品牌。
因此 MDK 的 portable/RVDS/… 目錄仍叫 RVDS,實質就是 Keil-MDK 用的 ARMCC/ARMCLANG 編譯器
我這里演示stm32F407VGT6移植gcc里面文件的過程:
來到gcc目錄下,找到與我芯片符合的ARM-CM4F符合的文件夾
將里面的c源文件與頭文件分別復制到src與inc里面
然后就沒了。keil平臺的話,就是來到RVDS文件夾下,找到對應的芯片型號的文件夾
復制對應的c源文件與頭文件到src與inc里面即可
對了還有一個比較重要的頭文件,這個也是我們后面交互比較多的文件,用于開啟freertos的某些鉤子(hook)函數,這個頭文件也需要根據各自的芯片架構來選擇!
2、來到./FreeRTOS/FreeRTOS/Demo選擇對應芯片的FreeRTOSConfig.h
沒了,就那么簡單!
完成之后,文件夾的內容:
3、MemMang文件夾
這里的MemMang是和內存管理有關的文件,這個直接復制對應的頭文件與源文件到我們創建文件src即可
通常只需要復制heap_4.c即可,這里提一嘴它們之間的關系,并且它們只能選擇一個!
[!補充] 補充
一句話總結
heap_1 ~ heap_5 是 同一個接口(pvPortMalloc / vPortFree)下的五種實現,差異只在 “能否 free、能否合并碎片、能否跨非連續內存” —— 選哪個文件,就決定了 FreeRTOS 堆的形態。
文件 | 能否 free | 合并相鄰碎片 | 支持多塊不連續內存 | 典型場景 |
---|---|---|---|---|
heap_1.c | ? | ? | ? | 只分配、不釋放的簡單應用 |
heap_2.c | ? | ? | ? | 早期版本,碎片嚴重,已不推薦 |
heap_3.c | ? | 依賴 C 庫 | ? | 直接包裝 malloc/free ,加線程安全 |
heap_4.c | ? | ? | ? | 官方默認,碎片最少,單塊 RAM |
heap_5.c | ? | ? | ? | heap_4 功能 + 可跨多塊 RAM/SDRAM |
使用規則
- 同一工程 只選 1 個
heap_x.c
放進 src中。 - heap_5 必須先調用
才能vPortDefineHeapRegions(xHeapRegions); // 傳入各段首地址+長度
pvPortMalloc
,否則第一次創建任務/隊列就掛。 - 推薦順序:
普通 MCU → heap_4;
需要外擴 RAM → heap_5;
極簡、永不釋放 → heap_1。
目前全部文件都移植完成!
三、修改或者增添部分文件的函數
1、修改部分文件的函數
修改FreeRTOSConfig.h部分內容:
這里提供一部分有些重要,有些不重要的內容
🔴 必須(動就炸)
宏 說明 configCPU_CLOCK_HZ
必須與 MCU 主頻一致(SystemCoreClock) configTICK_RATE_HZ
系統心跳,1 kHz 通用,> 10 kHz 會吃 CPU configPRIO_BITS
與芯片手冊匹配(F1/F4 為 4) configKERNEL_INTERRUPT_PRIORITY
&configMAX_SYSCALL_INTERRUPT_PRIORITY
中斷優先級位偏移,錯一位就 HardFault vPortSVCHandler
/xPortPendSVHandler
/xPortSysTickHandler
向量映射,名字必須對齊 configASSERT
調試開關,關閉就失去定位能力
🟡 重要(根據應用調)
宏 推薦值 提示 configTOTAL_HEAP_SIZE
75 kB 調大/調小,看剩余 RAM configMINIMAL_STACK_SIZE
130 以 字 為單位,F4 建議 128-256 configMAX_PRIORITIES
5-8 夠用即可,越大 RAM 越多 configCHECK_FOR_STACK_OVERFLOW
2 開發期開,量產關 configUSE_MALLOC_FAILED_HOOK
1 開發期開,方便抓內存泄漏 configUSE_TIMERS
1 用軟件定時器就開 configUSE_MUTEXES
/configUSE_RECURSIVE_MUTEXES
1 多任務共享資源必開
🟢 可選(可關可開)
宏 典型場景 configUSE_IDLE_HOOK
/configUSE_TICK_HOOK
低功耗/統計 configUSE_TRACE_FACILITY
開 TRACE 時才用 configGENERATE_RUN_TIME_STATS
性能分析 configUSE_CO_ROUTINES
幾乎沒人用
紅色不能改,黃色按需改,綠色隨意改。
所以我們可以關閉,configUSE_IDLE_HOOK / configUSE_TICK_HOOK功能、configUSE_TRACE_FACILITY、configGENERATE_RUN_TIME_STATS、configUSE_CO_ROUTINES
不過最后在FreeRTOSConfig.h文件中加入頭文件
2、stm32f4xx_it.c文件
我們可以在FreeRTOSConfig.h查看
![[Pasted image 20250824094014.png]]
所以我們需要去stm32f4xx_it.c中注釋掉對應的函數,因為這部分的函數實現在port.c中已經實現了。
SVC_Handler
PendSV_Handler
SysTick_Handler
3、delay函數
增加一個比較通用的delay文件。
在調度器啟動前,使用堵塞式的delay函數;
在調度器啟動后,使用FreeRTOS的delay函數。
可以確保一些初始化函數可以正確運行。
初始化調用:
delay_init(168); //168是STM32VGT6支持的時鐘頻率,這里請修改相應的時鐘頻率
函數使用:
delay_ms(nms); /* 延時nms */delay_us(nus); /* 延時nus */
delay.h
#ifndef __DELAY_H
#define __DELAY_H
#include "stm32f4xx.h" //若用 F1/F7/H7 改成對應頭文件
#include <misc.h>
void delay_init(uint16_t sysclk); /* 初始化延遲函數 */
void delay_ms(uint16_t nms); /* 延時nms */
void delay_us(uint32_t nus); /* 延時nus */
#endif
delay.c
#include "delay.h"
#include "stm32f4xx.h" // 若用 F1/F7/H7 改成對應頭文件
#include "FreeRTOS.h"
#include "task.h"
static uint32_t fac_us = 0;
/* 初始化時只算系數,不碰 SysTick 寄存器 */
void delay_init(uint16_t sysclk)
{SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);fac_us = sysclk / 8; // 時鐘 = HCLK/8
}
/*------------------------------------------* 微秒延時:調度器啟動前 → 純空循環* 調度器啟動后 → SysTick 計數*------------------------------------------*/
void delay_us(uint32_t nus)
{if (xTaskGetSchedulerState() == taskSCHEDULER_RUNNING){/* FreeRTOS 已運行:用 SysTick 忙等 */volatile uint32_t ticks, told, tnow, tcnt = 0, reload;UBaseType_t original_priority = uxTaskPriorityGet(NULL);vTaskPrioritySet(NULL, configMAX_PRIORITIES - 1);reload = SysTick->LOAD;ticks = nus * fac_us;told = SysTick->VAL;while (1){tnow = SysTick->VAL;if (tnow != told){if (tnow < told)tcnt += told - tnow;elsetcnt += reload - tnow + told;told = tnow;if (tcnt >= ticks)break;}}vTaskPrioritySet(NULL, original_priority);}else{/* 調度器未啟動:純空循環,不依賴 SysTick */uint32_t ticks = nus * fac_us;while (ticks--)__NOP();}
}
/*------------------------------------------* 毫秒延時:調度器啟動前 → 空循環* 調度器啟動后 → vTaskDelay*------------------------------------------*/
void delay_ms(uint16_t nms)
{if (xTaskGetSchedulerState() == taskSCHEDULER_RUNNING){vTaskDelay(pdMS_TO_TICKS(nms));}else{/* 調度器未啟動:空循環 1 ms × nms */for (uint32_t i = 0; i < nms; i++)delay_us(1000);}
}
4、定義兩個個鉤子函數
此鉤子函數可以作為一個調試接口。這里可以點燈 / 打印 / 斷點,方便調試
vApplicationStackOverflowHook
vApplicationMallocFailedHook
void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName)
{/* 這里可以點燈 / 打印 / 斷點,方便調試 */(void)xTask;(void)pcTaskName;/* 死循環,方便調試器停住 */taskDISABLE_INTERRUPTS();for (;;);
}void vApplicationMallocFailedHook(void)
{/* 可以點燈、打印或斷點 */taskDISABLE_INTERRUPTS();for (;;);
}
隨便在一個包含
#include “FreeRTOS.h”
#include “task.h”
文件下定義即可。
隨后只要在Makefile或者Keil里面添加對應的頭文件與源文件路徑即可
這里就不提供keil的添加方式了,這個流程在創建工程的時候就應該知道了。
四、后記
我們簡單使用一下,這個遷移過來的FreeRTOS工程。
1、包含頭文件
#include "FreeRTOS.h"
#include "task.h"
2、初始化延時函數(此步可選)
delay_init(168);
3、創建任務
500ms – LED翻轉
1s – LCD的顯示變化
//任務實現
void LED_Task(void *arg)
{for(;;){GPIO_ToggleBits(GPIOA, GPIO_Pin_0);vTaskDelay(500);}
}void LCD_Task(void *arg)
{ST_7735S_Clear(0xffff);int num = 0;char buff[20] = {0};for (;;){bzero(buff,sizeof(buff));sprintf(buff,"%d",num++);ST_7735S_ShowString(10, 20, "Hello!World!", RGB888to565(0xff, 0x00, 0xff), 0xffff);ST_7735S_ShowString(10, 40, buff, RGB888to565(0xff, 0x00, 0xff), 0xffff);vTaskDelay(500);}
}//任務創建
xTaskCreate(LED_Task,"LED",128,NULL,1,NULL);
xTaskCreate(LCD_Task, "LCD", 128, NULL, 2, NULL);
值得一提的是任務創建不局限xTaskCreate函數,這是動態創建的任務,還有創建靜態任務xTaskCreateStatic的方式。這里我們分析分析這個動態創建任務的函數:
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,const char * const pcName,const configSTACK_DEPTH_TYPE uxStackDepth,void * const pvParameters,UBaseType_t uxPriority,TaskHandle_t * const pxCreatedTask )
參數名 類型 含義 pxTaskCode
TaskFunction_t
任務入口函數指針(必須是一個 void func(void *pv)
形式的函數)pcName
const char *
任務名字(調試/Trace 用,長度受 configMAX_TASK_NAME_LEN
限制)uxStackDepth
configSTACK_DEPTH_TYPE
任務棧大小,單位是 “字”(不是字節)。
例如 128 → 512 B(32 位 MCU)pvParameters
void *
傳給任務的參數,可為 NULL
uxPriority
UBaseType_t
任務優先級,數值越大越優先;0 為空閑任務 pxCreatedTask
TaskHandle_t *
返回的任務句柄,之后可用它 vTaskDelete/ vTaskPrioritySet
等;
不需要句柄就填NULL
返回值
pdPASS
:創建成功errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY
:內存不足
4、開始任務
vTaskStartScheduler();
5、任務現象
正確的實現!
這里總結一下移植的流程
1.獲取FreeRTOS源碼
2.移植文件
3.修改部分FreeRTOSConfig.h文件
4.修改stm32f407xx_it.c注釋掉SVC_Handler、PendSV_Handler、SysTick_Handler函數實現
5.增加一個比較通用的delay函數
6.實現調試的鉤子函數
vApplicationStackOverflowHook、vApplicationMallocFailedHook
7.創建任務與開啟任務
以上為本文的內容。享受FreeRTOS吧!