STM32-FreeRTOS快速入门指南(上)

第一章 FreeRTOS系统配置

1. FreeRTOSConfig.h文件

针对 FreeRTOSConfig.h 文件,在 FreeRTOS 官方的在线文档中有详细的说明,网址为: https://www.freertos.org/a00110.html

FreeRTOS 使用 FreeRTOSConfig.h 文件进行配置和裁剪。 FreeRTOSConfig.h 文件中有几十个配置项,这使得用户能够很好地配置和裁剪 FreeRTOS。

FreeRTOSConfig.h 文件中的配置项可分为三大类:"config"配置项、"INCLUDE"配置项和其他配置项,下面就为读者详细地讲解这三类配置项。

2. "config"配置项

"config"配置项按照配置的功能分类,可分为十类,分别为基础配置项、内存分配相关定义、钩子函数相关定义、运行时间和任务状态统计相关定义、协程相关定义、软件定时器相关定义、中断嵌套行为配置、断言、 FreeRTOS MPU 特殊定义和 ARMv8-M 安全侧端口相关定义。下面就分别介绍这写"config"配置项。

2.1 基础配置项

  1. configUSE_PREEMPTION

此宏用于设置系统的调度方式。 当宏 configUSE_PREEMPTION 设置为 1 时,系统使用抢占式调度; 当宏 configUSE_PREEMPTION 设置为 0 时,系统使用协程式调度。抢占式调度和协程式调度的区别在于, 协程式调度是正在运行的任务主动释放 CPU 后才能切换到下一个任务,任务切换的时机完全取决于正在运行的任务。 协程式的优点在于可以节省开销,但是功能比较有限,现在的 MCU 性能都比较强大,建议使用抢占式调度。

  1. configUSE_PORT_OPTIMISED_TASK_SELECTION

FreeRTOS 支持两种方法来选择下一个要执行的任务,分别为通用方法和特殊方法。

当宏 configUSE_PORT_OPTIMISED_TASK_SELECTION 设置为 0 时,使用通用方法。 通用方法是完全使用 C 实现的软件算法,因此支持所用硬件,并且不限制任务优先级的最大值,但效率相较于特殊方法低。

当宏 configUSE_PORT_OPTIMISED_TASK_SELECTION 设置为 1 时,使用特殊方法。特殊方法的效率相较于通用方法高,但是特殊方法依赖于一个或多个特定架构的汇编指令(一般是类似计算前导零[CLZ]的指令),因此特殊方法并不支持所有硬件,并且对任务优先级的最大值一般也有限制,通常为 32。

  1. configUSE_TICKLESS_IDLE

当宏 configUSE_TICKLESS_IDLE 设置为 1 时,使能 tickless 低功耗模式;设置为 0 时, tick 中断则会移植运行。 tickless 低功耗模式并不适用于所有硬件。

  1. configCPU_CLOCK_HZ

此宏应设置为 CPU 的内核时钟频率,单位为 Hz。

  1. configSYSTICK_CLOCK_HZ

此宏应设置为 SysTick 的时钟频率,当 SysTick 的时钟源频率与内核时钟频率不同时才可以定义,单位为 Hz。

  1. configTICK_RATE_HZ

此宏用于设置 FreeRTOS 系统节拍的中断频率,单位为 Hz。

  1. configMAX_PRIORITIES

此 宏 用 于 定 义 系 统 支 持 的 最 大 任 务 优 先 级 数 量 , 最 大 任 务 优 先 级 数 值 为configMAX_PRIORITIES-1。

  1. configMINIMAL_STACK_SIZE

此宏用于设置空闲任务的栈空间大小,单位为 word。

  1. configMAX_TASK_NAME_LEN

此宏用于设置任务名的最大字符数。

  1. configUSE_16_BIT_TICKS

此宏用于定义系统节拍计数器的数据类型, 当宏 configUSE_16_BIT_TICKS 设置为 1 时,系统节拍计数器的数据类型为 16 位无符号整形; 当宏 configUSE_16_BIT_TICKS 设置为 0 时,系统节拍计数器的数据类型为 32 为无符号整型。

  1. configIDLE_SHOULD_YIELD

当宏 configIDLE_SHOULD_YIELD 设置为 1 时,在抢占调度下,同等优先级的任务可抢占空闲任务,并延用空闲任务剩余的时间片。

  1. configUSE_TASK_NOTIFICATIONS

当宏 configUSE_TASK_NOTIFICATIONS 设置为 1 时,开启任务通知功能。 当开启任务通知功能后,每个任务将多占用 8 字节的内存空间。

  1. configTASK_NOTIFICATION_ARRAY_ENTRIES

此宏用于定义任务通知数组的大小。

  1. configUSE_MUTEXES

此宏用于使能互斥信号量, 当宏 configUSE_MUTEXS 设置为 1 时,使能互斥信号量; 当宏configUSE_MUTEXS 设置为 0 时,则不使能互斥信号量。

  1. configUSE_RECURSIVE_MUTEXES

此宏用于使能递归互斥信号量,当宏 configUSE_RECURSIVE_MUTEXES 设置为 1 时,使能递归互斥信号量;当宏 configUSE_RECURSIVE_MUTEXES 设置为 0 时,则不使能递归互斥信号量。

  1. configUSE_COUNTING_SEMAPHORES

此宏用于使能计数型信号量, 当宏 configUSE_COUNTING_SEMAPHORES 设置为 1 时,使能计数型信号量; 当宏 configUSE_COUNTING_SEMAPHORES 设置为 0 时,则不使能计数型信号量。

  1. configUSE_ALTERNATIVE_API

此宏在 FreeRTOS V9.0.0 之后已弃用。

  1. configQUEUE_REGISTRY_SIZE

此宏用于定义可以注册的队列和信号量的最大数量。此宏定义仅用于调试使用。

  1. configUSE_QUEUE_SETS

此宏用于使能队列集, 当宏 configUSE_QUEUE_SETS 设置为 1 时,使能队列集; 当宏configUSE_QUEUE_SETS 设置为 0 时,则不使能队列集。

  1. configUSE_TIME_SLICING

此宏用于使能时间片调度, 当宏 configUSE_TIMER_SLICING 设置为 1 且使用抢占式调度时,使能时间片调度; 当宏 configUSE_TIMER_SLICING 设置为 0 时,则不使能时间片调度。

  1. configUSE_NEWLIB_REENTRANT

此宏用于为每个任务分配一个 NewLib 重入结构体,当宏configUSE_NEWLIB_REENTRANT 设置为 1 时, FreeRTOS 将为每个创建的任务的任务控制块中分配一个 NewLib 重入结构体。

  1. configENABLE_BACKWARD_COMPATIBILITY

此宏用于兼容 FreeRTOS 老版本的 API 函数。

  1. configNUM_THREAD_LOCAL_STORAGE_POINTERS

此宏用于在任务控制块中分配一个线程本地存储指着数组,当此宏被定义为大于 0 时, configNUM_THREAD_LOCAL_STORAGE_POINTERS 为线程本地存储指针数组的元素个数;当宏 configNUM_THREAD_LOCAL_STORAGE_POINTERS 为 0 时,则禁用线程本地存储指针数组。

  1. configSTACK_DEPTH_TYPE

此宏用于定义任务堆栈深度的数据类型,默认为 uint16_t。

  1. configMESSAGE_BUFFER_LENGTH_TYPE

此宏用于定义消息缓冲区中消息长度的数据类型,默认为 size_t。

2.2 内存分配相关定义

  1. configSUPPORT_STATIC_ALLOCATION

当宏 configSUPPORT_STSTIC_ALLOCATION 设置为 1 时, FreeRTOS 支持使用静态方式管理内存,此宏默认设置为 0。 如果将 configSUPPORT_STATIC_ALLOCATION 设置为 1,用户还 需 要 提 供 两 个 回 调 函 数 : vApplicationGetIdleTaskMemory() 和vApplicationGetTimerTaskMemory()

  1. configSUPPORT_DYNAMIC_ALLOCATION

当宏 configSUPPORT_DYNAMIC_ALLOCATION 设置为 1 时, FreeRTOS 支持使用动态方式管理内存,此宏默认设置为 1。

  1. configTOTAL_HEAP_SIZE

此宏用于定义用于 FreeRTOS 动态内存管理的内存大小,即 FreeRTOS 的内存堆,单位为Byte。

  1. configAPPLICATION_ALLOCATED_HEAP

此宏用于自定义 FreeRTOS 的内存堆, 当宏 configAPPLICATION_ALLOCATED_HEAP 设置为 1 时,用户需要自行创建 FreeRTOS 的内存堆,否则 FreeRTOS 的内存堆将由编译器进行分配。利用此宏定义,可以使用 FreeRTOS 动态管理外扩内存。

  1. configSTACK_ALLOCATION_FROM_SEPARATE_HEAP

此宏用于自定义动态创建和删除任务时,任务栈内存的申请与释放函数pvPortMallocStack()和vPortFreeStack(), 当宏configSTACK_ALLOCATION_FROM_SEPARATE_HEAP 设置为1是,用户需提供 pvPortMallocStack()和 vPortFreeStack()函数。

2.3 钩子函数相关定义

  1. configUSE_IDLE_HOOK

此宏用于使能使用空闲任务钩子函数, 当宏 configUSE_IDLE_HOOK 设置为 1 时,使能使用空闲任务钩子函数,用户需自定义相关钩子函数; 当宏 configUSE_IDLE_HOOK 设置为 0 时,则不使能使用空闲任务钩子函数。

  1. configUSE_TICK_HOOK

此宏用于使能使用系统时钟节拍中断钩子函数, 当宏 configUSE_TICK_HOOK 设置为 1 时,使能使用系统时钟节拍中断钩子函 数,用户需自定义相关钩子函数;当 宏configUSE_TICK_HOOK 设置为 0 时, 则不使能使用系统时钟节拍中断钩子函数。

  1. configCHECK_FOR_STACK_OVERFLOW

此宏用于使能栈溢出检测, 当宏 configCHECK_FOR_STACK_OVERFLOW 设置为 1 时,使用栈溢出检测方法一; 当宏 configCHECK_FOR_STACK_OVERFLOW 设置为 2 时,栈溢出检测方法二; 当宏 configCHECK_FOR_STACK_OVERFLOW 设置为 0 时,不使能栈溢出检测。

  1. configUSE_MALLOC_FAILED_HOOK

此宏用于使能使用动态内存分配失败钩子函数,当宏configUSE_MALLOC_FAILED_HOOK 设置为 1 时,使能使用动态内存分配失败钩子函数,用户需自定义相关钩子函数; 当宏 configUSE_MALLOC_FAILED_HOOK 设置为 0 时,则不使能使用动态内存分配失败钩子函数。

  1. configUSE_DAEMON_TASK_STARTUP_HOOK

此宏用于使能使用定时器服务任务首次执行前的钩子函数,当 宏configUSE_DEAMON_TASK_STARTUP_HOOK 设置为 1 时,使能使用定时器服务任务首次执行前的钩子函数, 此时用户需定义定时器服务任务首次执行的相关钩子函数; 当宏configUSE_DEAMON_TASK_STARTUP_HOOK 设置为 0 时,则不使能使用定时器服务任务首次执行前的钩子函数。

2.4 运行时间和任务状态统计相关定义

  1. configGENERATE_RUN_TIME_STATS

此宏用于使能任务运行时间统计功能, 当宏 configGENERATE_RUN_TIME_STATS 设置为1 时,使能任务运行时间统计功能,此时用户需要提供两个函数,一个是用于配置任务运行时间统计功能的函数 portCONFIGURE_TIMER_FOR_RUN_TIME_STATS(),一般是完成定时器的初始化,另一个函数是 portGET_RUN_TIME_COUNTER_VALUE(),该函数用于获取定时器的计时值; 当宏 configGENERATE_RUN_TIME_STATS 设置为 0 时,则不使能任务运行时间统计功能。

  1. configUSE_TRACE_FACILITY

此宏用于使能可视化跟踪调试, 当宏 configUSE_TRACE_FACILITY 设置为 1 时,使能可视化跟踪调试; 当宏 configUSE_TRACE_FACILITY 设置为 0 时,则不使能可视化跟踪调试。

  1. configUSE_STATS_FORMATTING_FUNCTIONS

当此宏与 configUSE_TRACE_FACILITY 同时设置为 1 时,将编译函数 vTaskList()和函数vTaskGetRunTimeStats(),否则将忽略编译函数 vTaskList()和函数 vTaskGetRunTimeStats()。

2.5 协程相关定义

  1. configUSE_CO_ROUTINES

此宏用于启用协程, 当宏 configUSE_CO_ROUTINES 设置为 1 时,启用协程; 当宏configUSE_CO_ROUTINES 设置为 0 时,则不启用协程。

  1. configMAX_CO_ROUTINE_PRIORITIES

