一、前提案例

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()

{

}

Logo

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

更多推荐