Utilizando a Programação Orientada a Objeto para Arduino 16

Nesse artigo vamos aprender técnicas simples que nos permitirão criar projetos maiores e mais complexos, utilizando programação Orientada a Objeto para Arduino. Em primeiro lugar, estudaremos mais a fundo o artigo “Tarefas rodando em paralelo no Arduino”, escrito pelo colega Luiz Cressoni, onde aprendemos uma técnica para gerenciamento do tempo. Em seguida, vamos avançar um pouco no mundo das máquinas de estado e, por fim, aprenderemos como deixar o nosso projeto mais organizado e arrojado usando a orientação a objetos.

Assobiar e chupar cana ao mesmo tempo!

Uma vez que aprendemos o básico no mundo da prototipagem eletrônica (piscar um LED, usar um sensor, acionar um botão e controlar um servo motor), nos sentimos instigados a construir projetos maiores e mais complexos, principalmente, projetos que unem os componentes mencionados. Infelizmente, percebemos que os sketchs que funcionam muito bem sozinhos quando condensados em um, simplesmente não dão certo.

A razão é que o Arduino é um processador muito simples que sequer possui um sistema operacional e consegue rodar um programa por vez. Porém, veremos nesse artigo que usando técnicas simples é possível criar um sketch que consegue lidar com múltiplas tarefas de maneira simples e arrojada.

Materiais necessários:

Circuito Fritizing

Conforme avançamos em nossos estudos,  o circuito vai ficando cada vez mais complexo. Nesse primeiro momento, vamos iniciar com nosso Arduino e dois LEDs em uma protoboard.

Imagem 1 - Programação Orientada a Objeto para Arduino

Pare de usar Delay

Provavelmente uma das primeiras coisas que você aprendeu quando começou a prototipar com Arduino foi usar a função delay(). Usar essa função para temporização é fácil e rápido, porém, com certeza, vai causar muitos problemas mais à frente quando você desejar adicionar funcionalidades ao seu projeto. Isso se dá porque a função delay monopoliza o processador: ela para o programa naquela linha de código e não permite que absolutamente nada aconteça em paralelo.

Enquanto o seu programa estiver estagnado na função delay, o Arduino não responderá a qualquer input, não processará qualquer dado e não vai alterar qualquer output. Claro, a função delay tem a sua importância e pode ser usada, porém, quando falamos em gerenciamento do tempo em um projeto com múltiplas tarefas usar a função delay pode levar seu projeto ao fracasso.

Lembra do famoso sketch Blink?

O Blink é provavelmente o sketch mais usado para introduzir o Arduino a iniciantes. Analisando o seu loop infinito podemos constatar que Arduino passa quase a totalidade do tempo esperando (sem performar ou executando qualquer outra ação).

void loop() {
digitalWrite(led, HIGH);   // Nível lógico alto na porta do LED
delay(1000);               // Espera um segundo
digitalWrite(led, LOW);    // Nível lógico baixo na porta do LED
delay(1000);               // Espera um segundo
}

Outro sketch que podemos analisar está localizado entre os exemplos do Arduino, em: Arquivos > Exemplos > Servo > Sweep.

Esse sketch é usado para testar servo motores, a função delay() é usada para controlar a velocidade do servo. Se nós tentarmos juntar os dois sketchs, Blink e Sweep, em um único projeto, vamos perceber que um funciona após o outro, mas perdemos toda a referência temporal e nada acontece da maneira que desejamos.

void loop() 
{ 
  digitalWrite(led, HIGH);   // Nível lógico alto na porta do LED
  delay(1000);               // Espera um segundo
  digitalWrite(led, LOW);    // Nível lógico baixo na porta do LED
  delay(1000);               // Espera um segundo
  
  for(pos = 0; pos <= 180; pos += 1) // vai de 0 graus a 180 graus 
  {                                  // move 1 grau por passo
    myservo.write(pos);              // diz ao servo para se mover a posição guardada na var ‘pos’ 
    delay(15);                       // espera 15 ms até o servo alcançar a posição 
  } 
  for(pos = 180; pos>=0; pos-=1)     // vai de 180 a 0 graus
  {                                
    myservo.write(pos);              //diz ao servo para se mover a posição guardada na var ‘pos’ 
    delay(15);                       // espera 15 ms até o servo alcançar a posição  
  } 
}

