FreeRTOS 源码结构解析与 STM32 HAL 库移植实践(任务创建、删除篇)

1. FreeRTOS源码结构介绍

1.1 下载源码

​ 点击官网地址,选择 FreeRTOS 202212.01非 LTS 版本(非长期支持版),因为这个版本有着最全的历程和更多型号处理器支持。

1.2 文件夹结构介绍

​ 下载后主文件 FreeRTOSv202212.01 下包含以下文件:

名称描述
FreeRTOSFreeRTOS 的核心源码,提供实时操作系统的**所有必要组件****,如任务调度、内存管理、信号量、队列等。
FreeRTOS-PlusFreeRTOS 的附加组件(非必要),如文件系统、TCP/IP 协议栈、图形界面库等。 提供了一些额外的功能,可以扩展应用场景。
tools这个文件夹通常包含一些辅助工具,用于支持 FreeRTOS 的开发,比如配置生成工具、调试工具、自动化脚本等。
其他文件一些网页链接和历史版本记录,项目以来关系,编译格式文件

1.2 FreeRTOS文件夹

​ 由于上面文件中仅有 FreeRTOS 是建立最小 FreeRTOS 例程的必要文件夹,因此对该文件下内容进一步做说明(在该文件下的 README.md 文件有官方的英文说明):

