跳至内容
FreeRTOS内核移植01

FreeRTOS内核移植01

2026-06-20

1. 准备工作

移植的方式有好多种

  1. 基于 CubeMX 的移植;
  2. 手动使用源码移植;

在cubemx中移植太简单了,直接勾选就可以运行了,但是会被封装一层,这里记录手动移植的过程,移植完成之后,再去看cubemx生成的代码,其实基本上一眼就看明白了。

1.1 源码准备

官方网址:https://freertos.org/

直接从官网下载源码即可,下载LTS结尾的长期支持版。

1.2 创建工程

前几天的文章写了如何使用vscode开发stm32,这里还是使用同样的方式开发,但是也会记录使用keil开发的话的区别(除了有个文件不一样,几乎没有区别)。

使用cubemx创建一个工程

  1. 时钟源选择一个基础定时器,这里选择TIM11
  2. 生成的工程类型选择makefile,如果使用keil选择MDK_ARM
  3. 这里配置一个LED和USART1用于创建一个任务测试

  1. 取消三个中断函数生成的代码
    1. PendSV_Handler
    2. SVC_Handler
    3. SysTick_Handler

1.3 添加源码

将下载的源码中的这个文件夹直接copy到我们的工程下

FreeRTOS-LTS\FreeRTOS\FreeRTOS-Kernel

然后对这些文件进行删减,删减后的目录如下

PS freertos_demo\FreeRTOS\FreeRTOS-Kernel> tree /F
C:.
│  croutine.c
│  event_groups.c
│  FreeRTOSConfig.h
│  FreeRTOSConfig_back.h
│  list.c
│  queue.c
│  stream_buffer.c
│  tasks.c
│  timers.c
├─include
│      atomic.h
│      CMakeLists.txt
│      croutine.h
│      deprecated_definitions.h
│      event_groups.h
│      FreeRTOS.h
│      list.h
│      message_buffer.h
│      mpu_prototypes.h
│      mpu_syscall_numbers.h
│      mpu_wrappers.h
│      newlib-freertos.h
│      picolibc-freertos.h
│      portable.h
│      projdefs.h
│      queue.h
│      semphr.h
│      StackMacros.h
│      stack_macros.h
│      stdint.readme
│      stream_buffer.h
│      task.h
│      timers.h
└─portable
    ├─GCC
    │  └─ARM_CM4F
    │          port.c
    │          portmacro.h
    ├─MemMang
    │      heap_4.c
    │      ReadMe.url
    └─RVDS
        └─ARM_CM4F
                port.c
                portmacro.h

说明

  1. FreeRTOS-Kernel文件夹下的所有源文件全部保留
  2. FreeRTOS-Kernel\include文件夹下的内容全部保留
  3. FreeRTOS-Kernel\portable\MemMang文件夹下只保留heap_4.c
  4. FreeRTOS-Kernel\portable\GCC\ARM_CM4FFreeRTOS-Kernel\portable\RVDS\ARM_CM4F这两个文件夹留一个,这里使用gcc编译就留下前者,如果使用keil编译就留下后者。如果是其他内核的芯片,最后一级目录根据芯片选择,这里是STM32F4,所以选择ARM_CM4F
  5. FreeRTOS-Kernel\examples中找一个FreeRTOSConfig.h文件,复制到FreeRTOS-Kernel目录下,或者FreeRTOS-Kernel\include目录下,看习惯。这里选择前者。

1.4 适配工作

适配工作基本都在添加的FreeRTOSConfig.h头文件中完成。

1.4.1 中断配置

在添加的FreeRTOSConfig.h头文件中添加如下宏定义,也就是我们的cubmex没有生成这三个中断处理函数,freertos已经在port.c中实现了,但是名字不一样,这里直接使用宏定义将其改个名字就可以了。

#define xPortPendSVHandler PendSV_Handler
#define vPortSVCHandler SVC_Handler
#define xPortSysTickHandler SysTick_Handler

为什么要处理中断,在移植rtthread-nano的文章中已经写过了,这里就不在赘述了。

1.4.2 系统时钟配置

在添加的FreeRTOSConfig.h头文件中修改如下宏定义,这里系统时钟为84M

