Paralelo no Arduino: Tarefas concorrentes - MakerHero

Tarefas rodando em paralelo no Arduino 17

Nem tudo funciona na velocidade que desejamos. Aprendemos, a duras penas, que saber esperar é uma virtude difícil de se conseguir. Dentro do universo dos processadores então, esperar um determinado tempo é tarefa crítica e é muito comum vermos programadores por vezes não tão iniciantes se perderem nesse assunto.

Aguardar pela conclusão de alguma tarefa, dentro de um microcontrolador, é algo que precisa ser estudado e implementado com cuidado para que, entre outras coisas, o processador não fique sem fazer nada – além de gastar tempo – esperando que um determinado intervalo de tempo se passe. Normalmente, se espera que um equipamento microcontrolado faça inúmeras tarefas simultaneamente e perder tempo nesse contexto é inadmissível.

O intuito desse artigo é apresentar uma pequena classe em C++ que implementa timers que podem ser ajustados para qualquer intervalo de tempo e consultados para saber se esse tempo já passou. Usaremos como exemplo o uso de tarefas em paralelo no Arduino, por ser uma plataforma bastante didática. Qualquer modelos serve, desde que suporte o comando millis() e tenha pelo menos três pino de GPIO disponíveis. Fácil. O código apresentado é padrão da plataforma Arduino, C++, mas é facilmente portável para outras plataformas.

Imagine, por exemplo, que o equipamento precise ler alguma informação de um sensor que leve um segundo para retornar o dado pedido mas que, ao mesmo tempo, outras coisas estão acontecendo que precisam ser atendidas em menos tempo do que isso. O programa não pode disparar o pedido de informação e simplesmente ficar ali, esperando passar um segundo, para ler o resultado e seguir o processo. Sabemos que é preciso disparar o pedido e sair para continuar o fluxo do programa, para ele executar tarefas em paralelo no Arduino. Após esse um segundo, aí sim volta para ler o resultado e seguir em frente novamente.

Exemplo de tarefas em paralelo no Arduino

Vamos fazer algo que é muuuuito chato de fazer sem uma classe bacana dessas: Fazer três LEDs piscarem em paralelo no Arduino, cada um numa frequência diferente, sem relação uma com a outra e que pode ser alterado a qualquer momento e de maneira fácil. Usaremos um Arduino Nano, mas qualquer outro modelo serve.

Vamos precisar de:

O esquema é bastante simples, basta ligar os LEDs nas portas 8, 9 e 10 da placa.

LEDs em paralelo no Arduino

No IDE do Arduino, crie um novo sketch e cole esse código, inclua os arquivos da biblioteca, que podem ser encontrados no GitHub da MakerHero, compile e suba no Arduino:

/**
 *  Programa de teste para a classe cTimer
 *  Conecte LEDs aos pinos GPIO 8, 9 e 10.
 *  
 */
#include "ctimer.h"

#define LED0  8
#define LED1  9
#define LED2  10

cTimer  g_Timer0(true);
cTimer  g_Timer1(true);
cTimer  g_Timer2(true);

// Defina aqui os tempos em ms para cada LED piscar:
#define   TEMPO_0   100
#define   TEMPO_1   275
#define   TEMPO_2   717
bool    gLed0 = false;
bool    gLed1 = false;
bool    gLed2 = false;


void setup() {
  pinMode(LED0, OUTPUT);
  digitalWrite(LED0, 0);
  pinMode(LED1, OUTPUT);
  digitalWrite(LED1, 0);
  pinMode(LED2, OUTPUT);
  digitalWrite(LED2, 0);

  g_Timer0.SetTimeOut(TEMPO_0);
  g_Timer1.SetTimeOut(TEMPO_1);
  g_Timer2.SetTimeOut(TEMPO_2);
  
}


void loop() {
  if(g_Timer0.IsTimeOut(true))
  {
    digitalWrite(LED0, gLed0);
    gLed0 = !gLed0;  
  }

  if(g_Timer1.IsTimeOut(true))
  {
    digitalWrite(LED1, gLed1);
    gLed1 = !gLed1;  
  }

  if(g_Timer2.IsTimeOut(true))
  {
    digitalWrite(LED2, gLed2);
    gLed2 = !gLed2;  
  }

  /*
   * Faca aqui o que voce quiser... 
   * 
   */
  }

O resultado são vários LEDs piscando em frequências diferentes.

É muito simples de utilizar objetos cTimer. Para cada LED, no exemplo, é preciso somente fazer o seguinte (examinando o LED0):

if(g_Timer0.IsTimeOut(true))
{
  digitalWrite(LED0, gLed0);
  gLed0 = !gLed0;  
}

Ou seja, se o timer já atingiu o tempo limite (IsTimeOut() retorna verdadeiro), inverta o estado do LED e reinicie o contador novamente (parâmetro true no IsTimeOut) .

O código claramente está escrito para ser didático. Utilizando-se vetores, por exemplo, consegue-se fazer a mesma coisa com cinco ou seis linhas… Mas o objetivo aqui é demonstrar o funcionamento do objeto timer.

Classe cTimer

