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

news2024/12/25 9:18:34

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

任务内存优化

变量在stack中

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

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

任务优先级

可以设置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() {}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/53638.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

初识Thread类与创建多线程的方法

目录 1.创建一个线程 2.start()方法与run()方法 3.查看线程 4.创建线程的各种方法 4.1实现Runnable接口 4.2使用匿名内部类 4.3使用匿名内部类,实现Runnable 4.4使用Lambda表达式 1.创建一个线程 Java操作线程最核心的类就是Thread类 创建线程有很多方法,下面我们写一…

[附源码]计算机毕业设计springboot人体健康管理app

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

SpringBoot结合XXL-JOB实现定时任务

《从零打造项目》系列文章 工具 比MyBatis Generator更强大的代码生成器 ORM框架选型 SpringBoot项目基础设施搭建SpringBoot集成Mybatis项目实操SpringBoot集成MybatisPlus项目实操SpringBoot集成Spring Data JPA项目实操 数据库变更管理 数据库变更管理&#xff1a;Liquibase…

阿里自爆秋招面试笔记,福音来了

前言 最近又掀起了一股面试招聘风潮&#xff0c;我们一边要巩固基础知识、备战试题的同时&#xff0c;另外与面试官的对达沟通也直接影响你的成功与否&#xff01;那我们应该如何做好准备呢&#xff1f; ​先说面试过程&#xff0c;今天阿鑫给大家整理了这三个小经验&#xf…

笔试强训Day2

选择题 1. 思路&#xff1a;e是按指数类型输出&#xff0c;f是按照浮点数类型输出&#xff08;输出只有f不用lf&#xff09; 输出默认是右对齐&#xff0c;-则是左对齐&#xff0c;四位精度就是小数点后保留几位用.4&#xff0c;输出字符数用整数30在小数点前表示即可。 如果…

MySQL数据库复习——索引

目录 一、什么是索引&#xff1f;索引的作用 二、索引的简单使用 三、索引背后的数据结构 1、B 树 2、B 树 一、什么是索引&#xff1f;索引的作用 索引是一种特殊的文件&#xff0c;包含着对数据表里所有记录的引用指针。可以对表中的一列或多列创建索引&#xff0c;并指…

Nacos 配置中心

1. 什么是配置中心 1.1 什么是配置 应用程序在启动和运行的时候往往需要读取一些配置信息&#xff0c;配置基本上伴随着应用程序的整个生命周期&#xff0c;比如&#xff1a;数据库连接参数、启动参数等。 配置主要有以下几个特点&#xff1a; 配置是独立于程序的只读变…

java基础讲义03

java基础讲义一 流程控制1.1流程控制语句介绍1.2顺序结构二 选择控制2.1 选择结构介绍2.2 选择语句if2.2.1 格式12.2.2 if格式22.2.3 if格式32.3 选择语句switch2.3.1 switch语句介绍2.3.2 switch注意事项2.3.3 switch面试题2.3.4 if语句与switch语句区别三 循环语句3.1 循环语…

Yolov5 计算访存量MAC与计算量FLOPS

说明&#xff1a;因为yolov5函数中已经计算了 FLOPS&#xff0c;因此如果想要计算访存量那么只需按照flops的位置,添加访存量的计算即可 一、先记住计算量和访存量的公式&#xff1a; 二、找到计算FLOPS的位置&#xff0c;并添加访存量 yolov5中计算flops的位置在torch_utile…

微服务外交官-Feign

引言 书接上篇 负载均衡组件Ribbon核心-LoadBalanced-下 我们讲完了Ribbon负载均衡原理之后&#xff0c;接下讲一个SpringCloud Alibaba新的组件&#xff1a;Fegin 前面章节我们使用Ribbon方式实现负载均衡版的远程调用&#xff0c; //方案4&#xff1a;使用Ribbon方式--带负…

不知道10年老电脑如何重装系统?其实很简单

肯定很多朋友家里都有一两台吃灰的10年老电脑了&#xff0c;当我们有空可以把它拿出来耍耍的时候&#xff0c;却发现电脑无法开机了&#xff0c;想拿去修又觉得不太值得。其实我们可以先试着给电脑重装系统&#xff0c;这样也能先排查系统是否有问题。如果你不知道这台10年老电…

【Linux】Linux编译器-gcc/g++使用

大家好我是沐曦希&#x1f495; 文章目录1.背景知识1.1 预处理1.2 编译&#xff08;生成汇编&#xff09;1.3 汇编&#xff08;生成机器可识别代码&#xff09;1.4 链接接(生成可执行文件或库文件&#xff09;1.5 习题习题一习题二2.函数库2.1 动态库2.2 静态库3.g的基本使用4.…

数据结构之二叉树

数据结构之二叉树什么是二叉树二叉树的特殊类型二叉树的遍历方式二叉树的叶子结点求法二叉树的高度求法什么是二叉树 二叉树&#xff08;Binary tree&#xff09;是树形结构的一个重要数据类型&#xff0c;想要成为二叉树必须满足两个条件。1、本身是有序树。2、树中包含的各个…

【小程序】导航栏和内容页面联动效果实现

&#x1f4ad;&#x1f4ad; ✨&#xff1a; 微信小程序导航栏和页面联动   &#x1f49f;&#xff1a;东非不开森的主页   &#x1f49c;: 因为很多东西来不及去做去看可是时间很快总是赶不上&#xff0c;所以要去成长呀&#x1f49c;&#x1f49c;   &#x1f338;: 如有错…

React—— HelloWorld

React 学习笔记Hello WorldJSX (JavaScript XML) 语法规则JavaScript 语法函数组件、类组件 & 属性 props组合组件生命周期函数 & 状态 state事件处理refs受控组件、非受控组件 & 高阶函数、函数的柯里化npm参考Hello World <!DOCTYPE html> <html lang&…

Request请求转发与Respones请求重定向有什么区别?

目录&#xff1a; 1.Request请求转发 2.Respones请求重定向 3.关于转发和重定向的路径问题 1.Request请求转发 请求转发(forward)是一种在服务器内部的资源跳转方式。 (1)浏览器发送请求给服务器&#xff0c;服务器中对应的资源A接收到请求 (2)资源A处理完请求后将请求发…

在html中使用js实现图片的无缝滚动(四种状态)

文章目录js原理实施任务1.从左往右无缝滚动代码示例运行效果2.从右往左无缝滚动代码示例运行效果3.从上往下无缝滚动代码示例运行效果4.从下往上无缝滚动代码示例运行效果js原理 获取整个ul和ul下面的所有li&#xff0c; 把ul里面的li内容添加一份&#xff0c;因为需要完成图片…

【加油站会员管理小程序】01需求分析

随着这两年微搭产品的迭代,目前组件基本够用,像常用的支付这种功能也是有的,因此我们就可以结合现有功能来完成一个实际使用的小程序的开发。 我们本次的实战课,是以加油站的业务场景为出发点,涵盖会员的开通、充值、消费、积分兑换等常用场景。结合实战案例,我们完整的…

12月2日:thinkphp中的链式操作

补充内容&#xff1a; 今天我们就来聊聊limit和page的区别以及group by需要注意的点&#xff0c;where和having的区别 limit和page 区别 Limit 限制查询数量&#xff0c;在进行分页查询的时候用的最多&#xff0c;但是limit在查询限制中的索引值是从0开始的&#xff0c;lim…

基于Java+Springboot+Vue+elememt疫情返乡人员管控系统设计实现

博主介绍&#xff1a;✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取联系&#x1f345;精彩专栏推荐订阅收藏&#x1f447;&…