#define configCPU_CLOCK_HZ    ( ( unsigned long ) 84000000 )

1.4.3 系统裁剪

这里做一个配置好的文件(能用,可以根据需求修改)

#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H

/******************************************************************************/
/* 系统基础配置                      . ****************************************/
/******************************************************************************/

// 基础调度器配置
#define configUSE_PREEMPTION                            1       // 1: 启用抢占式调度 (强烈建议)
#define configUSE_TIME_SLICING                          1       // 1: 启用时间片轮转
#define configUSE_PORT_OPTIMISED_TASK_SELECTION         1       // 1: 使用硬件CLZ指令优化任务选择 (Cortex-M4支持)

// 系统时钟与节拍配置
#define configCPU_CLOCK_HZ                              (84000000UL) // STM32F401 主频 (请根据实际时钟树调整)
#define configTICK_RATE_HZ                              ((TickType_t)1000) // 系统心跳 1ms

// 任务与内存配置 (针对64KB RAM的F401)
#define configMAX_PRIORITIES                            (10)     // 最大优先级数 (0~9)
#define configMAX_TASK_NAME_LEN                         (16)    // 任务名最大长度
#define configMINIMAL_STACK_SIZE                        (128)   // 空闲任务栈大小 (单位:字, 即512字节)
#define configSUPPORT_STATIC_ALLOCATION                 0       // 1: 允许静态创建任务 (需手动提供内存)
#define configSUPPORT_DYNAMIC_ALLOCATION                1       // 1: 允许动态创建任务 (从FreeRTOS堆中分配)
#define configTOTAL_HEAP_SIZE                           (4096)  // FreeRTOS内部堆大小 (分配4KB)
#define configAPPLICATION_ALLOCATED_HEAP                0       // 0: 由FreeRTOS内部自动分配堆内存 (若为1则需用户手动定义ucHeap数组)
#define configSTACK_ALLOCATION_FROM_SEPARATE_HEAP       0       // 0: 任务栈从主堆(configTOTAL_HEAP_SIZE)中分配
#define configUSE_MINI_LIST_ITEM                        0       // 0: 使用标准的列表项结构体 (1: 使用精简版以节省RAM)

// 钩子函数与调试
#define configUSE_IDLE_HOOK                             0       // 0: 关闭空闲任务钩子函数
#define configUSE_TICK_HOOK                             0       // 0: 关闭Tick心跳钩子函数
#define configCHECK_FOR_STACK_OVERFLOW                  0       // 0: 关闭栈溢出检测 (设为1或2时需实现vApplicationStackOverflowHook)
#define configUSE_MALLOC_FAILED_HOOK                    0       // 0: 关闭内存分配失败钩子函数
#define configUSE_DAEMON_TASK_STARTUP_HOOK              0       // 0: 关闭定时器服务任务启动钩子函数
#define configIDLE_SHOULD_YIELD                         1       // 1: 空闲任务让出CPU给同优先级的用户任务

// 高级配置
#define configUSE_TICKLESS_IDLE                         0       // 1: 启用Tickless低功耗模式 (需配合底层硬件实现)
#define configTICK_TYPE_WIDTH_IN_BITS                   TICK_TYPE_WIDTH_32_BITS // Tick计数器类型宽度 (32位MCU推荐32位)
#define configTASK_NOTIFICATION_ARRAY_ENTRIES           1U      // 每个任务的通知数组条目数 (1表示兼容旧版单通知机制)
#define configQUEUE_REGISTRY_SIZE                       0U      // 队列/信号量注册表大小 (0: 禁用,>0时用于调试工具查看)
#define configENABLE_BACKWARD_COMPATIBILITY             1       // 1: 启用旧版API名称兼容 (如vTaskDelayUntil替代vTaskDelayUntil等)
#define configNUM_THREAD_LOCAL_STORAGE_POINTERS         0       // 0: 每个任务的本地存储指针数量 (0: 禁用)
#define configSTACK_DEPTH_TYPE                          size_t  // 任务栈深度变量的数据类型 (使用size_t防止大栈溢出)
#define configMESSAGE_BUFFER_LENGTH_TYPE                size_t  // 消息缓冲区长度变量的数据类型 (使用size_t支持更大消息)
#define configUSE_NEWLIB_REENTRANT                      0       // 0: 不使用Newlib重入结构体 (若使用printf等C库函数且有多任务访问,建议设为1)

