實時操作系統FreeRTOS移植到STM32VGT6

一、前言

下載平臺: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

![[Pasted image 20250823233717.png]]

2、創建一個文件夾保存移植過來的源碼

創建一個文件夾ThirdParty,表示這是第三方庫,然后再里面創建FreeRTOS文件夾,然后創建inc與src,保存頭文件與源文件。
![[Pasted image 20250823233915.png]]

然后我們來到FreeRTOS的源文件開始移植!

第一步來到源碼的./FreeRTOS/FreeRTOS/Source

復制當前目錄下的全部的c語言源文件,到我們創建的目錄下的src里面了
![[Pasted image 20250823234422.png]]

復制完成之后的src文件夾下
![[Pasted image 20250823234620.png]]

第二步來到源碼的./FreeRTOS/FreeRTOS/Source/include

復制當前目錄下全部的頭文件,到我們創建目錄的inc里面
![[Pasted image 20250823235452.png]]

復制完成之后的inc文件夾下
![[Pasted image 20250823235537.png]]

第三步來到源碼的./FreeRTOS/FreeRTOS/Source/portable

![[Pasted image 20250824000508.png]]

此處需要移植兩處地方的源文件與頭文件,這里與前兩步不太一樣。前兩步無論在什么平臺都是通用的,但是這一步不同平臺有不同平臺的移植方式。

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符合的文件夾
![[Pasted image 20250824002011.png]]

將里面的c源文件與頭文件分別復制到src與inc里面
![[Pasted image 20250824002109.png]]

然后就沒了。keil平臺的話,就是來到RVDS文件夾下,找到對應的芯片型號的文件夾
![[Pasted image 20250824002246.png]]

復制對應的c源文件與頭文件到src與inc里面即可
![[Pasted image 20250824002330.png]]

對了還有一個比較重要的頭文件,這個也是我們后面交互比較多的文件,用于開啟freertos的某些鉤子(hook)函數,這個頭文件也需要根據各自的芯片架構來選擇!

2、來到./FreeRTOS/FreeRTOS/Demo選擇對應芯片的FreeRTOSConfig.h

![[Pasted image 20250824092439.png]]

![[Pasted image 20250824092601.png]]

沒了,就那么簡單!
完成之后,文件夾的內容:
![[Pasted image 20250824092636.png]]

![[Pasted image 20250824091422.png]]

3、MemMang文件夾

這里的MemMang是和內存管理有關的文件,這個直接復制對應的頭文件與源文件到我們創建文件src即可
![[Pasted image 20250824000712.png]]

通常只需要復制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. 同一工程 只選 1 個 heap_x.c 放進 src中。
  2. heap_5 必須先調用
    vPortDefineHeapRegions(xHeapRegions);   // 傳入各段首地址+長度
    
    才能 pvPortMalloc,否則第一次創建任務/隊列就掛。
  3. 推薦順序
    普通 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_SIZE75 kB調大/調小,看剩余 RAM
configMINIMAL_STACK_SIZE130 為單位,F4 建議 128-256
configMAX_PRIORITIES5-8夠用即可,越大 RAM 越多
configCHECK_FOR_STACK_OVERFLOW2開發期開,量產關
configUSE_MALLOC_FAILED_HOOK1開發期開,方便抓內存泄漏
configUSE_TIMERS1用軟件定時器就開
configUSE_MUTEXES / configUSE_RECURSIVE_MUTEXES1多任務共享資源必開

🟢 可選(可關可開)

典型場景
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文件中加入頭文件
![[Pasted image 20250824095303.png]]

![[Pasted image 20250824093718.png]]

2、stm32f4xx_it.c文件

我們可以在FreeRTOSConfig.h查看
![[Pasted image 20250824094014.png]]
所以我們需要去stm32f4xx_it.c中注釋掉對應的函數,因為這部分的函數實現在port.c中已經實現了。
SVC_Handler
PendSV_Handler
SysTick_Handler
![[Pasted image 20250824094352.png]]

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里面添加對應的頭文件與源文件路徑即可
![[Pasted image 20250824095659.png]]

這里就不提供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 ) 
參數名類型含義
pxTaskCodeTaskFunction_t任務入口函數指針(必須是一個 void func(void *pv) 形式的函數)
pcNameconst char *任務名字(調試/Trace 用,長度受 configMAX_TASK_NAME_LEN 限制)
uxStackDepthconfigSTACK_DEPTH_TYPE任務棧大小,單位是 “字”(不是字節)。
例如 128 → 512 B(32 位 MCU)
pvParametersvoid *傳給任務的參數,可為 NULL
uxPriorityUBaseType_t任務優先級數值越大越優先;0 為空閑任務
pxCreatedTaskTaskHandle_t *返回的任務句柄,之后可用它 vTaskDelete/ vTaskPrioritySet 等;
不需要句柄就填 NULL

