5 Comentários

ESP32 – Lidando com Multiprocessamento – Parte II

Multiprocessamento no ESP32
Este post faz parte da série Multiprocessamento no ESP32. Leia também os outros posts da série:

Olá caro leitor, seguindo com série sobre multiprocessamento com o ESP32, hoje vou trazer a você mais uma abordagem de como executar suas aplicações em um dos núcleos desejados. No artigo anterior foi apresentada a politica de multiprocessamento do ESP32, a qual definimos como simétrica, ou seja os dois núcleos do módulo consome o mesmo stream de instruções na memória de programa, sendo esse gerenciamento feito pelo RTOS interno do ESP32, totalmente transparente ao usuário. Apresentamos naquele texto então como delegar funções isoladas para determinado núcleo.

O ESP32 é multitarefa e dual-core, como ganhar com isso

Aqui no Embarcados temos diversos textos sobre uso e detalhes de multitarefa em sistemas embarcados com o uso de RTOS, assim sendo não pretendo entrar em detalhes deste tópico aqui (mas encorajo o leitor a fazer suas perguntas na seção de comentários abaixo), mas é bom lembrar que um dos grandes benefícios do uso de um RTOS em sistemas embarcados está na isolação entre os pedaços de uma aplicação, por exemplo com o uso de tasks (threads, ou tarefas) um grupo de desenvolvedores podem trabalhar separadamente de forma que uma parcela cuidaria de comunicações, outro grupo em sensores e outro  lidando com atuadores sem que , diretamente, os domínios de suas tarefas impactem entre si.

Ao falar sobre RTOS quase sempre lembramos da expressão: "...de ter múltiplas mains na aplicação", e o ESP32 deixa isso significativamente melhor, já que agora o RTOS não entrega fatias de um núcleo por task, mas  dois deles de forma que as politicas de execução ganham ainda mais flexibilidade.

Vamos pegar um exemplo de caso de drone. Sabemos que dois do seus grandes domínios são as comunicações com a base e o processamento de algoritmos de controle. Utilizando um RTOS comum teríamos que escolher corretamente as prioridades das tasks para que as comunicações tenham uma resposta agradável aos comandos da base e ao mesmo tempo precisaríamos garantir que a execução periódica dos algoritmos de estabilização e controle do drone não sofram atrasos.

Obter um núcleo extra nos permite separar melhor as coisas, podendo controlar além da prioridade, também qual núcleo deve lidar mais intensamente com o que.

Introduzindo o conceito de afinidade por núcleo

Antes de entrarmos no ponto prático, precisamos entender primeiro um pequeno conceito que envolvem as tasks quando estamos lidando com um processador com 2 ou mais núcleos, como o ESP32.

A afinidade de tarefa por núcleo, em que basicamente podemos determinar que uma task em particular será executada apenas num determinado núcleo mesmo se houver outra CPU disponível. De forma similar uma task pode não ter nenhuma afinidade, dessa forma o RTOS terá que lidar com essa task para entregá-la para um núcleo disponível. Para esse segundo caso não existe um procedimento padrão mas sim protocolos para balancear a carga de processamento entre os núcleos.

Para tasks sem afinidade no ESP32, o RTOS utiliza um protocolo de dar preferência para execução de tarefas com afinidade primeiro, caso o núcleo disponível não encontre nenhuma task pronta com sua afinidade então ele escolhe uma outra da lista que também não tenha afinidade, mantendo ao mesmo tempo, o protocolo de preempção padrão, ou seja dentre as várias tasks sem afinidade prontas para rodar, será selecionada aquela com maior prioridade, caso duas ou mais dessas tasks compartilhem dessa prioridade, o núcleo escolhera a task por ordem de chegada nessa lista.

Parece bastante coisa para o RTOS se preocupar não? Mas não se preocupe esse algoritmo acontece nos bastidores e o código de usuário raramente precisa se preocupar com qualquer coisa além de escolher se determinada task deve ter afinidade ou não durante a sua criação.

Exemplo de uso no Arduino

Agora que já sabemos o mínimo necessário do multiprocessamento no ESP32, vamos ilustrar o que discutimos, com o sketch para Arduino abaixo. Você tem total liberdade para copiar e colar na sua IDE e gravar no ESP32 que tiver a mão:

Vejam que o sketch é bem simples, como mencionamos no artigo anterior, loop() por si só já é uma task que roda no núcleo APP_CPU, que fica continuamente mostrando no console em qual núcleo ele está executando. Na função setup(), temos uma função da API do RTOS do ESP32, que é uma versão modificada do FreeRTOS, essa nova função para criação de tasks é igualzinha a do FreeRTOS conhecido por muitos, exceto pelo parâmetro extra.