/******************************************************************************/
/* 功能选项配置                      . ****************************************/
/******************************************************************************/

// 软件定时器
#define configUSE_TIMERS                                1       // 1: 启用软件定时器功能
#define configTIMER_TASK_PRIORITY                       ( configMAX_PRIORITIES - 1U ) // 定时器服务任务的优先级 (设为最高)
#define configTIMER_TASK_STACK_DEPTH                    configMINIMAL_STACK_SIZE // 定时器服务任务的栈大小 (与空闲任务相同)
#define configTIMER_QUEUE_LENGTH                        10U     // 定时器命令队列长度 (最多可缓存10条定时器操作命令)

// 组件选择配置
#define configUSE_TASK_NOTIFICATIONS                    0       // 1: 启用任务通知机制 (轻量级,可替代二值/计数信号量等)
#define configUSE_MUTEXES                               0       // 1: 启用互斥量 (支持优先级继承)
#define configUSE_RECURSIVE_MUTEXES                     0       // 1: 启用递归互斥量 (允许同一任务多次获取)
#define configUSE_COUNTING_SEMAPHORES                   0       // 1: 启用计数信号量
#define configUSE_QUEUE_SETS                            0       // 1: 启用队列集 (允许同时阻塞等待多个队列/信号量)
#define configUSE_APPLICATION_TASK_TAG                  0       // 1: 启用任务标签功能 (可为任务附加自定义上下文指针)
#define configUSE_STREAM_BUFFERS                        0       // 1: 启动连续字节流

// 接口支持 (按需开启,节省Flash)
#define INCLUDE_vTaskPrioritySet                        1       // 1: 包含运行时修改任务优先级的API
#define INCLUDE_uxTaskPriorityGet                       1       // 1: 包含获取任务优先级的API
#define INCLUDE_vTaskDelete                             1       // 1: 包含删除任务的API
#define INCLUDE_vTaskSuspend                            1       // 1: 包含挂起/恢复任务的API
#define INCLUDE_vTaskDelayUntil                         1       // 1: 包含绝对延时API (用于精确周期性任务)
#define INCLUDE_vTaskDelay                              1       // 1: 包含相对延时API
#define INCLUDE_xTaskGetSchedulerState                  1       // 1: 包含获取调度器当前状态的API
#define INCLUDE_xTaskGetCurrentTaskHandle               1       // 1: 包含获取当前正在运行任务句柄的API
#define INCLUDE_uxTaskGetStackHighWaterMark             1       // 1: 包含获取任务栈剩余最小水位的API (用于栈大小调优)
#define INCLUDE_xTaskGetIdleTaskHandle                  1       // 1: 包含获取空闲任务句柄的API
#define INCLUDE_eTaskGetState                           1       // 1: 包含获取任务当前状态的API
#define INCLUDE_xTimerPendFunctionCall                  0       // 1: 包含从ISR中挂起函数调用到定时器任务的API
#define INCLUDE_xTaskAbortDelay                         1       // 1: 包含强制中止任务延时状态的API
#define INCLUDE_xTaskGetHandle                          1       // 1: 包含通过任务名获取任务句柄的API
#define INCLUDE_xTaskResumeFromISR                      1       // 1: 包含在中断服务函数(ISR)中恢复任务的API

/******************************************************************************/
/* 特殊配置                          . ****************************************/
/******************************************************************************/

// 中断行为配置
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY         15      // 内核级中断(如PendSV/SysTick)优先级 (Cortex-M4中0为最高)
#define configKERNEL_INTERRUPT_PRIORITY                 (configLIBRARY_LOWEST_INTERRUPT_PRIORITY << 4)          // 寄存器低4位没用
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY    5       // 允许调用FreeRTOS 进入临界区时关断的最高中断优先级 (0表示所有中断都可调用)
#define configMAX_SYSCALL_INTERRUPT_PRIORITY            (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << 4)     // 寄存器低4位没用
#define configMAX_API_CALL_INTERRUPT_PRIORITY           configMAX_SYSCALL_INTERRUPT_PRIORITY    // configMAX_SYSCALL_INTERRUPT_PRIORITY 的别名 (向后兼容)

