FreeRTOS内核移植01
1. 准备工作
移植的方式有好多种
- 基于 CubeMX 的移植;
- 手动使用源码移植;
在cubemx中移植太简单了,直接勾选就可以运行了,但是会被封装一层,这里记录手动移植的过程,移植完成之后,再去看cubemx生成的代码,其实基本上一眼就看明白了。
1.1 源码准备
官方网址:https://freertos.org/
直接从官网下载源码即可,下载LTS结尾的长期支持版。
1.2 创建工程
前几天的文章写了如何使用vscode开发stm32,这里还是使用同样的方式开发,但是也会记录使用keil开发的话的区别(除了有个文件不一样,几乎没有区别)。
使用cubemx创建一个工程
- 时钟源选择一个基础定时器,这里选择
TIM11 - 生成的工程类型选择
makefile,如果使用keil选择MDK_ARM - 这里配置一个LED和USART1用于创建一个任务测试

- 取消三个中断函数生成的代码
PendSV_HandlerSVC_HandlerSysTick_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说明
FreeRTOS-Kernel文件夹下的所有源文件全部保留FreeRTOS-Kernel\include文件夹下的内容全部保留FreeRTOS-Kernel\portable\MemMang文件夹下只保留heap_4.cFreeRTOS-Kernel\portable\GCC\ARM_CM4F或FreeRTOS-Kernel\portable\RVDS\ARM_CM4F这两个文件夹留一个,这里使用gcc编译就留下前者,如果使用keil编译就留下后者。如果是其他内核的芯片,最后一级目录根据芯片选择,这里是STM32F4,所以选择ARM_CM4F- 从
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;
}