Day2--使用ESP32双核、U8G2 OLED任务、任务以绝对频率运行、任务内存优化

使用ESP32双核

ESP32-C系列为单核,ESP32的core0主要运行WI-FI和蓝牙

  API:

      xPortGetCoreID() 获取当前任务运行的核心

      xTaskCreate() 有系统选择运行核心,优先选择0

      xTaskCreatePinnedToCore() 指派任何给指定核心

Arduino的setup和loop默认运行在core1 

#include <Arduino.h>

void taskA(void *ptParam)
{
  while (1)
  {
    Serial.println(xPortGetCoreID()); // 获取当前任务运行的核心
  }
}

void setup()
{
  // put your setup code here, to run once:
  Serial.begin(115200);
  xTaskCreatePinnedToCore(taskA, "Task A", 1024 * 4, NULL, 1, NULL, 0); // 最后一个参数为核心0
}

void loop()
{
}

U8G2 OLED任务

 创建任务时使用空指针的原因,加快速度,可以接收任何类型的数据

注意事项,把U8G2相关的配置(初始化)写进任务中,不要写道setup中

#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); // 删除setup任务,节省内存空间
}

void loop()
{
}

任务以绝对频率运行

实时操作系统要求必须在指定的时间内处理数据(比如每3秒显示一次数据,不能快,也不能慢)

vTaskDelayUntil函数vTaskDelay函数定时精准,用处:周期性获取传感器数据(蓝牙发送数据)

vTaskDelayUntil函数vTaskDelay函数多了一个记录任务本次被唤醒的时刻的变量因此如果想要实现控制任务能够周期性运行的话,vTaskDelayUntil函数是一种比较简单的方法

注意:LastWakeTime中保存的是上次唤醒时间,进入vTaskDelayUntil后会判断,如果上次唤醒时间大于当前时间,说明节拍计数器溢出了,就不在延时直接执行了。

  API:

    vTaskDelayUntil(&xLastWakeTime, xFrequency)

      最后一次的唤醒时间是指针类型。

      本函数会自动更新xLastWakeTime为最后一次唤醒的时间

      所以无需手动在while循环内对其手动赋值

    xTaskGetTickCount()

      TickCount和 Arduino Millis一样

      uint32_t类型 49天后overflow(溢出)

void showStockTask(void *ptParam)
{
  static float stockPrice = 99.57; //股票价格
  //最后一次唤醒的tick count,第一次使用需要赋值
  //以后此变量会由vTaskDelayUntil自动更新
  TickType_t xLastWakeTime = xTaskGetTickCount(); // 相当于millis

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

任务内存优化

1024单位是STACK,不是字节,1STACK=4Byte,因此,1024K是4KB字节空间。1024*4是16KB字节空间。虽然ESP32的RAM有320KB,算是几款MCU中最大的了。但由于是STACK,也不能太任意的用了。需要注意用量。

创建任务时,如果分配的内存空间过小,则会导致程序不断重启。

变量在stack中

建立任务时会占用额外的内存空间,大概368 bytes 

如果有很多数据需要串口输出,单独创建一个task(例如蓝牙发送所有数据,其他任务通过多任务传参的方式把需要输出的数据发送给,打印输出task

  API:

    ESP.getHeapSize() //  本程序Heap最大尺寸(空间总大小)

    ESP.getFreeHeap() //  当前Free Heap最大尺寸(当前可用剩余空间大小)

    uxTaskGetStackHighWaterMark(taskHandle) // Task内存使用最大水位线,内存是水

/*
  程序: 内存管理
  公众号:孤独的二进制
  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'.
*/
#include <Arduino.h>
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);
  Serial.println("串口打印测试");
  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()
{
}

任务优先级

可以设置0~24之间的任意数字,0最小,24优先级最大

看门狗

防止死机,每一个CPU都有一只看门狗,ESP32有两个核心,所以有两只狗,看门狗是针对任务的,不是针对CPU,默认情况下在核心0有一只看门狗

队列单种数据(重要)

FIFO:first in first out

队列:queue,先进先出

队列是一种数据结构,可以包含一组固定大小的数据,在创建队列的同时,队列的长度和所包含数据类型的大小就确认下来了,一个队列可以有多个写入数据的任务和多个读取数据的任务。当一个任务试图从队列读取 数据的时候,它可以设置一个堵塞时间(block time)。这是当队列数据为空时,任务会进入阻塞状态的时间。当有数据在队列或者到达阻塞时间的时候,任务都会进入就绪状态。如果有多个任务同时在阻塞状态等待队列数据,优先级高的任务会在数据到达时进入就绪状态;在优先级相同的时候,等待时间长的任务会进入就绪状态。同理可以推及多个任务写入数据时候的运行状态。

使用步骤:

  1. 创建队列(设定队列长度,数据大小size)
  2. 往队列里发送数据
  3. 读取数据
  4. 案例:聊天室发信息

API:

    QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength,

                             UBaseType_t uxItemSize );

    BaseType_t xQueueSend(

                            QueueHandle_t xQueue,

                            const void * pvItemToQueue,

                            TickType_t xTicksToWait

                         );

    BaseType_t xQueueReceive(

                               QueueHandle_t xQueue,

                               void *pvBuffer,

                               TickType_t xTicksToWait

                            );