// 运行状态统计配置
#define configGENERATE_RUN_TIME_STATS                   0       // 0: 关闭任务运行时间统计 (开启需额外配置时钟源)
#define configUSE_TRACE_FACILITY                        0       // 0: 关闭Trace调试宏 (开启后支持vTaskList等可视化调试函数)
#define configUSE_STATS_FORMATTING_FUNCTIONS            0       // 0: 关闭统计信息的字符串格式化函数
#define configKERNEL_PROVIDED_STATIC_MEMORY             1       // 1: 允许内核为空闲任务和定时器任务自动提供静态内存

/******************************************************************************/
/* 中断接口定义                      . ****************************************/
/******************************************************************************/

// 将FreeRTOS内部处理函数重定向到CMSIS标准中断名称
#define xPortPendSVHandler                              PendSV_Handler    // 可挂起系统服务 (用于任务上下文切换)
#define vPortSVCHandler                                 SVC_Handler       // 系统服务调用 (用于启动调度器等)
#define xPortSysTickHandler                             SysTick_Handler   // 系统心跳定时器 (用于产生系统节拍)

#endif /* FREERTOS_CONFIG_H */

在 board.c 中实现 系统时钟配置(为 MCU、外设提供工作时钟)与 os tick 的配置 (为操作系统提供心跳 / 节拍)。

board.c文件中修改如下函数,以完成系统初始化和 OS Tick 的配置。

#include "main.h"
extern void SystemClock_Config(void); // 此函数默认是在main.c中,此处声明一下

/* cortex-m 架构使用 SysTick_Handler() */
void SysTick_Handler()
{
  rt_interrupt_enter(); /* 进入中断时必须调用 */

  rt_tick_increase();  /* RT-Thread 系统时钟计数 */

  rt_interrupt_leave(); /* 退出中断时必须调用 */
}

/**
 * This function will initial your board.
 */
void rt_hw_board_init(void)
{
    /* 
     * TODO 1: OS Tick Configuration
     * Enable the hardware timer and call the rt_os_tick_callback function
     * periodically with the frequency RT_TICK_PER_SECOND. 
     */

	  /* 1、系统、时钟初始化 */
	  HAL_Init(); // 初始化 HAL 库
	  SystemClock_Config(); // 配置系统时钟
	  SystemCoreClockUpdate(); // 对系统时钟进行更新
	
	  /* 2、OS Tick 频率配置,RT_TICK_PER_SECOND = 1000 表示 1ms 触发一次中断 */
	  SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND);
	
	
    /* Call components board initial (use INIT_BOARD_EXPORT()) */
#ifdef RT_USING_COMPONENTS_INIT
    rt_components_board_init();
#endif

#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
    rt_system_heap_init(rt_heap_begin_get(), rt_heap_end_get());
#endif
}

另外,SysTick_Handler()函数在裸机工程中应该也有,也需要去stm32l4xx_it.c文件中注释掉。

1.4.4 添加源码编译

将前面添加到工程中的源码,源文件全部添加编译,头文件全部添加路径。

这里需要修改的makefile如下,只需要添加源文件和头文件路径即可。

和keil的区别就是port.c文件。这里留下的是GCC文件夹下的,keil需要留下RVDS文件夹下的。

