1. FreeRTOS源码结构介绍
1.1 下载源码
点击官网地址,选择 FreeRTOS 202212.01非 LTS 版本(非长期支持版),因为这个版本有着最全的历程和更多型号处理器支持。
1.2 文件夹结构介绍
下载后主文件 FreeRTOSv202212.01 下包含以下文件:
名称 | 描述 |
---|---|
FreeRTOS | FreeRTOS 的核心源码,提供实时操作系统的**所有必要组件****,如任务调度、内存管理、信号量、队列等。 |
FreeRTOS-Plus | FreeRTOS 的附加组件(非必要),如文件系统、TCP/IP 协议栈、图形界面库等。 提供了一些额外的功能,可以扩展应用场景。 |
tools | 这个文件夹通常包含一些辅助工具,用于支持 FreeRTOS 的开发,比如配置生成工具、调试工具、自动化脚本等。 |
其他文件 | 一些网页链接和历史版本记录,项目以来关系,编译格式文件 |
1.2 FreeRTOS文件夹
由于上面文件中仅有 FreeRTOS 是建立最小 FreeRTOS 例程的必要文件夹,因此对该文件下内容进一步做说明(在该文件下的 README.md 文件有官方的英文说明):
名称 | 描述 |
---|---|
Demo | FreeRTOS 演示例程,支持多种芯片架构、多种型号芯片 |
Source | FreeRTOS 源码,最重要的文件夹(包含着移植FreeRTOS的必要文件) |
Test | 公用以及移植层测试代码 |
其他文件 | 许可证文件License,网页链接,说明文件 |
1.3 Source 文件夹
Source 文件夹存储着移植最小 FreeRTOS 系统的必要文件,可以说只要有Source文件就可以完成对 FreeRTOS 系统的最基础的功能移植,官网上也有说明所需的至少五个必要文件。
项目必须至少包含以下源文件:
- FreeRTOS/Source/tasks.c
- FreeRTOS/Source/queue.c
- FreeRTOS/Source/list.c
- FreeRTOS/Source/portable/[compiler]/[architecture]/port.c
- FreeRTOS/Source/portable/MemMang/heap_x.c 其中 "x" 可以是 1、2、3、4 或 5
名称 | 描述 |
---|---|
include | FreeRTOS 的公共头文件,定义了 FreeRTOS 的 API 和常用的宏、数据结构、常量等。 |
portable | FreeRTOS 为不同硬件平台(微控制器、处理器架构等)提供适配层的地方。每个目标硬件平台(如 ARM Cortex-M、AVR、RISC-V 等)都会有一个专门的适配代码,用于处理硬件相关的操作,例如上下文切换、时钟中断、任务堆栈管理等。 |
croutine | 协程相关文件 |
event_groups | 事件相关文件 |
list | 提供与链表相关的功能实现。FreeRTOS 中许多数据结构(如任务控制块、定时器等)都使用链表来管理。 |
queue | 提供队列功能的实现,任务之间可以通过队列进行通信和数据交换。 |
stream_buffer | 流式缓冲区相关文件 |
task | FreeRTOS 的任务调度和任务管理的核心实现。 |
timers | FreeRTOS 定时器管理的实现,定时器可用于执行周期性任务或超时任务。 |
1.4 portable文件夹
该文件的作用与意义:
- 平台适配 :
portable
文件夹的主要目的是提供硬件相关的低级实现,使得 FreeRTOS 能够在不同硬件平台上运行。不同的硬件平台可能有不同的上下文切换机制、定时器控制方法等,因此 FreeRTOS 必须为每个硬件平台提供专门的实现。 - 跨平台支持 :FreeRTOS 操作系统归根到底是一个软件层面的东西,需要跟硬件联系在一起, portable 文件夹里面的东西就是连接桥梁。通过
portable
文件夹,FreeRTOS 可以在多种架构上运行,如 ARM Cortex-M、AVR、RISC-V 等,确保嵌入式开发人员能够在多种平台上使用 FreeRTOS
对于在 Keil 平台开发 STM32 系列芯片,所以我们需要选择 Keil 文件夹,但是 Keil 文件夹下只有一个文本文件,指示我们去查看 RVDS 文件夹。此外 portable 文件夹下还有一个必须文件 MemMang。
名称 | 描述 |
---|---|
Keil | 指向 RVDS 文件夹 |
RVDS | 是 ARM 公司提供的一个嵌入式开发工具链,广泛应用于 ARM Cortex-M 系列和其他 ARM 架构的微控制器。 |
MemMang | 包含与内存管理相关的文件,负责 FreeRTOS 中的动态内存分配策略。 |
1.4.1 RVDS 文件夹
RVDS 文件夹包含了各种处理器相关的文件夹,FreeRTOS 是一个软件,单片机是一个硬件,FreeRTOS 要想运行在一个单片机上面,它们就必须关联在一起。 关联还是得通过写代码来关联,这部分关联的文件叫接口文件,通常由汇编和 C 联合编写。这些接口文件都是跟硬件密切相关的,不同的硬件接口文件是不一样的,但都大同小异。编写这些接口文件的过程我们就叫移植,移植的过程通常由 FreeRTOS 和 mcu 原厂 的人来负责,移植好的这些接口文件就放在 RVDS 这个文件夹的目录下。

