当前位置: 首页 > news >正文

ESP32基础知识-多任务

一、前提案例

1、单 LED 小灯控制

要求: LED灯每秒闪烁一次。

设备:Esp32开发板、面包板、led灯珠、电阻150欧

实现代码:

#include <Arduino.h>

#define LED_PIN 23 // 23号引脚

void setup()

{

Serial.begin(115200);

pinMode(LED_PIN, OUTPUT);

}

void loop()

{

digitalWrite(LED_PIN, HIGH);

delay(1000);

digitalWrite(LED_PIN, LOW);

delay(1000);

}

2、双 LED 小灯控制

要求: led1 小灯 每隔1s亮1次,led2 小灯 每隔3s亮1次。
按照 单 LED 小灯控制方式实现去观察效果。

#include <Arduino.h>

#define LED_PIN1 23 // 23号引脚

#define LED_PIN2 22 // 22号引脚

void setup()

{

Serial.begin(115200);

pinMode(LED_PIN1, OUTPUT);

pinMode(LED_PIN2, OUTPUT);

}

void loop()

{

// LED1小灯 每隔1s亮一次

digitalWrite(LED_PIN1, HIGH);

delay(1000);

digitalWrite(LED_PIN1, LOW);

delay(1000);

// LED3小灯 每隔3s亮一次

digitalWrite(LED_PIN2, HIGH);

delay(3000);

digitalWrite(LED_PIN2, LOW);

delay(3000);

}

二、问题分析

1、观察现象

双 LED 小灯控制中要求: led1 小灯 每隔1s亮1次,led2 小灯 每隔3s亮1次。 通过从上面的双 LED 小灯控制实验中可以看出:

  • LED1从熄灭到点亮,中间经历了7s 【1 + 3 + 3】
  • LED2从熄灭到点亮,中间经历了5s 【1 + 1 + 3】

2、原因与解决方案

通过观察代码发现,原因是在代码的 loop函数中的逻辑,它是一行行按顺序往下执行的,LED1和LED2的控制逻辑会相互影响,最终造成延迟增大。

为了实现 led1 小灯 每隔1s亮1次,led2 小灯 每隔3s亮1次 的需求,我们需要引入多任务RTOS(Real-Time Operating System, 实时操作系统),让两个任务相互不干扰,“同时”运行。

注意:RTOS的实现有很多,其中开源免费的FreeRTOS广受欢迎。

三、FreeRTOS

FreeRTOS是市场领先的面向微控制器和小型微处理器的实时操作系统(RTOS),与世界领先的芯片公司合作开发,现在每 170 秒下载一次。FreeRTOS 通过 MIT 开源许可免费分发,包括一个内核和一组不断丰富的 IoT 库,适用于所有行业领域。FreeRTOS 的构建突出可靠性和易用性。

FreeRTOS是RTOS中开源免费的。ESP32的Arduino框架里面已经内置了FreeRTOS框架,并且对ESP32的双核进行了完美的适配,所以我们在使用时,无需引入第三方库就可以直接使用。

四、FreeRTOS多任务函数

1、xTaskCreate函数

static inline IRAM_ATTR BaseType_t xTaskCreate(

TaskFunction_t pvTaskCode,

const char * const pcName,

const uint32_t usStackDepth,

void * const pvParameters,

UBaseType_t uxPriority,

TaskHandle_t * const pxCreatedTask

)

  • pvTaskCode:任务函数(任务要执行的逻辑),返回值必须为void,只有一个参数,参数类型必须为void*
  • pcName:自定义任务的名称,一个字符串,主要为了方便排查问题;
  • usStackDepth:给任务分配的最大堆栈大小,比如2048,单位是字节,这个值需要根据任务复杂度来选择,一般简单的任务,2048~4096范围的值就足够,如果该任务需要处理的逻辑确实比较繁重,可以适当增大,比如8192
  • pvParameters:传递给任务的参数,类型是void*;
  • uxPriority:任务优先级,取值范围为[0, 24],数字越大,优先级越高
  • pxCreatedTask:用于接收该任务的句柄,后续对该任务的操作,需要基于该句柄完成。

2、实现步骤

01、定义任务函数pvTaskCode

