ESP32

Atribuir tarefa a um núcleo do ESP32

ESP32 | Do bit Ao Byte | MicroPython no ESP32 | Processamento paralelo com ESP32 | Selecionar uma CPU | mutex no freertos | tasks no esp32 | FreeRTOS com ESP32 | timer com ESP32 | partições no ESP32

Lembro-me do tempo que Linux era tratado como um software; as pessoas perguntavam se “dava pra instalar no Windows”, porque em meados dos anos 90 era difícil compreender que existisse outra coisa que não o Windows. Hoje no mundo dos embarcados, até recentemente, o ESP8266 era tratado como um módulo WiFi para utilizar no Arduino e isso que me fez lembrar da época em que comecei a usar Linux.

Você já tem um ESP32?

Porque se não tem, vai perder a brincadeira. Sugiro que pegue o seu na CurtoCircuito.

O ESP é um processador da arquitetura Tensillica de 32 bits e pode rodar um sistema operacional de tempo real (RTOS). Ele é totalmente independente, não precisa estar atrelado a um Arduino, e ainda pode rodar diferentes firmwares como o Sming, o MicroPython e o NodeMCU. Mas o ESP32, ah, o ESP32; esse tem 2 núcleos de 240MHz, que traz além do WiFi, o bluetooth clássico e o BLE (dual mode). Tem 520KB de SRAM e 16MB de flash. Agora, de que adianta tantos recursos se não o utilizarmos corretamente? Bem, hoje eu vi um post que tratava da utilização de núcleos de forma independente, atribuindo a cada um uma respectiva tarefa. Confesso que deixei o ESP32 de lado um pouco porque sou apaixonado pelo Sming e não vi ainda a migração desse framework ocorrer para o ESP32, mas esse artigo que li me empolgou, então resolvi pesquisar um pouco mais sobre os recursos oferecidos pelo RTOS.


Loop infinito com delay no RTOS

Fazendo uma analogia, quando usamos o delay no Arduino, estamos bloqueando o fluxo do código, que permanecerá dentro do loop desse delay até a finalização dessa parada. Isso signifca que nada mais ocorrerá; sensores não serão lidos, a serial estará bloqueada e todo o resto também. Quando utilizamos um delay no ESP32 através da implementação para utilizar na IDE do Arduino, é chamada a função vTaskDelay, que é uma função do FreeRTOS, cuja documentação de seus recursos podem ser vistas nesse link.

Claro, é muito simples utilizar a função delay, mas apenas para esclarecer como utilizar essa função, segue um exemplo:

Esse é o mesmo exemplo utilizado na documentação; ou bem parecido, mas é um ótimo exemplo de blink, que servirá perfeitamente para utilizarmos no teste de tarefas paralelas.

A única coisa que varia entre o delay do Arduino é a declaração de um valor prévio para o delay. Mas o mais legal de utilizar os recursos do RTOS é a disponibilização de recursos que não temos no Arduino. Os recursos do FreeRTOS são tão amplos que não seria capaz de descrevê-los todos, por mais artigos que eu escreva.

Executando uma tarefa sem selecionar uma CPU

Outra coisa que acho fantástica em utilizar uma board como o ESP32 é a possibilidade de controlar cada CPU de forma independente. É muito prazeroso contar com triggers, interrupções e tasks, tudo em uma plaquinha que cabe no bolso!

Quando utilizamos a função xTaskCreate para criar uma tarefa, não especificamos um núcleo para sua execução, isso fica por conta do FreeRTOS, que selecionará a CPU livre para atribuir a tarefa. Também temos a possiblidade de associar a tarefa a um núcleo específico, como veremos mais adiante.

Como descobrir que núcleo está sendo usado para uma tarefa

Quando as tarefas estão sendo gerenciadas pelo FreeRTOS, não sabemos qual núcleo está sendo utilizado. Para obter essa informação, usamos a função xPortGetCoreID.

