FreeRTOS基础篇(一)——介绍

前言:
   本篇文章介绍FreeRTOS的移植方法,以stm32f103举例。

1 下载源码

  FreeRTOS 官网 支持中文,提供丰富的包括文档、教程和 API 参考等。
  可以从官网下载源码或者从github仓库clone源码。FreeRTOS提供三种版本。

仓库 版本类型 更新策略 适用场景 是否适合生产环境
FreeRTOS 正式版(Mainline) 频繁更新,加入新功能 需要最新功能的项目
FreeRTOS-LTS 长期支持版 只修复Bug,不新增功能 工业级稳定产品
FreeRTOS-Labs 实验性版本 不稳定,可能有新特性 评估、学习、Demo 开发

  学习为目的建议使用正式版,能接触到最新的功能,工业开发则建议使用长期支持版。
  开发项目建议选择长期支持版,功能更稳定。登陆官网可以看到 下载链接 ,但更推荐从github上获取 仓库
  执行以下指令将仓库克隆到本地。

1
git@github.com:FreeRTOS/FreeRTOS-LTS.git

 &esmp;clone仓库后还需要更新子仓库。

1
git submodule update --init

  切换分支可以选择需要的稳定版本,比如我使用最新的长期稳定版 202406-LTS

branch.png

2 freertos工程架构

  以下以stm32(STM32F1 Cortex-M3)为例移植freertos,其他平台移植类似。
&emps; 先认识下 FreeRTOS-LTS 的目录结构,202406-LTS版本的目录结构如下(其他版本类似)。

1
2
3
4
5
6
7
8
9
.
├── aws # AWS 相关的扩展和集成。
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── FreeRTOS # FreeRTOS-LTS的核心源代码。
├── LICENSE.md
├── manifest.yml
└── README.md

   AWS IoT 是亚马逊云科技提供的一个托管云平台,让互联设备(比如传感器、执行器、嵌入式设备)能够轻松、安全地与云应用程序和其他设备交互。它是物联网设备与AWS云之间的桥梁,本篇文章用不到,因此不介绍这部分的移植。主要关注 FreeRTOS 目录,里面是FreeRTOS-LTS的核心源码,目录结构如下。

1
2
3
4
5
6
7
8
9
10
.
├── backoffAlgorithm # 指数退避算法,网络连接失败的重试逻辑,防止网络拥塞。
├── coreHTTP # 轻量级HTTP客户端,实现HTTP协议。
├── coreJSON # 轻量级JSON解析器。
├── coreMQTT # 轻量级MQTT客户端,实现MQTT通讯协议。
├── corePKCS11 # 硬件安全模块接口。
├── coreSNTP # 简单网络时间协议。
├── FreeRTOS-Cellular-Interface # 蜂窝网络连接抽象层。
├── FreeRTOS-Kernel # 内核源码,是必要的核心部分。
└── FreeRTOS-Plus-TCP # TCP/IP协议栈,包含完整的 TCP、UDP、IP 协议实现。

  FreeRTOS采用模块设计,模块化设计让开发者可以按需选择组件,特别适合资源受限的嵌入式设备,只链接实际需要的功能,最大化优化内存和性能。因此以上模块不需要全部移植,只需要移植自己需要的部分。
  这里我们移植必须的 FreeRTOS-Kernel 部分,其他部分方法类似。继续熟悉FreeRTOS-Kernel的目录结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.
├── CMakeLists.txt # 构建系统。
├── croutine.c # 协程支持,轻量级的协作式人物(过时,已较少使用)。
├── cspell.config.yaml # 代码拼写检查配置。
├── event_groups.c # 事件组。
├── examples # 示例代码。
├── GitHub-FreeRTOS-Kernel-Home.url
├── History.txt
├── include # 内核头文件,包含FreeRTOS.h,task.h,queue.h, semphr.h等。
├── LICENSE.md
├── list.c # 内核链表,任务列表管理。
├── manifest.yml # 项目清单和依赖管理。
├── MISRA.md
├── portable # 处理器移植层,支持不同处理器架构,多种内存管理策略可选。
├── queue.c # 队列通信。
├── Quick_Start_Guide.url
├── README.md
├── sbom.spdx # 软件物料清单(Security Bill of Materials)
├── stream_buffer.c # 流缓冲区。
├── tasks.c # 任务管理核心,任务创建、删除、调度,优先级管理,调度器启动和控制。
└── timers.c # 软件定时器。

  需要重点了解的内核核心架构如下。

文件类别 核心文件 功能描述
调度核心 tasks.c, list.c 任务调度和管理
通信机制 queue.c, stream_buffer.c 任务间数据传递
同步原语 event_groups.c 任务同步和事件管理
时间管理 timers.c 定时器和时间服务
内存管理 portable/MemMang/ 动态内存分配策略
硬件抽象 portable/ 处理器架构移植层

  继续介绍 portable 的目录结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
