基于STM32标准库的FreeRTOS移植与任务创建

文章目录

FreeRTOS是一种功能强大的实时操作系统,可以运行在主流的单片机上,比如STM32F103、F407甚至更高的型号,是适用于中大型工程的项目基底。

为什么需要FreeRTOS?

如果不使用 FreeRTOS,嵌入式系统通常只能依靠裸机编程方式运行,也就是所有任务都在一个主循环中按顺序执行。

c 复制代码
while(1) {
    read_sensor();
    send_data();
    blink_led();
}

这样所有功能都在一个循环中执行,互相"抢时间"。但如果某个函数卡住了,其他任务就都被堵住了。

而在 FreeRTOS 里,这三个功能可以被写成三个"任务"(Task):每个任务都有自己的独立函数、自己的栈、自己的执行节奏。内核会自动帮你在它们之间"切换",看起来就像是同时执行。

当多个任务需要共享整个while循环的运行时间,也就是所谓的 并行 ,裸机系统往往只能通过轮询或状态机的方式来模拟多任务。例如,在主循环中不断判断各个任务的标志位或状态,然后依次执行相应的功能代码。这种方式在任务较少、逻辑简单时尚可,但一旦任务数量增多或响应时间要求提高,整个系统就容易出现任务抢占困难、响应延迟、代码耦合严重等问题。

FreeRTOS正是为了解决这一问题,它通过任务调度器让多个任务能够"并发"执行,为每个任务分配独立的栈空间与优先级,实现了真正意义上的多任务管理和实时响应。

多任务模型

FreeRTOS类似于多任务模型,通过主动出让CPU以及时间切片的方式,极短周期切换不同任务运行,达到感官上的多任务并行计算目的。

RTOS是通过主动出让的方式进行任务调度的。

内核组件

FreeRTOS 本质上就是一个让单片机"学会分身"的小系统内核。它不像电脑那样有完整的操作系统界面,而是专门负责在底层帮你调度任务、分配时间、管理资源。想象一下,它就像一个"超级管家",在后台帮你安排各个小工(任务)的工作时间。

任务(Task)

任务是 FreeRTOS 的基本运行单元,就像是系统中的一个"小工人"。每个任务都有自己的:

  • 函数入口(要干的活)
  • 独立栈空间(存放自己的数据)
  • 优先级(谁更重要谁先执行)

多个任务可以看起来同时运行,其实是 CPU 在高速切换它们。

调度器

调度器是整个系统的"大脑"。它决定哪个任务该运行、哪个任务该暂时让出 CPU。

FreeRTOS 支持两种调度方式:

  1. 抢占式:高优先级任务可以随时打断低优先级任务;
  2. 时间片轮转:同优先级的任务轮流执行。

调度器在任务之间切换时,会保存上一个任务的寄存器状态,再恢复下一个任务的寄存器,实现"无感切换"。这整个过程由底层中断(PendSV)机制完成。用于上下文切换,SVC用于启动第一个FreeRTOS的任务。

系统节拍

FreeRTOS 需要一个"心跳"来维持运行节奏,这个心跳就是 系统节拍(SysTick)。使用FreeRTOS时,SysTick定时器就会被进行默认初始化,这时候工程中不能对SysTick寄存器进行操作。

在 ARM 内核(如 STM32)上,FreeRTOS 的任务调度离不开两个特殊中断:

  1. SVC(Supervisor Call):启动调度器的钥匙,让系统从普通代码模式切入多任务;
  2. PendSV(Pending Supervisor):真正执行任务切换的引擎,保存旧任务、恢复新任务。

加上 SysTick(系统节拍),这三者共同构成了 FreeRTOS 的任务切换机制:

SVC 启动 → SysTick 触发 → PendSV 切换

任务间通信

当系统里有多个任务时,它们之间需要"交流"和"协作",这时就要用到通信机制。

机制 类比 功能
队列(Queue) 邮箱 任务之间安全传递数据
信号量(Semaphore) 红绿灯 控制任务的等待与唤醒
互斥锁(Mutex) 钥匙 保护共享资源不被同时访问
事件组(Event Group) 通知板 等待或触发多个事件
消息缓冲区(Message Buffer) 对讲机 连续发送或接收数据流

内存管理

