常规单核处理器一次只能执行一个任务,但多任务操作系统通过将处理器时间进行分片,并在时间片上运行不同任务, 使所有任务看起来像是同时在执行。下图展示了 三个任务相对于时间的执行模式。任务名称用不同颜色标示,并写在左侧。时间从左向右移动, 彩色线条显示在特定时间执行的任务。上方展示了所感知的并发执行模式, 下方展示了实际的多任务执行模式。

使用多任务操作系统可以简化原本复杂的软件应用程序的设计:
xTaskCreate 函数是用来创建一个新的任务,原型如下:
BaseType_t xTaskCreate(
TaskFunction_t pvTaskCode, /* 任务函数 */
const char * const pcName, /* 任务名称 */
const uint32_t uxStackDepth, /* 任务堆栈字数 */
void *pvParameters, /* 自定义参数指针 */
UBaseType_t uxPriority, /* 任务优先级 */
TaskHandle_t *pxCreatedTask /* 任务句柄指针 */
);
xTaskCreatePinnedToCore() 函数是是 ESP-IDF 对 FreeRTOS 的扩展函数,可创建一个在指定处理器核心上执行的任务,原型如下:
BaseType_t xTaskCreatePinnedToCore(
TaskFunction_t pvTaskCode, /* 任务函数 */
const char * const pcName, /* 任务名称 */
const uint32_t usStackDepth, /* 任务堆栈字数 */
void *pvParameters, /* 自定义参数指针 */
UBaseType_t uxPriority, /* 任务优先级 */
TaskHandle_t *pxCreatedTask, /* 任务句柄指针 */
const BaseType_t xCoreID /* 内核ID,对于ESP32通常为0或1 */
);
任务可以存在于以下状态中:
运行:当任务实际执行时,它被称为处于运行状态。任务当前正在使用处理器。 如果运行 RTOS 的处理器只有一个内核, 那么在任何给定时间内都只能有一个任务处于运行状态。
准备就绪:准备就绪任务指那些能够执行(它们不处于阻塞或挂起状态), 但目前没有执行的任务, 因为同等或更高优先级的不同任务已经处于运行状态。
阻塞:如果任务当前正在等待时间或外部事件,则该任务被认为处于阻塞状态。 例如,如果一个任务调用vTaskDelay(),它将被阻塞(被置于阻塞状态), 直到延迟结束——一个时间事件。 任务也可以通过阻塞来等待队列、信号量、事件组、通知或信号量事件。处于阻塞状态的任务通常有一个"超时"期, 超时后任务将被超时,并被解除阻塞, 即使该任务所等待的事件没有发生。“阻塞”状态下的任务不使用任何处理时间,不能被选择进入运行状态。
挂起:与“阻塞”状态下的任务一样, “挂起”状态下的任务不能被选择进入运行状态,但处于挂起状态的任务没有超时。相反,任务只有在分别通过 vTaskSuspend() 和 xTaskResume() API 调用明确命令时才会进入或退出挂起状态。