.
├── ARMClang # ARM Compiler 6,基于Clang的现代ARM编译器。
├── ARMv8M # ARM Cortex-M23/M33内核,架构移植,TrustZone 安全扩展支持。
├── BCC
├── CCS # Texas Instruments Code Composer Studio.
├── CMakeLists.txt
├── CodeWarrior
├── Common
├── GCC # GNU 编译器集合。
├── IAR # IAR Embedded Workbench,开发环境。
├── Keil # ARM Keil MDK,开发环境。
├── MemMang # 内存管理方案,提供了5种内存管理实现。
├── MikroC
├── MPLAB # Microchip MPLAB X IDE.
├── MSVC-MingW # Windows环境模拟,用于在Windows上测试FreeRTOS应用。
├── oWatcom
├── Paradigm
├── readme.txt
├── Renesas
├── Rowley # CrossWorks for ARM.
├── RVDS
├── SDCC
├── Softune
├── Tasking # Tasking编译器.
├── template
├── ThirdParty
└── WizC

   portable 目录是FreeRTOS可移植性的核心,包含了针对不同编译器和处理器架构的适配代码。总结为如下。

类别 目录/文件 功能描述
编译器支持 GCC/, IAR/, Keil/, ARMClang/ 不同编译器工具链的适配
内存管理 MemMang/ 5种动态内存分配策略实现
处理器架构 ARMv8M/, Common/ 特定CPU架构的移植代码
开发环境 CCS/, MPLAB/, MSVC-MingW/ 各厂商IDE的集成支持
其他编译器 Rowley/, Tasking/, SDCC/等 小众或专用编译器支持
模板文件 template/ 新端口开发的参考模板
第三方 ThirdParty/ 第三方移植和维护的代码

  portable的作用时移植层,主要作用有以下几点。

  • 硬件抽象 : 将 FreeRTOS 内核与具体硬件隔离
  • 编译器适配 : 处理不同编译器的汇编语法和调用约定
  • 架构优化 : 针对特定处理器优化任务切换和中断处理
  • 内存管理 : 提供可配置的内存分配方案

   MemMang 提供了5种内存管理方法可供选择。

1
2
3
4
5
6
└── MemMang/           # 内存管理方案
├── heap_1.c # 简单静态分配
├── heap_2.c # 最佳匹配算法
├── heap_3.c # 封装 malloc/free
├── heap_4.c # 碎片合并算法(最常用)
├── heap_5.c # 多内存块管理

  这种设计使得 FreeRTOS 可以轻松移植到任何支持C语言的微控制器上,只需实现对应的移植层即可。

3 移植FreeRTOS

  假定你已经拥有一个基础的芯片平台工程,已经实现基础的编译和运行环境,在此基础上移植FreeRTOS。我以stm32f103裸机工程移植FreeRTOS为例说明移植方法。

3.1 内核移植

  将FreeRTOS内核部分移植到本地工程,即将 FreeRTOS-LTS/FreeRTOS/FreeRTOS-Kernel 目录整个拷贝到自己的工程中,操作系统通常作为中间层(底层是微控制器,中间BSW,底层时ASW),我们在自己的工程创建目录 mw 意为Middleware。
   FreeRTOS-Kernel 其实是一个独立的仓库,通过子仓库的方式关联到FreeRTOS-LTS仓库,如果自己完全不做修改,也可以作为自己项目的子仓库关联,如果自己有修改则应将原本的 .git.github 目录删除,通过自己的git进行管理。建议不要改变FreeRTOS-Kernel下的目录结构,在原有的目录结构基础上保留需要使用的部分,删除不需要的目录和文件,这样方便升级FreeRTOS官方版本时的横向对比。如果芯片追求极致的代码量小,工程简化,并不需要追求随官方版本升级,可以只将需要的文件拷贝到自己工程中,做到最简化运行。应尽量避免去修改内核中源码,一旦修改,将来合入官方版本的修改时都需要考虑和自身修改的兼容性,官网的修改经过严格测试,带入自身的修改后理论上需要自己再做严格测试。

  • 导入源文件

  修改工程根目录的CMakeLists.txt,在 target_sources 中添加相关的源文件路径,主要是一些必要的内核文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
target_sources(${CMAKE_PROJECT_NAME} PRIVATE
# 芯片层源文件。
# ...

# FreeRTOS核心文件。
# 内核核心文件。
mw/FreeRTOS-Kernel/tasks.c
mw/FreeRTOS-Kernel/queue.c
mw/FreeRTOS-Kernel/list.c
mw/FreeRTOS-Kernel/timers.c
mw/FreeRTOS-Kernel/event_groups.c
mw/FreeRTOS-Kernel/stream_buffer.c

# 内存管理(选择 heap_4,最常用)。
mw/FreeRTOS-Kernel/portable/MemMang/heap_4.c

# Cortex-M3 移植层。
mw/FreeRTOS-Kernel/portable/GCC/ARM_CM3/port.c

# 应用层源文件。
# ...
)
  • 导入头文件

  继续修改根目录CMakeLists.txt,添加对应的头文件路径。