每个任务都需要自己的"工作区"(栈空间),FreeRTOS 通过内存管理模块来负责分配和回收这部分空间。它提供了多种分配策略(heap_1 到 heap_5),根据项目规模与 RAM 结构灵活选择:

  • heap_1:最简单,固定分配;
  • heap_2:支持释放;
  • heap_4:智能合并碎片;
  • heap_5:支持多内存区域。

内存管理模块是"仓库管理员",给每个任务分配合适的存储空间。

中断与临界区

在 FreeRTOS 中,中断(ISR)仍然非常重要。系统允许中断服务程序与任务交互,比如发送信号量、激活任务。

但当多个任务或中断同时访问共享资源时,容易出现数据竞争。这时就需要进入临界区(Critical Section):短暂关闭中断,保证关键代码在执行时不会被打断。

以上提到的内核组件(包括 SysTick、PendSV、SVC 等)以及任务调度、内存管理等初始化步骤,FreeRTOS 内核已经在启动阶段自动完成。开发者只需要在创建完任务后,调用一次 vTaskStartScheduler(),系统就会自动启动调度器,一切底层运行逻辑都已准备就绪!

STM32标准库移植FreeRTOS

这里以 FreeRTOS V10.4.3 LTS 版本 为例进行讲解,其他版本的移植步骤基本相同。首先从官网从中下载源码,FreeRTOS 源码可以从官方 GitHub 下载:

👉https://github.com/FreeRTOS/FreeRTOS-Kernel/tags

一、创建目录结构

在 Keil 工程中,新建一个名为 freertos 的文件夹,用于存放所有与 FreeRTOS 相关的源码文件。推荐结构如下:

c 复制代码
├── freertos
│   ├── include            // 头文件目录(含 FreeRTOSConfig.h)
│   ├── portable           // 移植层代码(与硬件/编译器相关)
│   └── *.c                // FreeRTOS 内核源文件

二、拷贝核心源码文件

将下载的 FreeRTOS 源码解压后,进入根目录,会看到几个核心的 .c 文件。请将以下 7 个文件 拷贝到你的 Keil 工程的 freertos 目录中:

这些文件构成了 FreeRTOS 的核心内核逻辑,例如任务调度、事件管理、消息队列、软件定时器等。

三、拷贝头文件目录

然后,将 FreeRTOS 源码中的 include 文件夹整体复制到工程的 freertos 目录下。其中包含系统所有的核心头文件与 API 声明。

💡 注意:

这里还需要在 include 目录下新建并放置你的项目配置文件 FreeRTOSConfig.h,用于定义系统运行参数,例如任务优先级数量、堆内存大小、系统时钟节拍等。这样做的好处是所有 FreeRTOS 头文件都在同一目录中,路径更清晰,工程结构更整洁。

FreeRTOSConfig.h 并不会包含在官方源码包中,因此需要自行从官网获取或参考官方模板进行编写。建议使用官方推荐版本 ,可在以下页面找到:

👉FreeRTOSConfig.h 官方配置说明

不过,官方模板通常需要根据具体芯片型号(如 STM32F4 系列)进行部分修改。以下是经过适配和优化后的版本,可直接复制使用,非常适合 STM32F103、F407 等主流开发板。这里我已经进行修改了,各位可以直接复制拿去使用。

c 复制代码
#ifndef __FREERTOS_CONFIG_H__
#define __FREERTOS_CONFIG_H__

/* Here is a good place to include header files that are required across
   your application. */
#include "stm32f4xx.h"
extern uint32_t SystemCoreClock;

#define configUSE_PREEMPTION                                        1
#define configUSE_PORT_OPTIMISED_TASK_SELECTION                     1
#define configUSE_TICKLESS_IDLE                                     0
#define configCPU_CLOCK_HZ                                          SystemCoreClock
#define configTICK_RATE_HZ                                          1000
#define configMAX_PRIORITIES                                        10
#define configMINIMAL_STACK_SIZE                                    128
#define configMAX_TASK_NAME_LEN                                     16
#define configUSE_16_BIT_TICKS                                      0
#define configIDLE_SHOULD_YIELD                                     1
#define configUSE_TASK_NOTIFICATIONS                                1
#define configTASK_NOTIFICATION_ARRAY_ENTRIES                       3
#define configUSE_MUTEXES                                           1
#define configUSE_RECURSIVE_MUTEXES                                 1
#define configUSE_COUNTING_SEMAPHORES                               1
#define configUSE_ALTERNATIVE_API                                   0 /* Deprecated! */
#define configQUEUE_REGISTRY_SIZE                                   10
#define configUSE_QUEUE_SETS                                        1
#define configUSE_TIME_SLICING                                      1
#define configUSE_NEWLIB_REENTRANT                                  0
#define configENABLE_BACKWARD_COMPATIBILITY                         0
#define configNUM_THREAD_LOCAL_STORAGE_POINTERS                     5
#define configUSE_MINI_LIST_ITEM                                    1
#define configSTACK_DEPTH_TYPE                                      uint32_t
#define configMESSAGE_BUFFER_LENGTH_TYPE                            size_t