Como resolver esse problema?

Uma técnica simples para resolver esse problema é usar as funções de tempo e ficar de olho no relógio. Ao invés de usar uma função que para o microcontrolador nós vamos monitorar o tempo regularmente para assim saber quando é o momento de tomar uma ação. Enquanto isso acontece o microcontrolador está livre para realizar outras ações.

Vamos analisar outro sketch que vem no pacote de exemplos da IDE do Arduino, o BlinkWithoutDelay. Para isso, vá em Arquivos > Exemplos > 02.Digital > BlinkWithoutDelay.

// Variáveis que não se alteram
// atribui números aos pinos:
const int ledPin =  13;      // número do pino do LED
 
// Variáveis que serão alteradas:
int ledState = LOW;             // ledState usada para determinar o nível do LED
long previousMillis = 0;        // vai lembrar a última vez que o LED foi acionado
 
// as próximas variáveis são do tipo long porque guardam valores em milisegundos,
// que rapidamente se tornam números grandes e não podem ser guardados no tipo int.
long interval = 1000;           // intervalo em milisegundos para o LED piscar
 
void setup() {
  // atribui o pino do LED como saída:
  pinMode(ledPin, OUTPUT);      
}
 
void loop()
{
 
  // faz a checagem para saber se é o momento de piscar o LED, ou seja, 
  // se a diferença entre o tempo atual e a última vez que piscou 
  // o LED é maior que o intervalo que foi definido para o evento acontecer

  unsigned long currentMillis = millis();
 
  if(currentMillis - previousMillis > interval) {
    // salva a última vez que piscou o LED 
    previousMillis = currentMillis;   
 
    // se o LED está ligado então ele é desligado e vice-versa:
    if (ledState == LOW)
      ledState = HIGH;
    else
      ledState = LOW;
 
    // atua o LED conforme o estado guardado na variável de estado
    digitalWrite(ledPin, ledState);
  }
}

Em um primeiro momento pode parecer que esse sketch não é interessante, que é só uma maneira mais complicada de fazer um LED piscar, entretanto, BlinkWithoutDelay ilustra um conceito muito importante que é Máquina de Estados.

Ao invés de se apoiar na função delay(), BlinkWithoutDelay lembra o estado atual do LED e a última vez que ele foi alterado. Cada vez que o Arduino passa pelo loop, ele olha o relógio, utilizando a função millis(), para ver se é o momento de alterar o estado do LED novamente.

Outra maneira fácil de implementar o gerenciamento do tempo no seu projeto é usando a classe cTimer, esse assunto foi muito bem abordado no artigo “Tarefas rodando em paralelo no Arduino”. Vale a pena conferir! Aqui eu vou continuar utilizando a função millis() pura e deixo como sugestão de exercício que você implemente a classe cTimer.

Máquina de Estados

Agora vou apresentar mais uma variante do sketch Blink. Dessa vez teremos temporizações diferentes para os períodos do LED ligado e Desligado, vamos chamar esse novo programa de FlashWithoutDelay.

// Essas variáveis guardam o padrão de funcionamento
// e o estado atual do LED
 
int ledPin =  13;      // número do pino do LED
int ledState = LOW;             // ledState usada para guardar o estado
unsigned long previousMillis = 0;        // vai guardar a ultima vez que o LED recebeu update
long OnTime = 250;           // milissegundos do tempo ligado
long OffTime = 750;          // milissegundos do tempo desligado
 
void setup() 
{
    pinMode(ledPin, OUTPUT);      
}
 
void loop()
{
  // Faz a checagem para saber se já é o momento de alterar o estado do LED
  unsigned long currentMillis = millis();
 
  if((ledState == HIGH) && (currentMillis - previousMillis >= OnTime))
  {
    ledState = LOW;  // Desliga o LED
    previousMillis = currentMillis;  // Guarda o tempo
    digitalWrite(ledPin, ledState);  // Faz o Update do LED
  }
  else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime))
  {
    ledState = HIGH;  // Liga o LED
    previousMillis = currentMillis;   //Guarda o tempo
    digitalWrite(ledPin, ledState);      // Faz o Update do LED
  }
}

