使用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 memoryu8g2.setFont(u8g2_font_ncenB08_tr);    // choose a suitable fontu8g2.drawStr(15, 10, "LONELY BINARY"); // write something to the internal memoryu8g2.sendBuffer();                     // transfer internal memory to the displayvTaskDelay(1000);}
}void setup()
{ // loopBack , Priority 1, Core 1xTaskCreatePinnedToCore(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(); // 相当于millisconst TickType_t xFrequency = 3000; // 间隔 3000 ticks = 3 secondsfor (;;){//恰恰算算,经过思考,既然我们叫做LastWakeTime,那么 vTaskDelayUntil 应该放在循环的第一句话//如果放在循环的最后一句话,应该改为xLastSleepTime 才更加合适vTaskDelayUntil(&xLastWakeTime, xFrequency);//验证当前唤醒的时刻tick countSerial.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 tasksince the task started executing - that is the amount of stack that remainedunused when the task stack was at its greatest (deepest) value. This is whatis 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 1typedef 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() {}

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

  1. 玩转u8g2 OLED库,一篇就够

    1.前言     最近博主听到QQ群里面问得比较多的问题:     "博哥,有玩过OLED吗?"     "博哥,有试过在ESP8266上调成功过SSD1306吗?&qu ...

  2. 深入学习Arduino u8g2 OLED库 MAX7219_32X8点阵模块

    授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... 快速导航 单片机菜鸟的博客快速索引(快速找到你 ...

  3. esp32 spi 驱动 oled 屏显示来自 PC 的画面

    esp32 spi 驱动 oled 屏显示来自 PC 的画面 实验代码 gayhub 实验源码 设备及运行环境 装有python的电脑 Python 3.8.2 (tags/v3.8.2:7b3ab5 ...

  4. ESP32双核CPU,利用核0实现蓝牙打印机打印,核1完成常规控制

    ESP32双核CPU,利用核0实现蓝牙打印机打印,核1完成常规控制 目的 程序编制 总结 目的 开发一个仪表,在使用过程中发现用ESP32控制打印机和主控制有冲突,会造成数据采集流程慢.而控制蓝牙打印 ...

  5. Wemos D1 R32 ESP32开发板OLED液晶屏显示

    目录 一.实验准备 二.实验代码 1.OLED液晶屏显示"Hello World" 2. OLED液晶满屏显示字符 3.OLED屏显示时钟 4.OLED屏显示矢量图片 5 OLED ...

  6. ESP32学习记录 OLED u8g2库

    ESP32 OLED & u8g2库 学习 编程环境 ARDUINO环境 VS Code Platformio环境 1. OLED简单显示 显示效果 就连学习也想着可爱囧菌呢!!! main. ...

  7. 【ESP32】15.OLED显示实验(SPI / 字模提取)

    上一篇: [ESP32]14.DHT11湿度传感器实验(DHT操作库) 前言: 先放电路连接图: OLED(有机发光二极管(Organic Light Emitting Diode)) 字模提取软件使 ...

  8. (4)ESP32 Python 用OLED播放Bad Apple

    之前已经实现过了,把OLED当作一个状态显示器.但是,仅仅显示文字肯定是不够炫酷的,因为有屏幕的地方就应该有Bad Apple. 这次我们尝试一下把OLED播放一下 Bad Apple. Bad Ap ...

  9. ESP32+INMP441+DHT11+OLED+网页+Arduino——“智能”语音天气站(2):INMP441录音生成wav文件

    参考视频: Recording using INMP441 参考代码:学会了代码复用 Recording using INMP441 知识 什么是wav文件 可以在维基百科找到wav文件的历史渊源.这 ...

最新文章

  1. jq 读取office插件_800+页麦肯锡经典图示发送!让你不用插件,轻松搞定逻辑图...
  2. Leetcode 279. 完全平方数 解题思路及C++实现
  3. 《Introduction to Tornado》中文翻译计划——第五章:异步Web服务
  4. spring的jar包以及相关的API文档的下载方式
  5. 带有Spring Boot 2支持的Apache Camel 2.22发布
  6. php 如何模拟浏览器,利用php的curl扩展进行模拟浏览器访问网页
  7. Intel Sandy Bridge/Ivy Bridge架构/微架构/流水线 (18) - 数据预取
  8. Python实战从入门到精通第十六讲——匿名函数捕获变量值
  9. QCon旧金山演讲总结:阿里无线技术架构演进
  10. 华为云再“祭”神器!
  11. Kickstart+HTTP+DHCP+TFTP全自动批量安装部署Linux系统
  12. JAVA编程思想——读书笔记 多态
  13. 【HDU4312】Meeting point-2(切比雪夫距离和曼哈顿距离的转化+前缀和后缀和去绝对值)
  14. 20155313 杨瀚 《网络对抗技术》实验五 MSF基础应用
  15. Windows XP 深度增强精简版下载 - Deepin XP Lite V2
  16. ios数据恢复工具:Mac FoneLab for Mac
  17. 自己实现SDIO wifi Marvell8801/Marvell88w8801驱动 介绍(一) ---- 芯片介绍
  18. 双稳态电路的两个稳定状态是什么_晶振电路中选择电容的方式有哪些?
  19. MFC中改变按钮颜色的方法
  20. 内存不能为“read”或“written”的解决方案

热门文章

  1. 把html页面转换为pdf
  2. 管不住嘴、挪不动腿?
  3. 微信小程序api记载
  4. 【Windows】服务程序
  5. OracleERP表结构--PO模块(1)
  6. JeecgBoot关于websocket的改进方案
  7. python生成word文档_python实现的生成word文档功能示例
  8. ecshop被加入了黑链
  9. extjs json 数据的操作 自由操作服务器返回的json数据
  10. Linux网络编程——基于tcp/ip的模拟聊天(文件传输)工具