/* Memory allocation related definitions. */
#define configSUPPORT_STATIC_ALLOCATION                             0
#define configSUPPORT_DYNAMIC_ALLOCATION                            1
#define configTOTAL_HEAP_SIZE                                       (10 * 1024)
#define configAPPLICATION_ALLOCATED_HEAP                            0

/* Hook function related definitions. */
#define configUSE_IDLE_HOOK                                 0
#define configUSE_TICK_HOOK                                 0
#define configCHECK_FOR_STACK_OVERFLOW                      1
#define configUSE_MALLOC_FAILED_HOOK                        1
#define configUSE_DAEMON_TASK_STARTUP_HOOK                  0
#define configUSE_SB_COMPLETED_CALLBACK                     0

/* Run time and task stats gathering related definitions. */
#define configGENERATE_RUN_TIME_STATS                       0
#define configUSE_TRACE_FACILITY                            0
#define configUSE_STATS_FORMATTING_FUNCTIONS                0

/* Co-routine related definitions. */
#define configUSE_CO_ROUTINES                               0
#define configMAX_CO_ROUTINE_PRIORITIES                     1

/* Software timer related definitions. */
#define configUSE_TIMERS                                    1
#define configTIMER_TASK_PRIORITY                           (configMAX_PRIORITIES - 1)
#define configTIMER_QUEUE_LENGTH                            32
#define configTIMER_TASK_STACK_DEPTH                        configMINIMAL_STACK_SIZE

/* Interrupt nesting behaviour configuration. */
#define configPRIO_BITS                         4
#define configKERNEL_INTERRUPT_PRIORITY         (15 << (8 - configPRIO_BITS))
#define configMAX_SYSCALL_INTERRUPT_PRIORITY    (5 << (8 - configPRIO_BITS))
#define configMAX_API_CALL_INTERRUPT_PRIORITY   configMAX_SYSCALL_INTERRUPT_PRIORITY

/* Define to trap errors during development. */
void vAssertCalled(const char *file, int line);
#define configASSERT( x ) if( ( x ) == 0 ) vAssertCalled( __FILE__, __LINE__ )

/* Optional functions - most linkers will remove unused functions anyway. */
#define INCLUDE_vTaskPrioritySet                1
#define INCLUDE_uxTaskPriorityGet               1
#define INCLUDE_vTaskDelete                     1
#define INCLUDE_vTaskSuspend                    1
#define INCLUDE_vTaskDelayUntil                 1
#define INCLUDE_vTaskDelay                      1
#define INCLUDE_xTaskGetSchedulerState          1
#define INCLUDE_xTaskGetCurrentTaskHandle       1
#define INCLUDE_uxTaskGetStackHighWaterMark     1
#define INCLUDE_uxTaskGetStackHighWaterMark2    1
#define INCLUDE_xTaskGetIdleTaskHandle          1
#define INCLUDE_eTaskGetState                   1
#define INCLUDE_xTimerPendFunctionCall          0
#define INCLUDE_xTaskAbortDelay                 0
#define INCLUDE_xTaskGetHandle                  1
#define INCLUDE_xTaskResumeFromISR              1

/* A header file that defines trace macro can be included here. */

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


#endif /* FREERTOS_CONFIG_H */

四、拷贝 portable 文件夹内容

在本次移植中,使用 STM32 + ARM Cortex-M4 内核,因此需要从源码中选择以下部分内容进行复制到工程的 freertos/portable 目录中:

1️⃣ portable/Common:该目录包含通用的移植支持文件,例如任务切换、堆管理通用接口等。这些文件是所有架构共享的,必须一并复制。

2️⃣ portable/RVDS/ARM_CM4F:此目录是 ARM Cortex-M4F 架构(带 FPU 浮点单元)的专用移植文件,包含任务切换的汇编实现、中断入口与栈切换逻辑等核心底层代码。
注意:如果你的芯片是 STM32F407、STM32F4xx 等 Cortex-M4 内核,就必须复制这一目录内容。

