ESP32 FREERTOS 笔记

FREERTOS

xTaskCreate(mpu6050Task, "MPU6050", 1024 * 8, NULL, 1, NULL);

函数名,函数定义标识,分配的内存空间,传入参数,优先级,handle)

程序整理自B站孤独的二进制视频内容

给任务传递单个参数

/*
  程序: FREERTOS - 单个参数传递
        大家在看本程序的时候,需要对指针非常的了解
        知道 * -> &的作用
  作业: 添加LED3_PIN 15
  公众号:孤独的二进制
*/
byte LED1_PIN = 23;
byte LED2_PIN = 21;

void task1(void *pt) {
  byte * pbLEDPIN;
  pbLEDPIN = (byte *)pt;

  byte LEDPIN;
  LEDPIN = *pbLEDPIN;
  pinMode(LEDPIN, OUTPUT);
  while (1) {
    digitalWrite(LEDPIN, !digitalRead(LEDPIN));
    vTaskDelay(1000);
  }
}

void task2(void *pt) {
  byte LEDPIN = *(byte *)pt;
  pinMode(LEDPIN, OUTPUT);
  while (1) {
    digitalWrite(LEDPIN, !digitalRead(LEDPIN));
    vTaskDelay(3000);
  }
}

void setup() {
  Serial.begin(9600);

  byte * pbLED1PIN;
  pbLED1PIN = &LED1_PIN;

  void * pvLED1PIN;
  pvLED1PIN = (void *)pbLED1PIN;

  if (xTaskCreate(task1,
                  "Blink 23",
                  1024,
                  pvLED1PIN,
                  1,
                  NULL) == pdPASS)
    Serial.println("Task1 Created.");

  if (xTaskCreate(task2,
                  "Blink 21",
                  1024,
                  (void *)&LED2_PIN,
                  1,
                  NULL) == pdPASS)
    Serial.println("Task2 Created.");

}

void loop() {
}

结构体多参数传递

/*
  程序: FREERTOS - 结构体多参数传递
  作业: 添加LED3
  公众号:孤独的二进制
*/

typedef struct {
  byte pin;
  int delayTime;
} LEDFLASH;

/*
void ledFlash(void *pt) {
  LEDFLASH * ptLedFlash = (LEDFLASH *)pt;
  LEDFLASH led = *ptLedFlash;

  pinMode(led.pin,OUTPUT);
  while (1) {
    digitalWrite(led.pin, !digitalRead(led.pin));
    vTaskDelay(led.delayTime);
  }
}
*/

void ledFlash(void *pt) {
  LEDFLASH * ptLedFlash = (LEDFLASH *)pt;
  byte pin = ptLedFlash->pin;
  int delayTime = ptLedFlash->delayTime;

  pinMode(pin,OUTPUT);
  while (1) {
    digitalWrite(pin, !digitalRead(pin));
    vTaskDelay(delayTime);
  }
}
 
LEDFLASH led1, led2;

void setup() {
  Serial.begin(9600);

 
  led1.pin = 23;
  led1.delayTime = 1000;
  led2.pin = 21;
  led2.delayTime = 3000;


if (xTaskCreate(ledFlash,
                  "FLASH LED",
                  1024,
                  (void *)&led1,
                  1,
                  NULL) == pdPASS)
    Serial.println("led1 flash task Created.");

if (xTaskCreate(ledFlash,
                  "FLASH LED",
                  1024,
                  (void *)&led2,
                  1,
                  NULL) == pdPASS)
    Serial.println("led2 flash task Created.");

}

void loop() {
}

任务共享全局变量(任务间通讯)

用全局变量可行,但有局限性

  • 在写操作只有一个task,读操作可以有N个task
  • 变量在esp32中需为volatile uint32_t

任务之间通过全局变量进行传递数据

/*
  程序: 任务之间通过全局变量进行传递数据 
  公众号:孤独的二进制
  要求:数量类型-和CPU一致
       写操作-有且只有一个任务
       读操作-可以一个或者多个任务
*/
// 养成良好习惯,被多进程和中断调用的变量使用 volatile 修饰符
volatile uint32_t inventory = 100; //总库存
volatile uint32_t retailCount = 0; //线下销售量


