Multitarefa

Como executar várias funções ao mesmo tempo?

Na maioria das vezes, temos que implementar várias funções no Arduino como requerido em nossos projetos.

Porém, nos deparamos com alguns problemas que são difíceis para um iniciante entender pois normalmente entendem que o problema está na lógica ou em algum detalhe místico do Arduino que ainda não entende ou alguma limitação que ainda não sabe que existe.

Estas situações ocorrem principalmente porque algumas tarefas requerem a contagem de tempo, um intervalo de espera ou a espera de alguma tarefa que dura um certo tempo para executar tipo: intervalo de tempo entre acendimento ou piscadas de leds; tempo de bounce de teclas; conversão de sinais analógicos; transferencias de portas seriais lentas; etc...

Para resolver este tipo de problema, eu geralmente utilizo algumas técnicas:

  1. Divisão de tempo usando pequenos delays;
  2. Substituindo delays por verificação de tempo decorrido;
  3. Uso de threads

Cada técnica tem suas vantagens e desvantagens, dificuldades e facilidades de implementação e principalmente suas próprias lógicas de programação.

Vamos comentar cada um deles a seguir.

1. Divisão de tempo usando pequenos delays

A técnica consiste na utilização de um pequeno delay(), por exemplo, de 10 milissegundos, no loop() principal para ser o único elemento de espera de tempo. Deste modo, todas as tarefas que precisam esperar por um tempo deverão ter seu próprio contador e calcular o tempo como multiplo do único delay() do código.

Por exemplo: temos 2 leds que devem piscar em intervalos diferentes. O Led0 deve permanecer ligado durante 3 segundos e permanecer desligado durante 1 segundo e o Led1 deve permanecer ligado durante 2 segundos e permanecer desligado durante 5 segundos.

Portanto, para cada led, devemos ter as seguintes variaveis de controle:

  • Pino do led
  • Tempo do led;
  • Contador de tempo do led;
  • Estado do led.

O código ficará do seguinte modo:

Sketch (1674 bytes)

#include "Arduino.h"

#define LED_0 13 // Led0 deve ser ligado ao pino 2 do Arduino

#define LED_1 12 // Led1 deve ser ligado ao pino 3 do Arduino

#define TEMPO_LOOP 10 // Define unidade de tempo = 10 milissegundos

#define TEMPO_LED_0 20 // 0,2 segundo (20 * TEMPO_LOOP)

#define TEMPO_LED_1 50 // 0,5 segundos (50 * TEMPO_LOOP)

int contaTempoLed0 = 0; // Conta tempo do led 0. Cada unidade = 10 ms

int contaTempoLed1 = 0; // Conta tempo do led 1. Cada unidade = 10 ms

bool Led0estaLigado = false; // Indica o estado atual do led 0

bool Led1estaLigado = false; // Indica o estado atual do led 1

void setup() { // executa somente 1 vez quando inicia o Arduino

pinMode(LED_0, OUTPUT); // Define o pino do led 0 como saida

pinMode(LED_1, OUTPUT); // Define o pino do led 1 como saida

}

void loop() { // Repete constantemente

if(contaTempoLed0 > TEMPO_LED_0) { // O tempo do led 0 expirou

Led0estaLigado = !Led0estaLigado;

digitalWrite(LED_0, Led0estaLigado); // Altera o led 0

contaTempoLed0 = 0; // Limpa o contador do led 0

}

if(contaTempoLed1 > TEMPO_LED_1) { // O tempo do led 1 expirou

Led1estaLigado = !Led1estaLigado;

digitalWrite(LED_1, Led1estaLigado); // Altera o led 1

contaTempoLed1 = 0; // Limpa o contador do led 1

}

delay(TEMPO_LOOP); // Pausa por 10 milissegundos

contaTempoLed0++; // Incrementa o contador do tempo do led 0

contaTempoLed1++; // Incrementa o contador do tempo do led 1

} // Final do loop()

2. Substituindo delays por verificação de tempo decorrido

A tecnica de divisão de tempo usando pequenos delays viabilizou a implementação de 2 tarefas diferentes com tempos de intervalos diferente, porém, caso desejarmos incluir mais alguma tarefa, iremos impactar na precisão da contagem de tempo, o que normalmente é o caso em vários projetos.