此宏用于设置协程的最大任务优先级数量,协程的最大任务优先级数值为configMAX_CO_ROUTINE_PRIORITIES-1

2.6 软件定时器相关定义

  1. configUSE_TIMERS

此宏用于启用软件定时器功能, 当宏 configUSE_TIMERS 设置为 1 时,启用软件定时器功能; 当宏 configUSE_TIMERS 设置为 0 时,则不启用软件定时器功能。

  1. configTIMER_TASK_PRIORITY

此宏用于设置软件定时器处理任务的优先级,当启用软件定时器功能时,系统会创建一个用于处理软件定时器的软件定时器处理任务。

  1. configTIMER_QUEUE_LENGTH

此宏用于定义软件定时器队列的长度, 软件定时器的开启、停止与销毁等操作都是通过队列实现的。

  1. configTIMER_TASK_STACK_DEPTH

此宏用于设置软件定时器处理任务的栈空间大小,当启用软件定时器功能时,系统会创建一个用于处理软件定时器的软件定时器处理任务。

2.7 中断嵌套行为配置

  1. configPRIO_BITS

此宏应定义为 MCU 的 8 位优先级配置寄存器实际使用的位数。

  1. configLIBRARY_LOWEST_INTERRUPT_PRIORITY

此宏应定义为 MCU 的最低中断优先等级,对于 STM32,在使用 FreeRTOS 时,建议将中断优先级分组设置为组 4,此时中断的最低优先级为 15。 此宏定义用于辅助配置宏configKERNEL_INTERRUPT_PRIORITY。

  1. configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY

此宏定义用于设置 FreeRTOS 可管理中断的最高优先级,当中断的优先级数值小于configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 时,此中断不受 FreeRTOS 管理。此宏定义用于辅助配置宏 configMAX_SYSCALL_INTERRUPT_PRIORITY。

  1. configKERNEL_INTERRUPT_PRIORITY

此宏应定义为 MCU 的最低中断优先等级在中断优先级配置寄存器中的值,对于 STM32,即宏 configLIBRARY_LOWEST_INTERRUPT_PRIORITY 偏移 4bit 的值。

  1. configMAX_SYSCALL_INTERRUPT_PRIORITY

此宏应定义为 FreeRTOS 可管理中断的最高优先等级在中断优先级配置寄存器中的值,对于 STM32,即宏 configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 偏移 4bit 的值。

  1. configMAX_API_CALL_INTERRUPT_PRIORITY

此宏为宏 configMAX_SYSCALL_INTERRUPT_PRIORITY 的新名称,只被用在 FreeRTOS官方一些新的移植当中,此宏与宏 configMAX_SYSCALL_INTERRUPT_PRIORITY 是等价的。

2.8 断言

  1. vAssertCalled(char, int)

此宏用于辅助配置宏 configASSERT( x )以通过串口打印相关信息。

  1. configASSERT( x )

此宏为 FreeRTOS 操作系统中的断言,断言会对表达式 x 进行判断,当 x 为假时,断言失败,表明程序出错,于是使用宏 vAssertCalled(char, int)通过串口打印相关的错误信息。断言常用于检测程序中的错误,使用断言将增加程序的代码大小和执行时间,因此建议在程序调试通过后将宏 configASSERT( x )进行注释,以较少额外的开销。

3. "INCLUDE"配置项

FreeRTOS 使用"INCLUDE"配置项对部分 API 函数进行条件编译,当"INCLUDE"配置项被定义为 1 时,其对应的 API 函数则会加入编译。对于用不到的 API 函数,用户则可以将其对应的"INCLUDE"配置项设置为 0,那么这个 API 函数就不会加入编译,以减少不必要的系统开销。"INCLUDE"配置项与其对应 API 函数的功能描述如下表所示:

4. 其他配置项

  1. ureconfigMAX_SECURE_CONTEXTS

此宏为 ARMv8-M 安全侧端口的相关配置项,本文暂不涉及 ARMv8-M 安全侧端口的相关内容,感兴趣的读者可自行查阅相关资料。

  1. endSVHandler 和 vPortSVCHandler

这两个宏为 PendSV 和 SVC 的中断服务函数,主要用于 FreeRTOS 操作系统的任务切换,有关 FreeRTOS 操作系统中任务切换的相关内容


第二章 FreeRTOS中断管理

1. FreeRTOS中断配置项

  1. configPRIO_BITS

此宏是用于辅助配置的宏,主要用于辅助配置宏 configKERNEL_INTERRUPT_PRIORITY和宏 configMAX_SYSCALL_INTERRUPT_PRIORITY 的, 此宏应定义为 MCU 的 8 位优先级配置寄存器实际使用的位数,因为 STM32 只使用到了中断优先级配置寄存器的高 4 位,因此,此宏应配置为 4。

  1. configLIBRARY_LOWEST_INTERRUPT_PRIORITY

此宏是用于辅助配置宏 configKERNEL_INTERRUPT_PRIORITY 的,此宏应设置为 MCU的最低优先等级,因为 STM32 只使用了中断优先级配置寄存器的高 4 位,因此 MCU 的最低优先等级就是 2^4-1=15,因此,此宏应配置为 15。

  1. configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY

此宏是用于辅助配置宏 configMAX_SYSCALL_INTERRUPT_PRIORITY 的,此宏适用于配置 FreeRTOS 可管理的最高优先级的中断,此功能就是操作 BASEPRI 寄存器来实现的。此宏的值可以根据用户的实际使用场景来决定,本教程的配套例程源码全部将此宏配置为 5,即中断优先级高于 5 的中断不受 FreeRTOS 影响,如下图所示:

  1. configKERNEL_INTERRUPT_PRIORITY

此宏应配置为 MCU 的最低优先级在中断优先级配置寄存器中的值,在 FreeRTOS 的源码中,使用此宏将 SysTick 和 PenSV 的中断优先级设置为最低优先级。 因为 STM32 只使用了中断优先级配置寄存器的高 4 位,因此,此宏应配置为最低中断优先级在中断优先级配置寄存器高 4 位的表示,即(configLIBRARY_LOWEST_INTERRUPT_PRIORITY<<(8-configPRIO_BITS))。

  1. configMAX_SYSCALL_INTERRUPT_PRIORITY

此宏用于配置 FreeRTOS 可管理的最高优先级的中断,在 FreeRTOS 的源码中,使用此宏来打开和关闭中断。 因为 STM32 只使用了中断优先级配置寄存器的高 4 位,因此,此宏应配置为(configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY<<(8-configPRIO_BITS))。

  1. configMAX_API_CALL_INTERRUPT_PRIORITY

此宏为宏 configMAX_SYSCALL_INTERRUPT_PRIORITY 的新名称,只被用在 FreeRTOS官方一些新的移植当中,此宏于宏 configMAX_SYSCALL_INTERRUPT_PRIORITY 是等价的。

2. 临界区

临界区是指那些必须完整运行的区域,在临界区中的代码必须完整运行,不能被打断。例如一些使用软件模拟的通信协议,通信协议在通信时,必须严格按照通信协议的时序进行,不能被打断。 FreeRTOS 在进出临界区的时候,通过关闭和打开受 FreeRTOS 管理的中断,以保护临界区中的代码。 FreeRTOS 的源码中就包含了许多临界区的代码,这部分代码都是用临界区进行保护,用户在使用 FreeRTOS 编写应用程序的时候,也要注意一些不能被打断的操作,并为这部分代码加上临界区进行保护。

对于进出临界区,FreeRTOS 的源码中有四个相关的宏定义,分别为 taskENTER_CRITICAL() 、 taskENTER_CRITICAL_FROM_ISR() 、 taskEXIT_CRITICAL() 、taskEXIT_CRITICAL_FROM_ISR(x), 这四个宏定义分别用于在中断和非中断中进出临界区, 定义代码如下所示:

c 复制代码
/* 进入临界区 */
#define taskENTER_CRITICAL() portENTER_CRITICAL()
#define portENTER_CRITICAL() vPortEnterCritical()
/* 中断中进入临界区 */
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()
/* 退出临界区 */
#define taskEXIT_CRITICAL() portEXIT_CRITICAL()
#define portEXIT_CRITICAL() vPortExitCritical()
/* 中断中退出临界区 */
#define taskEXIT_CRITICAL_FROM_ISR(x) portCLEAR_INTERRUPT_MASK_FROM_ISR(x)
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)

3. FreeRTOS中断测试

3.1 程序流程图

本实验主要用于测试 FreeRTOS 打开和关闭中断对中断的影响,本实验设计了两个任务,这两个任务的功能如下表所示:

本实验的程序流程图,如下图所示:

3.2 FreeRTOS函数解析

3.2.1 portDISABLE_INTERRUPTS()

功能:禁用处理器的全局中断,防止中断服务程序(ISR)打断当前正在执行的代码。

3.2.2 portENABLE_INTERRUPTS()

功能:重新启用处理器的全局中断。

3.2 代码分析

3.2.1 任务参数宏定义
c 复制代码
/*----------------任务函数配置区-----------------*/
// START_TASK配置
#define START_TASK_PRIO 1           // 任务优先级 
#define START_STK_SIZE  128         // 任务堆栈大小 
TaskHandle_t StartTask_Handler;     // 任务句柄 
void start_task(void *pvParameters);// 任务函数 

// TASK1配置
#define TASK1_PRIO      2                   
#define TASK1_STK_SIZE  128                 
TaskHandle_t Task1Task_Handler;          
void task1(void *pvParameters);
/*---------------------------------------------*/
3.2.2 start_task 任务实现
c 复制代码
// start_task函数实现
void start_task(void *pvParameters)
{
    taskENTER_CRITICAL(); // 进入临界区
    btim_tim3_int_init(10000-1, 24000-1);
    btim_tim5_int_init(10000-1, 24000-1);
    // 创建TASK1任务
    xTaskCreate((TaskFunction_t)task1,  
                (const char*)"task1",       
                (uint16_t)TASK1_STK_SIZE,   
                (void*)NULL,                
                (UBaseType_t)TASK1_PRIO,    
                (TaskHandle_t*)&Task1Task_Handler);
    vTaskDelete(StartTask_Handler); // 开始任务已经完成自己使命,删除自己
    taskEXIT_CRITICAL(); // 退出临界区
}
3.2.3 task1 任务实现
c 复制代码
// task1函数实现
void task1(void *pvParameters)
{
    uint32_t task1_count = 0;
    while(1)
    {
        if(++task1_count == 5)
        {
            printf("FreeRTOS关闭它能影响的中断,TIM3不受影响");
            portDISABLE_INTERRUPTS(); // 关闭中断
            LED0(0);
            delay_ms(5000);
            printf("FreeRTOS重新打开中断,TIM3继续工作");
            LED0(1);
            portENABLE_INTERRUPTS(); // 重新打开中断
        }
        vTaskDelay(1000);
    }
}
3.2.4 主函数
c 复制代码
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "mpu.h"
#include "lcd.h"
#include "key.h"
#include "malloc.h"
#include "freertos_demo.h"

int main(void)
{
    sys_cache_enable();                 /* 打开L1-Cache */
    HAL_Init();                         /* 初始化HAL库 */
    sys_stm32_clock_init(240, 2, 2, 4); /* 设置时钟, 480Mhz */
    delay_init(480);                    /* 延时初始化 */
    usart_init(115200);                 /* 串口初始化为115200 */
    led_init();                         /* 初始化LED */
    mpu_memory_protection();            /* 保护相关存储区域 */
    lcd_init();                         /* 初始化LCD */
    key_init();                         /* 初始化按键 */
    my_mem_init(SRAMIN);                /* 初始化内部内存池(AXI) */
    freertos_demo();                    /* 运行FreeRTOS例程 */
}

第三章 FreeRTOS任务基础

1. 创建和删除任务

函数 描述
xTaskCreate() 动态方式创建任务
xTaskCreateStatic() 静态方式创建任务
xTaskCreateRestricted() 动态方式创建使用 MPU 限制的任务
xTaskCreateRestrictedStatic() 静态方式创建使用 MPU 限制的任务
vTaskDelete() 删除任务

1.1 动态方式创建任务

xTaskCreate() 函数用于创建一个新的 FreeRTOS 任务并将其添加到就绪任务列表中,以便调度器进行管理和运行。

函数原型:

c 复制代码
BaseType_t xTaskCreate( TaskFunction_t pvTaskCode,
                        const char * const pcName,
                        unsigned short usStackDepth,
                        void *pvParameters,
                        UBaseType_t uxPriority,
                        TaskHandle_t *pxCreatedTask );