返回值

  • pdPASS:創建成功
  • errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY:內存不足

4、開始任務

vTaskStartScheduler();

5、任務現象

正確的實現!
![[IMG_20250824_102021.jpg]]

![[IMG_20250824_102026.jpg]]

這里總結一下移植的流程
1.獲取FreeRTOS源碼
2.移植文件
3.修改部分FreeRTOSConfig.h文件
4.修改stm32f407xx_it.c注釋掉SVC_Handler、PendSV_Handler、SysTick_Handler函數實現
5.增加一個比較通用的delay函數
6.實現調試的鉤子函數
vApplicationStackOverflowHook、vApplicationMallocFailedHook
7.創建任務與開啟任務

以上為本文的內容。享受FreeRTOS吧!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/94424.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/94424.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/94424.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

從線到機:AI 與多模態交互如何重塑 B 端與 App 界面設計

當下&#xff0c;界面設計已經不再是單純的“畫屏幕”。AI 的快速發展讓我們不得不重新審視&#xff1a;交互和視覺究竟會走向什么樣的未來&#xff1f;無論是移動端 App&#xff0c;還是復雜的 B 端產品&#xff0c;設計的核心都在于讓界面更懂用戶。本文嘗試從三個角度切入&a…

【智能化解決方案】大模型智能推薦選型系統方案設計

大模型智能推薦選型系統方案設計0 背景1 問題分析與定義2 模型假設與簡化3 核心模型構建3.1 決策變量與參數定義3.2 目標函數3.3 約束條件4 模型求解與驗證4.1 求解策略4.2 驗證方法4.3 模型迭代優化5 方案實施與系統設計5.1 系統架構設計5.2 工作流程5.3 關鍵算法實現5.4 時序…

【Java基礎】HashMap、HashTable與HashSet:區別、聯系與實踐指南

Java中HashMap、HashTable與HashSet的深度解析&#xff1a;區別、聯系與實踐指南 引言 在Java集合框架中&#xff0c;HashMap、HashTable與HashSet是最常用的哈希型數據結構。它們因高效的查找、插入與刪除性能&#xff08;平均時間復雜度O(1)&#xff09;&#xff0c;廣泛應用…

互聯網大廠Java面試實戰:核心技術棧與場景化提問解析(含Spring Boot、微服務、測試框架等)

互聯網大廠Java面試實戰&#xff1a;核心技術棧與場景化提問解析 本文通過模擬面試官與求職者謝飛機的對話&#xff0c;深入探討互聯網大廠Java開發的核心技術棧面試問題&#xff0c;涵蓋Java SE、Spring生態、微服務、大數據等多個領域&#xff0c;結合音視頻、電商、AIGC等業…

人工智能-python-深度學習-參數初始化與損失函數

文章目錄參數初始化與損失函數一、參數初始化1. 固定值初始化1.1 全零初始化1.2 全1初始化1.3 任意常數初始化2. 隨機初始化2.1 均勻分布初始化2.2 正態分布初始化3. Xavier初始化4. He初始化5. 總結二、損失函數1. 線性回歸損失函數1.1 MAE&#xff08;Mean Absolute Error&am…

Android Glide常見問題解決方案:從圖片加載到內存優化

全面總結Glide使用中的典型問題與解決方案&#xff0c;助力提升應用性能與用戶體驗作為Android開發中最流行的圖片加載庫之一&#xff0c;Glide以其簡單易用的API和強大的功能深受開發者喜愛。然而&#xff0c;在實際使用過程中&#xff0c;我們往往會遇到各種問題&#xff0c;…

linux系統ollama監聽0.0.0.0:11434示例

docker應用如dify訪問本地主機部署的ollama&#xff0c;base_url不管配"http://localhost:11434"&#xff0c;還是"http://host_ip:11434"都會報錯。這是因為1&#xff09;docker容器訪問http://localhost:11434&#xff0c;其實訪問的是docker容器自身的服…

Java微服務AI集成指南:LangChain4j vs SpringAI

今天想再完善一下做的微服務項目&#xff0c;想著再接入一個人工客服&#xff0c;于是學習了一下langchan4j的內容&#xff0c;未完一、技術定位辨析&#xff1a;LangChain4j vs Spring AI vs OpenAIOpenAI&#xff1a;AI模型提供商 提供大語言模型API&#xff08;如GPT-4o&…

華為光學設計面試題