Essa classe utiliza a função millis() para capturar o tempo decorrido. A função inicializa com zero quando o Arduino é ligado e seu valor é incrementado a cada milissegundo. Como é armazenado como um número longo sem sinal, o registrador ‘capota’ aproximadamente a cada 50 dias. Embora nem todos os sistemas irão rodar direto por tanto tempo, a classe leva isso em conta e trata esse overflow corretamente.

Como a implementação é feita na forma de classe, pode-se criar quantos timers forem necessários, cada um disparando no momento desejado sem que um interfira no outro, permitindo sincronizar tarefas e executar processos em qualquer intervalo de tempo quase intuitivamente.

A definição da classe é a seguinte:

class cTimer
{
private:
    uint32_t    m_u32step;
    uint32_t    m_u32TimeOut;
    uint32_t    m_u32timeNow;
    bool        mEnabled;
public:
    cTimer(bool _enabled = true);
    ~cTimer(void);
    void        SetTimeOut(uint32_t _u32mSeg);
    bool        IsTimeOut(bool _reset = false);
    uint32_t    ReadTimeOut(void);
    void        Enable(bool _enable);
    bool        IsEnabled(void);
};

Vamos analisar os métodos mais importantes dessa classe:

cTimer::cTimer(bool _enabled);

Pode-se criar um timer deixando-o já habilitado ou não. Abaixo, onde se verifica se o timer já disparou, a resposta depende do fato dele estar habilitado ou não.

cTimer::SetTimeOut(uint32_t _u32mSeg);

Esse método é usado para se definir o tempo necessário para que o timer dispare, em milissegundos, a partir do momento em que é chamado. Analisando internamente, o que o método faz é guardar o valor atual de millis() mais o valor passado no parâmetro. Ele também faz uma cópia do valor de millis() para poder tratar o caso de overflow discutido mais acima.

bool cTimer::IsTimeOut(bool _reset)

Esse método retorna se o tempo decorrido desde que SetTimeOut foi chamado já se esgotou. Caso o timer esteja desabilitado, ele sempre irá retornar falso. Em caso de retorno verdadeiro, se o parâmetro _reset for TRUE, o timer é setado novamente. Os demais métodos são bem diretos e servem para habilitar e desabilitar o timer, ou consultar quanto tempo ainda falta.

Gostou de aprender como fazer tarefas em paralelo no Arduino? Ajude-nos a melhorar o blog comentando abaixo sobre este tutorial.

Faça seu comentário

Acesse sua conta e participe

17 Comentários

  1. Que biblioteca top heim, obrigado por compartilhar o conhecimento, agradecer é o mínimo que se pode fazer. A tempos estava sofrendo com a doença do delay(), e agora com essa medição ai, consegui a cura intelectual kkkk. Parabéns pelo trabalho da FelipeFlop, e parabéns Luiz Cressoni.

    1. Quis dizer medicação kkkkkk

    2. Olá Tiago!

      Ficamos contentes em saber que pudemos te ajudar!

      Abraço,
      Rosana – Equipe MakerHero

  2. Estou iniciando
    Quero fazer uma seguecial de leds, que: começa ligando, deixa todos ligados, espera um tempo, e desliga na seguencia que iniciou.
    Finaliza todos desligados, espera um novo pulso esterno, roda e desliga novamente.

    Obrigado!

    1. Sebastião,

      Que dê tudo certo com seu projeto!!

      Abraços!
      Diogo – Equipe MakerHero

  3. Excelente trabalho. Tenho apenas uma duvida, a função millis é acumulada em uma variável do tipo long, portanto ela irá encher com o passar do tempo, como dito no texto em aproximadamente 50 dias. Para aplicações onde o Arduíno precisará ficar mais de 50 dias ligado o código não serve?

  4. Parabéns! ficou muito legal o seu trabalho.
    Mudei os Pin dos leds para 10, 11 e 12 e testei com o arduino multishielded .

    1. Olá Carlos,

      Que bom que deu certo!

      Abraço!
      Rosana – Equipe MakerHero

  5. Parabéns ! bem legal e educativo.

    1. Olá Carlos,

      É muito bom saber que você gostou do nosso trabalho 😀

      Abraço!
      Rosana – Equipe MakerHero

  6. Excelente material. Estava procurando algo semelhante para um projeto! Obrigado.

    1. Olá Marcos,

      É ótimo saber que nosso trabalho te ajudou!

      Abraço!
      Rosana – Equipe MakerHero

  7. Show. Bem interessante essa biblioteca.

    1. Olá André,

      É muito bom saber que você gostou do nosso trabalho 😀

      Abraço!
      Rosana – Equipe Filipe Flop

  8. Uma postagem com um assunto muito útil, mas eu acho que seria bom dar um exemplo um pouco mais complexo , pois o exemplo dado acaba sendo bem mais simples usando apenas ‘contadores’.

  9. Ótima abordagem sobre o assunto! Gostaria inclusive de sugerir ao Editor, uma breve pesquisa sobre a biblioteca Arduino Thread, que é uma ótima opção para quem quer trabalhar com otimização do tempo utilizando “Fake Threads” nas placas chinoitalianas e está incluída oficialmente no Gerenciador de Bibliotecas da Arduino IDE. Segue link: https://github.com/ivanseidel/ArduinoThread

    1. Olá Marcelo,

      Excelente dica!

      Abraço,
      Rosana – Equipe MakerHero