Manual
do
Maker
.
com
Nos artigos anteriores relacionados ao ESP32 com RTOS vocês devem ter reparado que só usei códigos de conceito. Nem todos eles rodam do jeitinho que estão escritos, porque na escrita fui modificando as variáveis e também não coloquei um código completo. Agora vamos programar de verdade utilizando os conceitos previamente explicados, mas para isso será necessário utilizar a IDE do Arduino, que facilitará demais as coisas. Mas se você já adicionou o ESP32 na IDE do Arduino, deve ter rodado bem o exemplo desse artigo.
Essa belezinha pode ser adquirida nesse link da CurtoCircuito, que é mais uma loja bastante interessante que está se voltando pra IoT. Vale conferir.
Derivados de Debian são eles: Linuxd Mint, Ubuntu e qualquer outro que use apt como gerenciador de pacotes.
Não vou me estender porque vou passar um código funcional para você fazer agora mesmo sua primeira compilação do ESP32 com RTOS.
Para instalar, apenas copie e cole essas linhas:
sudo usermod -a -G dialout $USER && \
sudo apt-get install git && \
wget https://bootstrap.pypa.io/get-pip.py && \
sudo python get-pip.py && \
sudo pip install pyserial && \
mkdir -p ~/Arduino/hardware/espressif && \
cd ~/Arduino/hardware/espressif && \
git clone https://github.com/espressif/arduino-esp32.git esp32 && \
cd esp32/tools/ && \
python get.py
Depois inicie a IDE do Arduino e selecione a sua board ESP32. Eu estou utilizando NODEMCU-32S.
Para instalar no Windows, siga esse link.
Agora vamos à brincadeira, utilizando o código de conceito do último artigo, mas com as devidas correções. Na documentação mesmo eu vi a utilização de uma função que não foi necessária e acabei pedindo essa dica pro José Morais, que agora tá cheio de "morais" comigo. Simplesmente não use vTaskStartScheduler(). Vou colocar o código comentado e em seguida faço as últimas observações.
//variável global a ser manipulada
byte my_shared_var = 1;
//criação do mutex
SemaphoreHandle_t xMutex;
//função da tarefa
void teste(void *pvParameters){
while (true){
xSemaphoreTake(xMutex,portMAX_DELAY);
Serial.println(my_shared_var);
my_shared_var = my_shared_var > 1 ? my_shared_var-1 : my_shared_var+1;
delay(500);
xSemaphoreGive(xMutex);
}
}
void setup(){
Serial.begin(115200);
//instância do mutex, que é um tipo de semáforo também
xMutex = xSemaphoreCreateMutex();
//se foi devidamente criado...
if(xMutex != NULL){
//duas instancias da função teste()
xTaskCreatePinnedToCore(teste, "Print1", 10000,NULL, 2, NULL,0);
xTaskCreatePinnedToCore(teste, "Print2", 10000,NULL, 3, NULL,0);
}
}
void loop(){
delay(2000);
Serial.println("batata");
}
Agora, aos detalhes. Foram criadas 2 tasks com a mesma função, mas cada vez que essa função é chamada, uma ação diferente é tomada, por isso uma hora imprime 1 e outra hora imprime 2.
A segunda task é executada depois de 500ms porque ela espera que a primeira libere o mutex. Será?
Muita atenção: não temos o identificador da task como referência, a função funciona como deveria, mas não sabemos qual task está executando. Isso pode gerar um grande engano. Resolvi testar criando uma segunda função para a task, passando um print para identificar. E adivinha? A prioridade da task2 era maior, então ela dominava o segundo núcleo. Como os intervalos eram iguais, ocasionalmente a task 1 era executada.
Como solucionar esse problema? - Simples, iguale as prioridades.
byte my_shared_var = 1;
SemaphoreHandle_t xMutex;
void teste(void *pvParameters){
while (true){
xSemaphoreTake(xMutex,portMAX_DELAY);
Serial.print("Task 1: ");
Serial.println(my_shared_var);
my_shared_var = my_shared_var > 1 ? my_shared_var-1 : my_shared_var+1;
delay(500);
xSemaphoreGive(xMutex);
}
}
void teste2(void *pvParameters){
while (true){
xSemaphoreTake(xMutex,portMAX_DELAY);
Serial.print("Task 2: ");
Serial.println(my_shared_var);
my_shared_var = my_shared_var > 1 ? my_shared_var-1 : my_shared_var+1;
delay(500);
xSemaphoreGive(xMutex);
}
}
void setup(){
Serial.begin(115200);
xMutex = xSemaphoreCreateMutex();
if(xMutex != NULL){
xTaskCreatePinnedToCore(teste, "Print1", 10000,NULL, 3, NULL,0);
xTaskCreatePinnedToCore(teste2, "Print2", 10000,NULL, 3, NULL,0);
}
}
void loop(){
delay(2000);
Serial.println("batata");
}
E a cada 2 segundos, "batata" é exibida também, executanda no núcleo 1, que está definida no main.cpp da API para o Arduino. A função delay() do Arduino para o FreeRTOS não faz um delay realmente, ela coloca o processador em idle.
Vamos evoluindo gradativamente, sugiro que brinque um pouco com isso, troque uma das tasks de núcleo, mude os intervalos e tire o mutex. Assim você poderá compreender melhor o paralelismo, prioridade e o mutex.
Inscreva-se no nosso canal Manual do Maker no YouTube.
Também estamos no Instagram.
Autor do blog "Do bit Ao Byte / Manual do Maker".
Viciado em embarcados desde 2006.
LinuxUser 158.760, desde 1997.