启用esp32的多核,以便并行执行任务,同时设置芯片的主频和freertos实时系统。

本文实现的效果如gif所示,两个显示屏并行执行,分别使用esp32的两个核心执行。

两个显示屏的显示,如果是串行执行,那么第二块显示屏的刷新率会受到第一块屏刷新内容的影响,所以应该将其设置为并行执行,esp32有两个核心,每个核心运行一个显示屏的显示任务。

1.设置主频

将esp32设置为其支持的最大频率240Mhz。同时esp32还支持160Mhz、80Mhz的频率。

#include "esp32-hal-cpu.h"

void setup(
  Serial.begin(115200);
  setCpuFrequencyMhz(240);
  Serial.println(getCpuFrequencyMhz());
)

2.启用多核

多核的启用使用freertos来实现。esp32的xtensa芯片一共有两个核心,arduino ide默认使用核心1,核心0是空闲的。通过freertos直接指定核心可以将任务发布到核心0或者核心1上。

使用xPortGetCoreID()获取当前使用的核心编号,默认是1,在loop()函数中编写的代码默认运行在核心1上。

arduino默认可以使用freertos,因此不需要导入额外的库。

使用freertos首先需要创建任务句柄,下面创建两个任务句柄,一个用来显示3.5寸LCD屏,另一个用来显示1.3寸OLED显示屏。

TaskHandle_t Task_Display;
TaskHandle_t Task_OLED;

然后在setup()函数中使用xTaskCreatePinnedToCore函数创建指定核心的任务分配。函数的形参说明如下:

xTaskCreatePinnedToCore(
      Task1code, /* Function to implement the task */
      "Task1", /* Name of the task */
      10000,  /* Stack size in words */
      NULL,  /* Task input parameter */
      0,  /* Priority of the task */
      &Task1,  /* Task handle. */
      0); /* Core where the task should run */

setup()中,添加两个任务句柄对应的具体任务分配。

xTaskCreatePinnedToCore(task_display, "Task_Display", 10000, NULL, 1, &Task_Display, 0);
delay(500);
xTaskCreatePinnedToCore(task_oled, "Task_OLED", 10000, NULL, 1, &Task_OLED, 1);
delay(500);

然后编写每个任务的函数代码,在这里,对于3.5寸的LCD屏幕,使用的是LVGL GUI库,具体参考ESP32使用LVGL GUI库,对于1.3寸的128x64 OLED屏幕,使用的是u8g2 GUI库,因此首先需要在arduino ide管理库中安装u8g2库,然后在主文件中初始化u8g2。

初始化u8g2的代码为:

#include <Arduino.h>
#include <U8g2lib.h>
#include <SPI.h>
#include <Wire.h>

U8G2_SH1106_128X64_NONAME_2_SW_I2C u8g2(U8G2_R0, 22, 21);

void setup(){
  u8g2.begin();
}

其中,U8G2_SH1106_128X64_NONAME_2_SW_I2C代表不同显示屏不同驱动的构造函数,笔者使用的OLED显示屏为SH1106的驱动,I2C通信方式。关于不同显示屏不同驱动的所有构造函数列表,参考u8g2setupcpp

完成u8g2的初始化后,freertos的任务分配函数的编写如下:

void task_display(void *pvParameters)
{
  for (;;)
  {
    lv_task_handler();
    delay(5);
  }
}

void task_oled(void *pvParameters)
{
  for (;;)
  {
    u8g2.firstPage();
    do
    {
      u8g2.setFont(u8g2_font_ncenB14_tr);
      std::string s = std::to_string(count);
      const char *ss = s.c_str();
      u8g2.drawStr(50, 24, ss);
      count++;
      if (count % 100 == 0)
      {
        count = 0;
      }
      delay(5);
    } while (u8g2.nextPage());
  }
}

其中,lv_task_handler()是上一篇文章ESP32使用LVGL GUI库中的内容。

最终的主文件(main.ino)代码如下:

#include <Arduino.h>
//#include "./includes/oled.h"
#include <lvgl.h>
#include <TFT_eSPI.h>
#include <lv_examples.h>
#include "esp32-hal-cpu.h"
// extern Adafruit_SH1106G display;
#include <U8g2lib.h>
#include <SPI.h>
#include <Wire.h>
#include <string>

TFT_eSPI tft = TFT_eSPI(); /* TFT instance */
static lv_disp_buf_t disp_buf;
static lv_color_t buf[LV_HOR_RES_MAX * 10];

U8G2_SH1106_128X64_NONAME_2_SW_I2C u8g2(U8G2_R0, 22, 21);

#if USE_LV_LOG != 0
/* Serial debugging */
void my_print(lv_log_level_t level, const char *file, uint32_t line, const char *dsc)
{

  Serial.printf("%s@%d->%s\r\n", file, line, dsc);
  Serial.flush();
}
#endif

/* Display flushing */
void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p)
{
  uint32_t w = (area->x2 - area->x1 + 1);
  uint32_t h = (area->y2 - area->y1 + 1);

  tft.startWrite();
  tft.setAddrWindow(area->x1, area->y1, w, h);
  tft.pushColors(&color_p->full, w * h, true);
  tft.endWrite();

  lv_disp_flush_ready(disp);
}

