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


    使用ESP32双核

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

      API:

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

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

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

    Arduino的setup和loop默认运行在core1 

    1. #include
    2. void taskA(void *ptParam)
    3. {
    4. while (1)
    5. {
    6. Serial.println(xPortGetCoreID()); // 获取当前任务运行的核心
    7. }
    8. }
    9. void setup()
    10. {
    11. // put your setup code here, to run once:
    12. Serial.begin(115200);
    13. xTaskCreatePinnedToCore(taskA, "Task A", 1024 * 4, NULL, 1, NULL, 0); // 最后一个参数为核心0
    14. }
    15. void loop()
    16. {
    17. }

    U8G2 OLED任务

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

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

    1. #include
    2. #include
    3. void oledTask(void *pvParam)
    4. {
    5. U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE);
    6. u8g2.begin();
    7. for (;;)
    8. {
    9. u8g2.clearBuffer(); // clear the internal memory
    10. u8g2.setFont(u8g2_font_ncenB08_tr); // choose a suitable font
    11. u8g2.drawStr(15, 10, "LONELY BINARY"); // write something to the internal memory
    12. u8g2.sendBuffer(); // transfer internal memory to the display
    13. vTaskDelay(1000);
    14. }
    15. }
    16. void setup()
    17. { // loopBack , Priority 1, Core 1
    18. xTaskCreatePinnedToCore(oledTask, "OLED Task", 1024 * 6, NULL, 1, NULL, 1);
    19. vTaskDelete(NULL); // 删除setup任务,节省内存空间
    20. }
    21. void loop()
    22. {
    23. }

    任务以绝对频率运行

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

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

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

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

      API:

        vTaskDelayUntil(&xLastWakeTime, xFrequency)

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

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

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

        xTaskGetTickCount()

          TickCount和 Arduino Millis一样

          uint32_t类型 49天后overflow(溢出)

    1. void showStockTask(void *ptParam)
    2. {
    3. static float stockPrice = 99.57; //股票价格
    4. //最后一次唤醒的tick count,第一次使用需要赋值
    5. //以后此变量会由vTaskDelayUntil自动更新
    6. TickType_t xLastWakeTime = xTaskGetTickCount(); // 相当于millis
    7. const TickType_t xFrequency = 3000; // 间隔 3000 ticks = 3 seconds
    8. for (;;)
    9. {
    10. //恰恰算算,经过思考,既然我们叫做LastWakeTime,那么 vTaskDelayUntil 应该放在循环的第一句话
    11. //如果放在循环的最后一句话,应该改为xLastSleepTime 才更加合适
    12. vTaskDelayUntil(&xLastWakeTime, xFrequency);
    13. //验证当前唤醒的时刻tick count
    14. Serial.println(xTaskGetTickCount());
    15. //验证xLastWake Time是否被vTaskDelayUntil更新
    16. // Serial.println(xLastWakeTime);
    17. // ------- 很复杂的交易股票计算,时间不定 ---------
    18. stockPrice = stockPrice * (1 + random(1, 20) / 100.0);
    19. vTaskDelay(random(500, 2000));
    20. Serial.print("Stock Price : $");
    21. Serial.println(stockPrice);
    22. //使用vTaskDelay试试看会如何
    23. // vTaskDelay(xFrequency); // 测试结果显示,差距很大,时间不精确
    24. }
    25. }
    26. void setup()
    27. {
    28. Serial.begin(115200);
    29. xTaskCreate(showStockTask, "Show Stock Price", 1024 * 6, NULL, 1, NULL);
    30. }
    31. void loop()
    32. {
    33. }

    任务内存优化

    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内存使用最大水位线,内存是水

    1. /*
    2. 程序: 内存管理
    3. 公众号:孤独的二进制
    4. API:
    5. ESP.getHeapSize() //本程序Heap最大尺寸
    6. ESP.getFreeHeap() //当前Free Heap最大尺寸
    7. uxTaskGetStackHighWaterMark(taskHandle) //Task内存使用最大水位线,内存是水
    8. What is the Highest Water Mark?
    9. the minimum amount of remaining stack space that was available to the task
    10. since the task started executing - that is the amount of stack that remained
    11. unused when the task stack was at its greatest (deepest) value. This is what
    12. is referred to as the stack 'high water mark'.
    13. */
    14. #include
    15. TaskHandle_t taskHandle; // 计算任务的空间大小使用
    16. int taskMem = 1024;
    17. void task(void *ptParam)
    18. {
    19. // volatile char hello[1000] = {0}; //必须要用volatile修饰语,否则会被编译器优化掉
    20. while (1)
    21. {
    22. //不推荐在task中执行,因为Serial.print也会消耗内存
    23. // vTaskDelay(2000);
    24. // int waterMark = uxTaskGetStackHighWaterMark(nullptr);
    25. // Serial.print("Task Free Memory: ");
    26. // Serial.print(waterMark);
    27. // Serial.println(" Bytes");
    28. // Serial.print("Task Used Memory: ");
    29. // Serial.print(taskMem - waterMark);
    30. // Serial.println(" Bytes");
    31. // Serial.println("");
    32. }
    33. }
    34. void setup()
    35. {
    36. Serial.begin(115200);
    37. Serial.println("串口打印测试");
    38. int heapSize = ESP.getHeapSize(); // 得到可用空间大小
    39. Serial.print("Total Heap Size: ");
    40. Serial.print(heapSize);
    41. Serial.println(" Bytes");
    42. int heapFree = ESP.getFreeHeap(); // 得到当前运行命令状态下所剩余尺寸
    43. Serial.print("Free Heap Size: ");
    44. Serial.print(heapFree);
    45. Serial.println(" Bytes");
    46. Serial.println("");
    47. Serial.println("Create Task ...");
    48. xTaskCreate(task, "", taskMem, NULL, 1, &taskHandle);
    49. Serial.print("Free Heap Size: ");
    50. Serial.print(ESP.getFreeHeap());
    51. Serial.println(" Bytes");
    52. Serial.println("");
    53. vTaskDelay(2000);
    54. /* 重要 */
    55. int waterMark = uxTaskGetStackHighWaterMark(taskHandle); // 计算任务用了多少内存
    56. Serial.print("Task Free Memory: "); // 任务剩余空间
    57. Serial.print(waterMark);
    58. Serial.println(" Bytes");
    59. Serial.print("Task Used Memory: ");
    60. Serial.print(taskMem - waterMark); // 任务使用空间
    61. Serial.println(" Bytes");
    62. }
    63. void loop()
    64. {
    65. }

    任务优先级

    可以设置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显示部分的程序改为自己需要的数据)

    1. /*
    2. 程序: 消息队列
    3. 公众号:孤独的二进制
    4. 说明:本实例使用结构体巧妙的通过当个队列传输多个设备多种数据类型
    5. 在接收方,我们通过deviceID来判断数据来源和value的意义
    6. API:
    7. QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength,
    8. UBaseType_t uxItemSize );
    9. BaseType_t xQueueSend(
    10. QueueHandle_t xQueue,
    11. const void * pvItemToQueue,
    12. TickType_t xTicksToWait
    13. );
    14. BaseType_t xQueueReceive(
    15. QueueHandle_t xQueue,
    16. void *pvBuffer,
    17. TickType_t xTicksToWait
    18. );
    19. */
    20. #include "DHTesp.h"
    21. #include
    22. LiquidCrystal_I2C lcd(0x27, 20, 4);
    23. /*设备ID,共有2个设备*/
    24. #define DHT22_ID 0
    25. #define LDR_ID 1
    26. typedef struct
    27. {
    28. byte deviceID;
    29. float value1;
    30. float value2;
    31. } SENSOR;
    32. QueueHandle_t queueSensor = xQueueCreate(8, sizeof(SENSOR)); // 长度8,采用sizeof自动计算需要的空间
    33. void dht22(void *ptParam)
    34. {
    35. const byte dhtPin = 32;
    36. DHTesp dhtSensor;
    37. dhtSensor.setup(dhtPin, DHTesp::DHT22);
    38. SENSOR dht22Sensor; // 创建一个结构体对象
    39. dht22Sensor.deviceID = DHT22_ID;
    40. while (1)
    41. {
    42. TempAndHumidity data = dhtSensor.getTempAndHumidity();
    43. // Serial.println("Temp: " + String(data.temperature, 2) + "°C");
    44. // Serial.println("Humidity: " + String(data.humidity, 1) + "%");
    45. dht22Sensor.value1 = data.temperature;
    46. dht22Sensor.value2 = data.humidity;
    47. /*往队列里发送数据,如果队列数据是满的,等待2s,如果2s之后,队列还是满的,就放弃写操作*/
    48. // TickType_t timeOut = portMAX_DELAY; 不要用这个,时间为49天
    49. TickType_t timeOut = 2000;
    50. if (xQueueSend(queueSensor, &dht22Sensor, timeOut) != pdPASS)
    51. {
    52. Serial.println("DHT22: Queue is full.");
    53. }
    54. vTaskDelay(1000); // 这个时间如果比较短,则队列很快就满了
    55. }
    56. }
    57. void ldr(void *ptParam)
    58. {
    59. const float GAMMA = 0.7;
    60. const float RL10 = 50;
    61. const byte ldrPIN = 27;
    62. pinMode(ldrPIN, INPUT);
    63. SENSOR ldrSensor;
    64. ldrSensor.deviceID = LDR_ID;
    65. while (1)
    66. {
    67. int analogValue = analogRead(ldrPIN);
    68. float voltage = analogValue / 4095. * 5;
    69. float resistance = 2000 * voltage / (1 - voltage / 5);
    70. float lux = pow(RL10 * 1e3 * pow(10, GAMMA) / resistance, (1 / GAMMA));
    71. // Serial.print("LDR Light Sensor lux : ");
    72. // Serial.println(lux);
    73. ldrSensor.value1 = lux;
    74. // ldrSensor.value2 = 0.0; 这条语句不要也行
    75. // TickType_t timeOut = portMAX_DELAY;
    76. TickType_t timeOut = 2000;
    77. if (xQueueSend(queueSensor, &ldrSensor, timeOut) != pdPASS)
    78. {
    79. Serial.println("LDR: Queue is full.");
    80. }
    81. vTaskDelay(1000);
    82. }
    83. }
    84. void lcdTask(void *ptParam)
    85. { // LCD任务主体
    86. lcd.init();
    87. lcd.backlight();
    88. lcd.setCursor(0, 0);
    89. lcd.print(" LONELY BINARY ");
    90. SENSOR data; //
    91. while (1)
    92. {
    93. // TickType_t timeOut = portMAX_DELAY;
    94. TickType_t timeOut = 2000;
    95. if (xQueueReceive(queueSensor, &data, timeOut) == pdPASS)
    96. {
    97. switch (data.deviceID)
    98. {
    99. case DHT22_ID:
    100. lcd.setCursor(0, 1);
    101. lcd.print("Temp: " + String(data.value1, 2) + "c");
    102. lcd.setCursor(0, 2);
    103. lcd.print("Humidity: " + String(data.value2, 1) + "%");
    104. break;
    105. case LDR_ID:
    106. lcd.setCursor(0, 3);
    107. if (data.value1 > 50)
    108. {
    109. lcd.print("Bright ");
    110. }
    111. else
    112. {
    113. lcd.print("Dark ");
    114. }
    115. // lcd.setCursor(0, 3);
    116. lcd.print(String(data.value1, 2) + " lux");
    117. break;
    118. default:
    119. Serial.println("LCD: Unkown Device");
    120. break;
    121. }
    122. }
    123. else
    124. {
    125. Serial.println("LCD: Message Queue is Empty");
    126. };
    127. vTaskDelay(2000);
    128. }
    129. }
    130. void setup()
    131. {
    132. Serial.begin(115200);
    133. xTaskCreate(dht22, "DHT22", 1024 * 4, NULL, 1, NULL);
    134. xTaskCreate(ldr, "LDR LIGHT", 1024 * 4, NULL, 1, NULL);
    135. xTaskCreate(lcdTask, "lcd", 1024 * 8, NULL, 1, NULL);
    136. }
    137. void loop() {}
  • 相关阅读:
    Mysql---第五篇
    Day59 下一个更大元素Ⅱ + 接雨水
    mockito 的 InjectMocks 和 Mock 有什么区别?
    c:预处理指令(#include、#define、#if等)
    PC-3000 Mobile Pro: 智能手机及平板设备数据提取工具
    五种多目标优化算法(MOJS、MOGWO、NSWOA、MOPSO、NSGA2)性能对比(提供MATLAB代码)
    【特别提醒】订阅此专栏的用户请先阅读本文再决定是否需要购买此专栏
    “空间代谢组学“用于食管鳞状细胞癌早期筛查的研究
    Win11怎么以管理员身份运行?Win11以管理员身份运行的设置方法
    C Primer Plus(6) 中文版 第5章 运算符、表达式和语句 5.6 带参数的函数
  • 原文地址:https://blog.csdn.net/qq_45355603/article/details/128120021