参数说明:

  • pvTaskCode: 指向任务入口函数的指针。任务函数通常实现为一个无限循环,并且不应该返回或退出。
  • pcName: 任务的描述性名称。主要用于调试,最大长度由 configMAX_TASK_NAME_LEN 定义 (默认为 16)。
  • usStackDepth: 任务堆栈的大小,以 为单位,而不是字节。例如,如果堆栈是 16 位宽,usStackDepth 为 100,则分配 200 字节的堆栈。
  • pvParameters: 指向将作为参数传递给被创建任务的指针。
  • uxPriority: 任务的优先级。
  • pxCreatedTask: 用于将创建的任务句柄传回的指针。这个句柄可以用来引用任务,例如在 vTaskDelete() 中删除任务。 此参数是可选的,可以设置为 NULL

返回值:

  • pdPASS:任务成功创建并添加到就绪列表。
  • 其他错误代码:定义在 errors.h 文件中。

注意事项:

  • 使用 xTaskCreate() 创建任务时,所需的 RAM 会自动从 FreeRTOS 堆中分配。
  • 如果系统支持 MPU,可以使用 xTaskCreateRestricted() 来创建受 MPU 约束的任务。
  • 如果 configSUPPORT_DYNAMIC_ALLOCATION 未定义或设置为 1,则 xTaskCreate() 函数可用。

1.2 静态方式创建任务

xTaskCreate() 动态分配内存不同,xTaskCreateStatic() 允许用户在编译时或运行时提供任务堆栈和任务控制块(TCB)所需的内存,从而避免了运行时的堆内存分配。这在对内存管理有严格要求或不能使用动态内存分配的嵌入式系统中非常有用。

函数原型:

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 );

参数说明:

  • pxTaskCode: 指向任务入口函数的指针。
  • pcName: 任务的描述性名称。
  • ulStackDepth: 任务堆栈的大小,以 为单位。
  • pvParameters: 将作为参数传递给被创建任务的指针。
  • uxPriority: 任务的优先级。
  • puxStackBuffer: 指向用户提供的、用于任务堆栈的 uint32_t 数组的指针。
  • pxTaskBuffer: 指向用户提供的、用于任务控制块 (TCB) 的 StaticTask_t 变量的指针。

返回值:

  • TaskHandle_t: 任务创建成功返回任务句柄,否则返回 NULL

注意事项:

  • 要使此函数可用,configSUPPORT_STATIC_ALLOCATION 必须在 FreeRTOSConfig.h 中定义为 1。
  • 用户需要自行管理 puxStackBufferpxTaskBuffer 所指向的内存生命周期。

1.3 动态方式创建使用 MPU 限制的任务

xTaskCreateRestricted() 用于创建具有内存保护单元 (MPU) 限制的任务。MPU 允许为任务定义独立的内存访问权限,增强系统的安全性、隔离性和稳定性。

函数原型:

c 复制代码
BaseType_t xTaskCreateRestricted(  const TaskParameters_t * const pxTaskDefinition,
                                   TaskHandle_t *pxCreatedTask );

参数说明:

  • pxTaskDefinition: 指向 TaskParameters_t 结构体的指针,该结构体包含任务的配置信息,包括任务代码、堆栈大小、优先级以及 MPU 区域定义等。
  • pxCreatedTask: 用于传回创建的任务句柄的指针。

返回值:

  • pdPASS:任务成功创建。
  • 其他错误代码。

注意事项:

  • 要使此函数可用,configENABLE_MPU 必须在 FreeRTOSConfig.h 中定义为 1。
  • 需要处理器支持 MPU 功能。

1.4 静态方式创建使用 MPU 限制的任务

xTaskCreateRestrictedStatic() 结合了 xTaskCreateRestricted() 的 MPU 限制功能和 xTaskCreateStatic() 的静态内存分配特性。它允许用户在编译时或运行时提供内存,并同时为任务设置 MPU 权限。

函数原型:

c 复制代码
TaskHandle_t xTaskCreateRestrictedStatic(  const TaskParameters_t * const pxTaskDefinition,
                                           TaskHandle_t *pxCreatedTask );

参数说明:

  • pxTaskDefinition: 指向 TaskParameters_t 结构体的指针,其中包含任务的配置信息,包括 MPU 区域定义以及指向用户提供的堆栈和 TCB 缓冲区的指针。
  • pxCreatedTask: 用于传回创建的任务句柄的指针。

返回值:

  • TaskHandle_t: 任务创建成功返回任务句柄,否则返回 NULL

注意事项:

  • 要使此函数可用,configENABLE_MPUconfigSUPPORT_STATIC_ALLOCATION 都必须在 FreeRTOSConfig.h 中定义为 1。
  • 需要处理器支持 MPU 功能。

1.5 删除任务:vTaskDelete()

vTaskDelete() 函数用于将任务从 FreeRTOS 实时内核的管理中移除。

函数原型:

c 复制代码
void vTaskDelete( TaskHandle_t xTask );

参数说明:

  • xTask: 要删除的任务的句柄。如果传入 NULL,则会删除调用此函数的任务自身。

注意事项:

  • 要使此函数可用,INCLUDE_vTaskDelete 必须在 FreeRTOSConfig.h 中定义为 1。
  • 被删除的任务将从所有就绪、阻塞、挂起和事件列表中移除。
  • 如果任务删除其他任务,FreeRTOS 内核分配的内存会在 API 中立即释放。如果任务删除自身,则由空闲任务 (idle task) 负责释放内核分配的内存。因此,如果应用程序调用 vTaskDelete(),确保空闲任务有足够的处理时间来释放内存非常重要。
  • 任务代码中分配的内存不会自动释放,应在任务删除之前手动释放。
  • 删除任务主要有两种情况:自删除(在任务本身的 TaskCode 中调用 vTaskDelete(NULL))和强制删除(在其他任务中删除另一个任务)。
  • 对于使用 xTaskCreate() 创建的任务,删除后存储资源将在空闲任务中自动释放。对于使用 xTaskCreateStatic() 静态创建的任务,存储资源不会被释放,需要手动释放。
  • 在删除任务时,需要考虑该任务是否占用了共享资源、是否申请了新资源,以及是否与关联任务或中断服务例程 (ISR) 存在通信关系,以避免后遗症。

2. FreeRTOS任务创建与删除测试

2.1 动态方法

2.1.1 任务配置
c 复制代码
/*----------------任务函数配置区-----------------*/
// START_TASK配置
#define START_TASK_PRIO 1           // 任务优先级 
#define START_STK_SIZE  128         // 任务堆栈大小 
TaskHandle_t StartTask_Handler;     // 任务句柄 
void start_task(void *pvParameters);// 任务函数 

// TASK1配置
#define TASK1_PRIO      2                   
#define TASK1_STK_SIZE  128                 
TaskHandle_t Task1Task_Handler;          
void task1(void *pvParameters);

// TASK2配置
#define TASK2_PRIO      3                   
#define TASK2_STK_SIZE  128                 
TaskHandle_t Task2Task_Handler;          
void task2(void *pvParameters);

// TASK3配置
#define TASK3_PRIO      4                   
#define TASK3_STK_SIZE  128                 
TaskHandle_t Task3Task_Handler;          
void task3(void *pvParameters);
/*---------------------------------------------*/
2.1.2 任务实现
c 复制代码
/*----------------任务函数实现区----------------*/
void freertos_demo(void)
{
    // 初始化LCD显示
    lcd_show_string(10, 47, 220, 30, 24, "Task Create&Delete", RED);
    lcd_draw_rectangle(5, 110, 115, 314, BLACK);
    lcd_draw_rectangle(125, 110, 234, 314, BLACK);
    lcd_draw_line(5, 130, 115, 130, BLACK);
    lcd_draw_line(125, 130, 234, 130, BLACK);
    lcd_show_string(15, 111, 110, 16, 16, "Task1: 000", BLUE);
    lcd_show_string(135, 111, 110, 16, 16, "Task2: 000", BLUE);

    // 创建START_TASK任务
    xTaskCreate((TaskFunction_t)start_task,        // 任务函数
                (const char*)"start_task",         // 任务名称
                (uint16_t)START_STK_SIZE,          // 任务堆栈大小
                (void*)NULL,                       // 传递给任务函数的参数
                (UBaseType_t)START_TASK_PRIO,      // 任务优先级
                (TaskHandle_t*)&StartTask_Handler);// 任务句柄
    // 开始任务调度
    vTaskStartScheduler();
}
2.1.3 主函数
c 复制代码
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "mpu.h"
#include "lcd.h"
#include "key.h"
#include "malloc.h"
#include "freertos_demo.h"

int main(void)
{
    sys_cache_enable();                 /* 打开L1-Cache */
    HAL_Init();                         /* 初始化HAL库 */
    sys_stm32_clock_init(240, 2, 2, 4); /* 设置时钟, 480Mhz */
    delay_init(480);                    /* 延时初始化 */
    usart_init(115200);                 /* 串口初始化为115200 */
    led_init();                         /* 初始化LED */
    mpu_memory_protection();            /* 保护相关存储区域 */
    lcd_init();                         /* 初始化LCD */
    key_init();                         /* 初始化按键 */
    my_mem_init(SRAMIN);                /* 初始化内部内存池(AXI) */
    freertos_demo();                    /* 运行FreeRTOS例程 */
}

2.2 静态方法

2.2.1 提供空闲任务和软件定时器服务任务
c 复制代码
/*---------------FreeRTOS系统配置区-------------*/
static StackType_t IdleTaskStack[configMINIMAL_STACK_SIZE]; // 空闲任务栈
static StaticTask_t IdleTaskTCB; // 空闲任务控制块
static StackType_t TimerTaskStack[configTIMER_TASK_STACK_DEPTH]; // 定时器任务栈
static StaticTask_t TimerTaskTCB; // 定时器任务控制块
/*---------------------------------------------*/

/*----------------任务函数配置区-----------------*/
/**
 * @brief       获取空闲任务地任务堆栈和任务控制块内存,因为本例程使用的
                静态内存,因此空闲任务的任务堆栈和任务控制块的内存就应该
                有用户来提供,FreeRTOS提供了接口函数vApplicationGetIdleTaskMemory()
                实现此函数即可。
 * @param       ppxIdleTaskTCBBuffer:任务控制块内存
                ppxIdleTaskStackBuffer:任务堆栈内存
                pulIdleTaskStackSize:任务堆栈大小
 * @retval      无
 */
void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer, 
                                   StackType_t  **ppxIdleTaskStackBuffer, 
                                   uint32_t     *pulIdleTaskStackSize)
{
    *ppxIdleTaskTCBBuffer   = &IdleTaskTCB;
    *ppxIdleTaskStackBuffer = IdleTaskStack;
    *pulIdleTaskStackSize   = configMINIMAL_STACK_SIZE;
}

/**
 * @brief       获取定时器服务任务的任务堆栈和任务控制块内存
 * @param       ppxTimerTaskTCBBuffer:任务控制块内存
                ppxTimerTaskStackBuffer:任务堆栈内存
                pulTimerTaskStackSize:任务堆栈大小
 * @retval      无
 */
void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer,
                                    StackType_t  **ppxTimerTaskStackBuffer,
                                    uint32_t     *pulTimerTaskStackSize)
{
    *ppxTimerTaskTCBBuffer  = &TimerTaskTCB;
    *ppxTimerTaskStackBuffer= TimerTaskStack;
    *pulTimerTaskStackSize  = configTIMER_TASK_STACK_DEPTH;
}
2.2.2 任务配置
c 复制代码
// START_TASK配置
#define START_TASK_PRIO 1           // 任务优先级 
#define START_STK_SIZE  128         // 任务堆栈大小 
StackType_t StartTaskStack[START_STK_SIZE]; // 任务堆栈
StaticTask_t StartTaskTCB; // 任务控制块
TaskHandle_t StartTask_Handler; // 任务句柄
void start_task(void *pvParameters);

// TASK1配置
#define TASK1_PRIO      2                   
#define TASK1_STK_SIZE  128                
StackType_t Task1TaskStack[TASK1_STK_SIZE]; 
StaticTask_t Task1TaskTCB; 
TaskHandle_t Task1Task_Handler;
void task1(void *pvParameters);

// TASK2配置
#define TASK2_PRIO      3                   
#define TASK2_STK_SIZE  128
StackType_t Task2TaskStack[TASK2_STK_SIZE]; 
StaticTask_t Task2TaskTCB;                
TaskHandle_t Task2Task_Handler;          
void task2(void *pvParameters);