void retailTask(void *pvParam) {
  while (1) {

    //以下实现了带有随机延迟的 inventory减1;
    //等效为 inventory--; retailCount++;
    uint32_t inv = inventory;
    for (int i; i < random(10, 100); i++) vTaskDelay(pdMS_TO_TICKS(i));
    if (inventory > 0) {
      inventory = inv - 1;
      retailCount++;
    }

  };

  vTaskDelay(10); //老板要求慢一些,客户升级后,可以再加快速度
}



void showTask(void *pvParam) {
  while (1) {

    printf("Inventory : %d\n", inventory);
    printf("  Retail : %d\n", retailCount);


    if (inventory == 0 ) {
      printf("\n-----SALES SUMMARY-----\n");
      printf("  Total Sales:  %d\n\n", retailCount);
    }
    vTaskDelay(pdMS_TO_TICKS(100));
  }
}

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);

  xTaskCreate(retailTask,
              "Online Channel",
              1024 * 4,
              NULL,
              1,
              NULL);

  xTaskCreate(showTask,
              "Display Inventory",
              1024 * 4,
              NULL,
              1,
              NULL);

}

void loop() {
}

演示 多任务对 共享资源(×)

/*
  程序: 演示 多任务对 共享资源 同时使用的时候,出现的问题
        多渠道销售,超卖的现象  
  公众号:孤独的二进制

*/

// 养成良好习惯,被多进程和中断调用的变量使用 volatile 修饰符
volatile uint32_t inventory = 100; //总库存
volatile uint32_t retailCount = 0; //线下销售量
volatile uint32_t onlineCount = 0; //线上销售量

void retailTask(void *pvParam) {
  while (1) {

    //以下实现了带有随机延迟的 inventory减1;
    //等效为 inventory--; retailCount++;
    uint32_t inv = inventory;
    for (int i; i < random(10, 100); i++) vTaskDelay(pdMS_TO_TICKS(i));
    if (inventory > 0) {
      inventory = inv - 1;
      retailCount++;
    }

  };

  vTaskDelay(10); //老板要求慢一些,客户升级后,可以再加快速度
}

void onlineTask(void *pvParam) {
  while (1) {

    //以下实现了带有随机延迟的 inventory减1;
    //等效为 inventory--; retailCount++;
    uint32_t inv = inventory;
    for (int i; i < random(10, 100); i++) vTaskDelay(pdMS_TO_TICKS(i));
    if (inventory > 0) {
      inventory = inv - 1;
      onlineCount++;
    }

    vTaskDelay(10); //老板要求慢一些,客户升级后,可以再加快速度
  }
}


void showTask(void *pvParam) {
  while (1) {

    printf("Inventory : %d\n", inventory);
    printf("  Retail : %d, Online : %d\n", retailCount, onlineCount);


    if (inventory == 0 ) {
      uint32_t totalSales = retailCount + onlineCount;
      printf("-----SALES SUMMARY-----\n");
      printf("  Total Sales:  %d\n", totalSales);
      printf("  OverSales:  %d\n", 100 - totalSales);
    }
    vTaskDelay(pdMS_TO_TICKS(100));
  }
}

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);

  xTaskCreate(onlineTask,
              "Online Channel",
              1024 * 4,
              NULL,
              1,
              NULL);

  xTaskCreate(retailTask,
              "Retail Channel",
              1024 * 4,
              NULL,
              1,
              NULL);

  xTaskCreate(showTask,
              "Display Inventory",
              1024 * 4,
              NULL,
              1,
              NULL);

}

void loop() {
}

Tasks之间数据传递(√)

/*
  程序: Tasks之间数据传递
        有多任务同时写入,或者数据大小超过cpu内存通道时,或者对共享资源的访问时候,需要有防范机制
        使用MUTEX对数据对Cirtical Section的内容进行保护
        可以想象成MUTEX就是一把锁

  公众号:孤独的二进制

  语法:
  SemaphoreHandle_t xHandler; 创建Handler
  xHandler = xSemaphoreCreateMutex(); 创建一个MUTEX 返回NULL,或者handler
  xSemaphoreGive(xHandler); 释放
  xSemaphoreTake(xHanlder, timeout); 指定时间内获取信号量 返回pdPASS, 或者pdFAIL

  理解方法:
  MUTEX的工作原理可以想象成
  共享的资源被锁在了一个箱子里,只有一把钥匙,有钥匙的任务才能对改资源进行访问
*/

