ESP32-C系列为单核,ESP32的core0主要运行WI-FI和蓝牙
API:
xPortGetCoreID() 获取当前任务运行的核心
xTaskCreate() 有系统选择运行核心,优先选择0
xTaskCreatePinnedToCore() 指派任何给指定核心
Arduino的setup和loop默认运行在core1
- #include
-
- 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相关的配置(初始化)写进任务中,不要写道setup中
- #include
- #include
-
- 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
- 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)。这是当队列数据为空时,任务会进入阻塞状态的时间。当有数据在队列或者到达阻塞时间的时候,任务都会进入就绪状态。如果有多个任务同时在阻塞状态等待队列数据,优先级高的任务会在数据到达时进入就绪状态;在优先级相同的时候,等待时间长的任务会进入就绪状态。同理可以推及多个任务写入数据时候的运行状态。
使用步骤:
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 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() {}