esp32启用多核
Published:
启用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文件夹后,会只编译改动的部分,从而提高编译的速度,但是感觉提速并不明显。