// TASK3配置
#define TASK3_PRIO      4                   
#define TASK3_STK_SIZE  128  
StackType_t Task3TaskStack[TASK3_STK_SIZE]; 
StaticTask_t Task3TaskTCB;                
TaskHandle_t Task3Task_Handler;          
void task3(void *pvParameters);
2.2.3 任务实现
c 复制代码
*----------------任务函数实现区----------------*/
void freertos_demo(void)
{
    // 初始化LCD显示
    lcd_show_string(10, 47, 220, 30, 24, "Task Create&Delete", RED);
    lcd_draw_rectangle(5, 110, 115, 314, BLACK);
    lcd_draw_rectangle(125, 110, 234, 314, BLACK);
    lcd_draw_line(5, 130, 115, 130, BLACK);
    lcd_draw_line(125, 130, 234, 130, BLACK);
    lcd_show_string(15, 111, 110, 16, 16, "Task1: 000", BLUE);
    lcd_show_string(135, 111, 110, 16, 16, "Task2: 000", BLUE);

    // 创建START_TASK任务
    StartTask_Handler = xTaskCreateStatic((TaskFunction_t)start_task,   // 任务函数
                                          (const char*)"start_task",    // 任务名称
                                          (uint16_t)START_STK_SIZE,     // 任务堆栈大小
                                          (void*)NULL,                  // 传递给任务函数的参数
                                          (UBaseType_t)START_TASK_PRIO, // 任务优先级
                                          (StackType_t*)StartTaskStack, // 任务堆栈
                                          (StaticTask_t*)&StartTaskTCB);// 任务控制块
    // 开始任务调度
    vTaskStartScheduler();
}

// start_task函数实现
void start_task(void *pvParameters)
{
    taskENTER_CRITICAL(); // 进入临界区
    // 创建TASK1任务
    Task1Task_Handler = xTaskCreateStatic((TaskFunction_t)task1,      
                                           (const char*)"task1",       
                                           (uint16_t)TASK1_STK_SIZE,   
                                           (void*)NULL,               
                                           (UBaseType_t)TASK1_PRIO,    
                                           (StackType_t*)Task1TaskStack,
                                           (StaticTask_t*)&Task1TaskTCB);
    // 创建TASK2任务
    Task2Task_Handler = xTaskCreateStatic((TaskFunction_t)task2,      
                                           (const char*)"task2",       
                                           (uint16_t)TASK2_STK_SIZE,   
                                           (void*)NULL,               
                                           (UBaseType_t)TASK2_PRIO,    
                                           (StackType_t*)Task2TaskStack,
                                           (StaticTask_t*)&Task2TaskTCB);
    // 创建TASK3任务
    Task3Task_Handler = xTaskCreateStatic((TaskFunction_t)task3,      
                                           (const char*)"task3",       
                                           (uint16_t)TASK3_STK_SIZE,   
                                           (void*)NULL,               
                                           (UBaseType_t)TASK3_PRIO,    
                                           (StackType_t*)Task3TaskStack,
                                           (StaticTask_t*)&Task3TaskTCB);
    vTaskDelete(StartTask_Handler); // 删除START_TASK任务
    taskEXIT_CRITICAL(); // 退出临界区
}

// task1函数实现
// 实现功能:记录自己的运行次数,每次运行时在LCD显示不同颜色
void task1(void *pvParameters)
{
    uint32_t task1_count = 0;
    while(1)
    {
        lcd_fill(6,131,114,313,lcd_discolor[++task1_count % 11]);
        lcd_show_xnum(71,111,task1_count,3,16,0x80,BLUE);
        vTaskDelay(500);
    }
}

// task2函数实现
// 实现功能:记录自己的运行次数,每次运行时在LCD显示不同颜色
void task2(void *pvParameters)
{
    uint32_t task2_count = 0;
    while(1)
    {
        lcd_fill(126,131,233,313,lcd_discolor[11-(++task2_count % 11)]);
        lcd_show_xnum(191,111,task2_count,3,16,0x80,BLUE);
        vTaskDelay(500);
    }
}

// task3函数实现
// 实现功能:按下KEY0删除task1任务,按下KEY1删除task2任务
void task3(void *pvParameters)
{
    uint8_t key_value;
    while(1)
    {
        key_value = key_scan(0);
        if(key_value == KEY0_PRES)
        {
            if(Task1Task_Handler != NULL)
            {
                vTaskDelete(Task1Task_Handler);
                Task1Task_Handler = NULL;
                lcd_show_string(15, 111, 110, 16, 16, "Task1: Del", RED); 
            }
        }
        if(key_value == KEY1_PRES)
        {
            if(Task2Task_Handler != NULL)
            {
                vTaskDelete(Task2Task_Handler);
                Task2Task_Handler = NULL;
                lcd_show_string(135, 111, 110, 16, 16, "Task2: Del", RED);
            }
        }
        vTaskDelay(10);
    }
}
/*---------------------------------------------*/
2.2.4 主函数
c 复制代码
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "mpu.h"
#include "lcd.h"
#include "key.h"
#include "malloc.h"
#include "freertos_demo.h"

int main(void)
{
    sys_cache_enable();                 /* 打开L1-Cache */
    HAL_Init();                         /* 初始化HAL库 */
    sys_stm32_clock_init(240, 2, 2, 4); /* 设置时钟, 480Mhz */
    delay_init(480);                    /* 延时初始化 */
    usart_init(115200);                 /* 串口初始化为115200 */
    led_init();                         /* 初始化LED */
    mpu_memory_protection();            /* 保护相关存储区域 */
    lcd_init();                         /* 初始化LCD */
    key_init();                         /* 初始化按键 */
    my_mem_init(SRAMIN);                /* 初始化内部内存池(AXI) */
    freertos_demo();                    /* 运行FreeRTOS例程 */
}

3. 挂起和恢复任务

函数 描述
vTaskSuspend() 挂起任务
vTaskResume() 恢复被挂起的任务
xTaskResumeFromISR() 在中断中恢复被挂起的任务

3.1 挂起任务

vTaskSuspend() 函数用于将一个任务置于挂起状态。当任务处于挂起状态时,它将不会被 FreeRTOS 调度器调度执行,即使它的优先级很高。

函数原型:

c 复制代码
void vTaskSuspend( TaskHandle_t xTaskToSuspend );

参数说明:

  • xTaskToSuspend: 要挂起的任务的句柄。如果传入 NULL,则会挂起调用此函数的任务自身。

注意事项:

  • 要使此函数可用,INCLUDE_vTaskSuspend 必须在 FreeRTOSConfig.h 中定义为 1。
  • 被挂起的任务将从所有就绪、阻塞和事件列表中移除,并放置在挂起列表中。
  • 被挂起的任务在被恢复之前不会消耗任何 CPU 时间。
  • 如果一个任务被挂起,即使有外部事件(如信号量、队列消息等)发生,它也不会被解除阻塞,直到它被明确地恢复。

3.2 恢复被挂起的任务

vTaskResume() 函数用于将一个处于挂起状态的任务恢复到就绪状态。一旦任务被恢复,它就可以被 FreeRTOS 调度器调度执行。

函数原型:

c 复制代码
void vTaskResume( TaskHandle_t xTaskToResume );

参数说明:

  • xTaskToResume: 要恢复的任务的句柄。

注意事项:

  • 要使此函数可用,INCLUDE_vTaskSuspend 必须在 FreeRTOSConfig.h 中定义为 1。
  • 此函数不能从中断服务例程 (ISR) 中调用。在 ISR 中恢复任务应使用 xTaskResumeFromISR()
  • 如果恢复一个已经处于就绪或阻塞状态的任务,vTaskResume() 将不执行任何操作。

3.3 在中断中恢复被挂起的任务

xTaskResumeFromISR() 函数是 vTaskResume() 的中断安全版本,用于在中断服务函数 (ISR) 中恢复一个被挂起的任务。

函数原型:

c 复制代码
BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume );

参数说明:

  • xTaskToResume: 要恢复的任务的句柄。

返回值:

  • pdTRUE:如果调用 xTaskResumeFromISR() 导致更高优先级的任务被解除阻塞(即需要进行上下文切换),则返回 pdTRUE
  • pdFALSE:否则返回 pdFALSE

注意事项:

  • 要使此函数可用,INCLUDE_vTaskSuspend 必须在 FreeRTOSConfig.h 中定义为 1。
  • 此函数通常在中断处理结束时用于检查是否需要进行任务切换。如果返回 pdTRUE,通常会调用 portYIELD_FROM_ISR() 或类似的宏来触发上下文切换,从而立即执行被恢复的高优先级任务。

4. 挂起和恢复任务测试

4.1 任务配置

c 复制代码
/*----------------任务函数配置区-----------------*/
// START_TASK配置
#define START_TASK_PRIO 1           // 任务优先级 
#define START_STK_SIZE  128         // 任务堆栈大小 
TaskHandle_t StartTask_Handler;     // 任务句柄 
void start_task(void *pvParameters);// 任务函数 

// TASK1配置
#define TASK1_PRIO      2                   
#define TASK1_STK_SIZE  128                 
TaskHandle_t Task1Task_Handler;          
void task1(void *pvParameters);

// TASK2配置
#define TASK2_PRIO      3                   
#define TASK2_STK_SIZE  128                 
TaskHandle_t Task2Task_Handler;          
void task2(void *pvParameters);

// TASK3配置
#define TASK3_PRIO      4                   
#define TASK3_STK_SIZE  128                 
TaskHandle_t Task3Task_Handler;          
void task3(void *pvParameters);
/*---------------------------------------------*/

4.2 任务实现

c 复制代码
/*----------------任务函数实现区----------------*/
void freertos_demo(void)
{
    // 初始化LCD显示
    lcd_show_string(10, 47, 220, 30, 24, "Task Create&Delete", RED);
    lcd_draw_rectangle(5, 110, 115, 314, BLACK);
    lcd_draw_rectangle(125, 110, 234, 314, BLACK);
    lcd_draw_line(5, 130, 115, 130, BLACK);
    lcd_draw_line(125, 130, 234, 130, BLACK);
    lcd_show_string(15, 111, 110, 16, 16, "Task1: 000", BLUE);
    lcd_show_string(135, 111, 110, 16, 16, "Task2: 000", BLUE);

    // 创建START_TASK任务
    xTaskCreate((TaskFunction_t)start_task,        // 任务函数
                (const char*)"start_task",         // 任务名称
                (uint16_t)START_STK_SIZE,          // 任务堆栈大小
                (void*)NULL,                       // 传递给任务函数的参数
                (UBaseType_t)START_TASK_PRIO,      // 任务优先级
                (TaskHandle_t*)&StartTask_Handler);// 任务句柄
    // 开始任务调度
    vTaskStartScheduler();
}

// start_task函数实现
void start_task(void *pvParameters)
{
    taskENTER_CRITICAL(); // 进入临界区
    // 创建TASK1任务
    xTaskCreate((TaskFunction_t)task1,      
                (const char*)"task1",       
                (uint16_t)TASK1_STK_SIZE,   
                (void*)NULL,               
                (UBaseType_t)TASK1_PRIO,    
                (TaskHandle_t*)&Task1Task_Handler);
    // 创建TASK2任务
    xTaskCreate((TaskFunction_t)task2,      
                (const char*)"task2",       
                (uint16_t)TASK2_STK_SIZE,   
                (void*)NULL,               
                (UBaseType_t)TASK2_PRIO,    
                (TaskHandle_t*)&Task2Task_Handler);
    // 创建TASK3任务
    xTaskCreate((TaskFunction_t)task3,      
                (const char*)"task3",       
                (uint16_t)TASK3_STK_SIZE,   
                (void*)NULL,               
                (UBaseType_t)TASK3_PRIO,    
                (TaskHandle_t*)&Task3Task_Handler);
    vTaskDelete(StartTask_Handler); // 删除START_TASK任务
    taskEXIT_CRITICAL(); // 退出临界区
}

// task1函数实现
// 实现功能:记录自己的运行次数,每次运行时在LCD显示不同颜色
void task1(void *pvParameters)
{
    uint32_t task1_count = 0;
    while(1)
    {
        lcd_fill(6,131,114,313,lcd_discolor[++task1_count % 11]);
        lcd_show_xnum(71,111,task1_count,3,16,0x80,BLUE);
        vTaskDelay(500);
    }
}

// task2函数实现
// 实现功能:记录自己的运行次数,每次运行时在LCD显示不同颜色
void task2(void *pvParameters)
{
    uint32_t task2_count = 0;
    while(1)
    {
        lcd_fill(126,131,233,313,lcd_discolor[11-(++task2_count % 11)]);
        lcd_show_xnum(191,111,task2_count,3,16,0x80,BLUE);
        vTaskDelay(500);
    }
}

// task3函数实现
// 实现功能:按下KEY0删除task1任务,按下KEY1删除task2任务
void task3(void *pvParameters)
{
    uint8_t key_value;
    while(1)
    {
        key_value = key_scan(0);
        if(key_value == KEY0_PRES)
        {
            if(Task1Task_Handler != NULL)
            {
                vTaskDelete(Task1Task_Handler);
                Task1Task_Handler = NULL;
                lcd_show_string(15, 111, 110, 16, 16, "Task1: Del", RED); 
            }
        }
        if(key_value == KEY1_PRES)
        {
            if(Task2Task_Handler != NULL)
            {
                vTaskSuspend(Task2Task_Handler); // 挂起task2任务
            }
        }
        if(key_value == WKUP_PRES)
        {
            if(Task2Task_Handler != NULL)
            {
                vTaskResume(Task2Task_Handler); // 恢复task2任务
            }
        }
        vTaskDelay(10);
    }
}
/*---------------------------------------------*/

4.3 主函数

c 复制代码
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "mpu.h"
#include "lcd.h"
#include "key.h"
#include "malloc.h"
#include "freertos_demo.h"

int main(void)
{
    sys_cache_enable();                 /* 打开L1-Cache */
    HAL_Init();                         /* 初始化HAL库 */
    sys_stm32_clock_init(240, 2, 2, 4); /* 设置时钟, 480Mhz */
    delay_init(480);                    /* 延时初始化 */
    usart_init(115200);                 /* 串口初始化为115200 */
    led_init();                         /* 初始化LED */
    mpu_memory_protection();            /* 保护相关存储区域 */
    lcd_init();                         /* 初始化LCD */
    key_init();                         /* 初始化按键 */
    my_mem_init(SRAMIN);                /* 初始化内部内存池(AXI) */
    freertos_demo();                    /* 运行FreeRTOS例程 */
}

第四章 FreeRTOS列表与列表项

1. 列表与列表项

函数 描述
vListInitialise() 初始化列表
vListInitialiseItem() 初始化列表项
vListInsertEnd() 列表末尾插入列表项
vListInsert() 列表插入列表项
uxListRemove() 列表移除列表项

1.1 初始化列表

vListInitialise() 函数用于初始化一个 FreeRTOS 列表。在使用任何列表操作之前,必须先调用此函数对列表进行初始化。

函数原型:

c 复制代码
void vListInitialise( List_t * const pxList );

参数说明:

  • pxList: 指向要初始化的 List_t 结构体变量的指针。

注意事项:

  • List_t 结构体通常在外部定义,例如在任务控制块(TCB)或事件组中。
  • 此函数将列表的成员(如链表头指针、列表项计数器等)设置为其初始状态,使其成为一个空列表。

1.2 初始化列表项

vListInitialiseItem() 函数用于初始化一个 FreeRTOS 列表项。每个要插入到 FreeRTOS 列表中的数据结构都必须包含一个 ListItem_tMiniListItem_t 类型的列表项成员,并且在使用前需要通过此函数进行初始化。

函数原型:

c 复制代码
void vListInitialiseItem( ListItem_t * const pxListItem );

参数说明:

  • pxListItem: 指向要初始化的 ListItem_t 结构体变量的指针。

注意事项:

  • ListItem_tMiniListItem_t 通常是用户定义结构体的一部分,用于将该结构体链接到 FreeRTOS 列表中。
  • 此函数将列表项的成员(如指向其所属列表的指针、节点值等)设置为其初始状态。

1.3 列表末尾插入列表项

vListInsertEnd() 函数用于将一个已初始化的列表项插入到指定列表的末尾。

函数原型:

c 复制代码
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem );

参数说明:

  • pxList: 指向目标列表的 List_t 结构体变量的指针。
  • pxNewListItem: 指向要插入的列表项的 ListItem_t 结构体变量的指针。

注意事项:

  • 此函数不会考虑列表项的值,而是简单地将其添加到列表的末尾。

1.4 列表插入列表项(按排序顺序)

vListInsert() 函数用于将一个已初始化的列表项插入到指定列表的正确位置,以保持列表的排序顺序。列表项的排序基于其"值"(xItemValue 成员)。

函数原型:

c 复制代码
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem );

参数说明:

  • pxList: 指向要插入列表项的 List_t 结构体变量的指针。
  • pxNewListItem: 指向要插入的 ListItem_t 结构体变量的指针。列表项的 xItemValue 成员将用于排序。

注意事项:

  • 此函数通常在 FreeRTOS 内部使用,例如将一个任务添加到就绪列表或阻塞列表中,并根据任务优先级进行排序。
  • 应用程序通常不需要直接调用此函数。

1.5 列表移除列表项

uxListRemove() 函数用于将一个列表项从其当前所在的列表中移除。

函数原型:

c 复制代码
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove );

参数说明:

  • pxItemToRemove: 指向要移除的 ListItem_t 结构体变量的指针。

返回值:

  • UBaseType_t: 返回移除列表项后,列表中剩余的列表项数量。

注意事项:

  • 此函数通常在 FreeRTOS 内部使用,例如当任务从阻塞状态变为就绪状态时,将其从阻塞列表中移除。
  • 应用程序通常不需要直接调用此函数

2. FreeRTOS列表项的插入与删除测试

2.1 任务配置

c 复制代码
/*----------------任务配置区-----------------*/
// START_TASK配置
#define START_TASK_PRIO 1           // 任务优先级 
#define START_STK_SIZE  128         // 任务堆栈大小 
TaskHandle_t StartTask_Handler;     // 任务句柄 
void start_task(void *pvParameters);// 任务函数 

// TASK1配置
#define TASK1_PRIO      2                   
#define TASK1_STK_SIZE  128                 
TaskHandle_t Task1Task_Handler;          
void task1(void *pvParameters);
/*-------------------------------------------*/

2.2 列表配置

c 复制代码
/*----------------列表配置区------------------*/
List_t TestList;      // 测试列表
ListItem_t ListItem1; // 测试列表项1
ListItem_t ListItem2; // 测试列表项2
ListItem_t ListItem3; // 测试列表项3
/*-------------------------------------------*/

2.3 任务实现

c 复制代码
/*------------------任务实现区----------------*/
void freertos_demo(void)
{
    lcd_show_string(10,47,220,24,24,"List Insert Demo",RED);
    // 创建START_TASK任务
    xTaskCreate((TaskFunction_t)start_task,        // 任务函数
                (const char*)"start_task",         // 任务名称
                (uint16_t)START_STK_SIZE,          // 任务堆栈大小
                (void*)NULL,                       // 传递给任务函数的参数
                (UBaseType_t)START_TASK_PRIO,      // 任务优先级
                (TaskHandle_t*)&StartTask_Handler);// 任务句柄
    // 开始任务调度
    vTaskStartScheduler();
}

// start_task函数实现
void start_task(void *pvParameters)
{
    taskENTER_CRITICAL(); // 进入临界区
    // 创建TASK1任务
    xTaskCreate((TaskFunction_t)task1,  
                (const char*)"task1",       
                (uint16_t)TASK1_STK_SIZE,   
                (void*)NULL,                
                (UBaseType_t)TASK1_PRIO,    
                (TaskHandle_t*)&Task1Task_Handler);
    vTaskDelete(StartTask_Handler); // 开始任务已经完成自己使命,删除自己
    taskEXIT_CRITICAL(); // 退出临界区
}

// task1函数实现
void task1(void *pvParameters)
{
    // 初始化列表和列表项
    vListInitialise(&TestList);
    vListInitialiseItem(&ListItem1);
    vListInitialiseItem(&ListItem2);
    vListInitialiseItem(&ListItem3);

    // 打印列表和列表项的地址
    printf("-----------------打印列表和列表项的地址-------------------\r\n");
    printf("项目\t\t\t地址\r\n");
    printf("TestList\t\t0x%p\t\r\n", &TestList);
    printf("TestList->pxIndex\t0x%p\t\r\n", TestList.pxIndex); // 列表头
    printf("TestList->xListEnd\t0x%p\t\r\n", (&TestList.xListEnd)); // 列表尾
    printf("ListItem1\t\t0x%p\t\r\n", &ListItem1);
    printf("ListItem2\t\t0x%p\t\r\n", &ListItem2);
    printf("ListItem3\t\t0x%p\t\r\n", &ListItem3);
    printf("-----------------------STEP1 END------------------------\r\n");
    printf("按下KEY0以继续\r\n");
    while(key_scan(0) != KEY0_PRES)
    {
        vTaskDelay(10);
    }

    // 列表项1插入列表中
    printf("--------------------列表项1插入列表中---------------------\r\n");
    vListInsert((List_t*)&TestList, (ListItem_t*)&ListItem1);
    printf("项目\t\t\t\t地址\r\n");
    printf("TestList->xListEnd->pxNext\t0x%p\r\n", (TestList.xListEnd.pxNext));
    printf("ListItem1->pxNext\t\t0x%p\r\n", (ListItem1.pxNext));
    printf("TestList->xListEnd->pxPrevious\t0x%p\r\n", (TestList.xListEnd.pxPrevious));
    printf("ListItem1->pxPrevious\t\t0x%p\r\n", (ListItem1.pxPrevious));
    printf("-----------------------STEP2 END------------------------\r\n");
    printf("按下KEY0以继续\r\n");
    while(key_scan(0) != KEY0_PRES)
    {
        vTaskDelay(10);
    }

    // 列表项2插入列表中
    printf("--------------------列表项2插入列表中---------------------\r\n");
    vListInsert((List_t*)&TestList, (ListItem_t*)&ListItem2);
    printf("项目\t\t\t\t地址\r\n");
    printf("TestList->xListEnd->pxNext\t0x%p\r\n", (TestList.xListEnd.pxNext));
    printf("ListItem1->pxNext\t\t0x%p\r\n", (ListItem1.pxNext));
    printf("ListItem2->pxNext\t\t0x%p\r\n", (ListItem2.pxNext));
    printf("TestList->xListEnd->pxPrevious\t0x%p\r\n", (TestList.xListEnd.pxPrevious));
    printf("ListItem1->pxPrevious\t\t0x%p\r\n", (ListItem1.pxPrevious));
    printf("ListItem2->pxPrevious\t\t0x%p\r\n", (ListItem2.pxPrevious));
    printf("-----------------------STEP3 END------------------------\r\n");
    printf("按下KEY0以继续\r\n");
    while(key_scan(0) != KEY0_PRES)
    {
        vTaskDelay(10);
    }

    // 列表项3插入列表中
    printf("--------------------列表项3插入列表中---------------------\r\n");
    vListInsert((List_t*)&TestList, (ListItem_t*)&ListItem3);
    printf("项目\t\t\t\t地址\r\n");
    printf("TestList->xListEnd->pxNext\t0x%p\r\n", (TestList.xListEnd.pxNext));
    printf("ListItem1->pxNext\t\t0x%p\r\n", (ListItem1.pxNext));
    printf("ListItem2->pxNext\t\t0x%p\r\n", (ListItem2.pxNext));
    printf("ListItem3->pxNext\t\t0x%p\r\n", (ListItem3.pxNext));
    printf("TestList->xListEnd->pxPrevious\t0x%p\r\n", (TestList.xListEnd.pxPrevious));
    printf("ListItem1->pxPrevious\t\t0x%p\r\n", (ListItem1.pxPrevious));
    printf("ListItem2->pxPrevious\t\t0x%p\r\n", (ListItem2.pxPrevious));
    printf("ListItem3->pxPrevious\t\t0x%p\r\n", (ListItem3.pxPrevious));
    printf("-----------------------STEP4 END------------------------\r\n");
    printf("按下KEY0以继续\r\n");
    while(key_scan(0) != KEY0_PRES)
    {
        vTaskDelay(10);
    }

    // 移除列表项2
    printf("------------------------移除列表项2-----------------------\r\n");
    uxListRemove((ListItem_t*)&ListItem2);
    printf("项目\t\t\t\t地址\r\n");
    printf("TestList->xListEnd->pxNext\t0x%p\r\n", (TestList.xListEnd.pxNext));
    printf("ListItem1->pxNext\t\t0x%p\r\n", (ListItem1.pxNext));
    printf("ListItem3->pxNext\t\t0x%p\r\n", (ListItem3.pxNext));
    printf("TestList->xListEnd->pxPrevious\t0x%p\r\n", (TestList.xListEnd.pxPrevious));
    printf("ListItem1->pxPrevious\t\t0x%p\r\n", (ListItem1.pxPrevious));
    printf("ListItem3->pxPrevious\t\t0x%p\r\n", (ListItem3.pxPrevious));
    printf("按下KEY0以继续\r\n");
    printf("-----------------------STEP5 END------------------------\r\n");
    while(key_scan(0) != KEY0_PRES)
    {
        vTaskDelay(10);
    }

   // 在列表末尾添加列表项2
    printf("-------------------在列表末尾添加列表项2-------------------\r\n");
    vListInsertEnd((List_t*)&TestList, (ListItem_t*)&ListItem2);
    printf("项目\t\t\t\t地址\r\n");
    printf("TestList->pxIndex\t\t0x%p\r\n", TestList.pxIndex);
    printf("TestList->xListEnd->pxNext\t0x%p\r\n", (TestList.xListEnd.pxNext));
    printf("ListItem1->pxNext\t\t0x%p\r\n", (ListItem1.pxNext));
    printf("ListItem2->pxNext\t\t0x%p\r\n", (ListItem2.pxNext));
    printf("ListItem3->pxNext\t\t0x%p\r\n", (ListItem3.pxNext));
    printf("TestList->xListEnd->pxPrevious\t0x%p\r\n", (TestList.xListEnd.pxPrevious));
    printf("ListItem1->pxPrevious\t\t0x%p\r\n", (ListItem1.pxPrevious));
    printf("ListItem2->pxPrevious\t\t0x%p\r\n", (ListItem2.pxPrevious));
    printf("ListItem3->pxPrevious\t\t0x%p\r\n", (ListItem3.pxPrevious));
    printf("-----------------------------END-------------------------\r\n");
    while(1)
    {
        vTaskDelay(10);
    }
}
/*-------------------------------------------*/