Podemos notar que nós temos dois tipos de variáveis, um tipo para guardar o estado atual do LED, se está ligado ou desligado, e outro tipo para monitorar quando essas mudanças de estado aconteceram. Isso caracteriza o Estado em uma Máquina de Estados.

Nós também temos linhas de código que olham para o estado e decidem quando e como o estado de ser alterado. Essa é a parte da Máquina. Toda vez que o Arduino inicia a função loop() ele está colocando a máquina em operação e esta, por sua vez, está atualizando o estado.

Agora precisamos combinar as máquinas de estado que controlam os componentes que selecionamos e fazer com que tudo funcione em conjunto.

Vamos fazer com que dois LEDs que instalamos no nosso projeto pisquem cada um com a sua temporização. Para isso temos que usar duas máquinas de estado permitindo que os LEDs atuem completamente independentes. Essa tarefa é surpreendentemente desafiadora e complicada de ser feita quando usamos somente a função delay().

// Essas variáveis guardam o padrão de funcionamento
// e o estado atual do LED
 
int ledPin1 =  12;      // número do pino do LED
int ledState1 = LOW;             // ledState usada para guardar o estado
unsigned long previousMillis1 = 0;      //vai guardar a última vez que o LED recebeu update
long OnTime1 = 250;           // milissegundos do tempo ligado
long OffTime1 = 750;          //milissegundos do tempo desligado
 
int ledPin2 =  13;      // número do pino do LED
int ledState2 = LOW;             // ledState usada para guardar o estado
unsigned long previousMillis2 = 0;        // vai guardar a última vez que o LED recebeu update
long OnTime2 = 330;           // milissegundos do tempo ligado
long OffTime2 = 400;          // milissegundos do tempo desligado
 
void setup() 
{
  pinMode(ledPin1, OUTPUT);      
  pinMode(ledPin2, OUTPUT);      
}
 
void loop()
{
  // Faz a checagem para saber se já é o momento de alterar o estado do LED
  unsigned long currentMillis = millis();
 
  if((ledState1 == HIGH) && (currentMillis - previousMillis1 >= OnTime1))
  {
    ledState1 = LOW;  // Desliga o LED
    previousMillis1 = currentMillis;  // Guarda o tempo
    digitalWrite(ledPin1, ledState);  // Faz o Update do LED
  }
  else if ((ledState1 == LOW) && (currentMillis - previousMillis1 >= OffTime1))
  {
    ledState1 = HIGH;  // Liga o LED
    previousMillis1 = currentMillis;   //Guarda o tempo
    digitalWrite(ledPin1, ledState1);      // Faz o Update do LED
  }
}
  
  if((ledState2 == HIGH) && (currentMillis - previousMillis2 >= OnTime2))
  {
    ledState2 = LOW;   // Desliga o LED
    previousMillis2 = currentMillis; // Guarda o tempo
    digitalWrite(ledPin2, ledState2);  // Faz o Update do LED
  }
  else if ((ledState2 == LOW) && (currentMillis - previousMillis2 >= OffTime2))
  {
    ledState2 = HIGH;  //Liga o LED
    previousMillis2 = currentMillis; //Guarda o tempo
    digitalWrite(ledPin2, ledState2);      // Faz o Update do LED
  }
}

Podemos adicionar mais máquinas de estado ao projeto até ficar sem memória ou pinos disponíveis. Cada máquina de estados funcionará independente uma da outra. Como exercício sugiro a você adicionar mais uma máquina de estado para controlar o terceiro LED do nosso projeto.

  1. Primeiro duplicar todas as variáveis de estado e código de uma máquina de estado.
  2. Depois renomeie todas as variáveis para evitar conflitos com a primeira máquina.

Não é difícil de ser feito, porém parece ser muito cansativo ter que repetir código o tempo todo para cada nova máquina de estado. Com certeza, deve existir uma forma mais eficiente de fazer isso. E, há!

Programação Orientada a Objeto para Arduino: uma solução chique 

Olhando para o sketch anterior podemos perceber que o código é quase completamente duplicado para cada máquina, mudando somente o nome das variáveis. Essa característica torna nosso projeto um sério candidato a uma Programação Orientada a Objeto para Arduino (POO).

Aqui vamos nos aproveitar de uma característica da linguagem de programação do Arduino que é uma variante da linguagem C++ que, por sua vez, suporta Programação Orientada a Objeto. Usando as funcionalidades do POO nós podemos juntar as variáveis de estado e as funcionalidades do LED Blink em uma classe C++.

