How to Enable Multi-Core on ESP32 Microcontroller

Although most small microcontrollers have only single-core processors, the ESP32 microcontroller is an exception, as it boasts a dual-core processor. This dual-core architecture enables the ESP32 to better utilize processor resources and achieve more efficient parallel processing. By enabling multi-core programming on the ESP32, developers can flexibly control processor execution and realize various complex application scenarios. So, how can the multi-core functionality of the ESP32 be implemented? Let’s delve into this topic in the following content!

Display effect of two displays

Setting the Clock Frequency

Here we set the ESP32 frequency to 240MHz, which can be modified to 160MHz or 80MHz if you have different requirements.

				
					#include "esp32-hal-cpu.h"
void setup() {
  Serial.begin(115200);
  setCpuFrequencyMhz(240);
  Serial.println(getCpuFrequencyMhz());
}
void loop() {
  // Your main code here
}
				
			

Enabling Multi-Core

The ESP32 chip features two processor cores, commonly referred to as core 0 and core 1. In the Arduino IDE, by default, code runs on core 1, while core 0 typically remains idle. Using FreeRTOS (a real-time operating system for embedded systems), tasks can be assigned to run on either core 0 or core 1 of the ESP32 chip.

To determine the current core number where a task is running, the function xPortGetCoreID() can be called. Generally, code written in the loop() function in Arduino runs on core 1 by default.

Arduino inherently supports FreeRTOS, so no additional library imports are necessary.

Finally, we need to create two task handles: one for operating the 3.5-inch LCD screen and the other for the 1.3-inch OLED display.

				
					TaskHandle_t Task_Display;
TaskHandle_t Task_OLED;
				
			

Use the xTaskCreatePinnedToCore() function in the setup() function to create a task and pin it to the specified processor core for execution.

				
					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 */
				
			

Add the specific task assignments corresponding to the two task handles in the setup() function.

				
					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);
				
			

Then you need to write two different task functions, one for driving the 3.5-inch LCD screen and the other for driving the 1.3-inch 128×64 OLED screen.

For the 3.5-inch LCD screen:

You need to use the LVGL GUI library to write the task function, which will be responsible for managing and controlling the display of the LCD screen.

For the 1.3-inch 128×64 OLED screen:

You need to first install the u8g2 library in the Arduino IDE’s library manager, which is used to drive OLED screens. Then, you need to initialize the u8g2 library in the main file so that it can be used in the task function. The specific initialization steps may involve selecting screen models and communication interfaces.

				
					#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();
}
				
			

After completing the initialization of the u8g2 library, you will write the function for FreeRTOS task allocation. In this function, tasks will be defined and configured to effectively manage and allocate task execution during system runtime.

				
					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());
  }
}
				
			

Where lv_task_handler() is from the article “A Step-by-Step Guide to Using LVGL with ESP32“. 

The final main file (main.ino) code is as follows:

				
					#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());
  // In the loop, the refresh rate of the OLED screen is slightly faster
  // 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());
}

				
			

VSCode Arduino Compilation Tips

Although we mentioned the Arduino IDE in this article, you can also use VSCode combined with the Arduino extension for development. However, this method has a drawback: each compilation starts from scratch, leading to extremely low work efficiency. If you decide to use VSCode, TechSparks suggests appending a line of content in the arduino.json file in the .vscode folder under the project directory: “output”: “./Build”.

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

This setting specifies the output directory for compilation as “./Build” instead of the default output directory. The advantage of this approach is that the intermediate files generated by each compilation and the final executable files will be saved in the specified directory, rather than scattered in the project root directory or the default output directory. This makes the project file structure clearer and also facilitates management and maintenance.

Make sure the path “./Build” exists and has appropriate permissions to save the compiled files.

You Might Be Interested

esp32 connect to wifi
Connecting ESP32 to WiFi in 4 Steps

Unlock the potential of your ESP32 with these 4 simple steps to WiFi connectivity! Dive into the world of IoT and remote control effortlessly, as

Getting Started with ESP-IDF in VSCode
Getting Started with ESP-IDF in VSCode

This guide introduces an efficient method, “ESP-IDF + VSCode,” for ESP32 development, simplifying environment setup and allowing seamless switching between ESP-IDF versions. It covers downloading

How to Enable Multi-Core on ESP32 Microcontroller
How to Enable Multi-Core on ESP32 Microcontroller

This comprehensive guide elucidates the process of enabling multi-core functionality on the ESP32 microcontroller, leveraging its dual-core architecture for enhanced parallel processing capabilities. Through detailed

esp32 pinout
ESP32 Pinout Reference Guide

The ESP32 is a versatile microcontroller with extensive functionalities. Its pinout includes GPIO pins for digital, analog, and capacitive touch, PWM pins for precise control,

Scroll to Top