1
2
3
4
5
6
7
8
9
10
11
12
target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE
{
# 芯片层源文件。
# ...

# FreeRTOS 头文件路径。
mw/FreeRTOS-Kernel/include
mw/FreeRTOS-Kernel/portable/GCC/ARM_CM3

# 应用层头文件。
# ...
}
  • 配置FreeRTOSConfig.h

  FreeRTOS的内核中含有examples,可以将其中的FreeRTOSConfig.h拷贝到自己的工程中作为初版配置使用,将 FreeRTOS-LTS/FreeRTOS/FreeRTOS-Kernel/examples/template_configuration/FreeRTOSConfig.h 拷贝到自己的工程,比如我将其拷贝到 cfg 目录下。在CMakeLists.txt的target_include_directories中添加该头文件路径(如果缺少路径)。

1
2
3
4
5
6
7
8
9
10
11
12
target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE
{
# 芯片层源文件。
# ...

# FreeRTOS 头文件路径。
cfg
# ...

# 应用层头文件。
# ...
}

  FreeRTOSConfig.h中的主要配置如下。

  • configTICK_TYPE_WIDTH_IN_BITS

  定义了系统节拍计数器的数据类型宽度,由于我使用的32不支持64位,因此需要将定义改为32位(默认是64位)。64位时节拍器技术之很大,几乎不用考虑溢出的问题。

1
#define configTICK_TYPE_WIDTH_IN_BITS              TICK_TYPE_WIDTH_32_BITS
  • configKERNEL_INTERRUPT_PRIORITY

  定义了最低优先级,比如我用的stm32f103系列使用4位优先级,范围为0~15,则定义为。

1
#define configKERNEL_INTERRUPT_PRIORITY          (15 << 4)
  • configMAX_SYSCALL_INTERRUPT_PRIORITY

  定义了系统调用的最大优先级,需要具体值要根据项目需求而定,这里暂时设置为1。

1
#define configMAX_SYSCALL_INTERRUPT_PRIORITY     (1 << 4)

  FreeRTOSConfig.h里有很多宏配置,这篇文章主要介绍移植的方法,后续在其他文章详细展开分析这些配置,这里不用太就接配置,我们先让移植程序跑起来即可。

  • 创建Task测试
      修改main.c文件,尝试使用FreeRTOS的任务调度,让程序运行起来。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include "FreeRTOSConfig.h"
#include "FreeRTOS.h"
#include "task.h"

// 任务句柄
TaskHandle_t Task1Handle;
TaskHandle_t Task2Handle;

// ========== FreeRTOS 钩子函数空实现 ==========
void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName)
{
(void)xTask;
(void)pcTaskName;

// 这里可以添加调试处理。
for(;;) {
// 死循环,或者重启系统
// HAL_NVIC_SystemReset(); // 如果需要重启
}
}

void Task1(void* argument)
{
for(;;)
{
vTaskDelay(500 / portTICK_PERIOD_MS); // 延迟500ms
}
}

void Task2(void* argument)
{
for(;;)
{
vTaskDelay(1000 / portTICK_PERIOD_MS); // 延迟1000ms
}
}

int main(void)
{
// 芯片初始化
// ...

// 创建任务1
xTaskCreate(Task1, "Task1", 128, NULL, 2, &Task1Handle);

// 创建任务2
xTaskCreate(Task2, "Task2", 128, NULL, 1, &Task2Handle);

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

// 如果调度器正常启动,不会执行到这里
while(1)
{
// 调度器启动失败处理
}
}

  由于FreeRTOS的port.c中定义了 SVC_Handler , PendSV_Handler , SysTick_Handler 三个函数的实现,这可能会芯片平台的定义重复,比如我使用的stm32f103芯片,在stm32f1xx_it.c中也定义了这三个函数的实现,这里我们选择使用FreeRTOS的定义,将stm32f1xx_it.c中实现的这三个函数注释调。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// void SVC_Handler(void)
// {
// /* USER CODE BEGIN SVCall_IRQn 0 */
//
// /* USER CODE END SVCall_IRQn 0 */
// /* USER CODE BEGIN SVCall_IRQn 1 */
//
// /* USER CODE END SVCall_IRQn 1 */
// }

// void PendSV_Handler(void)
// {
// /* USER CODE BEGIN PendSV_IRQn 0 */
//
// /* USER CODE END PendSV_IRQn 0 */
// /* USER CODE BEGIN PendSV_IRQn 1 */
//
// /* USER CODE END PendSV_IRQn 1 */
// }

// void SysTick_Handler(void)
// {
// /* USER CODE BEGIN SysTick_IRQn 0 */
//
// /* USER CODE END SysTick_IRQn 0 */
// HAL_IncTick();
// /* USER CODE BEGIN SysTick_IRQn 1 */
//
// /* USER CODE END SysTick_IRQn 1 */
// }

  同时在FreeRTOSConfig.h中添加以下定义。

1
2
3
#define vPortSVCHandler SVC_Handler
#define xPortPendSVHandler PendSV_Handler
#define xPortSysTickHandler SysTick_Handler

  编译、烧录、运行后可以看到成功运行到断点内,系统任务调度成功。

Run.png