Não é complicado de ser feito e nós praticamente já escrevemos todo o código para realizar essa tarefa, precisamos apenas reorganizar dentro de uma classe.

Definindo a classe

Vamos começar definindo uma classe “PiscaLed”. Depois nós colocamos dentro da classe todas as variáveis do programa FlashWithoutDelay. A partir do momento que elas estão dentro da classe podemos chamá-las de variáveis membro.

class PiscaLed
{
    // Variáveis membro da classe
    // São inicializadas no startup do programa
    int ledPin;      // número do pino do LED
    long OnTime;     // milissegundos do tempo ligado
    long OffTime;    // milissegundos do tempo desligado
 
    // Essas mantém o estado atual
    int ledState;                     // ledState usada para guardar o estado do LED
    unsigned long previousMillis;      // vai guardar o último acionamento do LED
};

Em seguida nós adicionamos um construtor. O construtor tem o mesmo nome da classe e a sua função é inicializar todas as variáveis.

class PiscaLed
{
    // Variáveis membro da classe
    // São inicializadas no startup do programa
    int ledPin;      // número do pino do LED
    long OnTime;     // milissegundos do tempo ligado
    long OffTime;    // milissegundos do tempo desligado
 
    // Essas mantém o estado atual
    int ledState;                     // ledState usada para guardar o estado do LED
    unsigned long previousMillis;      // vai guardar o último acionamento do LED

  public:
  PiscaLed(int pin, long on, long off)
  {
    ledPin = pin;
    pinMode(ledPin, OUTPUT);     
      
    OnTime = on;
    OffTime = off;
    
    ledState = LOW; 
    previousMillis = 0;
  }
};

Por último, nós pegamos o código que estava no loop() e colocamos em uma função membro chamada Update(). Note que é a mesma função com o nome diferente.

class PiscaLed
{
    // Variáveis membro da classe
    // São inicializadas no startup do programa
    int ledPin;      // número do pino do LED
    long OnTime;     // milissegundos do tempo ligado
    long OffTime;    // milissegundos do tempo desligado
 
    // Essas mantém o estado atual
    int ledState;                     // ledState usada para guardar o estado do LED
    unsigned long previousMillis;      // vai guardar o último acionamento do LED

  public:
  PiscaLed(int pin, long on, long off)
  {
    ledPin = pin;
    pinMode(ledPin, OUTPUT);     
      
    OnTime = on;
    OffTime = off;
    
    ledState = LOW; 
    previousMillis = 0;
  }

void Update()
{
      // Faz a checagem para saber se já é o momento de alterar o estado do LED
      unsigned long currentMillis = millis();
 
      if((ledState == HIGH) && (currentMillis - previousMillis >= OnTime))
      {
        ledState = LOW;  // Desliga o LED
        previousMillis = currentMillis;  // Guarda o tempo
        digitalWrite(ledPin, ledState);  // Faz o Update do LED
      }
      else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime))
      {
        ledState = HIGH;  // Liga o LED
        previousMillis = currentMillis;   //Guarda o tempo
        digitalWrite(ledPin, ledState);      // Faz o Update do LED
      }
     }
};

Simplesmente rearranjando o código nós conseguimos encapsular todas as variáveis (estado) e funcionalidade (máquina) para fazer um Blink independente em cada um dos LEDs.

Então, vamos usar a Programação Orientada a Objeto para Arduino?

Agora, para cada LED que queremos piscar nós criamos uma instância da classe PiscaLed chamando o construtor. E cada vez que o programa entrar no loop nós precisamos chamar a função Update() correspondente a cada instância.

Assim, não precisamos replicar toda a máquina de estado novamente, simplesmente chamamos outra instância da classe PiscaLed.

class PiscaLed
{
    // Variáveis membro da classe
    // São inicializadas no startup do programa
    int ledPin;      // número do pino do LED
    long OnTime;     // milissegundos do tempo ligado
    long OffTime;    // milissegundos do tempo desligado
 
    // Essas mantém o estado atual
    int ledState;                     // ledState usada para guardar o estado do LED
    unsigned long previousMillis;      // vai guardar o último acionamento do LED