3️⃣ portable/MemMang:该目录包含多种内存管理方式(heap_1.c ~ heap_5.c)。根据项目需求选择一种复制到 freertos/portable/ 下,例如:

  • heap_1.c:最简单的静态分配,不支持释放。
  • heap_2.c:支持释放,但不合并空闲块。
  • heap_4.c:最常用,支持分配与合并空闲内存,适合大多数项目。

💡 建议使用 heap_4.c,同时在 FreeRTOSConfig.h 中配置堆大小(configTOTAL_HEAP_SIZE)。

五、Keil 工程中添加头文件路径并编译

完成前面所有文件的拷贝后,FreeRTOS 的源码已经成功放入工程目录。接下来我们需要在 Keil µVision 中配置头文件搜索路径与源文件路径,让编译器能够正确找到并编译 FreeRTOS 的各个模块。

六、添加系统钩子函数(Hook)确保系统稳定运行

FreeRTOS 在运行过程中,如果检测到异常情况(如任务堆栈溢出、内存分配失败、断言错误),会主动调用用户定义的 钩子函数(Hook Function) 来执行自定义处理。我们需要在main文件下面添加如下代码,这里我用了串口1,并且重写了fputc方法用于日志输出。

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

/* 1. 堆栈溢出检测 */
void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName)
{
    printf("Stack Overflowed: %s\n", pcTaskName);
    configASSERT(0); // 触发断言,进入异常状态
}

/* 2. 断言失败处理 */
void vAssertCalled(const char* file, int line)
{
    printf("Assert Called: %s(%d)\n", file, line);
    // 可以在此处添加LED闪烁或日志上报等操作
}

/* 3. 内存分配失败处理 */
void vApplicationMallocFailedHook(void)
{
    printf("Malloc Failed\n");
    configASSERT(0); // 内存不足时触发断言
}

要确认配置宏已启用,这些钩子函数能否被调用,取决于 FreeRTOSConfig.h 中的相关宏定义。请确保以下宏已设置为 1:

c 复制代码
#define configCHECK_FOR_STACK_OVERFLOW     1
#define configUSE_MALLOC_FAILED_HOOK       1

我们可以使用一下方法创建任务,xTaskCreate() 的作用就是创建一个任务(Task),让 FreeRTOS 的调度器能够管理并运行它。函数原型如下:

c 复制代码
BaseType_t xTaskCreate(
    TaskFunction_t pxTaskCode,        // 任务函数指针
    const char * const pcName,        // 任务名字(调试可见)
    const configSTACK_DEPTH_TYPE usStackDepth, // 任务栈深度(单位:字)
    void *pvParameters,               // 传递给任务的参数
    UBaseType_t uxPriority,           // 任务优先级
    TaskHandle_t *pxCreatedTask       // 任务句柄(输出)
);

在任务创建完成后,可以在main函数中调用:

c 复制代码
vTaskStartScheduler();

这时 FreeRTOS 会自动完成:SysTick 定时中断初始化、SVC / PendSV 中断挂载、任务堆栈初始化与调度切换。自此,系统已经能够安全运行多任务调度,并能在出现异常时主动报告错误。


如有问题请在评论区留言!!!

相关推荐
R.lin2 小时前
MongoDB知识点与技巧总结
数据库·mongodb
幽水-椰子糖2 小时前
达梦守护搭建
数据库·达梦
时光の尘2 小时前
【STM32】DMA超详细解析·入门级教程
stm32·单片机·嵌入式硬件·mcu·串口·dma·usart
q***3752 小时前
Spring Boot 从 2.7.x 升级到 3.3注意事项
数据库·hive·spring boot
chao1898442 小时前
基于TMS320F28069 DSP开发板实现RS485通信
单片机·嵌入式硬件
王小小鸭2 小时前
【Oracle APEX开发小技巧17】交互式网格操作按钮根据条件/状态设置能否被点击生效
数据库·oracle·oracle apex
lang201509283 小时前
oracle 11查询数据库锁
数据库·oracle
hweiyu003 小时前
Oracle 基础入门:核心概念与实操指南(视频教程)
数据库·oracle
朱嘉鼎3 小时前
消费级MCU如何管理内存
单片机·嵌入式硬件