Este problema ocorre pois a grande maioria do tempo ocupado pela CPU é gasta no delay(), portanto, para contornar esta limitação, podemos utilizar a função millis() e comparar o tempo decorrido para executar as ações no tempo correto, possibilitando haver outras funções dentro do loop().

Sketch (1546 bytes)

#include "Arduino.h"
#define LED_0 13 // Led0 deve ser ligado ao pino 2 do Arduino
#define LED_1 12 // Led1 deve ser ligado ao pino 3 do Arduino
#define TEMPO_LED_0 200 // 0,2 segundos (200 milissegundos)
#define TEMPO_LED_1 500 // 0,5 segundos (500 milissegundos)
unsigned long tempoDecorridoLed0 = 0; // Indica tempo da ultima execução do led 0
unsigned long tempoDecorridoLed1 = 0; // Indica tempo da ultima execução do led 1
bool Led0estaLigado = false; // Indica o estado atual do led 0
bool Led1estaLigado = false; // Indica o estado atual do led 1
void setup() { // executa somente 1 vez quando inicia o Arduino

pinMode(LED_0, OUTPUT); // Define o pino do led 0 como saida

pinMode(LED_1, OUTPUT); // Define o pino do led 1 como saida

}
void loop() { // Repete constantemente

if(millis() - tempoDecorridoLed0 >= TEMPO_LED_0) { // O tempo do led 0 expirou

Led0estaLigado = !Led0estaLigado;

digitalWrite(LED_0, Led0estaLigado); // Altera o led 0

tempoDecorridoLed0 = millis(); // Atualiza o tempo do led 0

}

if(millis() - tempoDecorridoLed1 >= TEMPO_LED_1) { // O tempo do led 1 expirou

Led1estaLigado = !Led1estaLigado;

digitalWrite(LED_1, Led1estaLigado); // Altera o led 1

tempoDecorridoLed1 = millis(); // Atualiza o tempo do led 1

}

} // Final do loop()

3. Uso de Threads

O uso de Threads é a tecnica que é mais aceita por experts em C pois se assemelha com a orientação a objetos e possui maior controle sobre as tarefas concorrentes.

Neste caso, a complexidade está embutida na biblioteca Threads.h e ThreadController.h.

Note que no loop() somente tempos a instrução cpu.run() e aceita mais intruções sem afetar o funcionamento das threads.

Veja abaixo o exemplo:

Sketch (2248 Bytes)

#include "Arduino.h"
#include "Thread.h"                           // Bibioteca das threads
#include "ThreadController.h"                           // Bibioteca das threads
#define LED_0 13 // Led0 deve ser ligado ao pino 2 do Arduino
#define LED_1 12 // Led1 deve ser ligado ao pino 3 do Arduino
#define TEMPO_LED_0 200 // 0,2 segundos (200 milissegundos)
#define TEMPO_LED_1 500 // 0,5 segundos (500 milissegundos)
ThreadController cpu;
Thread Led0; // Define do led 0
Thread Led1; // Define do led 1
bool Led0estaLigado = false; // Indica o estado atual do led 0
bool Led1estaLigado = false; // Indica o estado atual do led 1
void AlteraLed0() { // Altera estado do LedA

Led0estaLigado = !Led0estaLigado;

digitalWrite(LED_0 , Led0estaLigado);

}
void AlteraLed1() { // Altera estado do LedB

Led1estaLigado = !Led1estaLigado;

digitalWrite(LED_1 , Led1estaLigado);

}
void setup() { // executa somente 1 vez quando inicia o Arduino

pinMode(LED_0, OUTPUT); // Define o pino do led 0 como saida

pinMode(LED_1, OUTPUT); // Define o pino do led 1 como saida

Led0.setInterval(TEMPO_LED_0); // Define o intervalo de tempo

Led0.onRun(AlteraLed0); // Define a funcao do led 0

Led1.setInterval(TEMPO_LED_1); // Define o intervalo de tempo

Led1.onRun(AlteraLed1); // Define a funcao do led 1

cpu.add(&Led0); // Adiciona ao grupo de Threads

cpu.add(&Led1); // Adiciona ao grupo de Threads

}
void loop() { // Repete constantemente

cpu.run(); // Executa o grupo de Threads

} // Final do loop()