// 养成良好习惯,被多进程和中断调用的变量使用 volatile 修饰符
volatile uint32_t inventory = 100; //总库存
volatile uint32_t retailCount = 0; //线下销售量
volatile uint32_t onlineCount = 0; //线上销售量

SemaphoreHandle_t xMutexInventory = NULL; //创建信号量Handler

TickType_t timeOut = 1000; //用于获取信号量的Timeout 1000 ticks


void retailTask(void *pvParam) {
  while (1) {

    // 在timeout的时间内如果能够获取就继续
    // 通俗一些:获取钥匙
    if (xSemaphoreTake(xMutexInventory, timeOut) == pdPASS) {
      //被MUTEX保护的内容叫做 Critical Section


      //以下实现了带有随机延迟的 inventory减1;
      //等效为 inventory--; retailCount++;
      uint32_t inv = inventory;
      for (int i; i < random(10, 100); i++) vTaskDelay(pdMS_TO_TICKS(i));
      if (inventory > 0) {
        inventory = inv - 1;
        retailCount++;

        //释放钥匙
        xSemaphoreGive(xMutexInventory);
      } else {
        //无法获取钥匙
      }


    };

    vTaskDelay(100); //老板要求慢一些,客户升级后,可以再加快速度
  }
}

void onlineTask(void *pvParam) {
  while (1) {

    // 在timeout的时间内如果能够获取二进制信号量就继续
    // 通俗一些:获取钥匙
    if (xSemaphoreTake(xMutexInventory, timeOut) == pdPASS) {
      //被MUTEX保护的内容叫做 Critical Section
      //以下实现了带有随机延迟的 inventory减1;
      //等效为 inventory--; retailCount++;
      uint32_t inv = inventory;
      for (int i; i < random(10, 100); i++) vTaskDelay(pdMS_TO_TICKS(i));
      if (inventory > 0) {
        inventory = inv - 1;
        onlineCount++;

        //释放钥匙
        xSemaphoreGive(xMutexInventory);
      } else {
        //无法获取钥匙
      }
    };

    vTaskDelay(100); //老板要求慢一些,客户升级后,可以再加快速度
  }
}


void showTask(void *pvParam) {
  while (1) {

    printf("Inventory : %d\n", inventory);
    printf("  Retail : %d, Online : %d\n", retailCount, onlineCount);


    if (inventory == 0 ) {
      uint32_t totalSales = retailCount + onlineCount;
      printf("-----SALES SUMMARY-----\n");
      printf("  Total Sales:  %d\n", totalSales);
      printf("  OverSales:  %d\n", 100 - totalSales);
    }
    vTaskDelay(pdMS_TO_TICKS(1000));
  }
}

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);

  xMutexInventory = xSemaphoreCreateMutex(); //创建MUTEX

  if (xMutexInventory == NULL) {
    printf("No Enough Ram, Unable to Create Semaphore.");
  } else {
    xTaskCreate(onlineTask,
                "Online Channel",
                1024 * 4,
                NULL,
                1,
                NULL);
    xTaskCreate(retailTask,
                "Retail Channel",
                1024 * 4,
                NULL,
                1,
                NULL);
    xTaskCreate(showTask,
                "Display Inventory",
                1024 * 4,
                NULL,
                1,
                NULL);
  }

}

void loop() {
}

Mutex实例

将多个任务写的全局变量添加一把锁和钥匙,同时只能一个去写,另一个等待,避免超卖现象。

使用MUTEX对MPU6050的读写做保护

/*
   程序:  MPU6050 & MUTEX
   公众号:孤独的二进制
*/
#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 20, 4);

Adafruit_MPU6050 mpu;
Adafruit_Sensor *mpu_temp, *mpu_accel, *mpu_gyro;

typedef struct {
  float temp;
  float accX;
  float accY;
  float accZ;
  float gyroX;
  float gyroY;
  float gyroZ;
} MPU6050;

MPU6050 mpu6050;

SemaphoreHandle_t xMutexMPU6050 = NULL; //创建信号量Handler
TickType_t timeOut = 1000; //用于获取信号量的Timeout 1000 ticks