/*Read the touchpad*/
bool my_touchpad_read(lv_indev_drv_t *indev_driver, lv_indev_data_t *data)
{
  uint16_t touchX, touchY;

  bool touched = tft.getTouch(&touchX, &touchY, 600);

  if (!touched)
  {
    data->state = LV_INDEV_STATE_REL;
  }
  else
  {
    data->state = LV_INDEV_STATE_PR;

    /*Set the coordinates*/
    data->point.x = touchX;
    data->point.y = touchY;

    Serial.print("Data x");
    Serial.println(touchX);

    Serial.print("Data y");
    Serial.println(touchY);
  }

  return false; /*Return `false` because we are not buffering and no more data to read*/
}

TaskHandle_t Task_Display;
TaskHandle_t Task_OLED;

char count = 0;
void setup()
{
  // put your setup code here, to run once:
  // Serial.begin(9600);
  // //testdrawcircle();
  // display.begin(i2c_Address,true);
  // testdrawbitmap(logo16_glcd_bmp, LOGO16_GLCD_HEIGHT, LOGO16_GLCD_WIDTH);
  Serial.begin(115200); /* prepare for possible serial debug */
  setCpuFrequencyMhz(240);
  lv_init();

#if USE_LV_LOG != 0
  lv_log_register_print_cb(my_print); /* register print function for debugging */
#endif

  tft.begin();        /* TFT init */
  tft.setRotation(1); /* Landscape orientation */

  uint16_t calData[5] = {275, 3620, 264, 3532, 1};
  tft.setTouch(calData);

  lv_disp_buf_init(&disp_buf, buf, NULL, LV_HOR_RES_MAX * 10);

  /*Initialize the display*/
  lv_disp_drv_t disp_drv;
  lv_disp_drv_init(&disp_drv);
  disp_drv.hor_res = 480;
  disp_drv.ver_res = 320;
  disp_drv.flush_cb = my_disp_flush;
  disp_drv.buffer = &disp_buf;
  lv_disp_drv_register(&disp_drv);

  /*Initialize the (dummy) input device driver*/
  lv_indev_drv_t indev_drv;
  lv_indev_drv_init(&indev_drv);
  indev_drv.type = LV_INDEV_TYPE_POINTER;
  indev_drv.read_cb = my_touchpad_read;
  lv_indev_drv_register(&indev_drv);

  u8g2.begin();

  lv_demo_benchmark();
  // lv_demo_music();
  //  lv_example_get_started_1();

  xTaskCreatePinnedToCore(task_display, "Task_Display", 10000, NULL, 1, &Task_Display, 0);
  delay(500);
  // xTaskCreatePinnedToCore(task_oled, "Task_OLED", 10000, NULL, 1, &Task_OLED, 1);
  // delay(500);
}

void task_display(void *pvParameters)
{
  for (;;)
  {
    lv_task_handler();
    delay(5);
  }
}

void task_oled(void *pvParameters)
{
  for (;;)
  {
    u8g2.firstPage();
    do
    {
      u8g2.setFont(u8g2_font_ncenB14_tr);
      std::string s = std::to_string(count);
      const char *ss = s.c_str();
      u8g2.drawStr(50, 24, ss);
      count++;
      if (count % 100 == 0)
      {
        count = 0;
      }
      delay(5);
    } while (u8g2.nextPage());
  }
}

void loop()
{
  // put your main code here, to run repeatedly:
  // testdrawcircle();
  // Serial.println("hello");
  // delay(1000);
  // Serial.println(getCpuFrequencyMhz());
  // delay(1000);
  // lv_task_handler();
  // delay(5);
  // Serial.println(getCpuFrequencyMhz());
  // Serial.println(xPortGetCoreID());

  //在loop里面,oled屏幕的刷新速率略快
  // u8g2.firstPage();
  //   do
  //   {
  //     u8g2.setFont(u8g2_font_ncenB14_tr);
  //     std::string s = std::to_string(count);
  //     const char *ss = s.c_str();
  //     u8g2.drawStr(50, 24, ss);
  //     count++;
  //     if (count % 100 == 0)
  //     {
  //       count = 0;
  //     }
  //     delay(5);
  //   } while (u8g2.nextPage());
}

3.提高vscode编译arduino项目的速度

虽然笔者一直在提arduino ide,但是笔者一直使用的是vscode配合arduino扩展进行开发,其缺点是编译要比arduino ide慢很多,解决方法是:

在项目目录的.vscode文件夹中的arduino.json文件中追加一行内容,”output”: “./Build” 。

{
    "configuration": "PartitionScheme=default,FlashMode=qio,FlashFreq=80,UploadSpeed=921600,DebugLevel=none",
    "board": "esp32:esp32:esp32wrover",
    "port": "COM6",
    "sketch": "main.ino",
    "output": "./Build"
}

vscode编译慢的原因可能是每次从头编译,添加一个build文件夹后,会只编译改动的部分,从而提高编译的速度,但是感觉提速并不明显。