Como vamos utilizar a comunicação serial para observar os resultados, já temos aí uma tarefa em execução, que é o setup(), onde inicializaremos a comunicação serial. Como essa função não recebe parâmetros, devemos chamá-la dentro da tarefa em execução desse modo:

Agora vamos iniciar uma nova tarefa com a utilização do xTaskCreate (do ESP32-IDF, que pode ser visto nesse link), e novamente analisaremos em que núcleo ela será executada. Nesse caso, o setup() ficaria assim:

Seus parâmetros são:

função string com o nome da tarefa stack size parâmetro prioridade handle
funcaoTeste “funcaoTeste” 10000 NULL 2 NULL

O primeiro parâmetro é o ponteiro para uma função criada por você.

A string define um nome para a tarefa. Pode ser qualquer coisa, mas para debugging é melhor que seja o mesmo nome da função para facilitar as coisas.

O stack especifica o número de variáveis que podem ser tratadas, isso não é o número de bytes. Um stack de 16 bits com usStackDepth é definido cmo 100,200 bytes que serão alocados.

O parâmetro é  o ponteiro que será usado para a tarefa que está sendo criada.

A prioridade é o peso da tarefa sobre as demais. Menor peso, menor prioridade, maior peso, maior prioridade.

O último parâmetro permite passar um manipulador pelo qual a tarefa criada pode ser referenciada.

Precisamos declarar a função que será utilizada pela tarefa, então:

Reparou no vTaskDelete(NULL)? Quando a tarefa não permanece em um loop infinito, precisamos desalocá-la da memória. Então, dentro da própria tarefa chamamos a função que a excluirá da lista.

Agora vamos colocar na função loop() apenas o print do núcleo de sua execução:

O código completo fica assim:

Listando as tarefas com vTaskList

Também existe uma função específica para listar as tarefas. Mas essa função não deve ser utilizada para outro fim que não debugging, porque ela desabilita as interrupções do sistema enquanto em execução. Essa função não tem retorno e recebe como parâmetro o ponteiro de um array de char, onde ela alocará o respectivo Byte da tabela ASCII denotando o estado de cada tarefa da seguinte maneira:

B blocked
R Ready
D Deleted
S Suspended ou bloqueado sem um timeout

O formato dessa função é:

E seu uso deve ser algo como:

 

A saída dessa função deve ser algo como:

A média é de 40 Bytes por tarefa e o valor que utilizei no array de char é hipotético, ajuste conforme sua necessidade.

Se quiser ver mais funcionalidades do ESP-IDF, consulte nesse link.

Jogar uma tarefa para ser executada em um núcleo específico permite que você tenha um código principal atuante sem delays ou interrupções, dando ao seu programa um comportamento parecido com threads, onde você poderá ter paralelismo, processamento assíncrono e segurança na tarefa principal – que ainda responderá às prioridades de interrupção. É muito poder ou não é?

No próximo artigo veremos como selecionar a CPU que executará uma determinada tarefa e daí cito mais algumas funcionalidades do FreeRTOS.

Inscreva-se no nosso newsletter, alí em cima à direita e receba novos posts por email.

Siga-nos no Do bit Ao Byte no Facebook.

Prefere twitter? @DobitAoByte.

Inscreva-se no nosso canal Do bit Ao Byte Brasil no YouTube.

Nossos grupos:

Arduino BR – https://www.facebook.com/groups/microcontroladorarduinobr/
Raspberry Pi BR – https://www.facebook.com/groups/raspberrybr/
Orange Pi BR – https://www.facebook.com/groups/OrangePiBR/
Odroid BR – https://www.facebook.com/groups/odroidBR/
Sistemas Embarcados BR – https://www.facebook.com/groups/SistemasEmbarcadosBR/
MIPS BR – https://www.facebook.com/groups/MIPSBR/
Do Bit ao Byte – https://www.facebook.com/groups/dobitaobyte/

Próximo post a caminho!

3 comments

Comments are closed.

%d blogueiros gostam disto: