Introduction: ESP32 With Arduino IDE - Multi-Core Programming

About: Do you like technology? Follow my channel on Youtube and my Blog. In them I put videos every week of microcontrollers, arduinos, networks, among other subjects.

It isn’t common for a relatively small microcontroller to have two cores. This is precisely why we will highlight today this marvel of ESP32, which is Multi-Core Programming. I have already mentioned this in other videos, which I intend to talk more about in a complete playlist. I also plan to discuss the FreeRTOS (Free real-time operating systems), which is an operating system for microcontrollers that allows for easy programming, deployment, protection, connection and management for devices of small and low capacity. Returning to the topic of today’s project, let's create a program where different tasks are executed simultaneously in different cores. To do so, we will introduce you to Multi-Core Programming in ESP32 in order to know its main functions.

In our assembly, as shown in the image above, we use an i2c display, a button, a LED, and a source from 110 to 5v, which feeds our circuit.

Step 1: Introduction

One of the many interesting features of ESP32 is that it has two Tensilica LX6 cores, which we can take advantage of to run our code with higher performance and more versatility.

• Both the SETUP and the main functions of the LOOP are executed with a priority of 1 and in core 1.

• Priorities can range from 0 to N, where 0 is the lowest priority.

• Core can be 0 or 1.

Tasks have an assigned priority so the scheduler can decide which task to execute. High-priority tasks that are ready to run will have preference over lower priority tasks. In an extreme case, the highest priority task needs the CPU all the time, and the lowest priority task would never run.

But with two cores available, the two tasks can be performed as long as they are assigned to different cores.

Step 2: Functions

Let's look now at some of the important functions we can use.

xTaskCreate

Create a new task and add it to the list of tasks that are ready to be executed.

xTaskCreatePinnedToCore

This function does exactly the same thing as xTaskCreate. However, we have an additional parameter, which is where we will define in which core the task will be executed.

xPortGetCoreID

This function returns the number of the kernel that is executing the current task.

TaskHandle_t

This is the reference type for the created task.

The xTaskCreate call returns (as a pointer to a parameter) a TaskHandle_t that can be used as a parameter by vTaskDelete to delete a task.

vTaskDelete

Delete a created task.

Step 3: WiFi NodeMCU-32S ESP-WROOM-32

Step 4: 16x2 Serial LCD Display With I2c Module

Step 5: Assembly

Our electric scheme is quite simple. Just follow it, and the project will work.

Step 6: Library

Add the "LiquidCrystal_I2C" library for communication with the LCD display.

Access the link and download the library.

Unzip the file and paste it into the libraries folder of the Arduino IDE.

C: / Program Files (x86) / Arduino / libraries

Step 7: Program

We will make a simple program that consists of making a LED blink and count how many times it blinks. We will also have a button that, when pressed, changes a variable of its state control. The display will update all this information.

We will program the display update task to run on the processor core UM, and the other operations will be on the ZERO core.

Step 8: Libraries and Variables

First, let's include the library responsible for display control and define the necessary settings. It’s also important to point out the variables for control of the LED, indicate the pins that will be used, as well as the variables that indicate the core.

#include <Wire.h>
#include <LiquidCrystal_IC2.h> //biblioteca responsável pelo controle do display LiquidCrystal_I2C lcd(0x27, 16, 2); //set the LCD address to 0x27 for a 16 chars and 2 line display int count = 0; int blinked = 0; String statusButton = "DESATIVADO"; //pinos usados const uint8_t pin_led = 4; const uint8_t pin_btn = 2; //variaveis que indicam o núcleo static uint8_t taskCoreZero = 0; static uint8_t taskCoreOne = 1;

Step 9: Setup

In this part, we initialize the LCD with the SDA and SCL pins. We turn on the display light, and create a task that will be executed in the coreTaskZero function, running in core 0 with priority 1.

void setup() {
pinMode(pin_led, OUTPUT); pinMode(pin_btn, INPUT); //inicializa o LCD com os pinos SDA e SCL lcd.begin(19, 23); // Liga a luz do display lcd.backlight(); lcd.setCursor(0, 0); lcd.print("Piscadas:"); //cria uma tarefa que será executada na função coreTaskZero, com prioridade 1 e execução no núcleo 0 //coreTaskZero: piscar LED e contar quantas vezes xTaskCreatePinnedToCore( coreTaskZero, /* função que implementa a tarefa */ "coreTaskZero", /* nome da tarefa */ 10000, /* número de palavras a serem alocadas para uso com a pilha da tarefa */ NULL, /* parâmetro de entrada para a tarefa (pode ser NULL) */ 1, /* prioridade da tarefa (0 a N) */ NULL, /* referência para a tarefa (pode ser NULL) */ taskCoreZero); /* Núcleo que executará a tarefa */ delay(500); //tempo para a tarefa iniciar

Also in the Setup, we created a task that will be executed in the coreTaskOne function with priority 2. We have also created another task that will be executed in the coreTaskTwo function, running in core 0 with priority 2.

//cria uma tarefa que será executada na função coreTaskOne, com prioridade 2 e execução no núcleo 1
//coreTaskOne: atualizar as informações do display xTaskCreatePinnedToCore( coreTaskOne, /* função que implementa a tarefa */ "coreTaskOne", /* nome da tarefa */ 10000, /* número de palavras a serem alocadas para uso com a pilha da tarefa */ NULL, /* parâmetro de entrada para a tarefa (pode ser NULL) */ 2, /* prioridade da tarefa (0 a N) */ NULL, /* referência para a tarefa (pode ser NULL) */ taskCoreOne); /* Núcleo que executará a tarefa */ delay(500); //tempo para a tarefa iniciar //cria uma tarefa que será executada na função coreTaskTwo, com prioridade 2 e execução no núcleo 0 //coreTaskTwo: vigiar o botão para detectar quando pressioná-lo xTaskCreatePinnedToCore( coreTaskTwo, /* função que implementa a tarefa */ "coreTaskTwo", /* nome da tarefa */ 10000, /* número de palavras a serem alocadas para uso com a pilha da tarefa */ NULL, /* parâmetro de entrada para a tarefa (pode ser NULL) */ 2, /* prioridade da tarefa (0 a N) */ NULL, /* referência para a tarefa (pode ser NULL) */ taskCoreZero); /* Núcleo que executará a tarefa */ } void loop() {}

Step 10: TaskZero

This function will change the LED status every second and will blink each time (cycle on and off), incrementing our blinked variable.

//essa função ficará mudando o estado do led a cada 1 segundo
//e a cada piscada (ciclo acender e apagar) incrementará nossa variável blinked void coreTaskZero( void * pvParameters ){ String taskMessage = "Task running on core "; taskMessage = taskMessage + xPortGetCoreID(); //Serial.println(taskMessage); //log para o serial monitor while(true){ digitalWrite(pin_led, !digitalRead(pin_led)); if (++count % 2 == 0 ) blinked++; delay(1000); } }

Step 11: TaskOne

This function will only be responsible for updating the information on the display every 100m.

//essa função será responsável apenas por atualizar as informações no
//display a cada 100ms void coreTaskOne( void * pvParameters ){ while(true){ lcd.setCursor(10, 0); lcd.print(blinked); lcd.setCursor(0,1); lcd.print(statusButton); delay(100); } }

Step 12: TaskTwo

Finally, this function will be responsible for reading the button state and updating the control variable.

//essa função será responsável por ler o estado do botão
//e atualizar a variavel de controle. void coreTaskTwo( void * pvParameters ){ while(true){ if(digitalRead(pin_btn)){ statusButton = "Ativado "; } else statusButton = "Desativado"; delay(10); } }