2.4 主函数

c 复制代码
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "mpu.h"
#include "lcd.h"
#include "key.h"
#include "malloc.h"
#include "freertos_demo.h"

int main(void)
{
    sys_cache_enable();                 /* 打开L1-Cache */
    HAL_Init();                         /* 初始化HAL库 */
    sys_stm32_clock_init(240, 2, 2, 4); /* 设置时钟, 480Mhz */
    delay_init(480);                    /* 延时初始化 */
    usart_init(115200);                 /* 串口初始化为115200 */
    led_init();                         /* 初始化LED */
    mpu_memory_protection();            /* 保护相关存储区域 */
    lcd_init();                         /* 初始化LCD */
    key_init();                         /* 初始化按键 */
    my_mem_init(SRAMIN);                /* 初始化内部内存池(AXI) */
    freertos_demo();                    /* 运行FreeRTOS例程 */
}

第五章 FreeRTOS任务切换

1. PendSV异常

PendSV(Pended Service Call,可挂起服务调用),是一个对 RTOS 非常重要的异常。 PendSV的中断优先级是可以编程的,用户可以根据实际的需求,对其进行配置。 PendSV 的中断由将中断控制状态寄存器(ICSR)中 PENDSVSET 为置一触发。PendSV 与 SVC 不同, PendSV 的中断是非实时的,即 PendSV 的中断可以在更高优先级的中断中触发,但是在更高优先级中断结束后才执行。

利用 PendSV 的这个可挂起特性,在设计 RTOS 时,可以将 PendSV 的中断优先级设置为最低的中断优先级,这么一来, PendSV 的中断服务函数就会在其他所有中断处理完成后才执行。任务切换时,就需要用到 PendSV 的这个特性。

首先,来看一下任务切换的一些基本概念,在典型的 RTOS 中,任务的处理时间被分为多个时间片, OS 内核的执行可以有两种触发方式,一种是通过在应用任务中通过 SVC 指令触发,例如在应用任务在等待某个时间发生而需要停止的时候,那么就可以通过 SVC 指令来触发 OS内核的执行,以切换到其他任务;第二种方式是, SysTick 周期性的中断,来触发 OS 内核的执行。 下图演示了只有两个任务的 RTOS 中,两个任务交替执行的过程:

在操作系统中,任务调度器决定是否切换任务。图中的任务及切换都是在 SysTick 中断中完成的, SysTick 的每一次中断都会切换到其他任务。

如果一个中断请求(IRQ)在 SysTick 中断产生之前产生,那么 SysTick 就可能抢占该中断请求,这就会导致该中断请求被延迟处理,这在实时操作系统中是不允许的,因为这将会影响到实时操作系统的实时性,如下图所示:

并且,当 SysTick 完成任务的上下文切换,准备返回任务中运行时,由于存在中断请求, ARM Cortex-M 不允许返回线程模式,因此,将会产生用法错误异常(Usage Fault)。在一些 RTOS 的设计中,会通过判断是否存在中断请求,来决定是否进行任务切换。虽然可以通过检查 xPSR 或 NVIC 中的中断活跃寄存器来判断是否存在中断请求,但是这样可能会影响系统的性能,甚至可能出现中断源在 SysTick 中断前后不断产生中断请求,导致系统无法进行任务切换的情况。PendSV 通过延迟执行任务切换,直到处理完所有的中断请求,以解决上述问题。为了达到这样的效果,必须将 PendSV 的中断优先级设置为最低的中断优先等级。如果操作系统决定切换任务,那么就将 PendSV 设置为挂起状态,并在 PendSV 的中断服务函数中执行任务切换,如下图所示:

  1. 任务一触发 SVC 中断以进行任务切换(例如,任务一正等待某个事件发生)。

  2. 系统内核接收到任务切换请求,开始准备任务切换,并挂起 PendSV 异常。

  3. 当退出 SVC 中断的时候,立刻进入 PendSV 异常处理,完成任务切换。

  4. 当 PendSV 异常处理完成,返回线程模式,开始执行任务二。

  5. 中断产生,并进入中断处理函数。

  6. 当运行中断处理函数的时候, SysTick 异常(用于内核时钟节拍) 产生。

  7. 操作系统执行必要的操作,然后挂起 PendSV 异常,准备进行任务切换。

  8. 当 SysTick 中断处理完成,返回继续处理中断。

  9. 当中断处理完成,立马进入 PendSV 异常处理,完成任务切换。

  10. 当 PendSV 异常处理完成,返回线程模式,继续执行任务一。

PendSV在RTOS的任务切换中,起着至关重要的作用, FreeRTOS的任务切换就是在PendSV中完成的。

2. 时间片调度测试

2.1 任务配置

c 复制代码
/*----------------任务配置区-----------------*/
// START_TASK配置
#define START_TASK_PRIO 1           // 任务优先级 
#define START_STK_SIZE  128         // 任务堆栈大小 
TaskHandle_t StartTask_Handler;     // 任务句柄 
void start_task(void *pvParameters);// 任务函数 

// TASK1配置
#define TASK1_PRIO      2                   
#define TASK1_STK_SIZE  128                 
TaskHandle_t Task1Task_Handler;          
void task1(void *pvParameters);

// TASK2配置
#define TASK2_PRIO      2 // 相同任务优先级                   
#define TASK2_STK_SIZE  128                 
TaskHandle_t Task2Task_Handler;          
void task2(void *pvParameters);
/*-------------------------------------------*/

2.2 任务实现

c 复制代码
/*------------------任务实现区----------------*/
void freertos_demo(void)
{
    lcd_show_string(10,47,220,24,24,"Task Demo",RED);
    // 创建START_TASK任务
    xTaskCreate((TaskFunction_t)start_task,        // 任务函数
                (const char*)"start_task",         // 任务名称
                (uint16_t)START_STK_SIZE,          // 任务堆栈大小
                (void*)NULL,                       // 传递给任务函数的参数
                (UBaseType_t)START_TASK_PRIO,      // 任务优先级
                (TaskHandle_t*)&StartTask_Handler);// 任务句柄
    // 开始任务调度
    vTaskStartScheduler();
}

// start_task函数实现
void start_task(void *pvParameters)
{
    taskENTER_CRITICAL(); // 进入临界区
    // 创建TASK1任务
    xTaskCreate((TaskFunction_t)task1,  
                (const char*)"task1",       
                (uint16_t)TASK1_STK_SIZE,   
                (void*)NULL,                
                (UBaseType_t)TASK1_PRIO,    
                (TaskHandle_t*)&Task1Task_Handler);
    xTaskCreate((TaskFunction_t)task2,  
                (const char*)"task2",       
                (uint16_t)TASK2_STK_SIZE,   
                (void*)NULL,                
                (UBaseType_t)TASK2_PRIO,    
                (TaskHandle_t*)&Task2Task_Handler);
    vTaskDelete(StartTask_Handler); // 开始任务已经完成自己使命,删除自己
    taskEXIT_CRITICAL(); // 退出临界区
}

// task1函数实现
void task1(void *pvParameters)
{
    uint32_t task1_num = 0;
    while(1)
    {
        taskENTER_CRITICAL(); // 进入临界区
        printf("任务1运行次数:%d\r\n",++task1_num);
        taskEXIT_CRITICAL(); // 退出临界区
                vTaskDelay(10);
    }
}

// task2函数实现
void task2(void *pvParameters)
{
    uint32_t task2_num = 0;
    while(1)
    {
        taskENTER_CRITICAL(); // 进入临界区
        printf("任务2运行次数:%d\r\n",++task2_num);
        taskEXIT_CRITICAL(); // 退出临界区
              vTaskDelay(10);
    }
}

2.3 主函数

c 复制代码
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "mpu.h"
#include "lcd.h"
#include "key.h"
#include "malloc.h"
#include "freertos_demo.h"

int main(void)
{
    sys_cache_enable();                 /* 打开L1-Cache */
    HAL_Init();                         /* 初始化HAL库 */
    sys_stm32_clock_init(240, 2, 2, 4); /* 设置时钟, 480Mhz */
    delay_init(480);                    /* 延时初始化 */
    usart_init(115200);                 /* 串口初始化为115200 */
    led_init();                         /* 初始化LED */
    mpu_memory_protection();            /* 保护相关存储区域 */
    lcd_init();                         /* 初始化LCD */
    key_init();                         /* 初始化按键 */
    my_mem_init(SRAMIN);                /* 初始化内部内存池(AXI) */
    freertos_demo();                    /* 运行FreeRTOS例程 */
}

第六章 FreeRTOS任务相关API函数介绍

1. 任务相关函数

函数 描述
uxTaskPriorityGet() 获取任务优先级
vTaskPrioritySet() 设置任务优先级
uxTaskGetSystemState() 获取所有任务的状态信息
vTaskGetInfo() 获取单个任务的状态信息
xTaskGetApplicationTaskTag() 获取任务 Tag
xTaskGetCurrentTaskHandle() 获取当前任务的任务句柄
xTaskGetHandle() 获取指定任务的任务句柄
xTaskGetIdleTaskHandle() 获取空闲任务的任务句柄
uxTaskGetStackHighWaterMark() 获取任务的栈历史剩余最小值
eTaskGetState() 获取任务状态
pcTaskGetName() 获取任务名
xTaskGetTickCount() 获取系统时钟节拍计数器的值
xTaskGetTickCountFromISR() 中断中获取系统使用节拍计数器的值
xTaskGetSchedulerState() 获取任务调度器状态
uxTaskGetNumberOfTasks() 获取系统中任务的数量
vTaskList() 以"表格"形式获取所有任务的信息
vTaskGetRunTimeStats() 获取任务的运行时间等信息
vTaskSetApplicationTaskTag() 设置任务 Tag
SetThreadLocalStoragePointer() 设置任务的独有数据记录数组指针
GetThreadLocalStoragePointer() 获取任务的独有数据记录数组指针

1.1 任务优先级相关函数

  • uxTaskPriorityGet()

    • 描述: 获取指定任务的当前优先级。
    • 函数原型: UBaseType_t uxTaskPriorityGet( TaskHandle_t xTask );
    • 参数: xTask - 任务句柄。如果传入 NULL,则返回调用此函数的任务的优先级。
    • 返回值: 任务的优先级。
    • 注意事项: 要使此函数可用,INCLUDE_uxTaskPriorityGet 需在 FreeRTOSConfig.h 中定义为 1。
  • vTaskPrioritySet()

    • 描述: 设置指定任务的优先级。
    • 函数原型: void vTaskPrioritySet( TaskHandle_t xTask, UBaseType_t uxNewPriority );
    • 参数:
      • xTask - 任务句柄。如果传入 NULL,则设置调用此函数的任务的优先级。
      • uxNewPriority - 新的优先级。
    • 注意事项: 要使此函数可用,INCLUDE_vTaskPrioritySet 需在 FreeRTOSConfig.h 中定义为 1。改变任务优先级可能会导致立即的上下文切换。