void mpu6050Task(void *pvParam) {

  mpu.begin();

  mpu_temp = mpu.getTemperatureSensor();
  mpu_temp->printSensorDetails();

  mpu_accel = mpu.getAccelerometerSensor();
  mpu_accel->printSensorDetails();

  mpu_gyro = mpu.getGyroSensor();
  mpu_gyro->printSensorDetails();

  sensors_event_t accel;
  sensors_event_t gyro;
  sensors_event_t temp;

  while (1) {

    if (xSemaphoreTake(xMutexMPU6050, timeOut) == pdPASS) {

      //获取MPU数据
      mpu_temp->getEvent(&temp);
      mpu_accel->getEvent(&accel);
      mpu_gyro->getEvent(&gyro);

      mpu6050.temp = temp.temperature;
      mpu6050.accX = accel.acceleration.x;
      mpu6050.accY = accel.acceleration.y;
      mpu6050.accZ = accel.acceleration.z;
      mpu6050.gyroX = gyro.gyro.x;
      mpu6050.gyroY = gyro.gyro.y;
      mpu6050.gyroZ = gyro.gyro.z;

      xSemaphoreGive(xMutexMPU6050); //释放钥匙
    } else {
      //Unable to obtain MUTEX
    }

    vTaskDelay(500);
  }
}

void lcdTask(void *ptParam) {  //LCD任务主体

  lcd.init();
  lcd.backlight();

  //定义是 2004 LCD
  byte lcdLine = 4;
  byte lcdChar = 20;

  //创建一个二维的的数组
  //注意长度是 lcdChar+1 最后还有一个位置要给换行符
  char line0[lcdChar + 1], line1[lcdChar + 1], line2[lcdChar + 1], line3[lcdChar + 1];
  char * line[] = { line0, line1, line2, line3, };

  while (1) {

    if (xSemaphoreTake(xMutexMPU6050, timeOut) == pdPASS) {

      // 组合数据
      sprintf(line0, "     MPU6050 %d", xTaskGetTickCount() / 100);
      sprintf(line1, " Temperature  %.2f", mpu6050.temp);
      sprintf(line2, " ACC  %.2f %.2f %.2f", mpu6050.accX, mpu6050.accY, mpu6050.accZ);
      sprintf(line3, " GYRO %.2f %.2f %.2f", mpu6050.gyroX, mpu6050.gyroY, mpu6050.gyroZ);

      xSemaphoreGive(xMutexMPU6050); //释放钥匙
    } else {
      //Unable to obtain MUTEX
    }

    // 显示数据
    for (int i = 0; i < 4; i++) {
      lcd.setCursor(0, i);
      lcd.print(line[i]);
    }

    vTaskDelay(1000);
  }
}

void setup()
{
  Serial.begin(115200);

  xMutexMPU6050 = xSemaphoreCreateMutex(); //创建MUTEX

  xTaskCreate(mpu6050Task, "MPU6050", 1024 * 8, NULL, 1, NULL);
  vTaskDelay(1000); //让MPU6050提前先运行一秒获取第一笔数据
  xTaskCreate(lcdTask, "lcd", 1024 * 8, NULL, 1, NULL);

}


void loop() {}

使用双核

esp32的C系列为单核,如esp32c3开发板

  • 1 - SETUP里的内容属于放入到对应的Task Setup部分中 ;
  • 2 - 使用 xTaskCreatePinnedToCore 手动指派任务到核心1 ;
  • 4- 分配给任务的内存多给点,别扣门
  • 5 - SETUP任务完成使命后,使用vTaskDelete(NULL) 自宫 ;

查看核心

在ESP32 dev 双核开发板中,loop任务运行在核心1中,核心0由系统多用于WiFi任务等,因此建议让自定义任务存放在核心1中

/*
  程序: 多核多任务

  API:
      xPortGetCoreID() 获取当前任务运行的核心
      xTaskCreate() 有系统选择运行核心,优先选择0
      xTaskCreatePinnedToCore() 指派任何给指定核心

  公众号:孤独的二进制
*/
#include <Arduino.h>

void taskA(void *ptParam)
{
  while (1)
  {
    Serial.printf("Task A is running on core %d\n", xPortGetCoreID());
    delay(1000);
  }
}