名称描述
DemoFreeRTOS 演示例程,支持多种芯片架构、多种型号芯片
SourceFreeRTOS 源码,最重要的文件夹(包含着移植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
名称描述
includeFreeRTOS 的公共头文件,定义了 FreeRTOS 的 API 和常用的宏、数据结构、常量等。
portableFreeRTOS 为不同硬件平台(微控制器、处理器架构等)提供适配层的地方。每个目标硬件平台(如 ARM Cortex-M、AVR、RISC-V 等)都会有一个专门的适配代码,用于处理硬件相关的操作,例如上下文切换、时钟中断、任务堆栈管理等。
croutine协程相关文件
event_groups事件相关文件
list提供与链表相关的功能实现。FreeRTOS 中许多数据结构(如任务控制块、定时器等)都使用链表来管理。
queue提供队列功能的实现,任务之间可以通过队列进行通信和数据交换。
stream_buffer流式缓冲区相关文件
taskFreeRTOS 的任务调度和任务管理的核心实现。
timersFreeRTOS 定时器管理的实现,定时器可用于执行周期性任务或超时任务。

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 添加必要的源码文件

  1. G:\FreeRTOSv202212.01\FreeRTOS\Source\include (整个文件夹)
  2. G:\FreevRTOSv202212.01\FreeRTOS\Source\croutine.c
  3. G:\FreeRTOSv202212.01\FreeRTOS\Source\event_groups.c
  4. G:\FreeRTOSv202212.01\FreeRTOS\Source\list.c
  5. G:\FreeRTOSv202212.01\FreeRTOS\Source\queue.c
  6. G:\FreeRTOSv202212.01\FreeRTOS\Source\stream_buffer.c
  7. G:\FreeRTOSv202212.01\FreeRTOS\Source\tasks.c
  8. 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 中配置如下:

#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 函数体中添加内容:

if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
{
    xPortSysTickHandler();
}

​ if 语句确保 FreeRTOS 的 SysTick 仅在调度器启动后才执行,避免在调度器未启动时调用 xPortSysTickHandler() 造成错误。xPortSysTickHandler() 函数是 FreeRTOS 系统滴答(SysTick)中断处理函数,用于 管理任务调度、时间片轮转、任务延时、时间基准 等功能。

xPortSysTickHandler() 主要功能

  1. 递增系统滴答计数(xTickCount

    • FreeRTOS 使用 滴答计数 作为时间基准,每次 SysTick 触发时都会增加 xTickCount
    • xTickCount 用于任务调度、定时器、任务延时(vTaskDelay())。
  2. 检查任务延时是否结束

    • 如果某个任务调用了 vTaskDelay()vTaskDelayUntil(),它会进入 阻塞状态,直到 xTickCount 递增到设定的时间。
    • xPortSysTickHandler() 负责检查 哪些任务的延时时间到了,并将其移动到 就绪 状态。
  3. 时间片轮转(任务切换)

    • FreeRTOS 采用 抢占式调度,多个任务可以 时间片轮转
    • xPortSysTickHandler() 负责检查 是否需要任务切换,如果当前任务的时间片到了,就触发任务切换。
  4. 触发上下文切换(PendSV 中断)

    • 如果 xPortSysTickHandler() 发现 需要切换任务,它会 触发 PendSV 进行任务切换

    • PendSV_Handler() 会完成实际的任务切换 (保存旧任务上下文,恢复新任务上下文)


3.FreeRTOS 数据类型与命名规范笔记

3.1 FreeRTOS 数据类型

3.1.1 TickType_t(系统滴答计数类型)
  • 作用:用于记录系统的滴答计数(Tick Count)。
  • 定义:
    • configUSE_16_BIT_TICKS = 116 位无符号类型(最大 65,535 Tick)。
    • configUSE_16_BIT_TICKS = 032 位无符号类型(默认,最大 4,294,967,295 Tick)。
3.1.2 BaseType_t(基本类型)
  • 作用:最适合 CPU 架构的 有符号整数,用于 API 返回值、状态标志等。
  • 定义:
    • 32 位架构 → int32_t
    • 16 位架构 → int16_t
3.1.3 UBaseType_t(无符号基本类型)
  • 作用BaseType_t 的无符号版本,常用于计数、索引等。
  • 定义:
    • 32 位架构 → uint32_t
    • 16 位架构 → uint16_t
3.1.4 StackType_t(堆栈数据类型)
  • 作用:存储任务堆栈的数据类型,适应不同架构的栈结构。
  • 定义:
    • 32 位架构 → uint32_t
    • 16 位架构 → uint16_t

3.2 FreeRTOS 命名规范

3.2.1 变量命名
  • 驼峰式命名,使用完整单词
  • 前缀规则:
    • uluint32_t(unsigned long)
    • usuint16_t(unsigned short)
    • ucuint8_t(unsigned char)
    • x → 非标准整数类型(如 BaseType_tTickType_t
    • uxUBaseType_t(无符号 BaseType_t
    • e → 枚举类型
    • p → 指针(如 pus 表示 uint16_t *
    • cunsigned char(仅存 ASCII)
    • pcchar *(仅存 ASCII 字符串)
3.2.2 函数命名
  • 驼峰式命名,使用完整单词
  • 前缀规则:
    • prv → 静态私有函数
    • v → 返回 void
    • API 以文件名为前缀,如 vTaskDelay():
      • vvoid
      • Task → 来自 task.c
      • Delay → 该函数用于任务延时
3.2.3 宏命名
  • 全部大写,使用下划线分隔
  • 前缀规则:
    • configFreeRTOSConfig.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 中的栈指针,确保任务运行状态正确

    typedef 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)任务设置

/* 启动任务配置 */
#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);
  1. 入口函数
/**
 * @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();
}
  1. 启动任务函数
/**
 * @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 )三个任务

/**
 * @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 静态创建任务原函数介绍
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 进行手动分配即可

/* 启动任务配置 */
#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();
}
对空闲任务进行内存分配
/* 空闲任务配置 */
#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;
}

这就完成了一个任务的静态创建。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/981468.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何评估所选择的PHP后端框架的性能?

大家在选择PHP后端框架的时候&#xff0c;如果想评估其性能如何&#xff0c;能不能扛得住你的项目&#xff1f;可以根据以下几点进行分析&#xff0c;帮助大家选择到更符合自己心目中的PHP后端框架。 1. 基准测试 基准测试是评估框架性能的基础方法&#xff0c;主要通过模拟高…

家政预约小程序用例图分析

在和客户进行需求沟通的时候&#xff0c;除了使用常规的问答的形式&#xff0c;我还使用图形化工具更深入的沟通。比如借助UML的用例图来开展系统分析&#xff0c;并且按照角色详细拆解了家政预约小程序的各个用例。在分析阶段思考的越多&#xff0c;沟通的越多&#xff0c;在系…

Java里的ArrayList和LinkedList有什么区别?

大家好&#xff0c;我是锋哥。今天分享关于【Java里的ArrayList和LinkedList有什么区别&#xff1f;】面试题。希望对大家有帮助&#xff1b; Java里的ArrayList和LinkedList有什么区别&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 ArrayList 和 Lin…

【JavaSE-3】运算符

1、什么是运算符 就是对常量或者变量进行操作的符号&#xff0c;如&#xff1a;&#xff0c;-&#xff0c;*&#xff0c;/ 表达式&#xff1a; 用运算符把常量或者变量连接起来的&#xff0c;符合java语法的式子就是表达式。 2、 算术运算符 2.1、基本四则运算符 - * / % 都…

服务器配置-从0到分析4:ssh免密登入

该部分涉及到公钥、私钥等部分knowledge&#xff0c;本人仅作尝试 若将本地机器 SSH Key 的公钥放到远程主机&#xff0c;就能无需密码直接远程登录远程主机 1&#xff0c;在客户端生成 ssh 公私钥&#xff1a; 也就是我们本地机器&#xff0c;windows电脑 一路回车即可&am…

端到端自动驾驶——cnn网络搭建

论文参考&#xff1a;https://arxiv.org/abs/1604.07316 demo 今天主要来看一个如何通过图像直接到控制的自动驾驶端到端的项目&#xff0c;首先需要配置好我的仿真环境&#xff0c;下载软件udacity&#xff1a; https://d17h27t6h515a5.cloudfront.net/topher/2016/November…

SQL-labs13-16闯关记录

http://127.0.0.1/sqli-labs/less-13/ 基于POST单引号双注入变形 1&#xff0c;依然是一个登录框&#xff0c;POST型SQL注入 2&#xff0c;挂上burpsuite&#xff0c;然后抓取请求&#xff0c;构造请求判断漏洞类型和闭合条件 admin 发生了报错&#xff0c;根据提示闭合方式是(…

2025年渗透测试面试题总结- 阿某云安全实习(题目+回答)

网络安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 阿里云安全实习 一、代码审计经验与思路 二、越权漏洞原理与审计要点 三、SSRF漏洞解析与防御 四、教…

Zookeeper 及 基于ZooKeeper实现的分布式锁

1 ZooKeeper 1.1 ZooKeeper 介绍 ZooKeeper是一个开源的分布式协调服务&#xff0c;它的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来&#xff0c;构成一个高效可靠的原语集&#xff0c;并以一系列简单易用的接口提供给用户使用。 原语&#xff1a;操作系统或…

开放鸿蒙OpenHarmony 5.0.0 Release 兼容性测试实战经验分享

OpenHarmony 5.0版本的发布时间是2024年12月20日至21日。这个版本带来了许多新特性和改进。现在5.0出了两个release 版本&#xff0c;分别是5.0.0和5.0.1。 就在5.0版本发布不到2周的时间内&#xff0c;2025年01月01日起&#xff0c;不支持新产品基于老分支&#xff08;OpenHar…

DeepSeek开源周Day6:DeepSeek V3、R1 推理系统深度解析,技术突破与行业启示

DeepSeek 在开源周第六天再次发文&#xff0c;中文原文、官方号在知乎 DeepSeek - 知乎DeepSeek-V3 / R1 推理系统概览 - 知乎deepseek-ai/open-infra-index: Production-tested AI infrastructure tools for efficient AGI development and community-driven innovation 引言 …

springBoot集成emqx 实现mqtt消息的发送订阅

介绍 我们可以想象这么一个场景&#xff0c;我们java应用想要采集到电表a的每小时的用电信息&#xff0c;我们怎么拿到电表的数据&#xff1f;一般我们会想 直接 java 后台发送请求给电表&#xff0c;然后让电表返回数据就可以了&#xff0c;事实上&#xff0c;我们java应用发…

Docker安装milvus及其基本使用说明

简介 Milvus 是一款开源的高性能、高可用的向量数据库&#xff0c;专为大规模机器学习和深度学习应用设计&#xff0c;旨在高效管理和检索高维向量数据。随着AI技术的飞速发展&#xff0c;向量数据库在图像识别、语音识别、自然语言处理、推荐系统等领域扮演着越来越重要的角色…

ssm_mysql_小型企业人事管理系统

收藏关注不迷路&#xff01;&#xff01; &#x1f31f;文末获取源码数据库&#x1f31f; 感兴趣的可以先收藏起来&#xff0c;还有大家在毕设选题&#xff08;免费咨询指导选题&#xff09;&#xff0c;项目以及论文编写等相关问题都可以给我留言咨询&#xff0c;希望帮助更多…

25年第四本【认知觉醒】

《认知觉醒》&#xff1a;一场与大脑的深度谈判 在信息爆炸的焦虑时代&#xff0c;我们像被抛入湍流的溺水者&#xff0c;拼命抓取各种自我提升的浮木&#xff0c;却在知识的漩涡中越陷越深。这不是一本简单的成功学指南&#xff0c;而是一场关于人类认知系统的深度对话&#…

盘古信息携手艾罗能源启动IMS数字化智能制造工厂项目,共筑新能源行业数字化标杆

在政策扶持下成长的新能源行业&#xff0c;如今已逐步进入商业化阶段。相比传统制造行业&#xff0c;新能源行业离散度高、自动化程度高。 面对迅疾的市场变化&#xff0c;在大环境中一枝独秀的新能源行业&#xff0c;亟需突破传统架构的限制&#xff0c;通过构建智能化生产体…

32.C++二叉树进阶1(二叉搜索树)

⭐上篇文章&#xff1a;31.C多态4&#xff08;静态多态&#xff0c;动态多态&#xff0c;虚函数表的存储位置&#xff09;-CSDN博客 ⭐本篇代码&#xff1a;c学习/18.二叉树进阶-二叉搜索树 橘子真甜/c-learning-of-yzc - 码云 - 开源中国 (gitee.com) ⭐标⭐是比较重要的部分…

图论基础算法: 二分图的判定(C++)

二分图的基本概念 什么是二分图? 二分图(Bipartite Graph)是指一个图的顶点集可以被分割为两个互不相交的子集 U U U 和 V V V, 并且图中的每一条边都连接 U U U 中的一个顶点和 V V V 中的一个顶点. 换句话说, 二分图中的顶点可以被分成两组, 组内的顶点之间没有边相连…

pyside6学习专栏(九):在PySide6中使用PySide6.QtCharts绘制6种不同的图表的示例代码

PySide6的QtCharts类支持绘制各种型状的图表&#xff0c;如面积区域图、饼状图、折线图、直方图、线条曲线图、离散点图等&#xff0c;下面的代码是采用示例数据绘制这6种图表的示例代码,并可实现动画显示效果&#xff0c;实际使用时参照代码中示例数据的格式将实际数据替换即可…

爬虫逆向实战小记——解决webpack实记

注意&#xff01;&#xff01;&#xff01;&#xff01;某XX网站实例仅作为学习案例&#xff0c;禁止其他个人以及团体做谋利用途&#xff01;&#xff01;&#xff01; aHR0cHM6Ly9wbW9zLnhqLnNnY2MuY29tLmNuOjIwMDgwL3B4Zi1zZXR0bGVtZW50LW91dG5ldHB1Yi8jL3B4Zi1zZXR0bGVtZW5…