  public:
  PiscaLed(int pin, long on, long off)
  {
    ledPin = pin;
    pinMode(ledPin, OUTPUT);     
      
    OnTime = on;
    OffTime = off;
    
    ledState = LOW; 
    previousMillis = 0;
  }

void Update()
{
      // Faz a checagem para saber se já é o momento de alterar o estado do LED
      unsigned long currentMillis = millis();
 
      if((ledState == HIGH) && (currentMillis - previousMillis >= OnTime))
      {
        ledState = LOW;  // Desliga o LED
        previousMillis = currentMillis;  // Guarda o tempo
        digitalWrite(ledPin, ledState);  // Faz o Update do LED
      }
      else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime))
      {
        ledState = HIGH;  // Liga o LED
        previousMillis = currentMillis;   //Guarda o tempo
        digitalWrite(ledPin, ledState);      // Faz o Update do LED
      }
     }
};

PiscaLed led1(12, 100, 400);
PiscaLed led2(13, 350, 350);
 
void setup()
{
}
 
void loop()
{
    led1.Update();
    led2.Update();
}

Menos é mais! É isso! Cada novo LED precisa somente de duas linhas de código!

É um código menor e mais fácil de ler. E desde que não há código repetido ele também é compilado menor o que nos deixa mais memória para fazer outras coisas!

Agora, ao Sweep

Vamos aplicar o mesmo princípio aos servos!

Antes de mais nada, precisamos incluir os servos no nosso projeto e aproveitar o encejo para já instalar mais um LED. Fica assim:

Imagem 2 - Programação Orientada a Objeto para Arduino

Vamos dar uma olhada no tradicional sketch Sweep da biblioteca de exemplos do Arduino. Note que ele usa o famigerado delay(). Vamos pegar algumas partes do código que precisamos e criar uma máquina de estados “Sweeper”.

#include <Servo.h> 
 
Servo myservo;              
 
int pos = 0;    
 
void setup() 
{ 
  myservo.attach(9);  
} 


void loop() 
{ 
  digitalWrite(led, HIGH);   // Nível lógico alto na porta do LED
  delay(1000);               // Espera um segundo
  digitalWrite(led, LOW);    // Nível lógico baixo na porta do LED
  delay(1000);               // Espera um segundo
  
  for(pos = 0; pos <= 180; pos += 1) // vai de 0 graus a 180 graus 
  {                                  // move 1 grau por passo
    myservo.write(pos);              // diz ao servo para se mover a posição guardada na var ‘pos’ 
    delay(15);                       // espera 15 ms até o servo alcançar a posição 
  } 
  for(pos = 180; pos>=0; pos-=1)     // vai de 180 a 0 graus
  {                                
    myservo.write(pos);              //diz ao servo para se mover a posição guardada na var ‘pos’ 
    delay(15);                       // espera 15 ms até o servo alcançar a posição  
  } 
}

A classe Sweeper abaixo encapsula as ações do sketch sweep, mas usa millis() para monitorar o tempo. Também precisamos usar as funções Attach() e Detach() para associar um servo a um determinado pino.

class Sweeper
{
  Servo servo;              //O servo
  int pos;              // posição atual do servo
  int increment;        // incremento que moverá o servo para uma direção
  int  updateInterval;      // intervalo entre incrementos
  unsigned long lastUpdate; // última posição
 
public: 
  Sweeper(int interval)
  {
    updateInterval = interval;
    increment = 1;
  }
  
  void Attach(int pin)
  {
    servo.attach(pin);
  }
  
  void Detach()
  {
    servo.detach();
  }
  
  void Update()
  {
    if((millis() - lastUpdate) > updateInterval)  // momento para atualizar
    {
      lastUpdate = millis();
      pos += increment;
      servo.write(pos);
      Serial.println(pos);
      if ((pos >= 180) || (pos <= 0)) // fim do sweep
      {
        // reversão 
        increment = -increment;
      }
    }
  }
};

E agora?

Agora nós podemos instanciar quantos PiscaLed e Sweeper nós quisermos!

Cada instância do PiscaLed necessita somente de duas linhas de código:

  • Uma para declarar a instância
  • Uma para chamar o Update() no loop

Cada instância do Sweeper requer três linhas de código:

  • Uma para declarar a instância
  • Uma para vincular o servo a um pino no setup
  • Uma para chamar o Update() no loop