void setup()
{
  // Task setup is running on core 1
  Serial.begin(115200);
  xTaskCreatePinnedToCore(taskA, "Task A", 1024 * 4, NULL, 1, NULL, 1);
  // 销毁当前的任务
  // vTaskDelete(NULL);
}

void loop()
{
  // Task loop is running on core 1
  Serial.printf("Task loop is running on core %d\n", xPortGetCoreID());
  delay(1000);
}

指派核心

/*
   程序:  ESP32 OLED 使用 U8G2库
          使用FREERTOS
   公众号:孤独的二进制
*/
#include <U8g2lib.h>
#include <Wire.h>

void oledTask(void * pvParam) {
  U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
  u8g2.begin();
  for (;;) {
    u8g2.clearBuffer();					// clear the internal memory
    u8g2.setFont(u8g2_font_ncenB08_tr);	// choose a suitable font
    u8g2.drawStr(15, 10, "LONELY BINARY");	// write something to the internal memory
    u8g2.sendBuffer();					// transfer internal memory to the display
    vTaskDelay(1000);
  }
}

void setup() { //loopBack , Priority 1, Core 1
  xTaskCreatePinnedToCore(oledTask, "OLED Task", 1024 * 6, NULL, 1, NULL, 1);

  vTaskDelete(NULL);
}

void loop() {

}
	

任务以绝对频率运行

视频位置

/*
  程序: 绝对任务延迟
  公众号:孤独的二进制
  API:
    vTaskDelayUntil(&xLastWakeTime, xFrequency)
      最后一次的唤醒时间是指针类型。
      本函数会自动更新xLastWakeTime为最后一次唤醒的时间
      所以无需手动在while循环内对其手动赋值
    xTaskGetTickCount()
      Tick Coun 和 Arduino Millis一样
      uint32_t类型 49天后overflow
*/

void showStockTask(void *ptParam) {
  static float stockPrice = 99.57; //股票价格

  //最后一次唤醒的tick count,第一次使用需要赋值
  //以后此变量会由vTaskDelayUntil自动更新
  TickType_t xLastWakeTime = xTaskGetTickCount();

  const TickType_t xFrequency = 3000; // 间隔 3000 ticks = 3 seconds

  for (;;) {
    //恰恰算算,经过思考,既然我们叫做LastWakeTime,那么 vTaskDelayUntil 应该放在循环的第一句话
    //如果放在循环的最后一句话,应该改为xLastSleepTime 才更加合适
    // 看懂的朋友, 请鼓掌
    // 哦,我无法听到掌声,干脆帮我按住 点赞三秒 对我的视频进行强力推荐吧
    vTaskDelayUntil(&xLastWakeTime, xFrequency);

    //验证当前唤醒的时刻tick count
    Serial.println(xTaskGetTickCount());
    //验证xLastWake Time是否被vTaskDelayUntil更新
    //Serial.println(xLastWakeTime);

    // ------- 很复杂的交易股票计算,时间不定 ---------
    stockPrice = stockPrice * (1 + random(1, 20) / 100.0); vTaskDelay(random(500, 2000));

    Serial.print("Stock Price : $");
    Serial.println(stockPrice);

    //使用vTaskDelay试试看会如何
    //vTaskDelay(xFrequency);
  }

}

void setup() {
  Serial.begin(115200);
  xTaskCreate(showStockTask, "Show Stock Price", 1024 * 6, NULL, 1, NULL);
}

void loop() {

}

软件定时器

作为实时操作系统的FreeRTOS给我们提供了一个软件实现的定时器。很多朋友,也许没有使用过Timer。这主要是开发板上硬件Timer太少了,轮不到我们最终用户使用。 FreeRTOS给我们打来了新的大门,只要内存允许,想要多少个Timer 就有多少个Timer。本节课讲会通过智能汽车来给大家介绍两种Timer:重复性的和一次性的Timer。

/*
  程序: Software Timer
  公众号:孤独的二进制
  API:
    xTimerCreate //创建时间
    xTimerStart //时间开始
    到时间后,会运行callback函数
*/

TimerHandle_t lockHandle, checkHandle;