FreeRTOS 为我们提供了cortex-m0、m3、m4和m7等内核的单片机的接口文件,根据 mcu的内核选择对应的接口文件即可。其实准确来说,不能够叫移植,应该叫使用官方的 移植,因为这些跟硬件相关的接口文件,RTOS官方都已经写好了,我们只是使用而已。
以ARM_CM3这个文件夹为例,里面只有"port.c"与"portmacro.h"两个文件:
- port.c文件:里面的内容是由FreeRTOS官方的技术人员为Cortex-M3内核的处理 器写的接口文件,里面核心的上下文切换代码是由汇编语言编写而成,对技术员的要求比 较高,我们只是使用的话只需拷贝过来用即可
- portmacro.h文件:port.c文件对应的头文件,主要是一些数据类型和宏定义
1.4.2 MemMang文件夹
MemMang 文件夹下存放的是跟内存管理相关的,总共有五个 heap文件以及一个 readme 说明文件。

这五个heap文件在移植的时候必须使用一个,因为FreeRTOS在创建内核对象的时候 使用的是动态分配内存,而这些动态内存分配的函数则在这几个文件里面实现,不同的分 配算法会导致不同的效率与结果,我们选用heap4.c即可
2. FreeRTOS 基于 HAL 库移植步骤示例
2.1 添加必要的源码文件
- G:\FreeRTOSv202212.01\FreeRTOS\Source\include (整个文件夹)
- G:\FreevRTOSv202212.01\FreeRTOS\Source\croutine.c
- G:\FreeRTOSv202212.01\FreeRTOS\Source\event_groups.c
- G:\FreeRTOSv202212.01\FreeRTOS\Source\list.c
- G:\FreeRTOSv202212.01\FreeRTOS\Source\queue.c
- G:\FreeRTOSv202212.01\FreeRTOS\Source\stream_buffer.c
- G:\FreeRTOSv202212.01\FreeRTOS\Source\tasks.c
- G:\FreeRTOSv202212.01\FreeRTOS\Source\timers.c
- G:\FreeRTOSv202212.01\FreeRTOS\Source\portable\RVDS\ARM_CM3\port.c
- G:\FreeRTOSv202212.01\FreeRTOS\Source\portable\RVDS\ARM_CM3\portmacro.h
前八个内容是通用内容,而后面俩个portable文件夹下内容需根据单片机内核架构的不同和开发环境的编译软件不同来进行适合的选择.
在keil工程文件中加入这些.c文件,include这些.h源文件,就完成了系统源码文件的移植,接下来将进行编写 FreeRTOS 的工程配置文件.
2.2 系统配置文件 FreeRTOSConfig.h 的编写(必要)
FreeRTOSConfig.h
是 FreeRTOS 配置文件,负责定义 FreeRTOS 内核运行时的各种参数和选项。它允许用户根据具体的硬件平台、应用需求和性能目标来定制 FreeRTOS 的行为。这个文件是 每个 FreeRTOS 项目必需的配置文件,它提供了调度策略、内存管理、任务优先级、时钟设置等重要的内核配置。
通过一些对变量的宏定义来选定是否开启指定功能(代码实现其实是通过上一节那些源码文件中的条件编译),整体的配置大致可以分为三类:
- INCLUDE 开头:一般是"INCLUDE_函数名",函数的使能,1 表示可用,0 表示禁用。
- config 开头:FreeRTOS 的一些功能配置,比如基本配置、内存配置、钩子配置、中断配置等。
- 其他配置:PendSV 宏定义、SVC 宏定义。
FreeRTOSConfig.h 中配置如下:
c
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H
/*-----------------------------------------------------------
* 应用程序特定的定义。
*
* 这些定义应该根据您的硬件和应用程序需求进行调整。
*
* 这些参数在FreeRTOS API文档的"配置"部分中有详细描述。
*
* 请参阅:http://www.freertos.org/a00110.html
*----------------------------------------------------------*/
#define configUSE_PREEMPTION 1 /* 启用抢占式调度 */
#define configUSE_IDLE_HOOK 0 /* 禁用空闲钩子 */
#define configUSE_TICK_HOOK 0 /* 禁用滴答钩子 */
#define configCPU_CLOCK_HZ ( ( unsigned long ) 72000000 ) /* CPU时钟频率,72 MHz */
#define configTICK_RATE_HZ ( ( TickType_t ) 1000 ) /* 时钟节拍频率,每秒1000次 */
#define configMAX_PRIORITIES ( 5 ) /* 最大优先级数 */
#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 128 ) /* 最小栈大小 */
#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 17 * 1024 ) ) /* 堆大小,17KB */
#define configMAX_TASK_NAME_LEN ( 16 ) /* 任务名称最大长度 */
#define configUSE_TRACE_FACILITY 0 /* 禁用追踪功能 */
#define configUSE_16_BIT_TICKS 0 /* 禁用16位滴答 */
#define configIDLE_SHOULD_YIELD 1 /* 空闲任务应让出CPU */
/* 设置以下定义为1以包含API函数,或设置为0以排除API函数 */
#define INCLUDE_vTaskPrioritySet 1 /* 启用设置任务优先级的API */
#define INCLUDE_uxTaskPriorityGet 1 /* 启用获取任务优先级的API */
#define INCLUDE_vTaskDelete 1 /* 启用删除任务的API */
#define INCLUDE_vTaskCleanUpResources 0 /* 禁用清理任务资源的API */
#define INCLUDE_vTaskSuspend 1 /* 启用挂起任务的API */
#define INCLUDE_vTaskDelayUntil 1 /* 启用延迟直到的API */
#define INCLUDE_vTaskDelay 1 /* 启用延迟的API */
/* 这是Cortex-M3 NVIC的原始值。值的范围为255(最低)到0(最高)。 */
#define configKERNEL_INTERRUPT_PRIORITY 255 /* 内核中断优先级 */
/* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY 不能设置为零 !!!!
请参阅 http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html。 */
#define configMAX_SYSCALL_INTERRUPT_PRIORITY 191 /* 等价于0xb0,或优先级11 */
/* 这是根据ST库使用的值,允许16个优先级值,0到15。
这必须与configKERNEL_INTERRUPT_PRIORITY设置相匹配。
此处15对应最低的NVIC值255。 */
#define configLIBRARY_KERNEL_INTERRUPT_PRIORITY 15 /* 库内核中断优先级 */
/* 由于 stm32 和 FreeRTOS 对变量命名的不同,我们进行宏定义 */
#define xPortPendSVHandler PendSV_Handler
#define vPortSVCHandler SVC_Handler
/* 开启任务调度器API,允许查询 FreeRTOS 调度器的状态 */
#define INCLUDE_xTaskGetSchedulerState 1
#endif /* FREERTOS_CONFIG_H */
2.3 在SysTick_Handler函数中添加
由于 stm32f1xx_it.c 中有 PendSV_Handler 和 SVC_Handler 函数,我们上面进行的宏定义会使发生重复定义,所以删去 stm32f1xx_it.c 中这俩个同名函数。并且在该文件下的 SysTick_Handler 函数体中添加内容:
c
if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
{
xPortSysTickHandler();
}
if 语句确保 FreeRTOS 的 SysTick 仅在调度器启动后才执行,避免在调度器未启动时调用 xPortSysTickHandler()
造成错误。xPortSysTickHandler() 函数是 FreeRTOS 系统滴答(SysTick)中断处理函数,用于 管理任务调度、时间片轮转、任务延时、时间基准 等功能。
xPortSysTickHandler() 主要功能:
递增系统滴答计数(
xTickCount
)
- FreeRTOS 使用 滴答计数 作为时间基准,每次
SysTick
触发时都会增加xTickCount
。xTickCount
用于任务调度、定时器、任务延时(vTaskDelay()
)。检查任务延时是否结束
- 如果某个任务调用了
vTaskDelay()
或vTaskDelayUntil()
,它会进入 阻塞状态 ,直到xTickCount
递增到设定的时间。xPortSysTickHandler()
负责检查 哪些任务的延时时间到了,并将其移动到 就绪 状态。时间片轮转(任务切换)
- FreeRTOS 采用 抢占式调度 ,多个任务可以 时间片轮转。
xPortSysTickHandler()
负责检查 是否需要任务切换,如果当前任务的时间片到了,就触发任务切换。触发上下文切换(PendSV 中断)
如果
xPortSysTickHandler()
发现 需要切换任务 ,它会 触发 PendSV 进行任务切换
PendSV_Handler()
会完成实际的任务切换 (保存旧任务上下文,恢复新任务上下文)。
3.FreeRTOS 数据类型与命名规范笔记
3.1 FreeRTOS 数据类型
3.1.1 TickType_t(系统滴答计数类型)
- 作用:用于记录系统的滴答计数(Tick Count)。
- 定义:
configUSE_16_BIT_TICKS = 1
→ 16 位无符号类型(最大 65,535 Tick)。configUSE_16_BIT_TICKS = 0
→ 32 位无符号类型(默认,最大 4,294,967,295 Tick)。
3.1.2 BaseType_t(基本类型)
- 作用 :最适合 CPU 架构的 有符号整数,用于 API 返回值、状态标志等。
- 定义:
- 32 位架构 →
int32_t
- 16 位架构 →
int16_t
- 32 位架构 →
3.1.3 UBaseType_t(无符号基本类型)
- 作用 :
BaseType_t
的无符号版本,常用于计数、索引等。 - 定义:
- 32 位架构 →
uint32_t
- 16 位架构 →
uint16_t
- 32 位架构 →
3.1.4 StackType_t(堆栈数据类型)
- 作用:存储任务堆栈的数据类型,适应不同架构的栈结构。
- 定义:
- 32 位架构 →
uint32_t
- 16 位架构 →
uint16_t
- 32 位架构 →
3.2 FreeRTOS 命名规范
3.2.1 变量命名
- 驼峰式命名,使用完整单词。
- 前缀规则:
ul
→uint32_t
(unsigned long)us
→uint16_t
(unsigned short)uc
→uint8_t
(unsigned char)x
→ 非标准整数类型(如BaseType_t
、TickType_t
)ux
→UBaseType_t
(无符号BaseType_t
)e
→ 枚举类型p
→ 指针(如pus
表示uint16_t *
)c
→unsigned char
(仅存 ASCII)pc
→char *
(仅存 ASCII 字符串)
3.2.2 函数命名
- 驼峰式命名,使用完整单词。
- 前缀规则:
prv
→ 静态私有函数v
→ 返回void
- API 以文件名为前缀,如 vTaskDelay():
v
→void
Task
→ 来自task.c
Delay
→ 该函数用于任务延时
3.2.3 宏命名
- 全部大写,使用下划线分隔。
- 前缀规则:
config
→FreeRTOSConfig.h
配置项(如configUSE_PREEMPTION
)。- 其他文件前缀为小写。
4. FreeRTOS 的任务创建和删除
4.1 任务创建和删除的 API 函数以及 TCB 介绍
任务的创建和删除本质就是调用 FreeRTOS 的 API 函数,主要如下:
API 函数 | 描述 |
---|---|
xTaskCreate() |
动态方式创建任务 |
xTaskCreateStatic() |
静态方式创建任务 |
vTaskDelete() |
删除任务 |
-
动态创建任务:
- 任务的 任务控制块(TCB) 及 任务栈空间 由 FreeRTOS 管理的堆 中分配。
-
静态创建任务:
- 任务的 任务控制块(TCB) 及 任务栈空间 由 用户手动分配,不使用 FreeRTOS 的堆。
-
删除任务:
- 通过输入 任务控制块(TCB) 来指导删除任务,输入 NULL 删除任务自身
任务控制块 tskTaskControlBlock(TCB) 介绍:
每个任务都对应一个TCB,用于存储 每个任务的状态信息,类似身份证
调度器通过
tskTaskControlBlock
管理任务状态(就绪、运行、挂起、阻塞等)任务切换时,内核保存并恢复
tskTaskControlBlock
中的栈指针,确保任务运行状态正确
ctypedef struct tskTaskControlBlock { volatile StackType_t * pxTopOfStack; /* 任务栈栈顶,必须为 TCB 的第一个 成员 */ ListItem_t xStateListItem; /* 任务状态列表项 */ ListItem_t xEventListItem; /* 任务事件列表项 */ UBaseType_t uxPriority; /* 任务优先级,数值越大, 优先级越大 */ StackType_t * pxStack; /* 任务栈起始地址 */ char pcTaskName[ configMAX_TASK_NAME_LEN ]; /* 任务名字 */ ...省略很多条件编译的成员 } tskTCB;
4.2 动态创建任务函数
4.2.1 动态创建任务原函数介绍
我们可以在 task.h 文件中找到动态任务创建函数说明如下 :