1.2 任务信息获取函数

  • uxTaskGetSystemState()

    • 描述: 获取系统中所有任务的状态信息,包括任务句柄、任务名称、优先级、堆栈使用情况等。这些信息存储在一个数组中。
    • 函数原型: UBaseType_t uxTaskGetSystemState( TaskStatus_t *pxTaskStatusArray, UBaseType_t uxArraySize, uint32_t *pulTotalRunTime );
    • 参数:
      • pxTaskStatusArray - 指向 TaskStatus_t 结构体数组的指针,用于存储任务状态信息。
      • uxArraySize - pxTaskStatusArray 数组的大小(即可以容纳多少个任务的状态信息)。
      • pulTotalRunTime - 可选参数,指向一个 uint32_t 变量的指针,用于返回自启动以来所有任务的总运行时间(如果 configGENERATE_RUNTIME_STATS 为 1)。
    • 返回值: 获取到的任务数量。
    • 注意事项: 要使此函数可用,configGENERATE_RUNTIME_STATS 需在 FreeRTOSConfig.h 中定义为 1。此函数会占用较长时间,因为它需要遍历所有任务。
  • vTaskGetInfo()

    • 描述: 获取单个任务的详细状态信息。
    • 函数原型: void vTaskGetInfo( TaskHandle_t xTask, TaskStatus_t *pxTaskStatus, BaseType_t xGetStackHighWaterMark, eTaskState eGetState );
    • 参数:
      • xTask - 要获取信息的任务句柄。
      • pxTaskStatus - 指向 TaskStatus_t 结构体的指针,用于存储任务信息。
      • xGetStackHighWaterMark - 如果为 pdTRUE,则计算并返回任务堆栈的历史剩余最小值。
      • eGetState - 如果为 pdTRUE,则返回任务的当前状态。
    • 注意事项: 要使此函数可用,INCLUDE_vTaskGetInfo 需在 FreeRTOSConfig.h 中定义为 1。
  • xTaskGetApplicationTaskTag()

    • 描述: 获取任务的应用层 Tag 值。Tag 是一个用户定义的指针,可以与任务关联,用于存储自定义数据或上下文。
    • 函数原型: TaskHookFunction_t xTaskGetApplicationTaskTag( TaskHandle_t xTask );
    • 参数: xTask - 任务句柄。如果传入 NULL,则返回当前任务的 Tag。
    • 返回值: 任务的应用层 Tag 值。
    • 注意事项: 要使此函数可用,configUSE_APPLICATION_TASK_TAG 需在 FreeRTOSConfig.h 中定义为 1。
  • xTaskGetCurrentTaskHandle()

    • 描述: 获取当前正在运行的任务的句柄。
    • 函数原型: TaskHandle_t xTaskGetCurrentTaskHandle( void );
    • 返回值: 当前任务的句柄。
    • 注意事项: 总是可用的,不需要额外配置。
  • xTaskGetHandle()

    • 描述: 根据任务名称获取任务的句柄。
    • 函数原型: TaskHandle_t xTaskGetHandle( const char *pcNameToQuery );
    • 参数: pcNameToQuery - 要查询的任务名称。
    • 返回值: 如果找到匹配的任务,则返回任务句柄;否则返回 NULL
    • 注意事项: 要使此函数可用,configINCLUDE_QUERY_HEAP_INFO 需在 FreeRTOSConfig.h 中定义为 1。任务名称必须是唯一的。
  • xTaskGetIdleTaskHandle()

    • 描述: 获取空闲任务的句柄。
    • 函数原型: TaskHandle_t xTaskGetIdleTaskHandle( void );
    • 返回值: 空闲任务的句柄。
    • 注意事项: 总是可用的,不需要额外配置。
  • uxTaskGetStackHighWaterMark()

    • 描述: 获取指定任务的堆栈历史剩余最小值("高水位线")。这个值表示任务运行期间堆栈的最小空闲空间。
    • 函数原型: UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask );
    • 参数: xTask - 任务句柄。如果传入 NULL,则返回调用此函数的任务的堆栈高水位线。
    • 返回值: 堆栈历史剩余最小值,以字为单位。
    • 注意事项: 要使此函数可用,INCLUDE_uxTaskGetStackHighWaterMark 需在 FreeRTOSConfig.h 中定义为 1。这个值对于调试堆栈溢出非常有用。
  • eTaskGetState()

    • 描述: 获取指定任务的当前状态(例如:就绪、运行、阻塞、挂起、删除)。
    • 函数原型: eTaskState eTaskGetState( TaskHandle_t xTask );
    • 参数: xTask - 任务句柄。
    • 返回值: 任务的当前状态,类型为 eTaskState 枚举。
    • 注意事项: 要使此函数可用,INCLUDE_eTaskGetState 需在 FreeRTOSConfig.h 中定义为 1。
  • pcTaskGetName()

    • 描述: 获取指定任务的名称。
    • 函数原型: char *pcTaskGetName( TaskHandle_t xTaskToQuery );
    • 参数: xTaskToQuery - 任务句柄。如果传入 NULL,则返回当前任务的名称。
    • 返回值: 指向任务名称字符串的指针。
    • 注意事项: 任务名称字符串是只读的。要使此函数可用,INCLUDE_pcTaskGetName 需在 FreeRTOSConfig.h 中定义为 1。

1.3 系统时间与调度器状态相关函数

  • xTaskGetTickCount()

    • 描述: 获取 FreeRTOS 系统自启动以来的时钟节拍计数器的值。
    • 函数原型: TickType_t xTaskGetTickCount( void );
    • 返回值: 系统时钟节拍计数器的当前值。
    • 注意事项: 此函数通常用于实现延时和超时机制。
  • xTaskGetTickCountFromISR()

    • 描述: 在中断服务例程 (ISR) 中获取 FreeRTOS 系统自启动以来的时钟节拍计数器的值。这是 xTaskGetTickCount() 的中断安全版本。
    • 函数原型: TickType_t xTaskGetTickCountFromISR( void );
    • 返回值: 系统时钟节拍计数器的当前值。
    • 注意事项: 只能在 ISR 中调用。
  • xTaskGetSchedulerState()

    • 描述: 获取 FreeRTOS 调度器的当前状态。
    • 函数原型: BaseType_t xTaskGetSchedulerState( void );
    • 返回值: 调度器状态(例如:调度器已启动、调度器已挂起)。
    • 注意事项: 总是可用的,不需要额外配置。
  • uxTaskGetNumberOfTasks()

    • 描述: 获取系统中当前存在的任务数量(包括空闲任务)。
    • 函数原型: UBaseType_t uxTaskGetNumberOfTasks( void );
    • 返回值: 系统中任务的总数量。
    • 注意事项: 总是可用的,不需要额外配置。

1.4 调试与统计相关函数

  • vTaskList()

    • 描述: 以格式化的字符串("表格"形式)获取所有任务的信息,包括任务名称、状态、优先级、堆栈使用情况等,以便于调试和监控。
    • 函数原型: void vTaskList( char *pcWriteBuffer );
    • 参数: pcWriteBuffer - 指向字符缓冲区的指针,用于存储格式化后的任务信息。
    • 注意事项: 要使此函数可用,configUSE_STATS_FORMATTING_FUNCTIONSconfigSUPPORT_DYNAMIC_ALLOCATION 需在 FreeRTOSConfig.h 中定义为 1。此函数会占用较长时间,且需要足够大的缓冲区。
  • vTaskGetRunTimeStats()

    • 描述: 获取所有任务的运行时间统计信息,前提是已配置了运行时间统计功能。
    • 函数原型: void vTaskGetRunTimeStats( char *pcWriteBuffer );
    • 参数: pcWriteBuffer - 指向字符缓冲区的指针,用于存储格式化后的运行时间统计信息。
    • 注意事项: 要使此函数可用,configGENERATE_RUNTIME_STATSconfigUSE_STATS_FORMATTING_FUNCTIONS 需在 FreeRTOSConfig.h 中定义为 1,并且需要一个硬件定时器来提供运行时间计数。

1.5 任务本地存储与 Tag 相关函数

  • vTaskSetApplicationTaskTag()

    • 描述: 设置指定任务的应用层 Tag 值。
    • 函数原型: void vTaskSetApplicationTaskTag( TaskHandle_t xTask, TaskHookFunction_t pxHookFunction );
    • 参数:
      • xTask - 任务句柄。如果传入 NULL,则设置当前任务的 Tag。
      • pxHookFunction - 要设置的 Tag 值(通常是一个函数指针或任意指针)。
    • 注意事项: 要使此函数可用,configUSE_APPLICATION_TASK_TAG 需在 FreeRTOSConfig.h 中定义为 1。
  • SetThreadLocalStoragePointer()

    • 描述: 设置任务的线程本地存储 (TLS) 指针。FreeRTOS 为每个任务提供了一个独立的 TLS 数组,每个元素可以存储一个指向任意数据的指针。
    • 函数原型: void SetThreadLocalStoragePointer( TaskHandle_t xTaskToSet, BaseType_t xIndex, void *pvValue );
    • 参数:
      • xTaskToSet - 要设置 TLS 指针的任务句柄。如果传入 NULL,则设置当前任务的 TLS 指针。
      • xIndex - TLS 数组的索引。
      • pvValue - 要存储的指针值。
    • 注意事项: 要使此函数可用,configTHREAD_LOCAL_STORAGE_POINTERS 需在 FreeRTOSConfig.h 中定义为大于 0。
  • GetThreadLocalStoragePointer()

    • 描述: 获取任务的线程本地存储 (TLS) 指针。
    • 函数原型: void *GetThreadLocalStoragePointer( TaskHandle_t xTaskToQuery, BaseType_t xIndex );
    • 参数:
      • xTaskToQuery - 要查询 TLS 指针的任务句柄。如果传入 NULL,则查询当前任务的 TLS 指针。
      • xIndex - TLS 数组的索引。
    • 返回值: 存储在指定 TLS 索引处的指针值。
    • 注意事项: 要使此函数可用,configTHREAD_LOCAL_STORAGE_POINTERS 需在 FreeRTOSConfig.h 中定义为大于 0。

2. 任务状态与信息查询测试

2.1 任务配置

c 复制代码
/*----------------任务配置区-----------------*/
// START_TASK配置
#define START_TASK_PRIO 1           // 任务优先级 
#define START_STK_SIZE  128         // 任务堆栈大小 
TaskHandle_t StartTask_Handler;     // 任务句柄 
void start_task(void *pvParameters);// 任务函数 

// TASK1配置
#define TASK1_PRIO      2                   
#define TASK1_STK_SIZE  128                 
TaskHandle_t Task1Task_Handler;          
void task1(void *pvParameters);
/*-------------------------------------------*/

2.2 任务实现

c 复制代码
/*------------------任务实现区----------------*/
void freertos_demo(void)
{
    lcd_show_string(10,47,220,24,24,"Task Demo",RED);
    // 创建START_TASK任务
    xTaskCreate((TaskFunction_t)start_task,        // 任务函数
                (const char*)"start_task",         // 任务名称
                (uint16_t)START_STK_SIZE,          // 任务堆栈大小
                (void*)NULL,                       // 传递给任务函数的参数
                (UBaseType_t)START_TASK_PRIO,      // 任务优先级
                (TaskHandle_t*)&StartTask_Handler);// 任务句柄
    // 开始任务调度
    vTaskStartScheduler();
}

// start_task函数实现
void start_task(void *pvParameters)
{
    taskENTER_CRITICAL(); // 进入临界区
    // 创建TASK1任务
    xTaskCreate((TaskFunction_t)task1,  
                (const char*)"task1",       
                (uint16_t)TASK1_STK_SIZE,   
                (void*)NULL,                
                (UBaseType_t)TASK1_PRIO,    
                (TaskHandle_t*)&Task1Task_Handler);
    vTaskDelete(StartTask_Handler); // 开始任务已经完成自己使命,删除自己
    taskEXIT_CRITICAL(); // 退出临界区
}