void carKey(void *ptParam) {
  byte lockPin = 23;
  pinMode(lockPin, INPUT_PULLUP);

  for (;;) {
    if (digitalRead(lockPin) == LOW) {
      //timeout 3000 ticks
      //xTimerStart 只是开启时间而已,而不是创造时间对象
      //所以如果多次按按钮的话,不会有多个时间对象生成
      //多次按按钮相当于每次对timer进行reset xTimerReset()
      if (xTimerStart(lockHandle, 3000) == pdPASS) {
        Serial.println("About to lock the car");
      } else {
        Serial.println("Unable to lock the car");
      };
      vTaskDelay(100); //very rude Button Debounce
    }

  }
}

void lockCarCallback(TimerHandle_t xTimer) {
  Serial.println("Timer CallBack: Car is Locked");
}

void checkCallback(TimerHandle_t xTimer) {
  // ------- 很复杂的检测汽车Sensors的方法,时间不定 ---------
  Serial.print(xTaskGetTickCount());Serial.println("  -  All Sensors are working."); vTaskDelay(random(10, 90));
}

void setup() {
  Serial.begin(115200);
  xTaskCreate(carKey,
              "Check If Owner Press Lock Button",
              1024 * 1,
              NULL,
              1,
              NULL);

  lockHandle = xTimerCreate("Lock Car",
                            2000,
                            pdFALSE,
                            (void *)0,
                            lockCarCallback);

  checkHandle = xTimerCreate("Sensors Check",
                             100,
                             pdTRUE,
                             (void *)1,
                             checkCallback);

  //必须要在 portMAX_DELAY 内开启 timer start
  //portMAX_DELAY is listed as value for waiting indefinitely
  //实际上0xFFFFFFFF 2^32-1  49天 7周
  //在此期间,此task进入Block状态
  xTimerStart(checkHandle, portMAX_DELAY);
}

void loop() {
}

内存管理

如何动态获取任务实际所占有的内存空间,并根据该值对任务的内存大小进行优化设置。

/*
  程序: 内存管理
  公众号:孤独的二进制
  API:
    ESP.getHeapSize() //本程序Heap最大尺寸
    ESP.getFreeHeap() //当前Free Heap最大尺寸
    uxTaskGetStackHighWaterMark(taskHandle) //Task内存使用最大水位线,内存是水
     
    What is the Highest Water Mark?
    the minimum amount of remaining stack space that was available to the task 
    since the task started executing - that is the amount of stack that remained 
    unused when the task stack was at its greatest (deepest) value. This is what 
    is referred to as the stack 'high water mark'.
*/
TaskHandle_t taskHandle;
int taskMem = 1024;

void task(void *ptParam) {
  //volatile char hello[1000] = {0}; //必须要用volatile修饰语,否则会被编译器优化掉
  while (1) {

    //不推荐在task中执行,因为Serial.print也会消耗内存
    // vTaskDelay(2000);
    // int waterMark = uxTaskGetStackHighWaterMark(nullptr);
    // Serial.print("Task Free Memory: ");
    // Serial.print(waterMark);
    // Serial.println(" Bytes");
    // Serial.print("Task Used Memory: ");
    // Serial.print(taskMem - waterMark);
    // Serial.println(" Bytes");
    // Serial.println("");

  }
}
void setup() {
  Serial.begin(115200);

  int heapSize = ESP.getHeapSize();
  Serial.print("Total Heap Size:  ");
  Serial.print(heapSize);
  Serial.println(" Bytes");

  int heapFree = ESP.getFreeHeap();
  Serial.print("Free Heap Size:  ");
  Serial.print(heapFree);
  Serial.println(" Bytes");
  Serial.println("");


  Serial.println("Create Task ...");
  xTaskCreate(task, "", taskMem, NULL, 1, &taskHandle);

  Serial.print("Free Heap Size:  ");
  Serial.print(ESP.getFreeHeap());
  Serial.println(" Bytes");
  Serial.println("");

  vTaskDelay(2000);
  int waterMark = uxTaskGetStackHighWaterMark(taskHandle);
  Serial.print("Task Free Memory: ");
  Serial.print(waterMark);
  Serial.println(" Bytes");
  Serial.print("Task Used Memory: ");
  Serial.print(taskMem - waterMark);
  Serial.println(" Bytes");

}

void loop() {

}

任务管理和多种状态

以下是task的多种状态:准备、暂停、运行、block

![image-20220825165529983](ESP32 FREERTOS.assets/image-20220825165529983.png)

