使用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)。这是当队列数据为空时,任务会进入阻塞状态的时间。当有数据在队列或者到达阻塞时间的时候,任务都会进入就绪状态。如果有多个任务同时在阻塞状态等待队列数据,优先级高的任务会在数据到达时进入就绪状态;在优先级相同的时候,等待时间长的任务会进入就绪状态。同理可以推及多个任务写入数据时候的运行状态。
使用步骤:
- 创建队列(设定队列长度,数据大小size)
- 往队列里发送数据
- 读取数据
- 案例:聊天室发信息
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() {}