xTaskCreatePinnedToCore() é utilizada para criar tasks (as "multiplas mains" da sua aplicação) e adicionalmente assinalar ou não uma afinidade por núcleo, que é o último argumento que a função leva, percebam que foram criadas duas tasks, cada uma com afinidade em um dos núcleos, para quem não lembra vamos refrescar a memória sobre o significado dos demais parâmetros:

  • O primeiro parâmetro é a função principal da sua task, uma função tipicamente em um loop infinito, embora ela possa retornar, esses casos não são tão comuns, perceba que o argumento nesse caso são basicamente o nome das funções que estão antes de setup() e loop();
  • O segundo parâmetro é um nome para essa task, sendo muito útil para fazer debug, além de ser usado pelo componente de trace do IDF (que podemos falar num próximo artigo);
  • O terceiro parâmetro é destinado ao tamanho de pilha para essa task, toda aplicação seja ela uma task ou bare-metal (sem sistema operacional) possui um espaço de pilha usado para alocação de variáveis locais, portanto quanto mais variáveis locais você alocar dentro task, ou quanto mais neveis de função são chamados, maior esse valor, a unidade é em bytes para o caso do ESP32;
  • O quarto parâmetro é o argumento para ser passado a task, toda task aceita um argumento do tipo (void *) sendo ele muito útil quando queremos usar uma mesma task para lidar com diferentes contextos;
  • O parâmetro a seguir é a prioridade dessa task, tenha em mente sobre o funcionamento do algoritmo de preempção do FreeRTOS, quanto mais alto esse valor, maior a prioridade da task, o RTOS do ESP32 oferece até 25 níveis de prioridade;
  • O sexto parâmetro é um local para guardar o Identificador único para essa task, raramente usado, pois o RTOS possui API para obter o ID de uma task depois que ela é criada;
  • E o último parâmetro é a afinidade dessa task, podendo ela ter afinidade com a PRO_CPU, com a APP_CPU, ou passe tskNO_AFFINITY caso queira que o RTOS decida em quais núcleos essa task vai rodar, lembrando que outros valores são considerados inválidos e a API vai retornar erro.

As funções de task basicamente irão imprimir o uptime, ou seja, quantos ticks do RTOS já se passaram desde que ele começou a rodar juntamente com o ID do núcleo em que essa task está executando, sendo uma forma bem clara de ver o que está acontecendo, você,  pode fazer o upload desse sketch no seu ESP32. Ligue o monitor serial em seguida e você deve ter algo assim na tela:

Figura 1 : Sketch mostrando mensagens das tasks

Observe um detalhe, como as tasks criadas rodam a cada 100ms, e a loop() a cada 500ms iremos ver a loop executando com menos frequência porém a relação sempre se mantêm, para cada 5 execuções das tasks, temos 1 da loop atestando o comportamento real-time do RTOS. O mais legal é que mesmo se travássemos o loop principal da task que roda na PRO_CPU, as demais funções continuariam executando normalmente já que estão fisicamente separadas em outro núcleo que apenas compartilha o mesmo binário.

A partir desse simples exemplo você já poderá trazer suas aplicações antigas no Arduino e repensar em como dividir as tarefas entre tasks menores e qual dos núcleos poderia tomar conta deles, ou mesmo criando tasks sem afinidade para que o RTOS faça o balanceamento entre os núcleos permitindo assim o usurário extrair todo o potencial dos 240MHz em dois núcleos do ESP32!

Conclusão - Multiprocessamento no ESP32

Nessa série de dois artigos apresentamos os fundamentos de como lidar com multiprocessamento no ESP32 e porque o leitor deve aproveitar esse componente capaz de entregar muito mais processamento permitindo que o usuário possa desenvolver ou prototipar aplicações ricas nas mais variadas características.

O ESP32 oferece dois modelos para utilização de multiprocessamento, sendo o IPC para funções comuns, o método de afinidade criando tasks especializadas para cada núcleo e o método de tasks sem afinidades para que o RTOS efetue o balanceamento de carga de processamento.

Espero que você, caro leitor, agora se sinta encorajado a aproveitar mais do seu ESP32 e aproveite que os componentes avançados do IDF podem ser acessados diretamente do Arduino. Teremos mais artigos legais a respeito de recursos do IDF que irei escrever sobre nos próximos texto, portanto fiquem ligados!

Referências

1 - Guia de referência SMP do IDF para o ESP32;

2 - Zephyr, Symmetric multiprocessing;

Outros artigos da série

<< ESP32 - Lidando com Multiprocessamento - Parte I
Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.

Receba os melhores conteúdos sobre sistemas eletrônicos embarcados, dicas, tutoriais e promoções.

Arduino » ESP32 - Lidando com Multiprocessamento - Parte II
Comentários:
Notificações
Notificar
guest
5 Comentários
recentes
antigos mais votados
Inline Feedbacks
View all comments
lcps
lcps
03/08/2020 11:05

Vamos supor que em uma task o ESP32 esteja em modo AP, e em outra, em modo STA.Isso seria possível ?

Alexandre Miguel Jorge
Alexandre Miguel Jorge
22/07/2020 15:48

Ótimos artigos. Porém o núcleo 0, aquele que não é utilizado por padrão, não consegue processar tempos inferiores a 1ms (milissegundo). As funções que envolvam microssegundos (us) Delaymicrossecond() e micros(), não funcionam.

Rodrigo Nishigasako
Rodrigo Nishigasako
05/06/2020 17:42

Muito bom os ensinamentos. mas como sou novo na programação em si apareceram inumeras duvidas. queria um help. 1 - a maioria das minhas variaveis são globais. 2 - Preciso rodar duas rotinas similares onde a primeira envia a informação para o cloud e a segunda enviam informações para os IOs tomarem as açoes. 3 - ambas as rotinas usam aquisições em variaveis distintas mas de um mesmo IO. Importante é que eu preciso ter as duas rotinas rodando simultaneamente, a primeira que toma ação imediata e a segunda um pouco mais lenta que envia informação para o cloud. 4-… Leia mais »

JUDENILSON ARAUJO SILVA
JUDENILSON ARAUJO SILVA
30/03/2020 00:39

Q TOP!
Já usei, mas quando se trata de concorrência fico aperreado, pois meu ESP32 sempre buga. kkkk
Tenho q aprender melhor a lidar com isso.

Fabio Luiz
Fabio luiz
21/01/2020 06:13

Show de tutorial obrigado ! continuo acompanhando seus tutoriais!

Talvez você goste:

Séries



Outros da Série

Menu

WEBINAR
 
Sensores e soluções para aplicações em indústria inteligente

Data: 13/08 às 15:00h - Apoio: STMicroelectronics
 
INSCREVA-SE AGORA »



 
close-link