建议同学们在学习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时,删除当前正在执行的任务。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