configSUPPORT_DYNAMIC_ALLOCATION 宏默认为 1(默认开启).

4.2.2 动态创建任务步骤
开启宏:#define configSUPPORT_DYNAMIC_ALLOCATION 1 (默认开启)
定义函数入口参数 -> 编写任务函数 -> 此函数创建的任务会立刻进入就绪态,由任务调度器调度运行
实验目标:
- start_task:用来创建其他的三个任务。
- task1:实现 LED1 每 1000ms 闪烁一次。
- task2:实现 LED2 每 100ms 闪烁一次。
- task3:判断按键 KEY1 是否按下,按下则删掉 task2。
1)任务设置
c
/* 启动任务配置 */
#define TASK_START_STACK_SIZE 128
#define TASK_START_PRIORITY 1
TaskHandle_t TaskStart_Handler;
void task_start(void *pvParameters);
/* 任务1配置 */
#define TASK1_STACK_SIZE 128
#define TASK1_PRIORITY 2
TaskHandle_t Task1_Handler;
void task1(void *pvParameters);
/* 任务2配置 */
#define TASK2_STACK_SIZE 128
#define TASK2_PRIORITY 3
TaskHandle_t Task2_Handler;
void task2(void *pvParameters);
/* 任务3配置 */
#define TASK3_STACK_SIZE 128
#define TASK3_PRIORITY 4
TaskHandle_t Task3_Handler;
void task3(void *pvParameters);
- 入口函数
c
/**
* @brief : FreeRTOS 入口函数:创建任务函数并开始调度
*
*/
void FreeRTOS_start()
{
xTaskCreate((TaskFunction_t)task_start, // 指向任务函数的指针
(char *)"task_start", // 任务名称
(configSTACK_DEPTH_TYPE)TASK_START_STACK_SIZE, // 任务堆栈大小
(void *)NULL, // 传递给任务函数的参数
(UBaseType_t)TASK_START_PRIORITY, // 任务优先级
(TaskHandle_t *)&TaskStart_Handler); // 任务句柄
/* 启动任务调度器:自动创建空闲任务 */
vTaskStartScheduler();
}
- 启动任务函数
c
/**
* @brief : 启动任务函数,创建三个任务,并删除自己
*
* @param pvParameters
*/
void task_start(void *pvParameters)
{
/* 进入临界区:保护临界区里的代码不会被打断 */
taskENTER_CRITICAL();
/* 创建三个任务 */
xTaskCreate((TaskFunction_t)task1,
(char *)"task1",
(configSTACK_DEPTH_TYPE)TASK1_STACK_SIZE,
(void *)NULL,
(UBaseType_t)TASK1_PRIORITY,
(TaskHandle_t *)&Task1_Handler);
xTaskCreate((TaskFunction_t)task2,
(char *)"task2",
(configSTACK_DEPTH_TYPE)TASK2_STACK_SIZE,
(void *)NULL,
(UBaseType_t)TASK2_PRIORITY,
(TaskHandle_t *)&Task2_Handler);
xTaskCreate((TaskFunction_t)task3,
(char *)"task3",
(configSTACK_DEPTH_TYPE)TASK3_STACK_SIZE,
(void *)NULL,
(UBaseType_t)TASK3_PRIORITY,
(TaskHandle_t *)&Task3_Handler);
/* 启动任务只需要执行一次即可,用完就删除自己 */
vTaskDelete(NULL);
/* 退出临界区 */
taskEXIT_CRITICAL();
}
4 )三个任务
c
/**
* @brief : 任务1函数,每隔1s翻转一次LED
*
* @param pvParameters
*/
void task1(void *pvParameters)
{
while (1)
{
led[0].state = !led[0].state;
printf("task1 runing...\r\n");
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, (GPIO_PinState)led[0].state);
vTaskDelay(1000);
}
}
/**
* @brief : 任务2函数,每隔100ms翻转一次LED
*
* @param pvParameters
*/
void task2(void *pvParameters)
{
while (1)
{
led[1].state = !led[1].state;
printf("task2 runing...\r\n");
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, (GPIO_PinState)led[1].state);
vTaskDelay(100);
}
}
/**
* @brief : 任务3函数,每隔100ms检测一次按键
*
* @param pvParameters
*/
void task3(void *pvParameters)
{
while (1)
{
for (int i = 0; i < 16; i++)
{
if (key[i].flag == 1)
{
if (i == 0)
{
printf("task2 delete...\r\n");
vTaskDelete(Task2_Handler);
Task2_Handler = NULL;
}
sprintf((char *)text, " key%d pressed ", i + 1);
OLED_ShowString(1, 0, (char *)text);
key[i].flag = 0;
}
}
vTaskDelay(100);
}
}
在 task.h 中有 typedef struct tskTaskControlBlock * TaskHandle_t;,所以
Task1_Handler
实际上是tskTaskControlBlock
的指针(TaskHandle_t
)
动态创建任务函数内部实现:
- 申请堆栈内存&任务控制块内存。
- TCB 结构体成员赋值。
- 添加新任务到就绪列表中。
4.3 静态创建任务
4.3.1 静态创建任务原函数介绍
c
TaskHandle_t xTaskCreateStatic(TaskFunction_t pxTaskCode, // 任务函数指针
const char *const pcName, // 任务名称
const uint32_t ulStackDepth, // 任务栈大小
void *const pvParameters, // 传递给任务函数的参数
UBaseType_t uxPriority, // 任务优先级
StackType_t *const puxStackBuffer, // 任务栈指针(用户手动分配)
StaticTask_t *const pxTaskBuffer); // 任务控制块(TCB)指针(用户手动分配)
4.3.2 静态创建任务步骤
- #define configSUPPORT_STATIC_ALLOCATION 1 /* 启用静态创建任务 */
- 当启用静态创建任务时,默认要求你提供 空闲任务(Idle Task) 的内存,所以需要对空闲任务进行配置
示例:动态创建任务改为静态创建任务,只要对任务栈和 TCB 进行手动分配即可
c
/* 启动任务配置 */
#define TASK_START_STACK_SIZE 128
#define TASK_START_PRIORITY 1
TaskHandle_t Task_start_Handle; // 如果后续不需要操作这个任务(删除,挂起等),可以省略这句
StackType_t start_task_stack[TASK_START_STACK_SIZE];
StaticTask_t start_task_tcb;
void task_start(void *pvParameters);
/**
* @brief : FreeRTOS 入口函数:创建任务函数并开始调度
*
*/
void FreeRTOS_start()
{
Task_start_Handle = xTaskCreateStatic((TaskFunction_t)task_start, // 任务函数指针
(char *)"task_start", // 任务名称
(configSTACK_DEPTH_TYPE)TASK_START_STACK_SIZE, // 任务栈大小
(void *)NULL, // 传递给任务函数的参数
(UBaseType_t)TASK_START_PRIORITY, // 任务优先级
(StackType_t *)start_task_stack, // 任务栈指针(用户手动分配)
(StaticTask_t *)&start_task_tcb); // 任务控制块(TCB)指针(用户手动分配)
/* 启动任务调度器:自动创建空闲任务 */
vTaskStartScheduler();
}
对空闲任务进行内存分配
c
/* 空闲任务配置 */
#define IDLE_TASK_STACK_SIZE configMINIMAL_STACK_SIZE
StaticTask_t idle_task_tcb;
StackType_t idle_task_stack[IDLE_TASK_STACK_SIZE];
/* 空闲任务内存申请函数 */
void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer,
StackType_t **ppxIdleTaskStackBuffer,
uint32_t *pulIdleTaskStackSize)
{
*ppxIdleTaskTCBBuffer = &idle_task_tcb;
*ppxIdleTaskStackBuffer = idle_task_stack;
*pulIdleTaskStackSize = IDLE_TASK_STACK_SIZE;
}
这就完成了一个任务的静态创建。