/*
  程序: 任务管理
  公众号:孤独的二进制
  API:
    BaseType_t xTaskCreate(,,,,,); //任务创建
    void vTaskDelete( TaskHandle_t xTask ); //任务删除 
    void vTaskSuspend( TaskHandle_t xTaskToSuspend ); //任务暂停
    void vTaskResume( TaskHandle_t xTaskToResume ); //任务恢复
*/
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 20, 4);

TaskHandle_t biliHandle = NULL; //Task Handler

void control(void *ptParam) {  //按钮控制
  
  pinMode(32, INPUT_PULLUP);
  pinMode(33, INPUT_PULLUP);
  pinMode(25, INPUT_PULLUP);
  pinMode(26, INPUT_PULLUP);

  while (1) {
    // 创建任务
    if (digitalRead(32) == LOW) {
      //判断是否之前已经创建了Bilibili channel task, 如果没有创建,则创建该Task
      if (biliHandle == NULL) {
        if (xTaskCreate(radioBilibili, "Bilibili Channel", 1024 * 8, NULL, 1, &biliHandle) == pdPASS) {
          Serial.print(xTaskGetTickCount());
          Serial.println(" - LOG: Task is Created.");
        } else {
          Serial.print(xTaskGetTickCount());
          Serial.println(" - WARNING: Unable to Create Task.");
        }
      } else {
        Serial.print(xTaskGetTickCount());
        Serial.println(" - WARNING: Task **WAS** Created.");
      }
      vTaskDelay(120); //粗暴的Button Debounce
    }

    //任务删除
    if (digitalRead(33) == LOW) {
      //注意在删除任务前,一定要确保任务是存在的
      //删除不存在的任务,比如连续删除两次,自动重启
      if (biliHandle != NULL) {
        vTaskDelete( biliHandle );
        lcdClear(); //清空LCD
        biliHandle = NULL; //手动将handler设置为空
      }

      if (biliHandle != NULL) {
        Serial.print(xTaskGetTickCount());
        Serial.println(" - WARNING: Unable to Delete Task.");
      } else {
        Serial.print(xTaskGetTickCount());
        Serial.println(" - LOG: Task is Deleted.");
      }
      vTaskDelay(120);  //粗暴的Button Debounce
    }

    // 任务暂停
    if (digitalRead(25) == LOW) {
      if (biliHandle != NULL) {
        vTaskSuspend(biliHandle);
        Serial.print(xTaskGetTickCount());
        Serial.println(" - LOG: Task is suspended.");
      } else {
        Serial.print(xTaskGetTickCount());
        Serial.println(" - WARNING: Unable to Suspend Task.");
      }
      vTaskDelay(120);  //粗暴的Button Debounce
    }

    // 任务恢复
    if (digitalRead(26) == LOW) { 
      if (biliHandle != NULL) {
        vTaskResume(biliHandle);
        Serial.print(xTaskGetTickCount());
        Serial.println(" - LOG: Task is resumed.");
      } else {
        Serial.print(xTaskGetTickCount());
        Serial.println(" - WARNING: Unable to Resume Task.");
      }
      vTaskDelay(120);  //粗暴的Button Debounce
    }
  }
}

void radioBilibili(void *ptParam) {  //任务主体

  lcd.init();
  lcd.backlight();
  lcd.setCursor(0, 0);
  lcd.print(" Bilibili   Channel ");
  lcd.setCursor(0, 1);
  lcd.print("-FreeRTOS on EPS32- ");
  lcd.setCursor(0, 2);
  lcd.print("Study Hard  &  Smart");

  while (1) {
    lcd.setCursor(9, 3);
    lcd.print(xTaskGetTickCount() / 100);
    vTaskDelay(100);
  }
}

void lcdClear() { //清空LCD
  lcd.clear();
  lcd.noBacklight();
  lcd.setCursor(0, 0);
  lcd.print("                                                                                                    ");
}

void setup()
{
  Serial.begin(115200);
  xTaskCreate(control, "control panel", 1024 * 8, NULL, 1, NULL);
}


void loop() {}

EOF

esp32c3各模组区别

esp32c3系列最小系统板

posted @ 2022-08-25 17:03  Dapenson  阅读(374)  评论(0编辑  收藏  举报