Core 0 and core 1 of the ESP32 is Xtensa 32-bit LX6 microprocessors. As a result, it is dual-core. When we run code in the Arduino IDE, it runs on core 1 by default. In this article, we’ll teach you how to use tasks to run code on the ESP32 second core. You can run programmes on both cores at the same time to make your ESP32 multitask.

INTRODUCTION

The ESP32 comes with 2 Xtensa 32-bit LX6 microprocessors, so it’s dual-core:

  • Core 0
  • Core 1
ESP32 DUAL CORE
ESP32 DUAL CORE

When we upload code to the ESP32 using the Arduino IDE, it just runs – we don’t have to worry about which core executes the code.

There’s a function that you can use to identify in which core the code is running:

xPortGetCoreID()

If you use that function in an Arduino sketch, you’ll see that both the setup() and loop() are running on core 1. Test it yourself by uploading the following sketch to your ESP32.

void setup() {

  Serial.begin(115200);

  Serial.print(“setup() running on core “);

  Serial.println(xPortGetCoreID());

}

void loop() {

  Serial.print(“loop() running on core “);

  Serial.println(xPortGetCoreID());

}

CREATE TASKS

FreeRTOS, a Real-Time Operating System, is supported by the Arduino IDE for the ESP32. This enables us to manage multiple jobs at the same time, each of which runs separately.

Tasks are snippets of code that carry out a certain action. Blinking an LED, sending a network request, measuring sensor readings, publishing sensor readings, and so forth…

Tasks are used to assign certain pieces of code to a specific core. You can choose which core a task will execute in, as well as its priority when creating it. Priority values begin at 0 and go up from there, with 0 being the lowest priority. The tasks with the highest priority will be executed first by the processor.

o create tasks you need to follow the next steps:

  1. Create a task handle. An example for Task1:

  TaskHandle_t Task1;

  1.  In the setup() create a a task assigned to a specific core using the xTaskCreatePinnedToCore function. That function takes several arguments, including the priority and the core where the task should run (the last parameter).

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

  1. After we’ve created the task, you should write a function that contains the task’s code. You must build the Task1code() function in this example. This is how the task function appears:

Void Task1code( void * parameter) {

  for(;;) {

Code for task 1 – infinite loop

(…)

  }

}

The for(;;) creates an infinite loop. So, this function runs similarly to the loop() function. You can use it as a second loop in your code, for example.

If during your code execution you want to delete the created task, you can use the vTaskDelete()function, that accepts the task handle (Task1) as argument:

vTaskDelete(Task1);

CREATE TASKS IN DIFFERENT CORES

To create different tasks running on different cores we’ll create two tasks that blink LEDs with different delay times.

We’ll create two tasks running on different cores:

  • Task1 runs on core 0;
  • Task2 runs on core 1;

PARTS REQUIRED

To follow this example, you need the following parts:

  • ESP32 DOIT DEVKIT V1 Board-The Esp32 DevKit v1 is one of the development board created to evaluate the ESP-WROOM-32 module. It is based on the ESP32 microcontroller that boasts Wifi, Bluetooth, Ethernet and Low Power support all in a single chip.The entire solution takes up the least amount of printed circuit board area.
  • 2x 5mm LED
  • 2x 330 Ohm resistor
  • Breadboard
  • Jumper wires

CODE

TaskHandle_t Task1;
TaskHandle_t Task2;

// LED pins
const int led1 = 2;
const int led2 = 4;

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(
                    Task1code,   /* 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 */
                    0);          /* pin task to core 0 */                  
  delay(500); 

  //create a task that will be executed in the Task2code() function, with priority 1 and executed on core 1
  xTaskCreatePinnedToCore(
                    Task2code,   /* 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 */
                    1);          /* pin task to core 1 */
    delay(500); 
}

//Task1code: blinks an LED every 1000 ms
void Task1code( void * pvParameters ){
  Serial.print("Task1 running on core ");
  Serial.println(xPortGetCoreID());

  for(;;){
    digitalWrite(led1, HIGH);
    delay(1000);
    digitalWrite(led1, LOW);
    delay(1000);
  } 
}

//Task2code: blinks an LED every 700 ms
void Task2code( void * pvParameters ){
  Serial.print("Task2 running on core ");
  Serial.println(xPortGetCoreID());

  for(;;){
    digitalWrite(led2, HIGH);
    delay(700);
    digitalWrite(led2, LOW);
    delay(700);
  }
}

void loop() {
  
}

HOW CODE WORKS

We construct two tasks in the code and assign one to core 0 and the other to core 1. By default, Arduino sketches run on core 1. As a result, you might write Task2’s code in the loop() method (there was no need to create another task). In this scenario, we design two separate assignments for the goal of learning.

The code starts by creating a task handle for Task1 and Task2 called Task1 and Task2.

TaskHandle_t Task1;

TaskHandle_t Task2;

Assign GPIO 2 and GPIO 4 to the LEDs:

const int led1 = 2; 

const int led2 = 4;

In the setup(), initialize the Serial Monitor at a baud rate of 115200:

Serial.begin(115200);

Declare the LEDs as outputs:

pinMode(led1, OUTPUT); 

pinMode(led2, OUTPUT);

Then, create Task1 using the xTaskCreatePinnedToCore() function:

xTaskCreatePinnedToCore(

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

0);        /* pin task to core 0 */

Task1 will be implemented with the Task1code() function. So, we need to create that function later on the code. We give the task priority 1, and pinned it to core 0.

We create Task2 using the same method:

xTaskCreatePinnedToCore(

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

1);         /* pin task to core 0 */

After creating the tasks, we need to create the functions that will execute those tasks.

void Task1code( void * pvParameters ){

 Serial.print(“Task1 running on core “);

 Serial.println(xPortGetCoreID());

  for(;;) {

digitalWrite(led1, HIGH);

delay(1000);

digitalWrite(led1, LOW);

delay(1000);

 }

}

The function to Task1 is called Task1code() (you can call it whatever you want). For debugging purposes, we first print the core in which the task is running:

Serial.print(“Task1 running on core “);

Serial.println(xPortGetCoreID());

Then, we have an infinite loop similar to the loop() on the Arduino sketch. In that loop, we blink LED1 every one second.

The same thing happens for Task2, but we blink the LED with a different delay time.

void Task2code( void * pvParameters ){

  Serial.print(“Task2 running on core “);

  Serial.println(xPortGetCoreID());

  for(;;){

    digitalWrite(led2, HIGH);

    delay(700);

    digitalWrite(led2, LOW);

    delay(700);

  }

}

Finally, the loop() function is empty:

void loop() { }

DEMONSTRATION

Upload the code to  ESP32. Make sure to have the right board and COM port selected.

Open the Serial Monitor at a baud rate of 115200. In the circuit, one LED should be blinking every 1 second, and the other should be blinking every 700 milliseconds.

CONCLUSION

  • The ESP32 is dual core;
  • Arduino sketches run on core 1 by default;
  • To use core 0 you need to create tasks;
  • You can use the xTaskCreatePinnedToCore() function to pin a specific task to a specific core;
  • Using this method you can run two different tasks independently and simultaneously using the two cores.

author avatar
Amith G Nair
Experience as a product developer, innovation coach, and electronics lecturer,a seasoned professional driven by passion for designing projects.expertise extends to 3D modelling, hardware designing, and web development using HTML, WordPress, and Django.