队列多种数据(超级重要)

案例:通过队列+结构体的方式,将DHT22的温度和湿度数据在不同的任务间传输

创建队列:长度(可以放几个数据)、大小(每个数据的大小)

编程技巧:可以创建一个结构体类型,然后创建多个对象进行调用。

可以采用这种方法给上位机发送数据(把LCD显示部分的程序改为自己需要的数据)

/*
   程序:  消息队列
   公众号:孤独的二进制
   说明:本实例使用结构体巧妙的通过当个队列传输多个设备多种数据类型
        在接收方,我们通过deviceID来判断数据来源和value的意义

   API:
    QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength,
                             UBaseType_t uxItemSize );

    BaseType_t xQueueSend(
                            QueueHandle_t xQueue,
                            const void * pvItemToQueue,
                            TickType_t xTicksToWait
                         );
    BaseType_t xQueueReceive(
                               QueueHandle_t xQueue,
                               void *pvBuffer,
                               TickType_t xTicksToWait
                            );

*/
#include "DHTesp.h"
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 20, 4);
/*设备ID,共有2个设备*/
#define DHT22_ID 0 
#define LDR_ID 1

typedef struct
{
  byte deviceID;
  float value1;
  float value2;
} SENSOR;

QueueHandle_t queueSensor = xQueueCreate(8, sizeof(SENSOR)); // 长度8,采用sizeof自动计算需要的空间

void dht22(void *ptParam)
{

  const byte dhtPin = 32;
  DHTesp dhtSensor;
  dhtSensor.setup(dhtPin, DHTesp::DHT22);

  SENSOR dht22Sensor;  // 创建一个结构体对象
  dht22Sensor.deviceID = DHT22_ID;

  while (1)
  {
    TempAndHumidity data = dhtSensor.getTempAndHumidity();

    // Serial.println("Temp: " + String(data.temperature, 2) + "°C");
    // Serial.println("Humidity: " + String(data.humidity, 1) + "%");

    dht22Sensor.value1 = data.temperature;
    dht22Sensor.value2 = data.humidity;

/*往队列里发送数据,如果队列数据是满的,等待2s,如果2s之后,队列还是满的,就放弃写操作*/
    // TickType_t timeOut = portMAX_DELAY;  不要用这个,时间为49天
    TickType_t timeOut = 2000;
    if (xQueueSend(queueSensor, &dht22Sensor, timeOut) != pdPASS)
    {
      Serial.println("DHT22: Queue is full.");
    }

    vTaskDelay(1000); // 这个时间如果比较短,则队列很快就满了
  }
}

