Programação ESP32 Dual Core com Arduino IDE

Tempo de leitura: 5 minutes

Os módulos ESP são populares por suas funcionalidades Wi-Fi, como ESP8266, ESP-12E, etc. Todos são módulos de microcontroladores poderosos com funcionalidades Wi-Fi. Há mais um módulo ESP que é mais poderoso e versátil do que os módulos ESP anteriores – seu nome é ESP32. Ele tem conectividade Bluetooth e Wi-Fi e já explicamos os recursos BLE do ESP32 e usamos o ESP32 em muitos projetos de IoT. Mas muito poucas pessoas sabem que o ESP32 é um microcontrolador Dual-core.

O ESP32 tem dois microprocessadores Tensilica Xtensa LX6 de 32 bits, o que o torna um microcontrolador de núcleo duplo (core0 e core1) poderoso. Ele está disponível em duas variantes single-core e dual-core. Mas a variante dual-core é mais popular porque não há diferença significativa de preço.

ESP32 pode ser programado usando Arduino IDE, Espressif IDF, Lua RTOS, etc. Durante a programação com Arduino IDE, o código só roda no Core1 porque o Core0 já está programado para comunicação RF. Mas aqui está este tutorial, vamos mostrar como usar os dois núcleos do ESP32 para realizar duas operações simultaneamente. Aqui, a primeira tarefa será piscar o LED integrado e a segunda tarefa será buscar os dados de temperatura do sensor DHT11.

Vejamos primeiro as vantagens de um processador multinúcleo em relação a um único núcleo.

Vantagens do processador multi-core

  1. Os processadores multi-core são úteis quando há mais de 2 processos para trabalhar simultaneamente.
  2. À medida que o trabalho é distribuído entre diferentes núcleos, sua velocidade aumenta e vários processos podem ser concluídos ao mesmo tempo.
  3. O consumo de energia pode ser reduzido porque quando qualquer núcleo está no modo ocioso, ele pode ser usado para desligar os periféricos que não estão em uso no momento.
  4. Os processadores de núcleo duplo precisam alternar entre threads diferentes com menos frequência do que os processadores de núcleo único porque podem lidar com dois de uma vez em vez de um de cada vez.

 

ESP32 e FreeRTOS

A placa ESP32 já possui o firmware FreeRTOS instalado. O FreeRTOS é um sistema operacional em tempo real de código aberto muito útil em multitarefa. RTOS ajuda a gerenciar os recursos e maximizar o desempenho do sistema. FreeRTOS tem muitas funções de API para diferentes propósitos e usando essas APIs, podemos criar tarefas e fazê-las rodar em diferentes núcleos.

A documentação completa das APIs do FreeRTOS pode ser encontrada aqui. Tentaremos usar algumas APIs em nosso código para construir um aplicativo multitarefa que será executado em ambos os núcleos.

 

Encontrar o ID principal do ESP32

Aqui, usaremos o IDE do Arduino para fazer upload do código no ESP32. Para saber o ID do núcleo no qual o código está sendo executado, há uma função API

xPortGetCoreID()

Esta função pode ser chamada a partir da função void setup() e void loop() para saber o ID do núcleo no qual essas funções estão sendo executadas.

Você pode testar esta API carregando o sketch abaixo:

void setup() {
  Serial.begin(115200);
  Serial.print("setup() função em execução no núcleo:");
  Serial.println(xPortGetCoreID());
}
void loop() {
  Serial.print("loop() função em execução no núcleo: ");
  Serial.println(xPortGetCoreID());
}

Após fazer o upload do esboço acima, abra o monitor serial e você verá que ambas as funções estão sendo executadas no core1 conforme mostrado abaixo.

A partir das observações acima, pode-se concluir que o sketch padrão do Arduino sempre roda no core1.

 

ESP32 Dual Core Programming

Arduino IDE oferece suporte a FreeRTOS para ESP32 e APIs FreeRTOS nos permitem criar tarefas que podem ser executadas independentemente em ambos os núcleos. A tarefa é o trecho de código que realiza alguma operação na placa, como led piscando, envio de temperatura, etc.

A função abaixo é usada para criar tarefas que podem ser executadas em ambos os núcleos. Nesta função, temos que fornecer alguns argumentos como uma prioridade, ID do núcleo, etc.

Agora, siga as etapas abaixo para criar tarefas e funções de tarefas.

1. Primeiro, crie tarefas na função de configuração de vazio. Aqui, criaremos duas tarefas, uma para LED piscando a cada 0,5 segundos e outra tarefa é obter a leitura da temperatura a cada 2 segundos.