定义 pvTaskCode 任务函数handle_led1 和 handle_led2,参数都只有1个,参数类型必须为void*, 且返回值为void类型。

=================================================================

void handle_led1(void *ptr)
{
pinMode(LED1, OUTPUT);
char *param = (char *)ptr;
Serial.print(param);
Serial.println(" started");
while (1)
{
digitalWrite(LED1, HIGH);
vTaskDelay(1000);
digitalWrite(LED1, LOW);
vTaskDelay(1000);
}
vTaskDelete(NULL);
}

================================================================

void handle_led2(void *ptr)
{
pinMode(LED2, OUTPUT);
char *param = (char *)ptr;
Serial.print(param);
Serial.println(" started");
while (1)
{
digitalWrite(LED2, HIGH);
vTaskDelay(3000);
digitalWrite(LED2, LOW);
vTaskDelay(3000);
}
vTaskDelete(NULL);
}

=================================================================

02、定义任务名称

分别为handle_led1 和 handle_led2任务函数定义任务名称: "task1"、"task2"

03、设置堆栈大小

分别为handle_led1 和 handle_led2任务设置堆栈大小:2048、2048

04、设置任务参数

分别为handle_led1 和 handle_led2设置任务的参数,类型是void*:(void *)"led1"、(void *)"led2"

05、设置优先级

分别为handle_led1 和 handle_led2设置任务的优先级分别为:1、1

06、设置任务句柄

分别为handle_led1 和 handle_led2设置任务句柄:

TaskHandle_t task1; TaskHandle_t task2;

3、完整xTaskCreate函数

xTaskCreate(handle_led1,"task1",2048,(void *)"led1",1,&task1);
xTaskCreate(handle_led2,"task2",2048,(void *)"led2",1,&task2);

4、双 LED 小灯控制多任务执行

#include <Arduino.h>

#define LED1 23 // 控制第一颗LED灯的引脚
#define LED2 22 // 控制第二颗LED灯的引脚

TaskHandle_t task1;
TaskHandle_t task2;

void handle_led1(void *ptr)
{
pinMode(LED1, OUTPUT);
char *param = (char *)ptr;
Serial.print(param);
Serial.println(" started");
while (1)
{
digitalWrite(LED1, HIGH);
vTaskDelay(1000);
digitalWrite(LED1, LOW);
vTaskDelay(1000);
}
vTaskDelete(NULL);
}

void handle_led2(void *ptr)
{
pinMode(LED2, OUTPUT);
char *param = (char *)ptr;
Serial.print(param);
Serial.println(" started");
while (1)
{
digitalWrite(LED2, HIGH);
vTaskDelay(3000);
digitalWrite(LED2, LOW);
vTaskDelay(3000);
}
vTaskDelete(NULL);
}

void setup()

{

Serial.begin(115200);

pinMode(LED_PIN1, OUTPUT);

pinMode(LED_PIN2, OUTPUT);

xTaskCreate(handle_led1, "task1_led", 2048, (void *)"led1", 1, &task1);

xTaskCreate(handle_led2, "task2_led", 2048, (void *)"led2", 1, &task2);

}

void loop()
{
}

五、其他函数

1、vTaskDelete函数:

  • xTaskToDelete:表示要删除的任务的句柄,这个句柄就是上面xTaskCreate函数中传入的最后一个参数~,如果参数设置为NULL,表示删除当前任务。一般建议,该函数在每个任务运行结束时传入NULL调用,不建议跨任务删除,有些情况下会产生一些不可预知的问题。

2、 vTaskDelay函数

delay的底层函数也是这个函数。

void vTaskDelay( const TickType_t xTicksToDelay )

xTicksToDelay是延迟的ticks数,对于ESP32,一个tick等于1ms,所以要延迟3秒,就应该调用vTaskDelay(3000)

六、任务绑定CPU

将创建的任务绑定到固定的cpu执行。

BaseType_t xTaskCreatePinnedToCore(

TaskFunction_t pvTaskCode,
const char * const pcName,
const uint32_t usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pvCreatedTask,
const BaseType_t xCoreID

);

xCoreID 是cpu编号

完整案例:

#include <Arduino.h>

