本篇文章基于STM32标准库、ARM_CM3架构和heap_4.c内存管理(FreeRTOS)编写,适用FreeRTOS入门后参考。
FreeRTOS的 核心文件
为了便于FreeRTOS的移植,可以直接创建一个FreeRTOS的文件夹用于存放需要移植的所有文件,而对于FreeRTOS来说,同样包含后缀为".c"的内核源码文件和".h"的公共头文件以及两个移植层文件。在FreeRTOS文件夹下创建名为src的源码文件夹存放".c"文件、创建名为inc的源码文件夹存放".h"文件和创建名为port的源码文件夹存放移植层文件。
src文件夹
tasks.c:任务管理核心:创建/删除任务、调度器启动、任务切换、延时、优先级管理。
queue.c:队列与信号量底层实现:消息队列、二值信号量、互斥量、计数信号量都基于此文件实现。
list.c:内核链表:FreeRTOS 内部所有任务调度、队列管理都用这个双链表实现。
timers.c:软件定时器:周期或一次性回调函数,依赖queue.c 和tasks.c 。
event_groups.c:事件标志组:等待多个事件同时发生,依赖queue.c。
croutine.c:协程(Co-routine):轻量级任务,适用于资源极小的设备。
inc文件夹
FreeRTOS.h:内核入口:必须最先包含。包含所有内核配置和 API 声明。
task.h:任务相关 API:xTaskCreate、vTaskSuspend、vTaskDelay等。
queue.h:队列与信号量API:xOueueCreate、
xQueueSend、xSemaphoreTake等。
semphr.h:信号量 API:二值信号量、互斥量、计数信号量(基于queue.h 实现)
timers.h:软件定时器 API:xTimerCreate、xTimerStart。
event_groups.h:事件标志组 API:xEventGroupCreate、xEventGroupSetBits。
list.h:内核链表 API:vListInitialise、vListInsert通常不直接调用。
portable.h:移植抽象层:定义栈类型、数据类型等。
projdefs.h:基础类型定义:TickType_t、BaseType_t等。
stack_macros.h/StackMacros.h:栈溢出检测宏configCHECK_FOR_STACK_OVERFLOW依赖。
atomic.h:原子操作:用于 SMP(多核)场景,单核可忽略。
message_buffer.h/stream_buffer.h:消息缓冲区:用于不定长数据流(如串口数据)。
mpu_prototypes.h /mpu_wrappers.h:MPU保护:用于带内存保护单元的芯片(如ARM Cortex-M3/M4带MPU)
deprecated_definitions.h:废弃宏定义:兼容旧版本。
croutine.h:协程 API:与croutine.c配套。
port文件夹
port.c:移植核心:任务切换汇编代码、中断管理、SysTick 配置、栈初始化。
heap_4.c:内存管理:内核动态分配内存(任务栈、队列、信号量等)。FreeRTOS提供5种heap实现,推荐 heap_4.c。
其他文件
文件stm32f10x_it.c
stm32f10x_it.c是STM32 标准库中的中断服务函数(ISR)文件,包含了所有外设中断和系统中断的弱定义空函数。stm32f10x_it.c中的SysTick_Handler、PendSV_Handler、SVC Handler中断在FreeRTOS都有对应的函数接管(xPortSysTickHandler()、vPortPendSVHandler()、vPortSVCHandler()),所以需要注释或删除掉SysTick_Handler、PendSV_Handler、SVC Handler。如果不修改,FreeRTOS 无法获得系统心跳,任务无法切换,系统将卡死在第一个任务中。
FreeRTOSConfig.h文件
FreeRTOSConfig.h头文件是 FreeRTOS 的核心配置文件,不是内核源码的一部分,FreeRTOS 源码本身不包含它。而是专门为项目提供的裁剪和配置头文件,这个配置文件由自己提供。它的作用是告诉 FreeRTOS 内核"项目的的系统有多大、有多少任务、优先级怎么划分、使用哪些功能、如何与硬件交互"。为了方便移植可以放置在inc文件夹里,并在 FreeRTOS.h 中通过 #include "FreeRTOSConfig.h" 引用。
FreeRTOSConfig.h 文件
cpp
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H
/* =========================== 基础配置 =========================== */
/* 抢占式调度:1 = 启用(推荐),0 = 协作式 */
#define configUSE_PREEMPTION 1
/* 空闲任务钩子:1 = 启用,0 = 禁用(不常用) */
#define configUSE_IDLE_HOOK 0
/* 系统滴答钩子:1 = 启用,0 = 禁用(不常用) */
#define configUSE_TICK_HOOK 0
/* CPU 主频(单位:Hz),必须与你的系统时钟配置一致 */
#define configCPU_CLOCK_HZ ( ( unsigned long ) 72000000 )
/* 系统心跳频率(单位:Hz),通常设为 1000(即 1ms 一个 tick) */
#define configTICK_RATE_HZ ( ( TickType_t )1000 )
/* 最大任务优先级数(0 ~ configMAX_PRIORITIES-1),建议 5~10 */
#define configMAX_PRIORITIES (5)
/* 最小任务栈大小(单位:字,即 4 字节),用于空闲任务 */
#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 128 )
/* 总堆大小(单位:字节),用于动态分配内存(任务栈、队列等) */
#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 17 * 1024 ) )
/* 任务名最大长度(含 '\0') */
#define configMAX_TASK_NAME_LEN (16)
/* =========================== 功能开关 =========================== */
/* 互斥量:1 = 启用(推荐) */
#define configUSE_MUTEXES 1
/* 递归互斥量:1 = 启用(需要时开启) */
#define configUSE_RECURSIVE_MUTEXES 0
/* 计数信号量:1 = 启用(需要时开启) */
#define configUSE_COUNTING_SEMAPHORES 1
/* 队列集:1 = 启用(需要时开启) */
#define configUSE_QUEUE_SETS 0
/* 任务通知:1 = 启用(推荐,轻量级通信) */
#define configUSE_TASK_NOTIFICATIONS 1
/* 软件定时器:1 = 启用(需要时开启) */
#define configUSE_TIMERS 1
#define configTIMER_TASK_PRIORITY (2) // 定时器任务优先级
#define configTIMER_QUEUE_LENGTH (10) // 定时器命令队列长度
#define configTIMER_TASK_STACK_DEPTH configMINIMAL_STACK_SIZE
/* 事件标志组:1 = 启用(需要时开启) */
#define configUSE_16_BIT_TICKS 0 // 0=32位tick,1=16位tick
/* =========================== 中断配置 =========================== */
/*
说明:STM32 使用 4 位中断优先级(即 0~15),数值越小优先级越高。
configMAX_SYSCALL_INTERRUPT_PRIORITY 决定了哪些中断可以调用带 FromISR 后缀的 API。
例如,设置为 5,则优先级 0~4 的中断可以调用 API,优先级 5~15 的中断则不能调用 FreeRTOS API。*/
/* 最低中断优先级(STM32 使用 4 位优先级,范围 0~15) */
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15
/* 最高可调用 API 的中断优先级 */
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
/* 内核中断优先级(用于 SysTick 和 PendSV) */
#define configKERNEL_INTERRUPT_PRIORITY 255
/* 可调用 FreeRTOS API 的最高中断优先级 */
#define configMAX_SYSCALL_INTERRUPT_PRIORITY 191 /* equivalent to 0xb0, or priority 11. */
/* ==================== 内核中断优先级配置 ==================== */
/* 内核中断优先级(库级):STM32 使用 4 位优先级,范围 0~15 */
#define configLIBRARY_KERNEL_INTERRUPT_PRIORITY 15
/* ==================== 中断向量映射(必须配置) ==================== */
/* PendSV 中断:FreeRTOS 的任务切换使用,必须重映射到 PendSV_Handler */
#define xPortPendSVHandler PendSV_Handler
/* SVC 中断:FreeRTOS 的系统服务调用,必须重映射到 SVC_Handler */
#define vPortSVCHandler SVC_Handler
/* SysTick 中断:FreeRTOS 的系统心跳,必须重映射到 SysTick_Handler */
#define xPortSysTickHandler SysTick_Handler
/* =========================== 调试与优化 =========================== */
/* 栈溢出检测:0=禁用,1=启用(简单版),2=启用(增强版)*/
#define configCHECK_FOR_STACK_OVERFLOW 0
/* 内存分配失败钩子:1 = 启用(可选) */
#define configUSE_MALLOC_FAILED_HOOK 0
/* 任务运行时间统计:1 = 启用(需额外配置定时器) */
#define configGENERATE_RUN_TIME_STATS 0
/* 统计格式化函数:1 = 启用(提供 vTaskList() 等) */
#define configUSE_STATS_FORMATTING_FUNCTIONS 0
/* 应用任务标签:1 = 启用(不常用) */
#define configUSE_APPLICATION_TASK_TAG 0
/* 动态内存分配失败钩子(可选) */
#define configUSE_MALLOC_FAILED_HOOK 0
/* =========================== 低功耗 =========================== */
/* Tickless 低功耗模式:1 = 启用 */
#define configUSE_TICKLESS_IDLE 0
/* 进入低功耗模式的最小空闲时间(单位:tick) */
#define configEXPECTED_IDLE_TIME_BEFORE_SLEEP 2
/* ==================== API 裁剪(INCLUDE 系列) ==================== */
/* 任务优先级设置 API:1 = 启用(允许调用 vTaskPrioritySet()) */
#define INCLUDE_vTaskPrioritySet 1
/* 任务优先级获取 API:1 = 启用(允许调用 uxTaskPriorityGet()) */
#define INCLUDE_uxTaskPriorityGet 1
/* 任务删除 API:1 = 启用(允许调用 vTaskDelete()) */
#define INCLUDE_vTaskDelete 1
/* 清理已删除任务资源 API(已废弃,保持 0) */
#define INCLUDE_vTaskCleanUpResources 0
/* 任务挂起/恢复 API:1 = 启用(允许调用 vTaskSuspend() 和 vTaskResume()) */
#define INCLUDE_vTaskSuspend 1
/* 绝对延时 API:1 = 启用(允许调用 vTaskDelayUntil(),用于固定周期任务) */
#define INCLUDE_vTaskDelayUntil 1
/* 相对延时 API:1 = 启用(允许调用 vTaskDelay(),用于普通延时) */
#define INCLUDE_vTaskDelay 1
/* 获取调度器状态 API:1 = 启用(允许调用 xTaskGetSchedulerState()) */
#define INCLUDE_xTaskGetSchedulerState 1
/* 获取任务栈剩余空间:1 = 启用(允许调用 uxTaskGetStackHighWaterMark()) */
#define INCLUDE_uxTaskGetStackHighWaterMark 1
/* 获取任务优先级 API:1 = 启用(允许调用 uxTaskPriorityGet()) */
#define INCLUDE_uxTaskPriorityGet 1
/* 设置任务优先级 API:1 = 启用(允许调用 vTaskPrioritySet()) */
#define INCLUDE_vTaskPrioritySet 1
/* 获取调度器状态 API:1 = 启用(允许调用 xTaskGetSchedulerState()) */
#define INCLUDE_xTaskGetSchedulerState 1
/* 获取当前任务句柄 API:1 = 启用(允许调用 xTaskGetCurrentTaskHandle()) */
#define INCLUDE_xTaskGetCurrentTaskHandle 1
/* ==================== 追踪与调试配置 ==================== */
/* 追踪功能:1 = 启用(支持可视化追踪调试),0 = 禁用(节省资源) */
#define configUSE_TRACE_FACILITY 0
/* ==================== 空闲任务调度策略 ==================== */
/* 空闲任务让步:1 = 启用(同优先级任务轮流执行),0 = 禁用 */
#define configIDLE_SHOULD_YIELD 1
#endif /* FREERTOS_CONFIG_H */
FreeRTOS的main.c结构
cpp
#include "stm32f10x.h"
#include "FreeRTOS.h"
#include "Task.h"
#include "queue.h"
#include "bsp_led.h" // 包含板级 LED 驱动头文件
//板级硬件初始化函数
void BSP_Init(void)
{
LED_Init();
}
//定义任务句柄变量
TaskHandle_t myTaskHandler;
//任务栈大小
#define MYTASK_STACK_SIZE 128
//优先级设置,值越大优先级越高
#define MYTASK_PRIORITY 4
/**
* @brief FreeRTOS 任务函数
* @param arg 任务参数,此处未使用,传入了 NULL
* @note 任务函数必须符合以下特征:
* 1. 返回类型为 void
* 2. 参数类型为 void*
* 3. 通常是一个无限循环,不能正常返回(如果返回,必须调用 vTaskDelete 删除自身)
*/
void myTask(void *pvParameters)
{
while(1)
{
LED1_Toggle();
vTaskDelay(pdMS_TO_TICKS(500));
}
}
/**
* @brief 主函数
* @note FreeRTOS 程序的标准入口,执行顺序:
* 1. 硬件初始化
* 2. 创建任务
* 3. 启动调度器(永不返回)
*/
int main(void)
{
BSP_Init();
/* 创建任务
* 参数1:任务函数入口
* 参数2:任务名称(用于调试)
* 参数3:任务栈深度,单位是字(word),STM32 中 1 word = 4 字节
* 参数4:NULL 任务参数,传递给任务函数
* 参数5:任务优先级,数值越大优先级越高
* 参数6:任务句柄,用于后续操作任务
*/
xTaskCreate(myTask, "myTask", MYTASK_STACK_SIZE, NULL, MYTASK_PRIORITY, &myTaskHandler);
/* 启动 FreeRTOS 调度器
* 此函数会:
* 初始化 SysTick 作为系统心跳
* 创建空闲任务(和可选的定时器任务)
* 从就绪列表中选出最高优先级的任务开始执行
* 此函数永远不会返回!
*/
vTaskStartScheduler();
while (1);
}
任务的创建、删除、挂起和恢复
cpp
//头文件引用
#include "task.h"
//FreeRTOSConfig.h 文件配置
/* 任务挂起/恢复 API:1 = 启用(允许调用 vTaskSuspend() 和 vTaskResume()) */
#define INCLUDE_vTaskSuspend 1
/* 任务删除 API:1 = 启用(允许调用 vTaskDelete()) */
#define INCLUDE_vTaskDelete 1
内容
- 创建任务:将项目实现的功能拆成多个独立任务,每个任务有独立的栈和优先级。
- 删除任务:回收任务占用的内存。
- 挂起任务:临时暂停任务执行,而不删除任务。
- 恢复任务:使挂起的任务恢复正常运行。
cpp
#include "FreeRTOS.h"
#include "task.h"
#include "bsp_led.h" // 包含板级 LED 驱动头文件
//定义任务句柄变量
TaskHandle_t xTask1Handle;
//任务栈大小
#define TASK1_STACK_SIZE 128
#define TASK2_STACK_SIZE 128
//优先级设置
#define TASK1_PRIORITY 1
#define TASK2_PRIORITY 2
//板级硬件初始化函数
void BSP_Init(void)
{
LED_Init();
}
//任务一
void vTask1(void *pvParameters)
{
while(1)
{
LED1_Toggle();
vTaskDelay(pdMS_TO_TICKS(500));
}
}
//任务二
void vTask2(void *pvParameters)
{
while(1)
{
// 挂起任务一2秒后恢复
vTaskSuspend(xTask1Handle);
vTaskDelay(pdMS_TO_TICKS(2000));
vTaskResume(xTask1Handle);
//删除任务本身
vTaskDelete(NULL);
}
}
int main(void)
{
BSP_Init();
xTaskCreate(vTask1, "Task1", TASK1_STACK_SIZE, NULL, TASK1_PRIORITY, &xTask1Handle);
xTaskCreate(vTask2, "Task2", TASK2_STACK_SIZE, NULL, TASK2_PRIORITY, NULL);
vTaskStartScheduler();
while(1);
}
调度器的挂起和恢复
cpp
//头文件引用
#include "task.h"
cpp
void vTask(void *pvParameters)
{
while(1)
{
vTaskSuspendAll(); // 禁止调度
// 批量修改共享数据
for(int i = 0; i < 100; i++) {
shared_array[i] = i;
}
xTaskResumeAll(); // 恢复调度
vTaskDelay(pdMS_TO_TICKS(100));
}
}
相/绝对延时
cpp
//头文件引用
#include "task.h"
cpp
//FreeRTOSConfig.h 文件配置
/* 绝对延时 API:1 = 启用(允许调用 vTaskDelayUntil(),用于固定周期任务) */
#define INCLUDE_vTaskDelayUntil 1
/* 相对延时 API:1 = 启用(允许调用 vTaskDelay(),用于普通延时) */
#define INCLUDE_vTaskDelay 1
示例:
cpp
// 相对延时(软延时)
vTaskDelay(pdMS_TO_TICKS(500));
// 绝对延时(固定周期)
TickType_t xLastWakeTime = xTaskGetTickCount();
while(1) {
// 周期任务代码
// ......
vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(1000));
// 任务代码(周期固定为 1000ms)
}
临界区
保护共享资源(如全局变量)不被多任务或中断同时访问。
任务级临界区关中断;中断级临界区只关调度器。
- 任务中使用 taskENTER_CRITICAL() / taskEXIT_CRITICAL() 包裹共享数据操作。
- 中断中使用 taskENTER_CRITICAL_FROM_ISR() / taskEXIT_CRITICAL_FROM_ISR()。
- 中断级临界区是指在中断服务函数(ISR)中,为了保护被中断和任务共享的全局变量或资源而设置的临界区。
在中断处理中,禁止更高优先级的中断嵌套,防止当前正在操作的数据被另一个中断破坏。
cpp
//头文件引用
#include "task.h"
cpp
// 任务级临界区
void vTask(void *pvParameters)
{
while(1)
{
taskENTER_CRITICAL();
// 操作共享全局变量
shared_counter++;
taskEXIT_CRITICAL();
vTaskDelay(pdMS_TO_TICKS(100));
}
}
// 中断级临界区(在中断服务函数中)
#define RX_BUF_SIZE 128
volatile uint8_t rx_buffer[RX_BUF_SIZE];
volatile uint16_t rx_head = 0;
volatile uint16_t rx_tail = 0;
void USART1_IRQHandler(void)
{
UBaseType_t uxSavedStatus;
if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
uint8_t data = USART_ReceiveData(USART1);
// 进入临界区,保护缓冲区操作
uxSavedStatus = taskENTER_CRITICAL_FROM_ISR();
uint16_t next_head = (rx_head + 1) % RX_BUF_SIZE;
if (next_head != rx_tail) // 缓冲区未满
{
rx_buffer[rx_head] = data;
rx_head = next_head;
}
taskEXIT_CRITICAL_FROM_ISR(uxSavedStatus);
}
}
信号量
- 二值信号量:任务同步(如中断通知任务)。
- 互斥信号量:保护共享资源(支持优先级继承,防止优先级反转)。
- 计数信号量:管理多个资源。
cpp
//头文件应引用
#include "semphr.h"
//FreeRTOSConfig.h 文件配置
/* 互斥量:1 = 启用(推荐) */
#define configUSE_MUTEXES 1
/* 递归互斥量:1 = 启用(需要时开启) */
#define configUSE_RECURSIVE_MUTEXES 0
/* 计数信号量:1 = 启用(需要时开启) */
#define configUSE_COUNTING_SEMAPHORES 1
二值信号量只有 0 和 1 两个值,初始值通常为 0支持任务与中断之间的同步。不是用来保护资源的,不具备优先级继承特性。
*
cpp
// 创建(main中)
SemaphoreHandle_t xBinarySem = xSemaphoreCreateBinary();
// 中断中释放
void USART1_IRQHandler(void)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(xBinarySem, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
// 任务中等待
void vTask(void *pvParameters)
{
while(1)
{
xSemaphoreTake(xBinarySem, portMAX_DELAY);
// 收到信号后处理
}
}
互斥信号量只有 0 和 1,支持优先级继承(防止优先级反转)必须由同一个任务执行Take和Give。用于保护共享资源,比如串口、I2C、共享内存等。不能在中断中使用,同一个任务必须负责释放。
*
cpp
// 创建(main中)
SemaphoreHandle_t xMutex = xSemaphoreCreateMutex();
void vTask1(void *pvParameters)
{
while(1)
{
xSemaphoreTake(xMutex, portMAX_DELAY);
USART_SendString("Task1 using USART\r\n");
xSemaphoreGive(xMutex);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void vTask2(void *pvParameters)
{
while(1)
{
xSemaphoreTake(xMutex, portMAX_DELAY);
USART_SendString("Task2 using USART\r\n");
xSemaphoreGive(xMutex);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
计数信号量值可以大于 1,最大值在创建时设定,初始值可自定义。
cpp
// 定义最大可存储事件数
#define MAX_EVENT_COUNT 10
SemaphoreHandle_t xEventCounter;
TaskHandle_t xEventProcessTaskHandle;
// 初始化
void EventCounter_Init(void)
{
// 创建计数信号量:最大 10 个事件,初始 0 个(还没有事件)
xEventCounter = xSemaphoreCreateCounting(MAX_EVENT_COUNT, 0);
}
// 中断:按键按下
void EXTI0_IRQHandler(void)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if (EXTI_GetITStatus(EXTI_Line0) != RESET)
{
// 释放信号量,记录一次按键事件
xSemaphoreGiveFromISR(xEventCounter, &xHigherPriorityTaskWoken);
EXTI_ClearITPendingBit(EXTI_Line0);
}
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
// 任务:处理按键事件
void vTask_ProcessEvents(void *pvParameters)
{
uint32_t total_events = 0;
while(1)
{
// 等待按键事件(阻塞,直到有按键按下)
if (xSemaphoreTake(xEventCounter, portMAX_DELAY) == pdTRUE)
{
total_events++;
USARTPrintf(USART1, "Key pressed! Total: %d\r\n", total_events);
// 如果达到 10 次,执行特殊操作
if (total_events >= 10)
{
USARTPrintf(USART1, "10 times! Action triggered!\r\n");
total_events = 0; // 重置计数
}
}
}
}
// 启动
int main(void)
{
EventCounter_Init();
xTaskCreate(vTask_ProcessEvents, "EventProcess", 128, NULL, 2, &xEventProcessTaskHandle);
vTaskStartScheduler();
while(1);
队列/队列集
cpp
//头文件引用
#include "queue.h"
//FreeRTOSConfig.h 文件配置
/* 队列集:1 = 启用(需要时开启) */
#define configUSE_QUEUE_SETS 0
队列
-
示例:生产者发送一递增数据,消费者接收并打印这个数据
cpp//生产者发送一递增数据,消费者接收并打印这个数据 #include "FreeRTOS.h" #include "task.h" #include "queue.h" #include "bsp_usart.h" // 定义队列句柄 QueueHandle_t xDataQueue; // 生产者任务:发送数据 void vTask_Sender(void *pvParameters) { int32_t data = 0; while(1) { data++; // 将数据发送到队列(不等待,如果队列满则丢弃) if (xQueueSend(xDataQueue, &data, 0) == pdPASS) { USARTPrintf(USART1, "Sent: %d\r\n", data); } else { USARTPrintf(USART1, "Queue full, data lost\r\n"); } vTaskDelay(pdMS_TO_TICKS(1000)); } } // 消费者任务:接收数据 void vTask_Receiver(void *pvParameters) { int32_t received_data; while(1) { // 阻塞等待队列中的数据(永久等待) if (xQueueReceive(xDataQueue, &received_data, portMAX_DELAY) == pdPASS) { USARTPrintf(USART1, "Received: %d\r\n", received_data); } } } int main(void) { USART1_Init(); // 创建队列:长度为 5,每个元素占 4 字节(int32_t) // 参数1:队列长度(最多能存放 5 个元素) // 参数2:每个元素的大小(字节) xDataQueue = xQueueCreate(5, sizeof(int32_t)); // 创建任务 xTaskCreate(vTask_Sender, "Sender", 128, NULL, 1, NULL); xTaskCreate(vTask_Receiver, "Receiver", 128, NULL, 2, NULL); vTaskStartScheduler(); while(1); }示例:创建两个队列,生产者同时发送到两个队列用于控制和显示。
cpp/* 传感器数据结构体 */ typedef struct { uint8_t temperature; uint8_t humidity; float light; uint16_t mq2_value; uint32_t timestamp; // 可选:时间戳 } SensorData_t; /* 定义创建两个队列 */ QueueHandle_t xControlQueue = xQueueCreate(5, sizeof(SensorData_t)); // 给控制任务 QueueHandle_t xDisplayQueue = xQueueCreate(5, sizeof(SensorData_t));; // 给显示任务 /* 生产者:采集任务 */ void vSensor_Task(void *pvParam) { SensorData_t data; const TickType_t xFrequency = pdMS_TO_TICKS(1000); TickType_t xLastWakeTime = xTaskGetTickCount(); while(1) { vTaskDelayUntil(&xLastWakeTime, xFrequency); data.temperature = g_temperature; data.humidity = g_humidity; data.light = g_light; data.mq2_value = g_mq2_value; data.timestamp = xTaskGetTickCount(); /* 关键:将同一份数据发送到两个队列 */ xQueueOverwrite(xControlQueue, &data); xQueueOverwrite(xDisplayQueue, &data); } } /* 消费者1:控制任务 */ void vControl_Task(void *pvParam) { SensorData_t data; while(1) { if(xQueueReceive(xControlQueue, &data, portMAX_DELAY) == pdTRUE) { /* 数据到达,执行控制逻辑 */ if(data.temperature > 34) { LED_Control(LED1_GPIO_PORT, LED1_GPIO_PIN1, 1); }else{ LED_Control(LED1_GPIO_PORT, LED1_GPIO_PIN1, 0); } if(data.humidity < 50) { LED_Control(LED1_GPIO_PORT, LED1_GPIO_PIN2, 1); }else{ LED_Control(LED1_GPIO_PORT, LED1_GPIO_PIN2, 0); } if(data.light < 100) { // 补光 / 开灯 LED_Control(LED1_GPIO_PORT, LED1_GPIO_PIN3, 1); }else{ LED_Control(LED1_GPIO_PORT, LED1_GPIO_PIN3, 0); } } } } /* 消费者2:显示任务 */ void vDisplay_Task(void *pvParam) { SensorData_t data; while (1) { if(xQueueReceive(xDisplayQueue, &data, portMAX_DELAY) == pdTRUE) { /* 更新 OLED 显示 */ taskENTER_CRITICAL(); OLED_ShowFormat(1,1,"WD:%-6d",data.temperature); OLED_ShowFormat(2,1,"SD:%-6d",data.humidity); OLED_ShowFormat(3,1,"GZ:%-8.2f",data.light); OLED_ShowFormat(4,1,"MQ:%-6d",data.mq2_value); taskEXIT_CRITICAL(); } } }队列集
同时等待多个队列或信号量,有事件触发时按优先级处理。
cpp
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
#include "bsp_usart.h"
// 定义队列句柄
QueueHandle_t xUART_Queue;
QueueHandle_t xKey_Queue;
SemaphoreHandle_t xTimer_Semaphore;
QueueSetHandle_t xQueueSet;
// 生产者任务:模拟串口数据
void vTask_UART_Sender(void *pvParameters)
{
uint8_t data = 'A';
while(1)
{
// 将数据发送到 UART 队列(不等待,队列满则丢弃)
// 参数1:目标队列句柄
// 参数2:要发送的数据的地址(队列会拷贝数据)
// 参数3:超时时间 0(不等待,立即返回)
xQueueSend(xUART_Queue, &data, 0);
data++;
if (data > 'Z') data = 'A';
vTaskDelay(pdMS_TO_TICKS(2000));
}
}
// 生产者任务:模拟按键事件
void vTask_Key_Sender(void *pvParameters)
{
uint8_t key = 0x01;
while(1)
{
key = (key == 0x01) ? 0x02 : 0x01;
xQueueSend(xKey_Queue, &key, 0);
vTaskDelay(pdMS_TO_TICKS(3000));
}
}
// 生产者任务:模拟定时器信号量
void vTask_Timer_Sender(void *pvParameters)
{
while(1)
{
vTaskDelay(pdMS_TO_TICKS(5000));
// 释放二值信号量(通知监控任务)
// 如果有任务在等待这个信号量,它会被唤醒
xSemaphoreGive(xTimer_Semaphore);
}
}
// 主监控任务:使用队列集同时等待多个源
void vTask_Monitor(void *pvParameters)
{
// 用于存储从队列集中返回的成员句柄(是哪个队列/信号量触发了)
QueueSetMemberHandle_t xMember;
uint8_t data;
while(1)
{
// 阻塞等待队列集中的任意一个成员有数据/事件
// 返回值:哪个队列/信号量触发了
xMember = xQueueSelectFromSet(xQueueSet, portMAX_DELAY);
if (xMember == xUART_Queue)
{
// 从 UART 队列取数据
if (xQueueReceive(xUART_Queue, &data, 0) == pdPASS)
{
USARTPrintf(USART1, "UART: %c\r\n", data);
}
}
else if (xMember == xKey_Queue)
{
// 从按键队列取数据
if (xQueueReceive(xKey_Queue, &data, 0) == pdPASS)
{
USARTPrintf(USART1, "Key: 0x%02X\r\n", data);
}
}
else if (xMember == xTimer_Semaphore)
{
// 从信号量取数据(信号量本质也是队列)
if (xSemaphoreTake(xTimer_Semaphore, 0) == pdTRUE)
{
USARTPrintf(USART1, "Timer event!\r\n");
}
}
}
}
int main(void)
{
USART1_Init();
// 创建队列 长度为 5,每个元素 1 字节
xUART_Queue = xQueueCreate(5, sizeof(uint8_t));
xKey_Queue = xQueueCreate(5, sizeof(uint8_t));
// 创建二值信号量:初始状态为不可用(0)
xTimer_Semaphore = xSemaphoreCreateBinary();
//创建队列集:最大可容纳 3 个成员
// 参数:队列集可以容纳的最大成员数(队列+信号量的总数)
xQueueSet = xQueueCreateSet(3);
//将队列添加到队列集
xQueueAddToSet(xUART_Queue, xQueueSet);
xQueueAddToSet(xKey_Queue, xQueueSet);
xQueueAddToSet(xTimer_Semaphore, xQueueSet);
// 创建任务
xTaskCreate(vTask_UART_Sender, "UART_Send", 128, NULL, 1, NULL);
xTaskCreate(vTask_Key_Sender, "Key_Send", 128, NULL, 1, NULL);
xTaskCreate(vTask_Timer_Sender, "Timer_Send", 128, NULL, 1, NULL);
xTaskCreate(vTask_Monitor, "Monitor", 256, NULL, 2, NULL);
vTaskStartScheduler();
while(1);
}
软件定时器
cpp
//头文件引用
#include "timer.h"
//FreeRTOSConfig.h 文件配置
/* 软件定时器:1 = 启用(需要时开启) */
#define configUSE_TIMERS 1
#define configTIMER_TASK_PRIORITY (2) // 定时器任务优先级
#define configTIMER_QUEUE_LENGTH (10) // 定时器命令队列长度
#define configTIMER_TASK_STACK_DEPTH configMINIMAL_STACK_SIZE
示例
*
cpp
#include "FreeRTOS.h"
#include "task.h"
#include "timers.h" // 软件定时器 API 在此头文件中
#include "bsp_usart.h" // 串口调试输出
#include "bsp_led.h" // LED 控制
// 定义软件定时器句柄
TimerHandle_t xTimer = NULL;
//软件定时器回调函数
void vTimerCallback(TimerHandle_t xTimer)
{
LED_Toggle();
}
int main(void)
{
// 硬件初始化(在创建定时器之前)
USART1_Init();
LED_Init();
// 创建软件定时器
xTimer = xTimerCreate(
"Timer", // 定时器名称
pdMS_TO_TICKS(1000), // 周期:1000ms
pdTRUE, // 自动重载
0, // 定时器 ID
vTimerCallback // 回调函数
);
// 检查定时器是否创建成功
if (xTimer == NULL)
{
// 创建失败处理
USARTPrintf(USART1, "ERROR: Timer creation failed!\r\n");
// 停止执行
while(1);
}
else
{
USARTPrintf(USART1, "Timer created successfully.\r\n");
}
// 启动定时器
if (xTimerStart(xTimer, 0) == pdPASS)
{
USARTPrintf(USART1, "Timer started successfully.\r\n");
}
else
{
USARTPrintf(USART1, "ERROR: Timer start failed!\r\n");
while(1);
}
// 启动调度器
vTaskStartScheduler();
while(1);
}
事件标志组
cpp
//头文件引用
#include "event_groups.h"
//FreeRTOSConfig.h 文件配置
/* 事件标志组:1 = 启用(需要时开启) */
#define configUSE_16_BIT_TICKS 0 // 0=32位tick,1=16位tick
示例:满足四个按键按下后led闪烁一次
cpp
#define EVENT_BIT_KEY1 (1 << 0) /* 按键1事件位:0x01 */
#define EVENT_BIT_KEY2 (1 << 1) /* 按键2事件位:0x02 */
#define EVENT_BIT_KEY3 (1 << 2) /* 按键3事件位:0x04 */
#define EVENT_BIT_KEY4 (1 << 3) /* 按键4事件位:0x08 */
#define EVENT_ALL_KEYS (EVENT_BIT_KEY1 | EVENT_BIT_KEY2 | EVENT_BIT_KEY3 | EVENT_BIT_KEY4) /* 0x0F */
// 创建事件标志组
// EventGroupHandle_t event_group = xEventGroupCreate();
//等待4个条件全部满足 => LED1闪烁一下
void myTask1(void *pvParameters){
while(1)
{
/* ============================================================
* 等待4个按键事件全部发生
* 参数1: event_group - 事件标志组句柄
* 参数2: uxBitsToWaitFor - 等待条件拼接成的int值(二进制 1111 ==> 十六进制 0x0f)
* 参数3: xClearOnExit - 退出时是否清除 (pdFALSE = 不清除(1 -> 0))
* 参数4: xWaitForAllBits - 是否等待全部位 (pdTRUE = 全部(1111))
* 参数5: xTicksToWait - 永久等待
============================================================ */
xEventGroupWaitBits(event_group,EVENT_ALL_KEYS, pdFALSE, pdTRUE, portMAX_DELAY);
LED_Toggle(GPIOA, GPIO_Pin_0);
vTaskDelay(pdMS_TO_TICKS(500));
LED_Toggle(GPIOA, GPIO_Pin_0);
vTaskDelay(pdMS_TO_TICKS(500));
// 可以手动清除标志位
xEventGroupClearBits(event_group, EVENT_ALL_KEYS);
}
}
void myTask2(void *pvParameters){
uint8_t key1_last = 0, key1_current = 0;
uint8_t key2_last = 0, key2_current = 0;
uint8_t key3_last = 0, key3_current = 0;
uint8_t key4_last = 0, key4_current = 0;
while(1)
{
// 读取当前状态
key1_current = (KEY_ReadState(KEY1) == RESET) ? 1 : 0;
key2_current = (KEY_ReadState(KEY2) == RESET) ? 1 : 0;
key3_current = (KEY_ReadState(KEY3) == RESET) ? 1 : 0;
key4_current = (KEY_ReadState(KEY4) == RESET) ? 1 : 0;
// 扫描按键 => 按下不同的按钮 将事件标志组中的对应位设置为1
// 处理按键1
if(key1_current == 1 && key1_last == 0) // 下降沿
{
// 在事件标志组的第0位设置为1
xEventGroupSetBits(event_group, EVENT_BIT_KEY1);
}
vTaskDelay(pdMS_TO_TICKS(10));
// 处理按键2
if(key2_current == 1 && key2_last == 0) // 下降沿
{
// 在事件标志组的第1位设置为1
xEventGroupSetBits(event_group, EVENT_BIT_KEY2);
}
vTaskDelay(pdMS_TO_TICKS(10));
// 处理按键3
if(key3_current == 1 && key3_last == 0) // 下降沿
{
// 在事件标志组的第2位设置为1
xEventGroupSetBits(event_group, EVENT_BIT_KEY3);
}
// 处理按键4
if(key4_current == 1 && key4_last == 0) // 下降沿
{
// 在事件标志组的第3位设置为1
xEventGroupSetBits(event_group, EVENT_BIT_KEY4);
}
// 更新上一次状态
key1_last = key1_current;
key2_last = key2_current;
key3_last = key3_current;
key4_last = key4_current;
// 周期性扫描,间隔 20ms(消抖 + 扫描周期)
vTaskDelay(pdMS_TO_TICKS(20));
}
}
直接任务通知
cpp
//头文件引用
#include "task.h"
//FreeRTOSConfig.h 文件配置
/* 任务通知:1 = 启用(推荐,轻量级通信) */
#define configUSE_TASK_NOTIFICATIONS 1
示例:直接任务通知代替二值信号量
cpp
#include "FreeRTOS.h"
#include "task.h"
#include "bsp_usart.h"
#include "bsp_led.h"
// 定义任务句柄(接收通知的任务)
TaskHandle_t xTaskWithBinaryHandle = NULL;
void EXTI0_IRQHandler(void)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if (EXTI_GetITStatus(EXTI_Line0) != RESET)
{
// 向任务发送通知(中断专用版本)
xTaskNotifyFromISR(xTaskWithBinaryHandle, // 目标任务句柄
1, // 通知值(固定为 1)
eSetValueWithOverwrite, // 覆盖旧值 → 二值行为
&xHigherPriorityTaskWoken);
// 清除中断标志
EXTI_ClearITPendingBit(EXTI_Line0);
}
// 如果有更高优先级任务被唤醒,请求切换
// pdFALSE 不切换,继续执行被中断的任务 pdTRUE立即切换,唤醒的任务优先执行
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
// 接收任务:等待通知(二值信号量模式)
void vTask_Binary_Notification(void *pvParameters)
{
uint32_t ulNotifiedValue;
while(1)
{
// ========== 代替 xSemaphoreTake() ==========
// 等待通知(永久等待)
// 参数1:pdTRUE 表示清零通知值 pdFALSE表示不清零通知值
// 参数2:永久等待
// 返回值:收到的通知次数(这里固定为 1)
ulNotifiedValue = ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
if (ulNotifiedValue > 0)
{
// 事件发生,执行一次动作
LED_Toggle();
USARTPrintf(USART1, "Binary Event Received!\r\n");
}
}
}
int main(void)
{
USART1_Init();
LED_Init();
// 配置外部中断(此处省略了GPIO配置)
// 创建接收任务(先创建获取句柄)
xTaskCreate(vTask_Binary_Notification, "BinaryTask", 128, NULL, 2, &xTaskWithBinaryHandle);
vTaskStartScheduler();
while(1);
}
示例:直接任务通知代替计数型信号量
cpp
#include "FreeRTOS.h"
#include "task.h"
#include "bsp_usart.h"
#include "bsp_led.h"
// 定义任务句柄(接收通知的任务)
TaskHandle_t xTaskWithCountingHandle = NULL;
/**
* @brief EXTI0 中断服务函数(按键中断)
* @note 每次按键按下,通知值 +1(计数型信号量行为)
*/
void EXTI0_IRQHandler(void)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if (EXTI_GetITStatus(EXTI_Line0) != RESET)
{
// ========== 计数型信号量行为 ==========
// 使用 xTaskNotifyGiveFromISR 让通知值 +1
// 相当于计数信号量的 Give 操作
// 每次按键按下,通知值累加:0→1→2→3→...
xTaskNotifyGiveFromISR(xTaskWithCountingHandle, &xHigherPriorityTaskWoken);
// 清除中断标志
EXTI_ClearITPendingBit(EXTI_Line0);
}
// 如果有更高优先级任务被唤醒,请求切换
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
/**
* @brief 接收任务:等待通知(计数信号量模式)
* @note 每收到3次通知,翻转一次LED
*/
void vTask_Counting_Notification(void *pvParameters)
{
uint32_t ulNotifiedValue;
uint32_t total_count = 0; // 累计按键次数
while(1)
{
// ========== 代替 xSemaphoreTake() ==========
// 等待通知(永久等待)
// 参数1:pdTRUE 表示清零通知值
// 参数2:永久等待
// 返回值:本次被唤醒时,通知值的大小(累积的通知次数)
ulNotifiedValue = ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
// ulNotifiedValue 表示本次积压了多少次按键
// 例如:如果连续按了3次才被调度,则 ulNotifiedValue = 3
USARTPrintf(USART1, "Received %d notifications\r\n", ulNotifiedValue);
// 累加总按键次数
total_count += ulNotifiedValue;
// 如果累计按键次数 >= 3,翻转LED并重置计数
if (total_count >= 3)
{
LED_Toggle();
USARTPrintf(USART1, "LED Toggled! (Total: %d)\r\n", total_count);
total_count = 0; // 重置计数
}
}
}
int main(void)
{
USART1_Init();
LED_Init();
// 配置外部中断(此处省略了GPIO配置)
// 创建接收任务(先创建获取句柄)
xTaskCreate(vTask_Counting_Notification, "CountingTask", 128, NULL, 2, &xTaskWithCountingHandle);
vTaskStartScheduler();
while(1);
}
示例:直接任务通知代替事件标志组
cpp
#include "FreeRTOS.h"
#include "task.h"
#include "bsp_led.h"
#include "bsp_usart.h"
#include "key.h"
// ============================================================
// 1. 定义事件位(4个按键对应4个位)
// ============================================================
#define EVENT_BIT_KEY1 (1 << 0) // 0x01
#define EVENT_BIT_KEY2 (1 << 1) // 0x02
#define EVENT_BIT_KEY3 (1 << 2) // 0x04
#define EVENT_BIT_KEY4 (1 << 3) // 0x08
#define ALL_EVENTS (EVENT_BIT_KEY1 | EVENT_BIT_KEY2 | EVENT_BIT_KEY3 | EVENT_BIT_KEY4) // 0x0F
// ============================================================
// 2. 定义任务句柄(接收通知的任务)
// ============================================================
TaskHandle_t xTaskWithNotifyHandle = NULL;
// ============================================================
// 3. LED闪烁任务(等待4个按键全部按下)
// ============================================================
void vTask_WaitForAllKeys(void *pvParameters)
{
uint32_t ulNotifiedValue;
while(1)
{
// ============================================================
// 代替:xEventGroupWaitBits(event_group, 0x0f, pdFALSE, pdTRUE, portMAX_DELAY)
// ============================================================
// 参数1:ulBitsToClearOnEntry - 进入等待前清零的位(0 表示不清零)
// 参数2:ulBitsToClearOnExit - 退出等待后清零的位(0xFFFFFFFF 表示全部清零)
// 参数3:pulNotificationValue - 存储当前通知值
// 参数4:xTicksToWait - 等待时间(永久等待)
//
// 返回值:pdTRUE 表示收到通知,pdFALSE 表示超时
if (xTaskNotifyWait(0, // 进入时不清零
0xFFFFFFFF, // 退出时全部清零(相当于清除事件标志)
&ulNotifiedValue, // 获取当前通知值
portMAX_DELAY) == pdTRUE)
{
// 检查是否所有4个按键都按下了
if ((ulNotifiedValue & ALL_EVENTS) == ALL_EVENTS)
{
// LED闪烁一次(亮500ms,灭500ms)
LED_Toggle(GPIOA, GPIO_Pin_0);
vTaskDelay(pdMS_TO_TICKS(500));
LED_Toggle(GPIOA, GPIO_Pin_0);
vTaskDelay(pdMS_TO_TICKS(500));
USARTPrintf(USART1, "All 4 keys pressed! LED blinked!\r\n");
}
else
{
// 只按下了部分按键,打印当前状态
USARTPrintf(USART1, "Current bits: 0x%02X (need 0x0F)\r\n", ulNotifiedValue);
}
}
}
}
// ============================================================
// 4. 按键扫描任务(代替 xEventGroupSetBits)
// ============================================================
void vTask_KeyScan(void *pvParameters)
{
uint8_t key1_last = 0, key1_current = 0;
uint8_t key2_last = 0, key2_current = 0;
uint8_t key3_last = 0, key3_current = 0;
uint8_t key4_last = 0, key4_current = 0;
while(1)
{
// 读取当前状态
key1_current = (KEY_ReadState(KEY1) == RESET) ? 1 : 0;
key2_current = (KEY_ReadState(KEY2) == RESET) ? 1 : 0;
key3_current = (KEY_ReadState(KEY3) == RESET) ? 1 : 0;
key4_current = (KEY_ReadState(KEY4) == RESET) ? 1 : 0;
// ============================================================
// 代替:xEventGroupSetBits(event_group, 1 << n)
// 使用 xTaskNotify 的 eSetBits 模式,按位或设置通知值
// ============================================================
// 处理按键1(下降沿)
if(key1_current == 1 && key1_last == 0)
{
// 向通知值按位或 EVENT_BIT_KEY1
xTaskNotify(xTaskWithNotifyHandle, EVENT_BIT_KEY1, eSetBits);
USARTPrintf(USART1, "Key1 pressed -> BIT0 set\r\n");
}
// 处理按键2(下降沿)
if(key2_current == 1 && key2_last == 0)
{
xTaskNotify(xTaskWithNotifyHandle, EVENT_BIT_KEY2, eSetBits);
USARTPrintf(USART1, "Key2 pressed -> BIT1 set\r\n");
}
// 处理按键3(下降沿)
if(key3_current == 1 && key3_last == 0)
{
xTaskNotify(xTaskWithNotifyHandle, EVENT_BIT_KEY3, eSetBits);
USARTPrintf(USART1, "Key3 pressed -> BIT2 set\r\n");
}
// 处理按键4(下降沿)
if(key4_current == 1 && key4_last == 0)
{
xTaskNotify(xTaskWithNotifyHandle, EVENT_BIT_KEY4, eSetBits);
USARTPrintf(USART1, "Key4 pressed -> BIT3 set\r\n");
}
// 更新上一次状态
key1_last = key1_current;
key2_last = key2_current;
key3_last = key3_current;
key4_last = key4_current;
// 周期性扫描,间隔 20ms(消抖 + 扫描周期)
vTaskDelay(pdMS_TO_TICKS(20));
}
}
// ============================================================
// 5. 主函数
// ============================================================
int main(void)
{
USART1_Init();
LED_Init();
KEY_Init();
// 创建任务(先创建等待任务,获取句柄)
xTaskCreate(vTask_WaitForAllKeys, "WaitKeys", 128, NULL, 2, &xTaskWithNotifyHandle);
// 创建按键扫描任务(优先级稍低)
xTaskCreate(vTask_KeyScan, "KeyScan", 256, NULL, 1, NULL);
vTaskStartScheduler();
while(1);
}
低功耗模式 FreeRTOSConfig.h 配置
cpp
// FreeRTOSConfig.h 配置
#define configUSE_TICKLESS_IDLE 1
#define configEXPECTED_IDLE_TIME_BEFORE_SLEEP 2
获取任务数
cpp
#include "FreeRTOS.h"
#include "task.h"
#include "bsp_usart.h"
void vTask_GetTaskCount(void *pvParameters)
{
UBaseType_t task_count;
while(1)
{
// 获取当前系统中的任务总数(包括空闲任务和定时器任务)
task_count = uxTaskGetNumberOfTasks();
USARTPrintf(USART1, "Current Task Count: %d\r\n", task_count);
vTaskDelay(pdMS_TO_TICKS(5000));
}
}
获取任务剩余空间
cpp
// FreeRTOSConfig.h 配置
/* 获取任务栈剩余空间:1 = 启用(允许调用 uxTaskGetStackHighWaterMark()) */
#define INCLUDE_uxTaskGetStackHighWaterMark 1
cpp
#include "FreeRTOS.h"
#include "task.h"
#include "bsp_usart.h"
TaskHandle_t xMyTaskHandle = NULL;
void vMyTask(void *pvParameters)
{
while(1)
{
// ... 任务代码 ...
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void vTask_GetStackInfo(void *pvParameters)
{
UBaseType_t stack_remaining;
while(1)
{
// 获取指定任务的栈剩余空间(历史最小值)
stack_remaining = uxTaskGetStackHighWaterMark(xMyTaskHandle);
USARTPrintf(USART1, "Task Stack Remaining: %d words (%d bytes)\r\n",
stack_remaining, stack_remaining * 4);
// 获取当前任务自身的栈剩余空间
stack_remaining = uxTaskGetStackHighWaterMark(NULL);
USARTPrintf(USART1, "Current Task Stack Remaining: %d words\r\n", stack_remaining);
vTaskDelay(pdMS_TO_TICKS(5000));
}
}
int main(void)
{
USART1_Init();
// 创建任务(保存句柄)
xTaskCreate(vMyTask, "MyTask", 256, NULL, 1, &xMyTaskHandle);
xTaskCreate(vTask_GetStackInfo, "StackInfo", 128, NULL, 2, NULL);
vTaskStartScheduler();
while(1);
}
注:以上内容仅个人理解,不具备唯一性和绝对正确性,仅供参考。