We are familiar with multiple features of the ESP32 module. Like Wi-Fi capabilities, classic Bluetooth, Bluetooth low energy (BLE), inbuilt sensors etc.

Hello readers, I hope you all are doing great. We have already mentioned in our previous tutorials that, ESP32 is also featured with a Dual-Core Processor. Which provides better performance and efficiency.

In this tutorial, we will learn how to use ESP32’s dual cores. ESP32 has two (32-bit each) Tensilica Xtensa LX6 microprocessors namely, core0 and core1 which makes it a powerful dual-core microcontroller and hence stands apart from its predecessors.

When we compile and upload a code using Arduino IDE, we do not have to worry about which core executes the code. It just runs.

Accessing ESP32 Dual Core, FreeRTOS in ESP32, esp32 dual core, dual core in esp32, dual core access in esp32, esp32 dual core access

Fig. 1 ESP32 dual-core processor

Features of Dual-Core processor

  • Power Efficiency: A single-core processor can rapidly hit 100% of its workload. On the other hand, a dual-core processor allows the efficient allocation of resources with a multitasking environment.
  • Increased Performance to Multithread Programs: Aside from being able to run multiple programs at the same time, dual-core processors can also collaborate to make a single program faster and more efficient. Multithreading allows programmers to send different instructions from the same program into two processing paths. On a single processor with hyper-threading, the program is still limited to the single core's maximum processing speed. However, on a dual-core, this effectively doubles the speed available to that program.
  • Two programs running simultaneously: Two cores allow two programs to run at the same time. The many complex calculations that a computer must perform to create the average browsing experience are difficult to quantify; however, single-core processors only create the illusion of multitasking through technologies such as hyper-threading.

FreeRTOS in ESP32

A FreeRTOS (real-time operating system) firmware is already available in the ESP32 module. FreeRTOS is helpful in improving the system performance and managing the resources of the module. FreeRTOS allows users to handle several tasks like measuring sensor readings, making a network request, controlling motor speed etc. all of them run simultaneously and independently.

FreeRTOS offers multiple APIs for different applications. Those APIs can be used to create tasks and make them run on different cores. You need to create tasks to assign a particular part of code to a specific core. You can also prioritize that on which core the code will run. Priority value starts with level_0.

Accessing ESP32’s both cores using Ardui1no IDE

Whenever we run a code on Arduino IDE, it runs on core_1 by default.

  • How to check on which core the code is running?

There is a function you can use to check on which core the code is running on.

xPortGetCoreID()

Run the following code in Arduino IDE:

void setup()

{

Serial.begin(115200);

Serial.print( " setup() is running on: Core_" );

Serial.println( xPortGetCoreID() );

delay(1000);

}

void loop()

{

Serial.print( " loop() is running on: Core_" );

Serial.println( xPortGetCoreID() );

delay(1000);

}

  • Open the serial monitor with a 115200 baud rate.
  • Press the EN button from the ESP32 development board.
Accessing ESP32 Dual Core, FreeRTOS in ESP32, esp32 dual core, dual core in esp32, dual core access in esp32, esp32 dual core access

Fig. 2 Serial Monitor

From the results on the serial monitor, you can see that both setup() function and loop() function are running on core_1.

Steps to be followed to create a task are:

  • Create a task handle to keep a track of the task created. For example, a task handle named task is created below:
Accessing ESP32 Dual Core, FreeRTOS in ESP32, esp32 dual core, dual core in esp32, dual core access in esp32, esp32 dual core access

Fig. 3

  • Inside setup() function, create a task assigned to a specific core. xTaskCreatedPinnedToCore() function is used to create a task assigned to a specific core.

This function takes seven arguments:

  1. Name of the function to implement the task
  2. Name of the task
  3. Stack size assigned to the task in words (where 1 word = 2 Bytes)
  4. Task Input parameter
  5. Priority of the task
  6. Task handler
  7. Core where the task should run
Accessing ESP32 Dual Core, FreeRTOS in ESP32, esp32 dual core, dual core in esp32, dual core access in esp32, esp32 dual core access

Fig. 4

  • Create a function that contains the code for the task you have been created.

For example, we have created a function named task_code(). Inside the task_code() function a for(;;) loop is used which will create an infinite loop. All the instructions to be given for a particular core to perform a particular task like led blinking, sensor readings etc. will be written inside this for(;;) loop.

Accessing ESP32 Dual Core, FreeRTOS in ESP32, esp32 dual core, dual core in esp32, dual core access in esp32, esp32 dual core access

Fig. 5

  • How to delete the created task, during the code execution?

You can use the function vTaskDelete() during the code execution to delete a task. This function takes the task handle (task) as an argument.

  • Code for creating and assigning the separate task to each core

In this code we will use two LEDs to be processed by different core.

TaskHandle_t task1;

TaskHandle_t task2;

// Assign GPIOs pins to LEDs

const int led1 = LED_BUILTIN;

const int led2 = 25;