16. 題目&#xff1a;設計一個用于機器視覺檢測的光學系統時&#xff0c;如何保證在高速運動下成像的清晰度和穩定性&#xff1f;(出處&#xff1a;華為智能制造光學檢測項目組招聘面試題)17. 題目&#xff1a;請說明在光學系統設計中&#xff0c;如何權衡景深和分辨率的關系&a…

vue3和react的異同點

這是一個前端領域非常核心的話題。Vue 3 和 React 都是極其優秀的現代前端框架&#xff0c;它們在理念和實現上既有相似之處&#xff0c;也有顯著區別。 下面我將從多個維度詳細對比它們的異同點。核心哲學與設計理念特性Vue 3React設計理念漸進式框架與 “救世主”聲明式 UI 庫…

assetbuddle hash 比對

1.測試 &#xff1a;當在預設上的數據有修改時&#xff0c;生成的ab也會有修改&#xff0c;具體到某個ab的.manifest里會有相應的變化&#xff0c;AssetFileHash 會修改 如圖所示&#xff1a; ManifestFileVersion: 0 CRC: 2818930197 Hashes: AssetFileHash: serializedVersio…

Spring Boot `@Configuration` 與 `@Component` 筆記

Spring Boot Configuration 與 Component 筆記 1?? 基本概念注解作用是否有代理適用場景Component標記普通組件&#xff0c;將類交給 Spring 容器管理? 沒有 CGLIB 代理普通 Bean&#xff0c;工具類、過濾器、監聽器等Configuration標記配置類&#xff0c;用來聲明 Bean? 有…

二、JVM 入門——(三)棧

棧的定義 棧也是一塊區域&#xff0c;用來存放數據的。棧也叫棧內存&#xff0c;主管Java程序的運行。 棧是私有的&#xff0c;是在線程創建時創建&#xff0c;所以它的生命期是跟隨線程的生命期&#xff0c;線程結束棧內存也就釋放。 因此對于棧來說不存在垃圾回收問題&…

深度學習入門第一課——神經網絡實現手寫數字識別

昨天我們講了深度學習的大致框架&#xff0c;下面我們用深度學習網絡來實現一個小項目——手寫數字識別。完整代碼import torch from torch import nn from torch.utils.data import DataLoader from torchvision import datasets from torchvision.transforms import ToTensor…

Vue中的scoped屬性

理解&#xff1a; 在 .vue 文件中&#xff0c;scoped 是 <style> 標簽的一個屬性&#xff0c;作用是讓樣式只作用于當前組件&#xff0c;避免樣式污染其他組件 scoped 讓樣式只在自己的組件內生效&#xff0c;不會影響到其他組件的同名元素 舉例 沒有 scoped 的情況&…

S2B2B系統哪個好,商淘云、數商云、金蝶云蒼穹供應鏈批發哪個比較靠譜

在數字化商業浪潮洶涌的當下&#xff0c;S2B2B系統已成為眾多企業優化供應鏈、提升運營效率、拓展業務版圖的關鍵利器。從大型企業的復雜產業鏈協同&#xff0c;到中小企業對上下游資源的整合與把控&#xff0c;S2B2B系統的身影無處不在。但面對市場上層出不窮的各類S2B2B系統&…

利用3臺機器搭建Kubernetes集群

文章目錄1 機器準備修改主機名(可選)配置IP地址和主機名映射關閉防火墻關閉swap2 安裝docker修改并更新軟件源安裝docker設置路由轉發規則&#xff0c;并使配置生效安裝 kubelet&#xff0c;kubeadm&#xff0c;kubectl3 初始化集群4 集群初始化5 work節點加入6 部署網絡插件本…

C6.3:發射結交流電阻

當一個小交流電壓加在發射結上&#xff0c;會產生發射極交流電流&#xff0c;該交流電流的大小取決于Q點位置&#xff0c;而曲線是彎曲的&#xff0c;所以Q點在曲線上的位置越高&#xff0c;發射極交流電流的峰值越大。發射極總電流是直流分量和交流分量疊加而成&#xff0c;即…

python中生成器

生成器是根據算法生成數據的一種機制&#xff0c;每次調用生成器只生成一個值&#xff0c;可以節省大量內存生成器推導式gg ( i * 2 for i in range(5))print(gg)print(next(gg)) # 0 print(next(gg)) # 2 print(next(gg)) # 4 print(next(gg)) # 6 print(next(gg)) # 8yield…

【RAGFlow代碼詳解-28】部署和基礎設施

Docker 部署架構 RAGFlow 使用多階段 Docker 構建過程和 Docker Compose 編排進行部署。該系統支持完整和輕量級部署變體。 多階段 Docker 構建構建過程由 Dockerfile 2-214中定義的三個階段組成&#xff1a; 基礎階段 &#xff1a;使用系統依賴項、ML 模型和運行時環境設置 Ub…