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
Display LCD 16x2 Serial com módulo i2c
Montagem
Nosso esquema elétrico é
bastante simples. Basta segui-lo que o projeto vai funcionar.
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.
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:
12 Comentários
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?
ResponderExcluirmesma duvida?
Excluirutilize os inos sda e scl do esp e substitua lcd.begin(19,23); por lcd.int(); que vai funcionar.
ExcluirTroca os pinos para 21, 22
Excluir//inicializa o LCD com os pinos SDA e SCL
lcd.begin(21, 22);
Troca os pinos para 21, 22
Excluir//inicializa o LCD com os pinos SDA e SCL
lcd.begin(21, 22);
Boa tarde, Fernando.
ResponderExcluirTive 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
Olá, eu consegui, segue abaixo trecho do meu código:
Excluirvoid 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();
}
}
Muito bom, o conteúdo que você produz.
ResponderExcluirOlá, teria a possibilidade de colocar um watchdog em um task somente?
ResponderExcluirOlá, percebi que voce altera e utiliza as variáveis em cores distintos, não precisa de nenhum semáforo? Não vai travar o programa, em algum momento?
ResponderExcluirAtt
Luciano Dourado
Boa tarde Fernando K.
ResponderExcluirExcelente os videos, que você tem apresentado, para formação de "Makers".
Apesar de extenso passeio pela NET, não consegui ter certeza de uma informação.
Estou fazendo um projeto com ESP32, e irei usar multi core.
Mas eu queria saber: Por "default" quais tasks rodam no core 0 do ESP32?
Encontrei alguns comentários em fóruns dizendo que o wifi e o BT
rodam no task 0.
Estão corretas estas informações?
Tem algo mais que roda no core 0?
WiFi --- > core ?
BT ---> core?
??? ---> core?
Obrigado pela atenção
Rui
Como trabalhar com WatchDOG com programação multi-core?
ResponderExcluir