A função xTaskCreatePinnedToCore() leva 7 argumentos:

  1. Nome da função para implementar a tarefa (tarefa1)
  2. Qualquer nome dado à tarefa (“tarefa1”, etc)
  3. Tamanho da pilha atribuído à tarefa em palavras (1 palavra = 2 bytes)
  4. Parâmetro de entrada da tarefa (pode ser NULL)
  5. Prioridade da tarefa (0 é a prioridade mais baixa)
  6. Identificador de tarefa (pode ser NULL)
  7. ID do núcleo onde a tarefa será executada (0 ou 1)

Agora, crie Task1 para piscar o led, fornecendo todos os argumentos na função xTaskCreatePinnedToCore().

xTaskCreatePinnedToCore(Task1code, "Task1", 10000, NULL, 1, NULL,  0);

Da mesma forma, crie Task2 para Task2 e faça o ID do núcleo 1 no 7º argumento.

xTaskCreatePinnedToCore(Task2code, "Task2", 10000, NULL, 1, NULL,  1);

Você pode alterar a prioridade e o tamanho da pilha dependendo da complexidade da tarefa.

2. Agora, implementaremos a função Task1code e a função Task2code. Essas funções contêm o código para a tarefa necessária. No nosso caso, a primeira tarefa irá piscar o led e outra tarefa irá buscar a temperatura. Portanto, crie duas funções separadas para cada tarefa fora da função de configuração de vazio.

A função Task1code para piscar o led integrado após 0,5 segundos é implementada conforme mostrado abaixo.

Void Task1code( void * parameter) {
  Serial.print("Tarefa 1 em execução no núcleo ");
  Serial.println(xPortGetCoreID());
  for(;;) {//Loop infinito
    digitalWrite(led, HIGH);
    delay(500);
    digitalWrite(led, LOW);
   ​​ delay(500);
  }
}

Da mesma forma, implemente a função Task2code para obter a temperatura.

void Task2code( void * pvParameters ){
  Serial.print("Tarefa 2 em execução no núcleo ");
  Serial.println(xPortGetCoreID());
  for(;;){
     float t = dht.readTemperature();
     Serial.print("Temperatura: ");
     Serial.print(t);
     delay(2000);
  }
}

3. Aqui, a função de void loop permanecerá vazia. Como já sabemos, a função de loop e configuração é executada no core1, portanto, você também pode implementar a tarefa core1 na função de void loop.

Agora que a parte de codificação acabou, basta fazer o upload do código usando o IDE do Arduino, escolhendo a placa ESP32 no menu Ferramentas. Certifique-se de ter conectado o sensor DHT11 ao pino D13 do ESP32.

Agora os resultados podem ser monitorados no Serial Monitor ou Arduino IDE conforme mostrado abaixo:

Aplicativos complexos como o sistema em tempo real podem ser construídos executando várias tarefas simultaneamente usando núcleos duplos do ESP32.

Código

#include "DHT.h"
#define DHTPIN 13
#define DHTTYPE DHT11 
const int led = 2;  
DHT dht(DHTPIN, DHTTYPE);
void setup() {
  Serial.begin(115200); 
  pinMode(led, OUTPUT);
  dht.begin();
  xTaskCreatePinnedToCore(Task1code, "Task1", 10000, NULL, 1, NULL,  1); 
  delay(500); 
  xTaskCreatePinnedToCore(Task1code, "Task1", 10000, NULL, 1, NULL,  0); 
  delay(500); 
}
void Task1code( void * pvParameters ){
  Serial.print("Tarefa 1 em execução no núcleo ");
  Serial.println(xPortGetCoreID());
  for(;;){
    digitalWrite(led, HIGH);
    delay(300);
    digitalWrite(led, LOW);
    delay(300);
  } 
}
void Task2code( void * pvParameters ){
  Serial.print("Tarefa 2 em execução no núcleo ");
  Serial.println(xPortGetCoreID());
  for(;;){
      float h = dht.readHumidity();
      float t = dht.readTemperature();
      float f = dht.readTemperature(true);
      Serial.print("Temperatura: ");
      Serial.print(t);
      Serial.print(" *C \n ");
      if (isnan(h) || isnan(t) || isnan(f)) {
         Serial.println("Falha ao ler do sensor DHT!");
         return;
      }
      delay(2000);
   }
}
void loop() {
}

Para facilitar estamos disponibilizando o código completo, para seu entendimento – (Link)