实时操作系统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代码仓库,下载源码或者直接克隆仓库

shell 复制代码
https://github.com/FreeRTOS/FreeRTOS-Kernel/archive/refs/tags/V11.2.0.tar.gz

或者
克隆的话一定要加--recurse-submodules,不然得到的源码不完整!

shell 复制代码
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. 同一工程 只选 1 个 heap_x.c 放进 src中。

  2. heap_5 必须先调用

    c 复制代码
    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_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函数。

可以确保一些初始化函数可以正确运行。

初始化调用:

c 复制代码
	delay_init(168); //168是STM32VGT6支持的时钟频率,这里请修改相应的时钟频率

函数使用:

c 复制代码
	delay_ms(nms);      /* 延时nms */

	delay_us(nus);      /* 延时nus */

delay.h

c 复制代码
#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

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;
                else
                    tcnt += 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

c 复制代码
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、包含头文件

c 复制代码
#include "FreeRTOS.h"
#include "task.h"

2、初始化延时函数(此步可选)

c 复制代码
delay_init(168);

3、创建任务

500ms -- LED翻转

1s -- LCD的显示变化

c 复制代码
//任务实现
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的方式。这里我们分析分析这个动态创建任务的函数:

c 复制代码
    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、开始任务

c 复制代码
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吧!