Multiprocessamento com ESP32
As vezes é necessário que façamos o uso de sistemas multicores para as mais diversas finalidades, como por exemplo, fazer verificações de dados pela Serial, I2C ou sensores, enquanto o outro processador (Core) faz uma outra atividade em que não possa ser interrompida ou seja indesejado esse tipo de ação. Aprenderemos o que é multiprocessamento e usaremos o incrível ESP32 que tem ao todo três cores, para criar dois LOOP(), permitindo que você rode dois códigos ao mesmo tempo!
Lembrando: O terceiro core do ESP32 é um ULP, que é apenas programado em Assembly. Também é possível programa-lo para as mais diversas finalidades. Será mostrado apenas a programação do core principal.
Para conhecer mais sobre o ESP32 você pode conferir nosso tutorial: Conhecendo o ESP32
[toc]
Computação paralela – Multiprocessamento
Computação paralela é um assunto extremamente complicado, porem podemos simplificar de um método prático:
Você tem um supermercado com um caixa e em horários de pico, o caixa não é rápido suficiente para atender os clientes, então é necessário contratar outro funcionário que fique em outro caixa, fazendo assim que a velocidade de espera seja até 50% menor. (será atendido o dobro de clientes em relação a apenas um caixa)
Um sistema computacional paralelo, permite realizar diversos cálculos (tarefas, algoritmos, etc) simultaneamente. Há diversas maneiras de se paralelizar um código que é normalmente escrito em sequência: em bit, instrução, de dado ou de tarefa. O método mais simples é por exemplo dividir um FOR() pela metade a atribuir cada metade em cores diferentes, diminuindo o tempo de execução em até 50%. Apesar de parecer ser extremamente útil, há inúmeros problemas relacionados, como condição de corrida: o acesso simultâneo da mesma variável pode gerar erros em cálculos ou se o próprio calculo for dependentes de outros resultados, a paralelização disto é inconveniente, uma vez que utilizar semáforos para sincronizar as tarefas (exclusão mutua), pode ser mais lento que o simples código em sequencial. A questão à se pensar com uso de semáforos para sincronia de processos, deve ser analisada com o grau de granulação, uma vez que o uso excessivo de semáforos, pode deixar o processo mais lento que o código sequencial (single core).
Mãos à obra
Componentes necessários
- 1x ESP32
- Arduino IDE
Código do projeto
int tempo;//Variavel que armazena o tempo. void setup() { Serial.begin(115200);//Inicia a comunicaçao serial pinMode(2, OUTPUT);//Define o led Onboard como saída Serial.printf("\nsetup() em core: %d", xPortGetCoreID());//Mostra no monitor em qual core o setup() foi chamado xTaskCreatePinnedToCore(loop2, "loop2", 8192, NULL, 1, NULL, 0);//Cria a tarefa "loop2()" com prioridade 1, atribuída ao core 0 delay(1); } void loop()//O loop() sempre será atribuído ao core 1 automaticamente pelo sistema, com prioridade 1 { Serial.printf("\n Tempo corrido: %d", tempo++); delay(1000);//Mantem o processador 1 em estado ocioso por 1seg } void loop2(void*z)//Atribuímos o loop2 ao core 0, com prioridade 1 { Serial.printf("\nloop2() em core: %d", xPortGetCoreID());//Mostra no monitor em qual core o loop2() foi chamado while (1)//Pisca o led infinitamente { digitalWrite(2, !digitalRead(2)); delay(100); } }
Colocando para funcionar
Podemos observar tanto no Serial Monitor quanto no ESP32, o funcionamento esperado do código. Enquanto o core 1 faz a contagem do tempo e espera 1seg para repetir a mensagem (o que o deixa em IDLE [travado]), o core 0 pisca o led, mostrando que um não interfere no outro.
Entendendo a fundo
Software
-Função xTaskCreatePinnedToCore()
xTaskCreatePinnedToCore(loop2, "loop2", 8192, NULL, 1, NULL, 0);
Esta função cria uma tarefa e atribuí a um especifico processador. O FreeRTOS pode definir automaticamente em qual core a tarefa será rodada, para isto, use xTaskCreate() (Mais informações no site FreeRTOS).
Neste caso, criamos a tarefa loop2, com “tamanho” de 8192 Bytes (words), nenhum parâmetro, prioridade 1 e atribuída ao core 0.
Vamos esclarecer os parâmetros em ordem (da esquerda à direita):
xTaskCreatePinnedToCore(pxTaskCode, pcName, usStackDepth, pvParameters, uxPriority, pxCreatedTask, xCoreID)
pxTaskCode: Ponteiro para a tarefa, apenas o nome da tarefa. loop2.
pcName: O nome (String) da tarefa (entre aspas), é usado para facilitar o debug. “loop2”.
usStackDepth: O tamanho de Stack reservada à tarefa, mais informações clique aqui. 8192.
pvParameters: O valor a ser passado para a tarefa no momento de criação. Nossa tarefa não precisa de parâmetros, então: NULL.
uxPriority: A prioridade da tarefa. É comum se usar 1 para tarefas simples, já que funções de delay (IDLE) tem prioridade 0. 1.
Se duas tarefas com mesma prioridade estiverem na fila, a primeira da fila irá ser executada e depois a próxima. Caso uma tarefa com prioridade 1 esteja na fila, porém uma tarefa com prioridade 2 também, será executado a tarefa com prioridade 2 e depois as outras.
pxCreatedTask: Valor opcional caso seja necessário manipulação das tarefas (Handle). NULL.
xCoreID: Atribuí a tarefa a um core especifico. 0.
Observações:
- Se você por exemplo criar dois loops, ao chamar uma subrotina (função), ela será executada no core em que foi chamada.
- Caso você não crie uma tarefa “infinita”, será necessário deletar a tarefa com xTaskDelete().
- No caso do loop2() criado, é necessário o uso de pelo menos delay(1) dentro do loop, para que o Task Watchdog não seja ativado. Há maneiras de contornar isso, mas precisa fazer alterações no BootLoader.
-Função xPortGetCoreID()
xPortGetCoreID()
Esta função retorna o core em que a tarefa esta sendo executada.
Fechamento
A computação paralela se mostra útil quando necessário alto poder computacional ou monitoramento de itens específicos. Este assunto é gigantesco, complicado e intrigante, foi mostrado apenas o básico sobre o assunto; se você quer se aprofundar mais, veja os PDFs que foram citados no começo.
Dúvidas? Sugestões? Críticas? Comente abaixo!
Estudante de Engenharia da Computação pela USC, pretende se aprimorar e fazer a diferença nesta imensa área da tecnologia. Apaixonado por IoT, sistemas embarcados, microcontroladores e integração da computação nos mais diversos fins práticos e didáticos.
17 Comments
Deixe uma pergunta, sugestão ou elogio! Estamos ansiosos para ter ouvir!Cancelar resposta
Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.
Parabéns pelo código.
Simples e descomplicado.
Você já aplicou os serviços de web server (asyncwebserver ou outro mais wifi hotspot) em core separado deixando o outro core para outras funções como inputs, outputs, interrupções etc?
por favor , gostaria de saber como faço para chamar uma tarefa dentro do loop do esp32 ele executar a tarefa e retornar para o loop.
Isso é feito por uma função normal, não precisa ser uma tarefa do RTOS.
Parabéns pelo post! Você sabe como faz p associar rotinas de interrupções a um core específico? Por exemplo, uma interrupção de GPIO ser gerenciada por um core e outra função de callback ser gerenciada por outro?
Obrigada
As interrupções são alocadas no core em que foi chamada a função de criação da interrupção, assim, quando você definir a interrupção numa task ocorrendo no CORE0, ela ficará no CORE0. É um pouco complexo de se olhar, mas mais informações sobre: https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/system/intr_alloc.html
Tmb tenho a mesma dúvida… Se possível, faça um minitutorial simples com um exemplo de como funcionaria isso…
Olá, estou enfrentando um problema com o esp32 que não consegui encontrar explicação que esclarecesse minha dúvida…Como faço para executar funções sem travar o processamento do looping? No Arduino eu usava o Millis e fazia as temporizaçoes, no esp32 não consigo fazer nada similar pois sempre caio no travamento do programa até a execução do delay.
Será que você pode me ajudar?
Obrigado.
Procure por FreeRTOS com o ESP32, permite a criação de “loops independentes”. Entretanto, é exatamente o que este post trouxe, então você vai fazer tarefas separadas, assim não irá travar as outras.
Olá, quando eu compilo na IDE arduino 1.8.7 ele dá erro: ” ‘xPortGetCoreID’ was not declared in this scope” na linha 9, esta classe seria do freeRTOS certo? depende de qual versao?
Nunca vi ocorrer isso desde que uso (2016), então creio que não é com versão. Você esta com o esp32 selecionado nas placas da IDE? Se ainda não funcionar, declare as bibliotecas onde se encontram essa função e teste.
Parabéns José, De qualidade.
Durante os comentários e especificações, fiz alguns testes com ESP 32 usando seu exemplo ficou bom, se adicionar o MCP para ampliar as saídas ele funciona bem, mas relatei um problema ao adicionar um TIMER com interrupções rápidas, gera uma falha na comunicação MCP.
Tem algum conhecimento sobre a velocidade que funciona o processador quando se usa dois loops simultâneos?
ou a forma de trabalhar com a linguagem I2C para MCP com dois processadores simultâneos.
obs: apenas utilizei a linguagem MCP no loop 1 para piscar um led com a contagem do timer.
A velocidade dos 2 processadores continuará a mesma com 0 ou 200 tarefas, ele apenas estará fazendo uma ou varias coisas ao mesmo tempo, o que ocasiona (dependendo da prioridade) uma lentidão geral no sistema, mas a velocidade continuará a mesma.
Em relação ao I2C e MCP, você deve se atentar ao utilizar um barramento ou periférico com varias tarefas simultaneas, já que ambas podem tentar utilizar o mesmo item ao mesmo tempo, ocasionando problemas fatais. Sua solução caso seja isso, é semaforos.
PARABENS José Morais
QUE PROSSIGAS BRILHANTEMENTE
passei a manha lendo sobre o ESP32, tem alguns sites que falam de 4MB de Flash, outros de 16Mb, outros de 32Mb, qual mesmo o tamanho? Existe algum código para depurar parametros como, espaço livre, espaço ocupado, velocidade do processador, etc?? Eu sei que no ESP8266 tem, já usei esse código. Não sei se para o ESP32 existe algo.
O problema sobre tamanho é que alguns sites falam em Mb e outros MB, então um site que fala 32Mb é igual o site que fala 4MB, entretanto essa questão de memória, tanto FLASH ou RAM é questionável, uma vez que é possível adicionar itens externos, por exemplo: a RAM padrão dos módulos WROOM é 520kB, mas você pode adicionar uma RAM externa para totalizar 4MB. Há funções que mostram o Clock e a FreeHeap sim, como por exemplo: ESP.getFreeHeap();
O ESP32 realmente parece ser um microcontrolador que veio para dar um upgrade muito legal no NodeMCU. Ótima explicação do assunto, sem se alongar muito nessa questão de paralelismo.
O processamento é maior em que muitos PIC32 ou SMT32 (chegando até 4x), mas ainda peca na pinagem em relação a esses concorrentes.