C_SOURCES =  \
Core/Src/main.c \
Core/Src/gpio.c \
Core/Src/usart.c \
Core/Src/stm32f4xx_it.c \
Core/Src/stm32f4xx_hal_msp.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_uart.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_rcc.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_rcc_ex.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_flash.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_flash_ex.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_flash_ramfunc.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_gpio.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_dma_ex.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_dma.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_pwr.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_pwr_ex.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_cortex.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_exti.c \
Core/Src/system_stm32f4xx.c \
Core/Src/sysmem.c \
Core/Src/syscalls.c \
Core/Src/stm32f4xx_hal_timebase_tim.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_tim.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_tim_ex.c \
FreeRTOS/FreeRTOS-Kernel/croutine.c \
FreeRTOS/FreeRTOS-Kernel/event_groups.c \
FreeRTOS/FreeRTOS-Kernel/list.c \
FreeRTOS/FreeRTOS-Kernel/queue.c \
FreeRTOS/FreeRTOS-Kernel/stream_buffer.c \
FreeRTOS/FreeRTOS-Kernel/tasks.c \
FreeRTOS/FreeRTOS-Kernel/timers.c \
FreeRTOS/FreeRTOS-Kernel/portable/GCC/ARM_CM4F/port.c \
FreeRTOS/FreeRTOS-Kernel/portable/MemMang/heap_4.c \
Icode/task/task_start.c \
Icode/task/task_led.c \

# C includes
C_INCLUDES =  \
-ICore/Inc \
-IDrivers/STM32F4xx_HAL_Driver/Inc \
-IDrivers/STM32F4xx_HAL_Driver/Inc/Legacy \
-IDrivers/CMSIS/Device/ST/STM32F4xx/Include \
-IDrivers/CMSIS/Include \
-IFreeRTOS/FreeRTOS-Kernel \
-IFreeRTOS/FreeRTOS-Kernel/include \
-IFreeRTOS/FreeRTOS-Kernel/portable/GCC/ARM_CM4F \
-IFreeRTOS/FreeRTOS-Kernel/portable/MemMang \
-IIcode/task/

更新

看到个新语法 源文件不用一个个罗列,可以添加文件夹下的所有源文件

# 追加文件夹下的所有源文件
C_SOURCES += $(wildcard FreeRTOS/FreeRTOS-Kernel/*.c)
C_SOURCES += $(wildcard Icode/task/*.c)

就可以写成这样

C_SOURCES =  \
Core/Src/main.c \
Core/Src/gpio.c \
Core/Src/usart.c \
Core/Src/stm32f4xx_it.c \
Core/Src/stm32f4xx_hal_msp.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_uart.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_rcc.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_rcc_ex.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_flash.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_flash_ex.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_flash_ramfunc.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_gpio.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_dma_ex.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_dma.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_pwr.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_pwr_ex.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_cortex.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_exti.c \
Core/Src/system_stm32f4xx.c \
Core/Src/sysmem.c \
Core/Src/syscalls.c \
Core/Src/stm32f4xx_hal_timebase_tim.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_tim.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_tim_ex.c \

C_SOURCES += $(wildcard FreeRTOS/FreeRTOS-Kernel/*.c)
C_SOURCES += FreeRTOS/FreeRTOS-Kernel/portable/GCC/ARM_CM4F/port.c
C_SOURCES += FreeRTOS/FreeRTOS-Kernel/portable/MemMang/heap_4.c
C_SOURCES += $(wildcard Icode/task/*.c)

1.5 LED闪烁

工程中添加Icode/task/路径,添加以下接口

#include "FreeRTOS.h"
#include "task.h"

#include "gpio.h"
#include "usart.h"

void task_led(void *parms)
{
    while (1)
    {
        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
        vTaskDelay(1000);
        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
        vTaskDelay(1000);
    }
}

TaskHandle_t led_task_handle;
void FreeRTOS_Start(void)
{
    // 创建任务
    xTaskCreate(task_led,          // 任务函数体
                "task_led",        // 任务名
                128,               // 堆栈大小(多少个字 而不是字节)
                (void *)NULL,      // 任务参数
                3,                 // 优先级
                &led_task_handle); // 任务句柄

    // 启动调度器
    vTaskStartScheduler();
}

// 在main的while之前调用即可

1.6 printf

gcc实现printf,串口中添加如下方法即可。

如果使用HAL_UART_Transmit直接发送,性能略差,但是RTOS中的一些现象不好模拟出来。

如果直接操作寄存器,更容易看出来资源争抢等现象。

int __io_putchar(int ch)
{
  // HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);

  // 等待发送区为空
  while ((USART1->SR & USART_SR_TXE) == 0)
    ;

  // 数据写到发送寄存器
  USART1->DR = ch;

  return ch;
}