#include <Servo.h>

class PiscaLed
{
    // Variáveis membro da classe
    // São inicializadas no startup do programa
    int ledPin;      // número do pino do LED
    long OnTime;     // milissegundos do tempo ligado
    long OffTime;    // milissegundos do tempo desligado
 
    // Essas mantém o estado atual
    int ledState;                     // ledState usada para guardar o estado do LED
    unsigned long previousMillis;      // vai guardar o último acionamento do LED

  public:
  PiscaLed(int pin, long on, long off)
  {
    ledPin = pin;
    pinMode(ledPin, OUTPUT);     
      
    OnTime = on;
    OffTime = off;
    
    ledState = LOW; 
    previousMillis = 0;
  }

void Update()
{
      // Faz a checagem para saber se já é o momento de alterar o estado do LED
      unsigned long currentMillis = millis();
 
      if((ledState == HIGH) && (currentMillis - previousMillis >= OnTime))
      {
        ledState = LOW;  // Desliga o LED
        previousMillis = currentMillis;  // Guarda o tempo
        digitalWrite(ledPin, ledState);  // Faz o Update do LED
      }
      else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime))
      {
        ledState = HIGH;  // Liga o LED
        previousMillis = currentMillis;   //Guarda o tempo
        digitalWrite(ledPin, ledState);      // Faz o Update do LED
      }
     }
};

class Sweeper
{
  Servo servo;              //O servo
  int pos;              // posição atual do servo
  int increment;        // incremento que moverá o servo para uma direção
  int  updateInterval;      // intervalo entre incrementos
  unsigned long lastUpdate; // última posição
 
public: 
  Sweeper(int interval)
  {
    updateInterval = interval;
    increment = 1;
  }
  
  void Attach(int pin)
  {
    servo.attach(pin);
  }
  
  void Detach()
  {
    servo.detach();
  }
  
  void Update()
  {
    if((millis() - lastUpdate) > updateInterval)  // momento para atualizar
    {
      lastUpdate = millis();
      pos += increment;
      servo.write(pos);
      Serial.println(pos);
      if ((pos >= 180) || (pos <= 0)) // fim do sweep
      {
        // reversão 
        increment = -increment;
      }
    }
  }
};


PiscaLed led1(11, 123, 400);
PiscaLed led2(12, 350, 350);
PiscaLed led3(13, 200, 222);
 
Sweeper sweeper1(15);
Sweeper sweeper2(25);
 
void setup() 
{ 
  Serial.begin(9600);
  sweeper1.Attach(9);
  sweeper2.Attach(10);
} 
 
 
void loop() 
{ 
  sweeper1.Update();
  sweeper2.Update();
  
  led1.Update();
  led2.Update();
  led3.Update();
}

Agora nós temos 5 tarefas independentes rodando sem parar e sem interferência. Nosso loop() tem somente 5 linhas de código!

Vamos agora adicionar um botão a toda essa mistura e interagir com algumas dessas tarefas.

Tudo junto e misturado! Aplicação da Programação Orientada a Objeto para Arduino

Outro problema com o delay() é que inputs, como botões, tendem a serem ignorados porque o microcontrolador não pode checar o botão quando está preso no delay(). Como estamos usando o monitoramento do tempo via millis(), nosso microcontrolador fica livre para checar os inputs com regularidade o que nos possibilita criar projetos maiores e mais complexos que permanecem responsivos.Vamos colocar em prática?

Segue o circuito para montagem na protoboard:

Imagem 3 - Programação Orientada a Objeto para Arduino

O código abaixo checa o botão cada vez que a função loop() é acionada, caso o botão esteja pressionado as instâncias Led1 e Sweeper2 não serão atualizadas.

#include <Servo.h>

class PiscaLed
{
    // Variáveis membro da classe
    // São inicializadas no startup do programa
    int ledPin;      // número do pino do LED
    long OnTime;     // milissegundos do tempo ligado
    long OffTime;    // milissegundos do tempo desligado
 
    // Essas mantém o estado atual
    int ledState;                     // ledState usada para guardar o estado do LED
    unsigned long previousMillis;      // vai guardar o último acionamento do LED