#define LED_PIN1 23 // 23号引脚

#define LED_PIN2 22 // 22号引脚

TaskHandle_t task1;

TaskHandle_t task2;

void handle_led1(void *pra)

{

char *param = (char *)pra;

Serial.print("handle_led1 传入的数据参数是:");

Serial.println(param);

while (1)

{

Serial.print("handle_led1 绑定的CPU是:");

Serial.println(xPortGetCoreID());

digitalWrite(LED_PIN1, HIGH);

vTaskDelay(1000);

digitalWrite(LED_PIN1, LOW);

vTaskDelay(1000);

}

vTaskDelete(NULL);

}

void handle_led2(void *pra)

{

char *param = (char *)pra;

Serial.print("handle_led2 传入的数据参数是:");

Serial.println(param);

while (1)

{

Serial.print("handle_led2 绑定的CPU是:");

Serial.println(xPortGetCoreID());

digitalWrite(LED_PIN2, HIGH);

vTaskDelay(3000);

digitalWrite(LED_PIN2, LOW);

vTaskDelay(3000);

}

vTaskDelete(NULL);

}

void setup()

{

Serial.begin(115200);

pinMode(LED_PIN1, OUTPUT);

pinMode(LED_PIN2, OUTPUT);

xTaskCreatePinnedToCore(handle_led1, "task1_led", 2048, (void *)"led1", 1, &task1, 0);

xTaskCreatePinnedToCore(handle_led2, "task2_led", 2048, (void *)"led2", 1, &task2, 1);

}

void loop()

{

}

http://www.gsyq.cn/news/1627292.html

相关文章:

  • 学习线程基础
  • 2026网文剧本AI工具横评:实测5大创作助手,新手避坑指南
  • 豆包与抖音功能联动及性能实测大纲
  • 【Linux之旅】Linux 网络层协议详解:从 IP 报文到路由转发的底层逻辑
  • 微信聊天记录导出终极指南:免费工具帮你永久保存珍贵回忆
  • 【Springboot毕设全套源码+文档】基于springboot社会养老平台的设计与实现(丰富项目+远程调试+讲解+定制)
  • 心脏瓣膜病的症状与临床识别——从“无症状”到典型信号
  • 选收银系统要注意什么?一份来自零售从业者的避坑指南
  • 终极免费方案:零门槛获取Sketchfab 3D模型资源的完整指南
  • 如何快速实现Unity游戏自动翻译:XUnity.AutoTranslator完整配置指南
  • U位报警功能实测:精准预警,零误报
  • 移动应用安全测试自动化框架性能优化实战:十大核心指标与避坑指南
  • PyTorch 深度学习框架与 GPU 加速生态:从入门到理解整个技术栈
  • 基于ASM330LHH和STM32F334R8的高精度运动跟踪系统设计
  • AMD Ryzen硬件调试利器:SMUDebugTool完全实战手册
  • 可变油缸行程能调节?这个功能很多人不知道
  • 2026年标书咨询避坑指南:如何挑选靠谱供应商的三大关键
  • 上海长宁区专业的公寓装修公司
  • M24C04-R EEPROM与PIC18F87J50 MCU的嵌入式存储方案
  • 收藏!揭秘小红书AI算法岗高薪内幕:年薪82W起步,R7竟高达200W?小白程序员必看!
  • RTX Spark深度解析:AI原生PC如何重塑个人计算与AI代理开发
  • GitLab高危漏洞深度解析:从攻击链到安全加固实战指南
  • 终极免费微信聊天记录导出工具WeChatExporter:一键永久保存你的珍贵对话
  • 大模型选型避坑指南:拒绝虚假榜单,聚焦业务场景适配
  • 记录一次看牙的经历
  • 无刷电机FOC控制:基于ATSAME70的高性能实现方案
  • MuleSoft驱动的企业级AI编排实践:LLM治理与生产落地
  • Dalle Mini本地部署指南:CPU上运行文本生成图像模型
  • 3种实战场景:如何用OCRmyPDF智能提取PDF文档元数据,让搜索效率提升90%
  • 【教师备课效率革命】:ChatGPT辅助备课的7大黄金场景与实测提效43%的落地模板