void setup() {

Serial.begin(115200 );

pinMode( led1, OUTPUT );

pinMode( led2, OUTPUT );

//create a task that will be executed in the Task1code() function, with priority 1 and executed on core 0

xTaskCreatePinnedToCore(task_1code, // Task function.

"Task1", // name of task.

10000, // Stack size of task

NULL, // parameter of the task

1, // priority of the task

&task1, // Task handle to keep track of created task

1); // pin task to core 1

delay(1000);

//create a task that will be executed in the Task2code() function, with priority 1 and executed on core 1

xTaskCreatePinnedToCore(task_2code, //Task function.

"task2", //name of task.

10000, //Stack size of task

NULL, //parameter of the task

1, //priority of the task

&task2, //Task handle to keep track of created task

0); //pin task to core 0

delay(1000);

}

//task_1code: blinks an LED every 1000 ms

void task_1code( void * pvParameters ){

Serial.print( "task1 running on: core " );

Serial.println( xPortGetCoreID() );

for(;;)

{

digitalWrite( led1, HIGH);

delay(1000);

digitalWrite(led1, LOW);

delay(1000);

}

}

//task_2code: blinks an LED every 500 ms

void task_2code( void * pvParameters )

{

Serial.print( "task2 running on: core " );

Serial.println(xPortGetCoreID() );

for(;;){

digitalWrite(led2, HIGH );

delay(500);

digitalWrite(led2, LOW );

delay(500);

}

}

void loop()

{

Serial.print( " loop() is running on: Core " );

Serial.println( xPortGetCoreID() );

delay(1000);

}

Code Description

  • We have created two task handle to keep track of two different tasks created for each core. Task1 is for core_1 and task2 is for core_0.
  • Although we do not need to create a new task and a task handle for core_1 because the void loop() function be default run on core_1 but for better understanding we are creating a different task for each core.
Accessing ESP32 Dual Core, FreeRTOS in ESP32, esp32 dual core, dual core in esp32, dual core access in esp32, esp32 dual core access

Fig. 6

  • Allocate GPIOs to the LEDs. Led1 is the inbuilt one that is connected with GPIO2 (internally) and another one is led2 to be connected with GPIO 25.
Accessing ESP32 Dual Core, FreeRTOS in ESP32, esp32 dual core, dual core in esp32, dual core access in esp32, esp32 dual core access

Fig. 7

Setup()

  • Initialize the serial monitor with a 115200 baud rate for debugging purposes.
  • Set the mode of LEDs as OUTPUT.
Accessing ESP32 Dual Core, FreeRTOS in ESP32, esp32 dual core, dual core in esp32, dual core access in esp32, esp32 dual core access

Fig. 8 setup() function

  • Create a task (task1) using xTaskCreatePinnedToCore() This task will take seven parameters which include; the task function namely task_1code(), name of the task, stack size of the task, priority level, task handle (if created), core to which you want to assign the task.
Accessing ESP32 Dual Core, FreeRTOS in ESP32, esp32 dual core, dual core in esp32, dual core access in esp32, esp32 dual core access

Fig. 9

  • Create a similar task for another core. We will create a different function for this task namely task_2code() and will pass task_2 as a task handle and the core selected is
Accessing ESP32 Dual Core, FreeRTOS in ESP32, esp32 dual core, dual core in esp32, dual core access in esp32, esp32 dual core access

Fig. 10

  • The next step is to create functions to execute the above task for each core.
  • task_1code() function is passed as a parameter in
  • xPortGetCoreID() function is used to fetch the core number on which the current function is running on.
  • This function will make the inbuilt LED (GPIO2) or led1 blink with a delay of 1 second or 1000msec.
  • This for(;;) loop will make led1 blink continuously which is similar to the instructions executed inside loop() function.
  • print() function is used to print the results on the serial monitor for debugging purposes.
Accessing ESP32 Dual Core, FreeRTOS in ESP32, esp32 dual core, dual core in esp32, dual core access in esp32, esp32 dual core access

Fig. 11

  • task_2code() function is called for task2.
  • This code will be executed in core0.
  • Led2 (GPIO25) will blink continuously with a delay of 0.5sec.
Accessing ESP32 Dual Core, FreeRTOS in ESP32, esp32 dual core, dual core in esp32, dual core access in esp32, esp32 dual core access

Fig. 12

  • Inside the loop function we called the xPortGetCoreID() function to get the core number which is responsible to execute the instructions written inside loop() function

Accessing ESP32 Dual Core, FreeRTOS in ESP32, esp32 dual core, dual core in esp32, dual core access in esp32, esp32 dual core access

Fig. 13 loop function

Testing

Components required:

  • ESP32 development board
  • Breadboard
  • 2*Jumper wires
  • 1*LED
  • 1*resistor(330 ohms)

Steps for testing:

  • Connect the LED with ESP32 as shown below:
Accessing ESP32 Dual Core, FreeRTOS in ESP32, esp32 dual core, dual core in esp32, dual core access in esp32, esp32 dual core access

Fig. 14 connecting LED with ESP32

  • Upload the code into the ESP32 development board.
  • Open the serial monitor with a 115200 baud rate.
  • Press the EN from the ESP32 development board.
  • Results observed from the serial monitor are shown below:
Accessing ESP32 Dual Core, FreeRTOS in ESP32, esp32 dual core, dual core in esp32, dual core access in esp32, esp32 dual core access

Fig. 15 Results on the serial monitor

  • On the serial monitor, we can see that task1 functions are processed by core 1 and task2 is processed by core 0.
  • We have already mentioned that core1 is the default processing core so instructions inside the loop() function are processed by core1.

This concludes the tutorial. We hope you found this helpful and also hope to see you soon with a new tutorial on ESP32.