Task是开发框架中封装FreeRTOS任务相关函数的类,方便使用者操作任务,如创建、挂起、恢复、改变优先级等。
在Task类的实现上,通过定义初始化函数(执行一次)和循环体函数(重复执行)来简化使用。
任务启动,代码如下(src/framework/src/task.h):
int Start(uint32_t stack_depth, uint8_t priority) {
return xTaskCreate(
[](void *parameter) {
Task* task = (Task *)parameter;
if (task->init_function_!=nullptr) {
task->init_function_();
}
if (task->loop_function_!=nullptr) {
while (1) {
task->loop_function_();
}
}
vTaskDelete(NULL);
}, /* 任务函数 TaskFunction_t */
name_.c_str(), /* 任务名称 const char* */
stack_depth, /* 堆栈字数 */
this, /* 自定义参数指针 void* */
priority, /* 任务优先级 UBaseType_t */
&task_handle_ /* 任务句柄指针 TaskHandle_t* */
);
}
Start方法接收两个参数,其中stack_depth为堆栈字数,priority为优先级,
xTaskCreate创建任务,任务函数为lambda表达式函数,若设置了初始化函数,则执行它,若设置了循环体函数,则在循环内执行它,若未设置循环体函数,则删除任务,相当于只执行一次的任务。
从 https://gitee.com/billyzh/esp32-cpp-lesson 下载本教程的源码到本地硬盘文件夹,如d:\esp32-cpp-lesson
在VSCode中,选择【文件】->【打开文件夹...】选择上一步保存的文件夹打开
打开项目后,选择config.h文件,修改第10行为
#define APP_LESSON62 1
打开unit6-lesson62/board_config.h文件,设置LED使用的引脚,
#define BUILTIN_LED_PIN GPIO_NUM_4
配置启用GpioLed
#define CONFIG_USE_LED_GPIO 1
创建LED实例,代码如下(unit6-lesson62/my_board.cpp):
MyBoard::MyBoard() : Board() {
Log::Info(TAG, "===== Create Board ...... =====");
Log::Info(TAG, "initial led.");
led_ = new GpioLed(BUILTIN_LED_PIN, false);
Log::Info( TAG, "===== Board config completed. =====");
}
程序很简单,就是创建一个GpioLed实例。
多任务应用
代码如下(unit6-lesson62/my_application.cpp):
void MyApplication::OnInit() {
task1_ = new Task(std::string("task1"));
task1_->OnLoop([this](){
Task1Loop();
});
task1_->Start(4096, tskIDLE_PRIORITY+1);
task2_ = new Task(std::string("Task2"));
task2_->OnLoop([this](){
Task2Loop();
});
task2_->Start(4096, tskIDLE_PRIORITY+1);
}
/**
* 任务1的循环执行体
*/
void MyApplication::Task1Loop() {
Led *led = Board::GetInstance().GetLed();
if (state_==0) {
led->TurnOn();
state_ = 1;
} else {
led->TurnOff();
state_ = 0;
}
delay(1000);
}
/**
* 任务2的循环执行体
*/
void MyApplication::Task2Loop() {
long long n = 0;
for (int i=1; i<10000000; i++) {
n += i;
}
Log::Info(TAG, "1...10000000 = %lld", n);
delay(2000);
}
程序解读
1. 在OnInit方法内,创建两个Task实例,然后启动任务,Task是封装FreeRTOS任务相关函数的类;
2. Task1Loop方法是任务1的循环体方法,具体为控制Led的打开和关闭;
3. Task2Loop方法是任务2的循环体方法,具体为计算从1到10000000的累加值;
编译项目并上传开发板检验
SPI是串行外设接口(Serial Peripheral Interface)的缩写,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚。
本文本介绍配置飞书机器人为MimiClaw的一个输入/输出端,和添加一个控制WS2812与LED的控制技能。
一块 30 块钱的开发板 + 一个大模型 API,就能做出可以听懂人话的智能硬件。 本文记录完整安装过程和踩坑经验,确保你跟着做就能跑通。
本文将从手绘架构图入手,逐层拆解 MimiClaw 的分层设计、核心模块、数据流转与底层实现,带你解剖这只“智能虾”的技术骨架,看懂在 C 语言加持下,AI 智能体如何以可穿戴设备的形态,在你身边稳稳运行、离线服务、主动响应。
本文介绍如何在不脱离 ArduinoIDE 可视化开发的前提下,通过一个名为 platform.local.txt 的小文件,实现对 ESP32 编译流程的精准控制。
本文将系统分析程序体积增长的五大根源,并提供经过验证的优化方案,帮助减小固件大小。
本文所DIY的语音助手设备端使用的是MicroPython、服务端是Python,对于很多开发者来说MicroPython入门没难度。
本小节使用音频开发框架实现一个音频录制到文件的示例。
I2S协议通过BCLK、LRCLK和DATA三线精准传输音频数据,但时序边沿、帧格式、时钟源等细节常引发噪声或断连。本文详解ESP32的I2S实现,从协议原理到ESP-IDF v5.x代码配置,助你避开常见陷阱,确保音频稳定传输。
本小节介绍音频的基础知识、音频开发框架和AudioCodec的简介,用一个音频播放示例来说明音频管道的使用。
MimiClaw是基于ESP32-S3芯片的超轻量级AI助手,通过Telegram或WebSocket提供Claude/GPT智能服务。