void ldr(void *ptParam)
{
  const float GAMMA = 0.7;
  const float RL10 = 50;
  const byte ldrPIN = 27;
  pinMode(ldrPIN, INPUT);

  SENSOR ldrSensor;
  ldrSensor.deviceID = LDR_ID;

  while (1)
  {
    int analogValue = analogRead(ldrPIN);

    float voltage = analogValue / 4095. * 5;
    float resistance = 2000 * voltage / (1 - voltage / 5);
    float lux = pow(RL10 * 1e3 * pow(10, GAMMA) / resistance, (1 / GAMMA));

    // Serial.print("LDR Light Sensor lux : ");
    // Serial.println(lux);

    ldrSensor.value1 = lux;
    // ldrSensor.value2 = 0.0; 这条语句不要也行

    // TickType_t timeOut = portMAX_DELAY;
    TickType_t timeOut = 2000;
    if (xQueueSend(queueSensor, &ldrSensor, timeOut) != pdPASS)
    {
      Serial.println("LDR: Queue is full.");
    }

    vTaskDelay(1000);
  }
}

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

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

  lcd.setCursor(0, 0);
  lcd.print("   LONELY  BINARY  ");

  SENSOR data;  // 
  while (1)
  {
    // TickType_t timeOut = portMAX_DELAY;
    TickType_t timeOut = 2000;
    if (xQueueReceive(queueSensor, &data, timeOut) == pdPASS)
    {

      switch (data.deviceID)
      {
      case DHT22_ID:
        lcd.setCursor(0, 1);
        lcd.print("Temp: " + String(data.value1, 2) + "c");
        lcd.setCursor(0, 2);
        lcd.print("Humidity: " + String(data.value2, 1) + "%");
        break;
      case LDR_ID:
        lcd.setCursor(0, 3);
        if (data.value1 > 50)
        {
          lcd.print("Bright ");
        }
        else
        {
          lcd.print("Dark ");
        }
        // lcd.setCursor(0, 3);
        lcd.print(String(data.value1, 2) + " lux");
        break;
      default:
        Serial.println("LCD: Unkown Device");
        break;
      }
    }
    else
    {
      Serial.println("LCD: Message Queue is Empty");
    };

    vTaskDelay(2000);
  }
}

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

  xTaskCreate(dht22, "DHT22", 1024 * 4, NULL, 1, NULL);
  xTaskCreate(ldr, "LDR LIGHT", 1024 * 4, NULL, 1, NULL);
  xTaskCreate(lcdTask, "lcd", 1024 * 8, NULL, 1, NULL);
}

void loop() {}
  • 6
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用kill命令的选项"-9"可以强制终止进程。要批量使用kill -9命令杀死任务,可以通过组合使用ps、grep和awk命令来实现。首先,使用ps命令获取所有相关的进程。然后,使用grep命令过滤出需要终止的进程。最后,使用awk命令将需要终止的进程ID提取出来并传递给kill命令。以下是具体的命令示例: ps -ef | grep java | grep datax | grep -v grep | awk '{print "kill -9 "$2}' | sh 以上命令的执行过程如下:首先,使用ps -ef命令列出所有正在运行的进程;然后,使用grep命令过滤出包含"java"和"datax"关键字的进程;着,使用grep -v grep命令去除掉grep进程本身;最后,使用awk命令将过滤结果中的进程ID提取出来,并使用kill -9命令强制终止这些进程。 请注意,kill -9命令会立即终止进程,但也可能会导致数据丢失或其他不可预测的后果。因此,在使用kill -9命令之前,请确保您已经保存了所有需要保存的数据,并确保您理解并接受了潜在的风险。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [oracle kill -9 会话](https://blog.csdn.net/weixin_39629969/article/details/116350055)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [Linux批量kill进程 (批量杀掉进程)、CentOS](https://blog.csdn.net/sunny_day_day/article/details/127973923)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值