  public:
  PiscaLed(int pin, long on, long off)
  {
    ledPin = pin;
    pinMode(ledPin, OUTPUT);     
      
    OnTime = on;
    OffTime = off;
    
    ledState = LOW; 
    previousMillis = 0;
  }

void Update()
{
      // Faz a checagem para saber se já é o momento de alterar o estado do LED
      unsigned long currentMillis = millis();
 
      if((ledState == HIGH) && (currentMillis - previousMillis >= OnTime))
      {
        ledState = LOW;  // Desliga o LED
        previousMillis = currentMillis;  // Guarda o tempo
        digitalWrite(ledPin, ledState);  // Faz o Update do LED
      }
      else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime))
      {
        ledState = HIGH;  // Liga o LED
        previousMillis = currentMillis;   //Guarda o tempo
        digitalWrite(ledPin, ledState);      // Faz o Update do LED
      }
     }
};

class Sweeper
{
  Servo servo;              //O servo
  int pos;              // posição atual do servo
  int increment;        // incremento que moverá o servo para uma direção
  int  updateInterval;      // intervalo entre incrementos
  unsigned long lastUpdate; // última posição
 
public: 
  Sweeper(int interval)
  {
    updateInterval = interval;
    increment = 1;
  }
  
  void Attach(int pin)
  {
    servo.attach(pin);
  }
  
  void Detach()
  {
    servo.detach();
  }
  
  void Update()
  {
    if((millis() - lastUpdate) > updateInterval)  // momento para atualizar
    {
      lastUpdate = millis();
      pos += increment;
      servo.write(pos);
      Serial.println(pos);
      if ((pos >= 180) || (pos <= 0)) // fim do sweep
      {
        // reversão 
        increment = -increment;
      }
    }
  }
};


PiscaLed led1(11, 123, 400);
PiscaLed led2(12, 350, 350);
PiscaLed led3(13, 200, 222);
 
Sweeper sweeper1(15);
Sweeper sweeper2(25);
 
void setup() 
{ 
  Serial.begin(9600);
  sweeper1.Attach(9);
  sweeper2.Attach(10);
} 

void loop() 
{ 
  sweeper1.Update();
  
  if(digitalRead(2) == HIGH)
  {
     sweeper2.Update();
     led1.Update();
  }
  
  led2.Update();
  led3.Update();
}

Os 3 LEDs piscarão às suas próprias taxas. Os 2 sweepers varrerão em suas próprias taxas. Mas quando pressionamos o botão, sweeper2 e led1 param até liberarmos o botão. Como não há delay() no loop, o botão tem uma resposta quase instantânea.

Então agora temos 5 tarefas sendo executadas de forma independente e com uma entrada do usuário. Não há delay() para amarrar o processador. E nosso eficiente código Orientado a Objetos deixa muito espaço para expansão!

Obrigado por chegar até aqui! O próximo passo é usar as técnicas aprendidas aqui para explorar outras maneiras de tornar seu Arduino responsivo a eventos externos, enquanto gerencia várias tarefas.

Se você deseja ler mais conteúdos que vão te ajudar a melhorar as suas habilidades com Arduino, visite o blog da MakerHero. Siga nas redes sociais e não deixe de conferir as novidades!

Faça seu comentário

Acesse sua conta e participe

