banner

Ir para o Forum

ESP32 com Arduino IDE - Programação Multi-Core




Não é comum um microcontrolador relativamente pequeno ter dois core. Justamente por isso que vamos destacar hoje essa maravilha do ESP32, que é a Programação Multi-Core. Já mencionei esse assunto em outros vídeos, o qual pretendo abordar ainda em uma playlist, assim como o FreeRTOS (Free real-time operating systems), que é um sistema operacional para microcontroladores que permite programar, implantar, proteger, conectar e gerenciar facilmente dispositivos de borda pequenos e de baixa capacidade. Voltando ao nosso projeto de hoje, então, vamos criar um programa no qual diferentes tarefas são executadas simultaneamente em diferentes núcleos. Para tal, vamos fazer uma introdução sobre a Programação Multi-Core no ESP32 a fim de conhecer suas principais funções.


Na nossa montagem, na imagem acima, utilizamos um display i2c, um botão, um led e uma fonte de 110 para 5v, que alimenta o nosso circuito.

Introdução

Uma das muitas características interessantes do ESP32 é que ele tem dois  núcleos Tensilica LX6, que podemos aproveitar para executar nosso código com maior desempenho e mais versatilidade.
  • Tanto o SETUP quanto as funções principais do LOOP são executadas com prioridade de 1 e no núcleo 1.
  • Prioridades podem ir de 0 a N, onde 0 é a menor prioridade.
  • Núcleo pode ser 0 ou 1.
As tarefas têm uma prioridade atribuída que o agendador usa para decidir qual tarefa será executada. Tarefas de alta prioridade que estejam prontas para serem executadas terão preferência sobre as tarefas de menor prioridade. Em um caso extremo que a tarefa de maior prioridade precise da CPU o tempo todo, a de menor prioridade nunca seria executada.
Mas, com dois núcleos disponíveis, as duas tarefas podem ser executadas, desde que elas sejam atribuídas para núcleos diferentes.


Funções

Vejamos agora alguma das funções importantes que podemos utilizar.

xTaskCreate
Cria uma nova tarefa e adiciona à lista de tarefas que estão prontas para serem executadas. 

xTaskCreatePinnedToCore
Essa função faz exatamente a mesma coisa que a xTaskCreate, porém temos um parâmetro adicional, que é onde definiremos em qual núcleo a tarefa será executada.

xPortGetCoreID
Essa função retorna o número do núcleo que está executando a tarefa atual.

TaskHandle_t
É o tipo de referência para a tarefa criada.
A chamada xTaskCreate retorna (como ponteiro para um parâmetro) um TaskHandle_t que pode ser usado como parâmetro por vTaskDelete para deletar uma tarefa.

vTaskDelete
Deleta uma tarefa criada.


WiFi NodeMCU-32S ESP-WROOM-32

WiFi NodeMCU-32S ESP-WROOM-32


Display LCD 16x2 Serial com módulo i2c

Display LCD 16x2 Serial com módulo i2c


Montagem

Nosso esquema elétrico é bastante simples. Basta segui-lo que o projeto vai funcionar.

Esquema elétrico do circuito

Biblioteca

Adicione a biblioteca “LiquidCrystal_I2C” para comunicação com o display LCD.
Acesse o link e faça download da biblioteca.
Descompacte o arquivo e cole na pasta de bibliotecas da IDE do arduino.
C:/Program Files (x86)/Arduino/libraries


Programa

Faremos um programa simples que consiste em fazer um led piscar e contar quantas vezes ele piscou. Teremos também um botão que, ao ser pressionado, muda uma variável de controle de estado do mesmo. O display ficará atualizando todas essas informações.
Programaremos a tarefa de atualização do display para executar no núcleo UM do processador, e as outras operações no núcleo ZERO.

Montagem

Bibliotecas e variáveis

Primeiramente vamos incluir a biblioteca responsável pelo controle do display e fazer as configurações necessárias. Vamos ainda apontar as variáveis para controle do Led, indicar os pinos que serão usados, bem como as variáveis que indicam o núcleo.

#include <Wire.h>
#include <LiquidCrystal_I2C.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;

Setup

Nesta parte, inicializamos o LCD com os pinos SDA e SCL. Ligamos a luz do display e criamos uma tarefa que será executada na função coreTaskZero, com prioridade 1 e execução no núcleo 0.


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

Ainda no Setup, criamos uma tarefa que será executada na função coreTaskOne, com prioridade 2. Ainda criamos uma outra tarefa que será executada na função coreTaskTwo, com prioridade 2 e execução no núcleo 0.


//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() {}

TaskZero

Esta 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.


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

TaskOne

Já esta função será responsável apenas por atualizar as informações no display a cada 100ms.


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

TaskTwo

Por fim, essa função será responsável por ler o estado do botão e atualizar a variável de controle.



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







Faça o download dos aquivos:


INO



4 comentários:

  1. Olá professor, fiquei confuso da forma que ligou a pinagem do i2c, conforme o diagrama do esp32 o SCL fica no GPI 22 e o SDA no GPI 21, mas ligou no GPI 23 e GPI 19, por favor, poderia me explicar o motivo?

    ResponderExcluir
  2. Boa tarde, Fernando.
    Tive um problema no meu projeto: não consigo fazer o OTA funcionar com multi-core, já que não é executado o conteúdo do loop a linha de comando ArduinoOTA.handle(); não é executada. Já tentei inserir esse método nas tarefas, mas não surtiu efeito.

    Abraço

    ResponderExcluir
    Respostas
    1. Olá, eu consegui, segue abaixo trecho do meu código:

      void loop(){
      }

      //Programa Rodando no CORE 0
      void coreTaskZero( void * pvParameters ){

      String taskMessage = "Task running on core ";
      taskMessage = taskMessage + xPortGetCoreID();

      while(true){

      }
      }

      //Programa Rodando no CORE 1
      void coreTaskOne( void * pvParameters ){
      while(true){
      //Handle é descritor que referencia variáveis no bloco de memória
      ArduinoOTA.handle();

      }
      }

      Excluir
  3. Muito bom, o conteúdo que você produz.

    ResponderExcluir

Tecnologia do Blogger.