// task1函数实现
void task1(void *pvParameters)
{
    uint32_t i = 0;
    UBaseType_t task_num = 0;
    TaskStatus_t *status_array = NULL;
    TaskHandle_t task_handle = NULL;
    TaskStatus_t *task_info = NULL;
    eTaskState task_state = eInvalid;
    char *task_state_str = NULL;
    char *task_info_buf = NULL;

    /*--------------------函数uxTaskGetSystemState()的使用------==--------------*/
    printf("-------------Step1 函数uxTaskGetSystemState()的使用-------------\r\n");
    task_num = uxTaskGetNumberOfTasks(); // 获取系统中任务数量
    status_array = mymalloc(SRAMIN, task_num*sizeof(TaskStatus_t)); // 内存分配
    task_num = uxTaskGetSystemState((TaskStatus_t*)status_array, // 任务状态信息
                                    (UBaseType_t)task_num, // buffer大小
                                    (uint32_t*)NULL); // 不获取任务运行时间信息
    printf("任务名\t\t优先级\t\t任务编号\r\n");
    for(i=0;i<task_num;i++)
    {
        printf("%s\t%s%ld\t\t%ld\r\n",
        status_array[i].pcTaskName,
        strlen(status_array[i].pcTaskName) > 7 ? "": "\t",
        status_array[i].uxCurrentPriority,
        status_array[i].xTaskNumber);
    }   
    myfree(SRAMIN, status_array);     
    printf("-----------------------------END-------------------------------\r\n");
    printf("按下KEYO以继续!!!\r\n");
    while(key_scan(0) != KEY0_PRES)
    {
        vTaskDelay(10);
    }

    /*-------------------------函数vTaskGetInfo()的使用--------------------------*/
    printf("-----------------Step2 函数vTaskGetInfo()的使用------------------\r\n");
    task_info = mymalloc(SRAMIN, sizeof(TaskStatus_t)); // 内存分配
    task_handle = xTaskGetHandle("task1"); // 获取任务句柄
    vTaskGetInfo((TaskHandle_t)task_handle, // 任务句柄
                 (TaskStatus_t*)task_info,  // 任务状态信息
                 (BaseType_t)pdTRUE,        // 允许统计任务堆栈历史最小值
                 (eTaskState)eInvalid);     // 任务状态
    printf("任务名:\t\t\t%s\r\n", task_info->pcTaskName);
    printf("任务编号:\t\t%ld\r\n", task_info->xTaskNumber);
    printf("任务壮态:\t\t%d\r\n", task_info->eCurrentState);
    printf("任务当前优先级:\t\t%ld\r\n", task_info->uxCurrentPriority);
    printf("任务基优先级:\t\t%ld\r\n", task_info->uxBasePriority);
    printf("任务堆栈基地址:\t\t0x%p\r\n", task_info->pxStackBase);
    printf("任务堆栈历史剩余最小值:\t%d\r\n", task_info->usStackHighWaterMark);
    myfree(SRAMIN, task_info);
    printf("-----------------------------END-------------------------------\r\n");
    printf("按下KEYO以继续!!!\r\n");
    while(key_scan(0) != KEY0_PRES)
    {
        vTaskDelay(10);
    }

    /*-------------------------函数eTaskGetState()的使用--------------------------*/
    printf("-----------------Step3 函数eTaskGetState()的使用------------------\r\n");
    task_state_str = mymalloc(SRAMIN, 10); // 内存分配
    task_handle = xTaskGetHandle("task1");
    task_state = eTaskGetState(task_handle);
    sprintf(task_state_str, task_state == eRunning ? "Runing" :
                            task_state == eReady ? "Ready" :
                            task_state == eBlocked ? "Blocked" :
                            task_state == eSuspended ? "Suspended" :
                            task_state == eDeleted ? "Deleted" :
                            task_state == eInvalid ? "Invalid" :
                                                     "");
    printf("任务状态值: %d,对应状态为: %s\r\n", task_state, task_state_str);
    myfree(SRAMIN, task_state_str);
    printf("-----------------------------END-------------------------------\r\n");
    printf("按下KEYO以继续!!!\r\n");
    while(key_scan(0) != KEY0_PRES)
    {
        vTaskDelay(10);
    }

    /*---------------------------函数vTaskList()的使用--------------------------*/
    printf("------------------Step4 函数vTaskList()的使用-------------------\r\n");
    task_info_buf = mymalloc(SRAMIN, 500);
    vTaskList(task_info_buf); // 打印任务列表
    printf("任务名\t\t状态\t优先级\t剩余栈\t任务序号\r\n");
    printf("%s\r\n", task_info_buf);
    myfree(SRAMIN, task_info_buf);
    printf("-----------------------------END-------------------------------\r\n");
    while(1)
    {
        vTaskDelay(10);
    }
}
/*-------------------------------------------*/

2.3 主函数

c 复制代码
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "mpu.h"
#include "lcd.h"
#include "key.h"
#include "malloc.h"
#include "freertos_demo.h"

int main(void)
{
    sys_cache_enable();                 /* 打开L1-Cache */
    HAL_Init();                         /* 初始化HAL库 */
    sys_stm32_clock_init(240, 2, 2, 4); /* 设置时钟, 480Mhz */
    delay_init(480);                    /* 延时初始化 */
    usart_init(115200);                 /* 串口初始化为115200 */
    led_init();                         /* 初始化LED */
    mpu_memory_protection();            /* 保护相关存储区域 */
    lcd_init();                         /* 初始化LCD */
    key_init();                         /* 初始化按键 */
    my_mem_init(SRAMIN);                /* 初始化内部内存池(AXI) */
    freertos_demo();                    /* 运行FreeRTOS例程 */
}

3. 任务运行时间统计测试

3.1 定时器配置

c 复制代码
#include "btim.h"
#include "FreeRTOS.h"

TIM_HandleTypeDef g_tim3_handle;        /* 定时器3句柄 */

uint32_t FreeRTOSRunTimeTicks;          /* FreeRTOS时间统计所用的节拍计数器 */

void ConfigureTimeForRunTimeStats(void)
{
    FreeRTOSRunTimeTicks = 0;           /* 节拍计数器初始化为0 */
    btim_tim3_int_init(10-1, 2400-1);   /* 初始化TIM3 */
}

/**
 * @brief       基本定时器TIM3定时中断初始化函数
 * @note
 *              基本定时器的时钟来自APB1,当D2PPRE1≥2分频的时候
 *              基本定时器的时钟为APB1时钟的2倍, 而APB1为120M, 所以定时器时钟 = 240Mhz
 *              定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
 *              Ft=定时器工作频率,单位:Mhz
 *
 * @param       arr: 自动重装值。
 * @param       psc: 时钟预分频数
 * @retval      无
 */
void btim_tim3_int_init(uint16_t arr, uint16_t psc)
{
    g_tim3_handle.Instance = BTIM_TIM3_INT;                      /* 通用定时器3 */
    g_tim3_handle.Init.Prescaler = psc;                          /* 分频 */
    g_tim3_handle.Init.CounterMode = TIM_COUNTERMODE_UP;         /* 向上计数器 */
    g_tim3_handle.Init.Period = arr;                             /* 自动装载值 */
    g_tim3_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;   /* 时钟分频因子 */
    HAL_TIM_Base_Init(&g_tim3_handle);

    HAL_TIM_Base_Start_IT(&g_tim3_handle);   /* 使能定时器3和定时器3更新中断 */
}

/**
 * @brief       定时器底层驱动,开启时钟,设置中断优先级
                此函数会被HAL_TIM_Base_Init()函数调用
 * @param       无
 * @retval      无
 */
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == BTIM_TIM3_INT)
    {
        BTIM_TIM3_INT_CLK_ENABLE();                      /* 使能TIM3时钟 */
        HAL_NVIC_SetPriority(BTIM_TIM3_INT_IRQn, 4, 0);  /* 设置中断优先级,抢占优先级4,子优先级0 */
        HAL_NVIC_EnableIRQ(BTIM_TIM3_INT_IRQn);          /* 开启ITM3中断 */
    }
}

/**
 * @brief       定时器中断服务函数
 * @param       无
 * @retval      无
 */
void BTIM_TIM3_INT_IRQHandler(void)
{
    HAL_TIM_IRQHandler(&g_tim3_handle);
}

/**
 * @brief       定时器更新中断回调函数
 * @param       htim:定时器句柄指针
 * @retval      无
 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim == (&g_tim3_handle))
    {
        FreeRTOSRunTimeTicks++;
    }
}

3.1 任务配置

c 复制代码
/*----------------任务配置区-----------------*/
// START_TASK配置
#define START_TASK_PRIO 1           // 任务优先级 
#define START_STK_SIZE  128         // 任务堆栈大小 
TaskHandle_t StartTask_Handler;     // 任务句柄 
void start_task(void *pvParameters);// 任务函数 

// TASK1配置
#define TASK1_PRIO      2                   
#define TASK1_STK_SIZE  128                 
TaskHandle_t Task1Task_Handler;          
void task1(void *pvParameters);

// TASK2配置
#define TASK2_PRIO      3                   
#define TASK2_STK_SIZE  128                 
TaskHandle_t Task2Task_Handler;          
void task2(void *pvParameters);

// TASK3配置
#define TASK3_PRIO      4                   
#define TASK3_STK_SIZE  128                 
TaskHandle_t Task3Task_Handler;          
void task3(void *pvParameters);                
/*-------------------------------------------*/

3.3 任务实现

c 复制代码
/*------------------任务实现区----------------*/
void freertos_demo(void)
{
    lcd_show_string(10,47,220,24,24,"Get Run Time Stats",RED);
    lcd_draw_rectangle(5, 110, 115, 314, BLACK);
    lcd_draw_rectangle(125, 110, 234, 314, BLACK);
    lcd_draw_line(5, 130, 115, 130, BLACK);
    lcd_draw_line(125, 130, 234, 130, BLACK);
    lcd_show_string(15, 111, 110, 16, 16, "Task1: 000", BLUE);
    lcd_show_string(135, 111, 110, 16, 16, "Task2: 000", BLUE);
    // 创建START_TASK任务
    xTaskCreate((TaskFunction_t)start_task,        // 任务函数
                (const char*)"start_task",         // 任务名称
                (uint16_t)START_STK_SIZE,          // 任务堆栈大小
                (void*)NULL,                       // 传递给任务函数的参数
                (UBaseType_t)START_TASK_PRIO,      // 任务优先级
                (TaskHandle_t*)&StartTask_Handler);// 任务句柄
    // 开始任务调度
    vTaskStartScheduler();
}

// start_task函数实现
void start_task(void *pvParameters)
{
    taskENTER_CRITICAL(); // 进入临界区
    // 创建TASK1任务
    xTaskCreate((TaskFunction_t)task1,  
                (const char*)"task1",       
                (uint16_t)TASK1_STK_SIZE,   
                (void*)NULL,                
                (UBaseType_t)TASK1_PRIO,    
                (TaskHandle_t*)&Task1Task_Handler);
    // 创建TASK2任务
    xTaskCreate((TaskFunction_t)task2,  
                (const char*)"task2",       
                (uint16_t)TASK2_STK_SIZE,   
                (void*)NULL,                
                (UBaseType_t)TASK2_PRIO,    
                (TaskHandle_t*)&Task2Task_Handler);
    // 创建TASK3任务
    xTaskCreate((TaskFunction_t)task3,  
                (const char*)"task3",       
                (uint16_t)TASK3_STK_SIZE,   
                (void*)NULL,                
                (UBaseType_t)TASK3_PRIO,    
                (TaskHandle_t*)&Task3Task_Handler);
    vTaskDelete(StartTask_Handler); // 开始任务已经完成自己使命,删除自己
    taskEXIT_CRITICAL(); // 退出临界区
}

// task1函数实现
void task1(void *pvParameters)
{
    uint32_t task1_num = 0;
    while(1)
    {
        lcd_fill(6,131,114,313,lcd_discolor[++task1_num%11]);
        lcd_show_xnum(71,111,task1_num,3,16,0x80,BLUE);
        vTaskDelay(1000);
    }
}

// task2函数实现
void task2(void *pvParameters)
{
    uint32_t task2_num = 0;
    while(1)
    {
        lcd_fill(126,131,233,313,lcd_discolor[11-(++task2_num%11)]);
        lcd_show_xnum(191,111,task2_num,3,16,0x80,BLUE);
        vTaskDelay(1000);
    }
}

// task3函数实现
void task3(void *pvParameters)
{
    uint8_t key_value = 0;
    char *runtime_info = NULL;
    while(1)
    {
        key_value = key_scan(0);
        switch(key_value)
        {
            case KEY0_PRES:
            {
                runtime_info = mymalloc(SRAMIN ,100);
                vTaskGetRunTimeStats(runtime_info);
                printf("任务名\t\t运行时间\t运行所占百分比\r\n");
                printf("%s\r\n", runtime_info);
                myfree(SRAMIN, runtime_info);
                break;
            }
            default:
            {
                break;
            }
        }
        vTaskDelay(10);
    }
}
/*-------------------------------------------*/

3.4 主函数

c 复制代码
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "mpu.h"
#include "lcd.h"
#include "key.h"
#include "malloc.h"
#include "freertos_demo.h"

int main(void)
{
    sys_cache_enable();                 /* 打开L1-Cache */
    HAL_Init();                         /* 初始化HAL库 */
    sys_stm32_clock_init(240, 2, 2, 4); /* 设置时钟, 480Mhz */
    delay_init(480);                    /* 延时初始化 */
    usart_init(115200);                 /* 串口初始化为115200 */
    led_init();                         /* 初始化LED */
    mpu_memory_protection();            /* 保护相关存储区域 */
    lcd_init();                         /* 初始化LCD */
    key_init();                         /* 初始化按键 */
    my_mem_init(SRAMIN);                /* 初始化内部内存池(AXI) */
    freertos_demo();                    /* 运行FreeRTOS例程 */
}

文中完整工程下载:https://github.com/hazy1k/FreeRTOS-Quick-Start-Guide/tree/main/2.code