16 Comentários

  1. Ótimo post, mas ao tentar aplicar no meu projeto, não consegui fazer funcionar.

    Trata-se de uma classe para o sensor ultrassônico HC-SR04, eu preciso usar 3 destes no projeto e para evitar fazer 3 funções no programa principal, para isso, preciso de um método que retorne o valor da distância em centímetros, mas não funciona.

    Código:
    class UltraHC
    {
    private:
    const float soundSpeed = 0.034;
    int trigPin;
    int echoPin;

    public:
    UltraHC(int trig, int echo)
    {
    trigPin = trig;
    echoPin = echo;
    pinMode(trigPin, OUTPUT);
    pinMode(echoPin, OUTPUT);
    }
    int getDistanceCm(int trigPin, int echoPin){

    long duration;
    int distanceCm;

    // Clears the trigPin
    digitalWrite(trigPin, LOW);
    delayMicroseconds(2);
    // Sets the trigPin on HIGH state for 10 micro seconds
    digitalWrite(trigPin, HIGH);
    delayMicroseconds(10);
    digitalWrite(trigPin, LOW);

    // Reads the echoPin, returns the sound wave travel time in microseconds
    duration = pulseIn(echoPin, HIGH);

    // Calculate the distance
    distanceCm = duration * soundSpeed/2;

    return distanceCm;
    }
    };

    UltraHC HC1(5, 18);

    int distancia;

    void setup() {
    Serial.begin(115200);
    }

    void loop() {
    distancia = HC1.getDistanceCm();
    Serial.print(“A distância é: “); Serial.print(distancia); Serial.println(“cm”);
    }

    Erro:
    exit status 1
    no matching function for call to ‘UltraHC::getDistanceCm()’

    Algumas considerações importantes:
    1- Sou inexperiente em POO, estou começando agora, tanto que este é o primeiro projeto prático que a uso, a fim de internalizar o conceito.
    2- Estou usando a ESP32 no projeto e, por alguma razão, nenhuma biblioteca existente funcionou, me deixando apenas a opção de fazer o processo de distância manualmente.

    Desde já grato!

    Alberto Bruno Silvestre de Oliveria
    1. Olá Alberto,

      O erro ocorre porque quando você definiu o método getDistanceCm na classe você colocou int trigPin e int echoPin como parâmetros e ao chamar a função no loop você não colocou parâmetro nenhum, dessa forma ele busca por uma função chamada getDistanceCm que não recebe parâmetros e não encontra, o que causa o erro. Você precisa ou remover os parâmetros na declaração da função na classe, ou passar os parâmetros ao chamar a função.

      Abraços!
      Vinícius – Equipe MakerHero

  2. Olá Mauricio,

    Excelente postagem!!!

    Teria uma forma de alterar a variável on e off a cada ciclo de forma q eu tenha uma redução ou ampliação no intervalo que o led fica on e off?

    Por exemplo, em PiscaLed led1(12, 100, 400); teria uma forma do 400 ir decrementando 399, 398, 397, 396… a cada ciclo e o led piscar mais rápido?

    1. Olá Douglas,

      Sim, você pode decrementar as variáveis OnTime e OffTime a cada chamada do Update colocando a linha de código adequada.

      Abraços!
      Vinícius – Equipe MakerHero

  3. Excelente!!!!! Tenho ministrado cursos de princípios de robótica para crianças e adolescente, inclusive fazendo uso do kitmaker da Filipflop e irei utilizar com a sua licença esse material para integrá-lo ao conteúdo.

    1. Olá Daniel,

      Pode utilizar, basta citar a fonte.

      Ficamos felizes em saber que nossos conteúdos estão ajudando em cursos para estimular novos makers 🙂

      Abraços!
      Vinícius – Equipe MakerHero

  4. Excelente material. Parabens. Uso Platfomio para trabalhar profissionalmente com arduino. Desenvolvo placa com diversos modelos de MCUs e costumo criar minha próprias libs. Essa matéria sozinha ensina mais que muitos cursos universitários.

    1. Olá Jeilson,

      Obrigado! Feedbacks como esses nos mostram que estamos no caminho certo.

      Abraços!
      Vinícius – Equipe MakerHero

  5. Boa Noite. Eu testei o penúltimo código do “Agora ao Sweep” no simulador e ele apresenta algum problema em um dos Servos. Eu acredito que seja devido a classe Servo dentro da Classe Sweep. Como você faz o attach a um pino, sem criar um objeto novo ele (Classe Servo) não faz o attach no segundo motor corretamente.

  6. Vejo professores reclamando de aula à distância enquanto vc com apenas um texto e imagens consegue passar o conteúdo de maneira muito eficaz. O segredo está em gostar do que faz e ser organizado.

    Parabéns.

    1. Rafael,

      Com certeza! Gosto de frisar principalmente sobre organização, isso ajuda muito a ter um conteúdo de qualidade!

      Abraços!
      Diogo – Equipe MakerHero

  7. Excelente material, muito didático! Parabéns.

    LEONARDO BRUNO MEDEIROS SILVA
  8. Muito bom, vou usar esse post como referência par mim. 😉

    1. Nós que agradecemos! Continue aprendendo 😉

      Abraços, e cuide-se com o COVID-19!
      Diogo – Equipe MakerHero

  9. Sensacional, muito bem articulado.
    Obrigado por compartilhar seu conhecimento.