FreeRTOS动态任务创建
建议同学们在学习32的过程中边学FreeRTOS,学习32用标准库,注重硬件原理,FReeRTOS需要HAL库,符合当下需求。
FreeRTOS 是一款免费、开源、轻量级的实时操作系统内核(RTOS),专门用于单片机 / 嵌入式系统,用来实现多任务并发、实时响应、资源管理。
一、名字含义
• Free:免费 + 自由(MIT 许可证,商业产品也可免费使用,无需开源)
• RTOS:Real-Time Operating System,实时操作系统,核心是 “准时、确定、快速响应”
二、解决什么问题(裸机 vs FreeRTOS)
裸机(无 OS):
while(1) {
读传感器(); // 若这里耗时久,其他任务被卡住
处理数据();
发串口();
}
单循环,一个任务阻塞,全体卡住
• 难实现 “同时” 做几件事(如:同时控电机 + 收数据 + 刷新屏幕)
FreeRTOS:
• 把程序拆成多个独立任务(如:任务 1 读传感器、任务 2 发串口、任务 3 控电机)
• 内核快速切换任务(毫秒 / 微秒级),单核 CPU 也能 “并发运行”
• 按优先级调度:高优先级任务可抢占低优先级,保证关键任务实时响应
三、核心特点
1. 轻量小巧
◦ 内核仅占:ROM 4–9KB,RAM <1KB,适合 STM32、ESP32、Cortex‑M 等小资源 MCU
2. 开源免费、商业友好
◦ MIT 协议:免费商用、可闭源、无版权费
3. 跨平台、可移植
◦ 已移植到40 + 处理器架构(ARM、RISC‑V、PIC、AVR 等)
4. 核心功能(够用、精炼)
◦ ✅ 任务管理:创建 / 删除 / 挂起 / 恢复任务、优先级(0~32)
◦ ✅ 通信同步:队列、信号量、互斥量、事件组
◦ ✅ 时间管理:软件定时器、延时、滴答(Tick)
◦ ✅ 内存管理:静态 / 动态内存分配(5 种策略)
◦ ✅ 低功耗:Tickless 模式(空闲时关滴答,降功耗)
四、谁在用、用在哪
• 全球最流行的嵌入式 RTOS:市场份额约38%,Cortex‑M 上采用率 **>60%**
• 典型场景:
◦ 智能家居(灯、开关、传感器)
◦ 工业控制(PLC、电机驱动、数据采集)
◦ 物联网设备(ESP32、WiFi / 蓝牙设备)
◦ 消费电子(遥控器、小家电、手环)
五、一句话总结
FreeRTOS 是嵌入式世界的 “轻量级任务管家”:免费、小巧、好用,帮单片机轻松实现多任务、高实时、低功耗的复杂控制逻辑。
有了FReeRTOS这个操作系统,我们单片机就有点类似小型电脑,比裸机开发功能强大得多。
给出下面一段代码
#include "freertos_demo.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
/*FreeRTOS*********************************************************************************************/
#include "FreeRTOS.h"
#include "task.h"
/******************************************************************************************************/
/*FreeRTOS配置*/
/* 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); /* 任务函数 */
/******************************************************************************************************/
/* LCD刷屏时使用的颜色 */
uint16_t lcd_discolor[11] = {WHITE, BLACK, BLUE, RED,
MAGENTA, GREEN, CYAN, YELLOW,
BROWN, BRRED, GRAY};
/**
* @brief FreeRTOS例程入口函数
* @param 无
* @retval 无
*/
void freertos_demo(void)
{
lcd_show_string(10, 10, 220, 32, 32, "STM32", RED);
lcd_show_string(10, 47, 220, 24, 24, "Task Create & Del", RED);
lcd_show_string(10, 76, 220, 16, 16, "ATOM@ALIENTEK", 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);
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();
}
/**
* @brief start_task
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); /* 进入临界区 */
/* 创建任务1 */
xTaskCreate((TaskFunction_t )task1, /* 任务函数 */
(const char* )"task1", /* 任务名称 */
(uint16_t )TASK1_STK_SIZE, /* 任务堆栈大小 */
(void* )NULL, /* 传入给任务函数的参数 */
(UBaseType_t )TASK1_PRIO, /* 任务优先级 */
(TaskHandle_t* )&Task1Task_Handler); /* 任务句柄 */
/* 创建任务2 */
xTaskCreate((TaskFunction_t )task2, /* 任务函数 */
(const char* )"task2", /* 任务名称 */
(uint16_t )TASK2_STK_SIZE, /* 任务堆栈大小 */
(void* )NULL, /* 传入给任务函数的参数 */
(UBaseType_t )TASK2_PRIO, /* 任务优先级 */
(TaskHandle_t* )&Task2Task_Handler); /* 任务句柄 */
/* 创建任务3 */
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(); /* 退出临界区 */
}
/**
* @brief task1
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
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(500);
}
}
/**
* @brief task2
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
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(500);
}
}
/**
* @brief task3
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void task3(void *pvParameters)
{
uint8_t key = 0;
while (1)
{
key = key_scan(0);
switch (key)
{
case KEY0_PRES: /* 删除任务1 */
{
if (Task1Task_Handler != NULL)
{
vTaskDelete(Task1Task_Handler);
Task1Task_Handler = NULL;
}
break;
}
case KEY1_PRES: /* 删除任务2 */
{
if (Task2Task_Handler != NULL)
{
vTaskDelete(Task2Task_Handler);
Task2Task_Handler = NULL;
}
break;
}
default:
{
break;
}
}
vTaskDelay(10);
}
}
我先讲如何创建,以及一些函数的作用,关于相关硬件,例如LCD,下一章专门来讲。
三、任务宏配置(4 个任务:开始任务 + 任务 1/2/3)
1. 开始任务 START_TASK(启动任务,只运行一次)
#define START_TASK_PRIO 1 /* 任务优先级 */
#define START_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t StartTask_Handler; /* 任务句柄 */
void start_task(void *pvParameters); /* 任务函数 */
START_TASK_PRIO 1:优先级 = 1(数字越大,优先级越高)
• START_STK_SIZE 128:任务堆栈大小 = 128 字(STM32 中 1 字 = 4 字节)
• TaskHandle_t StartTask_Handler:任务句柄,唯一标识这个任务,删除 / 挂起任务都用它
• void start_task(void *pvParameters):任务函数声明(这个任务的内容)
简单说:任务句柄就是任务的 “身份证 / 遥控器”,用来唯一标识并控制一个任务。
一、本质
• 类型:TaskHandle_t(本质是个指针,指向该任务的 TCB(任务控制块))
• TCB:内核为每个任务分配的一小块内存,里面存着任务的优先级、状态、栈指针、任务函数等所有信息。
• 一句话:句柄 = 指向任务控制块的指针。
二、在你代码里的样子
// 1. 先声明一个全局句柄变量(初始为NULL)
TaskHandle_t Task1Task_Handler;
// 2. 创建任务时,把句柄地址传进去
xTaskCreate(
task1, // 任务函数
"task1", // 任务名
128, // 栈大小
NULL, // 传参
2, // 优先级
&Task1Task_Handler // 【输出】创建成功后,内核把句柄填到这里
);
创建成功后,Task1Task_Handler 就成了任务 1 的唯一标识。
三、有什么用(怎么控制任务)
有了句柄,才能用 FreeRTOS API 操作这个任务:
vTaskDelete(Task1Task_Handler); // 删除任务1(你代码里按键功能)
vTaskSuspend(Task1Task_Handler); // 挂起(暂停)
vTaskResume(Task1Task_Handler); // 恢复运行
没有句柄:找不到、删不了、控不住任务。
四、通俗比喻
• 任务 = 一台电视
• TCB = 电视内部的电路、屏幕、系统
• 句柄 = 遥控器
你不用管电视内部怎么工作,拿遥控器(句柄)就能控制它(开机 / 关机 / 暂停)。
五、关键点总结
1. 唯一性:一个任务对应一个句柄;任务被删除后,句柄失效(野指针)。
2. 作用:用来操作 / 管理任务(删、挂起、恢复、发通知等)。
3. 获取:xTaskCreate() 创建任务时,通过最后一个参数 “输出” 给你。
4. 习惯:一般定义成全局变量,方便其他任务或中断使用。
堆和栈是两个非常重要的概念,对于我们的电脑和单片机内存空间管理起着必不可少的作用。
放在后面来讲。
后面任务1,2,,3跟着模仿就行了,先不说了;
任务创建
// 创建开始任务
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);
// 启动FreeRTOS调度器
vTaskStartScheduler();
xTaskCreate () —— 动态创建任务
◦ 第 1 个参数:任务函数start_task
◦ 第 2 个参数:任务名称"start_task"
◦ 第 3 个参数:堆栈大小START_STK_SIZE
◦ 第 4 个参数:传递给任务的参数NULL
◦ 第 5 个参数:任务优先级START_TASK_PRIO
◦ 第 6 个参数:任务句柄&StartTask_Handler
vTaskStartScheduler()
◦ 启动 FreeRTOS 内核调度器
◦ 调用后,系统开始按优先级调度任务,程序正式进入 RTOS 运行模式
六、开始任务函数:start_task ()
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); /* 进入临界区 */
/* 创建任务1 */
xTaskCreate((TaskFunction_t )task1,
(const char* )"task1",
(uint16_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_PRIO,
(TaskHandle_t* )&Task1Task_Handler);
/* 创建任务2 */
xTaskCreate(..., &Task2Task_Handler);
/* 创建任务3 */
xTaskCreate(..., &Task3Task_Handler);
vTaskDelete(StartTask_Handler); /* 删除开始任务 */
taskEXIT_CRITICAL(); /* 退出临界区 */
}
核心解析:
1. taskENTER_CRITICAL() / taskEXIT_CRITICAL()
◦ 进入 / 退出临界区:临界区内代码不被中断打断,防止创建任务时被干扰
2. 一次性创建任务 1、任务 2、任务 3
3. vTaskDelete(StartTask_Handler)
◦ 删除开始任务自身
◦ 开始任务只负责创建其他任务,完成后自杀,释放资源
补充:
一.为什么要进入临界区,就是要保护我们的代码不被打扰,创建任务不可被打扰,中断和任务调度都可以打扰到他,因此要进入临界区。
二.任务删除函数
vTaskDelete(StartTask_Handler);
形参为任务句柄,也就是结构体地址,结构体中有任务栈的地址,形参为NULL时,删除当前正在执行的任务。
更多推荐
所有评论(0)