Publicidade:

segunda-feira, 6 de junho de 2016

Arduino - Race Game com display LCD

Semana passada postei uma implementação minha do clássico Snake Game em um display LCD 16x2. Baseado naquele exemplo, implementei um jogo de corrida. Apesar de simples ficou legalzinho.

Vídeo:


Código-Fonte:

 /*
Fabiano A. Arndt - 2016
www.youtube.com/user/fabianoallex
www.facebook.com/dicasarduino
fabianoallex@gmail.com
*/
    
#include <LiquidCrystal.h>
 
/*************************************************************************************************************
*******************************BIT ARRAY *********************************************************************
**************************************************************************************************************
mais informações aqui: http://fabianoallex.blogspot.com.br/2015/09/arduino-array-de-bits.html
**************************************************************************************************************/
class BitArray{
  private:
    int _num_bits;   //quantidade de bits a serem gerenciados
    int _num_bytes;  //quantidade de bytes utilizados para armazenar os bits a serem gerenciados
    byte * _bytes;   //array de bytes onde estarão armazenados os bits
  public:
    BitArray(int num_bits){
      _num_bits  = num_bits;
      _num_bytes = _num_bits/8 + (_num_bits%8 ? 1 : 0) + 1;
      _bytes = (byte *)(malloc( _num_bytes * sizeof(byte) ) );
    }
    void write(int index, byte value) {
      byte b = _bytes[ index/8 + (index%8 ? 1 : 0) ];
      unsigned int bit = index%8;
      if (value) { b |= (1 << bit); } else { b &= ~(1 << bit);  }
      _bytes[ index/8 + (index%8 ? 1 : 0) ] = b;
    }
    void write(byte value) { for(int j=0; j<_num_bytes;j++) { _bytes[j] = value ? B11111111 : B00000000;  }  }
    int read(int index) {
      byte b = _bytes[ index/8 + (index%8 ? 1 : 0) ];
      unsigned int bit = index%8;
      return (b & (1 << bit)) != 0;
    }
    ~BitArray(){ free ( _bytes ); }
};
 
class BitArray2D {
  private:
    unsigned int _rows;
    unsigned int _columns;
    unsigned int _cols_array; //pra cada 8 colunas, 1 byte é usado 
    byte**       _bits;
  public:
    BitArray2D(unsigned int rows, unsigned int columns){
      _rows       = rows;
      _columns    = columns;
      _cols_array = columns/8 + (_columns%8 ? 1 : 0) + 1; //divide por 8 o número de colunas
      _bits = (byte **)malloc(_rows * sizeof(byte *));
      for(int i=0;i<_rows;i++){ _bits[i] = (byte *)malloc(  _cols_array  *  sizeof(byte)); } //cria varios arrays
      clear();
    }
    unsigned int rows(){ return _rows; }
    unsigned int columns(){ return _columns; }
      
    void clear() { 
      for(int i=0;i<_rows;i++){      
        for(int j=0; j<_cols_array;j++) { _bits[i][j] = B00000000; }       
      }   
    }
    
    void write(unsigned int row, unsigned int column, int value){
      byte b = _bits[row][ column/8 + (column%8 ? 1 : 0) ];
      unsigned int bit = column%8;
        
      if (value) { b |= (1 << bit); } else { b &= ~(1 << bit);  }
        
      _bits[row][ column/8 + (column%8 ? 1 : 0) ] = b;
    }
      
    void write(byte value){
      for(int i=0;i<_rows;i++){      
        for(int j=0; j<_cols_array;j++) {      
          _bits[i][j] = value ? B11111111 : B00000000;     
        }       
      }  
    }
      
    int read(unsigned int row, unsigned int column){
      byte b = _bits[row][ column/8 + (column%8 ? 1 : 0) ];
      unsigned int bit = column%8;
        
      return (b & (1 << bit)) != 0;
    }
      
    void toggle(unsigned int row, unsigned int column){ write(row, column, !read(row, column)); }
    void toggle(){ for(int i=0;i<_rows;i++){      for(int j=0; j<_columns;j++) {      toggle(i,j);   }   }   }
};
  
/*************************************************************************************************************
*******************************FIM BIT ARRAY *****************************************************************
**************************************************************************************************************/
 
/*************************************************************************************************************
************************************CLASSE UNIQUE RANDOM******************************************************
**************************************************************************************************************
mais informações aqui: http://fabianoallex.blogspot.com.br/2015/09/arduino-numeros-aleatorio-repetidos-e.html
*************************************************************************************************************/
   
class UniqueRandom{
  private:
    int _index;
    int _min;
    int _max;
    int _size;
    int* _list;
    void _init(int min, int max) {
      _list = 0; 
      if (min < max) { _min = min; _max = max; } else { _min = max; _max = min; }
      _size = _max - _min; 
      _index = 0;
    }    
  public:
    UniqueRandom(int max)           { _init(0,   max); randomize(); } //construtor com 1 parametro
    UniqueRandom(int min, int max)  { _init(min, max); randomize(); } //construtor com 2 parametros
       
    void randomize() {
      _index = 0;
        
      if (_list == 0) { _list = (int*) malloc(size() * sizeof(int)); }  
      for (int i=0; i<size(); i++) {   _list[i] = _min+i;  }   //preenche a lista do menor ao maior valor
         
      //embaralha a lista
      for (int i=0; i<size(); i++) {  
        int r = random(0, size());     //sorteia uma posição qualquer
        int aux = _list[i];               
        _list[i] = _list[r];
        _list[r] = aux;
      }
    }
       
    int next() {                                  //retorna o proximo numero da lista
      int n = _list[_index++];
      if (_index >= size() ) { _index = 0;} //após recuper o ultimo numero, recomeça na posicao 0
      return n;
    }
       
    int size() { return _size; }
       
    ~UniqueRandom(){ free ( _list ); }  //destrutor
};
/*************************************************************************************************************
************************************FIM CLASSE UNIQUE RANDOM**************************************************
*************************************************************************************************************/
 
/*************************************************************************************************************
*******************************CLASSE RACE GAME***************************************************************
**************************************************************************************************************/
struct Position {
  byte lin : 4;  //maximo 15
  byte col : 4;  //maximo 15, mas será no máximo 6 EM DISPLAY 16X2 E TERÁ 12 EM DISPLAY 20X4. PARA MAIS DE 15 LINHAS. ALTERAR AQUI
};

const int RACE_MAX_OBSTACLES = 30;  //quantidade maxima de obstáculos na tela
const int RACE_TIME_INIT = 500; //tempo entre deslocamento dos obstáculos
const int RACE_TIME_INC  = 15;  //incremento da velocidade

enum RaceGameStatus { RACE_GAME_ON, RACE_GAME_OVER };
enum Direction { DIR_STOP, DIR_TOP, DIR_LEFT, DIR_BOTTOM, DIR_RIGHT};

class RaceGame{
  private:
    BitArray2D * _display;
    Position _position;
    Position _obstacles_positions[RACE_MAX_OBSTACLES];
    Direction _direction;
    byte _lengthObstacles;
    byte _controlObstacle : 2;  //máximo = 3 B11
    byte _isCountdown : 1;      //apenas 1 ou 0   -------- contagem regressiva
    byte _countUpdate : 3;       //maximo = 5 B101
    byte _countdown : 3;       //ma´ximo = 5 B101
    unsigned long _last_millis;
    unsigned long _last_millis_car;
    unsigned long _millisCountdown;
    int _time;
    int _score;
    RaceGameStatus _raceGameStatus;
    UniqueRandom * _ur;  //utilizado no game over
    UniqueRandom * _urObstacles;  //utilizado no game over
    
    void _gameOver(){ 
      _raceGameStatus = RACE_GAME_OVER; 
      _direction   = DIR_STOP;
      _time = 20;
    }
      
    void _inc_speed(){
      //_length++; 
      _score++;
      _time -= RACE_TIME_INC;
    }
      
    void _runGameOver(){
      int r   = _ur->next();
      int lin = (r / _display->columns());
      int col = (r % _display->columns());
        
      _display->write(lin, col, HIGH );
        
      //if ( r>=(_ur->size()-1) || _direction != DIR_STOP ) {  
      if ( r>=(_ur->size()-1) ) {  
        _ur->randomize();
        start(); 
      }
    }
    
    void _generateObstacles() {  
      if (_controlObstacle == 0){
        _urObstacles->randomize();
        
        //quantidade de obstaculos estara entre 1 e quantidade de linhas -1
        int numObstacles = random(0, _display->rows()-4) + 4;   
        
        if (_lengthObstacles + numObstacles > RACE_MAX_OBSTACLES) {
          numObstacles = RACE_MAX_OBSTACLES - _lengthObstacles;
        }
        
        for (int i=0; i<numObstacles; i++){
          _lengthObstacles++;
          
          _obstacles_positions[_lengthObstacles-1].lin = _urObstacles->next();
          _obstacles_positions[_lengthObstacles-1].col = _display->columns()-1;
        }
      }
      _controlObstacle = (_controlObstacle < 3) ? _controlObstacle+1 : 0;  //a cada 4 atualizacoes gera novos obstáculos
    }
    
    void _runCountdown(){
      
      for (int lin=0; lin<_display->rows(); lin++) {
        for (int col=0; col<_display->columns(); col++) {
          _display->write( lin, col,  0 );
        }
      }
      
      unsigned long dif = millis() - _millisCountdown;
      
      if ( dif < 1000){
        _display->write( 1, 3,  1 );  //5
        _display->write( 1, 4,  1 );
        _display->write( 1, 5,  1 );
        _display->write( 2, 3,  1 );
        _display->write( 3, 3,  1 );
        _display->write( 3, 4,  1 );
        _display->write( 3, 5,  1 );
        _display->write( 4, 5,  1 );
        _display->write( 5, 5,  1 );
        _display->write( 5, 4,  1 );
        _display->write( 5, 3,  1 );
      } else if (dif < 2000) {
        _display->write( 1, 3,  1 );  //4
        _display->write( 1, 5,  1 );
        _display->write( 2, 3,  1 );
        _display->write( 2, 5,  1 );
        _display->write( 3, 3,  1 );
        _display->write( 3, 4,  1 );
        _display->write( 3, 5,  1 );
        _display->write( 4, 5,  1 );
        _display->write( 5, 5,  1 );
      } else if (dif < 3000) {
        _display->write( 1, 3,  1 );  //3
        _display->write( 1, 4,  1 );
        _display->write( 1, 5,  1 );
        _display->write( 2, 5,  1 );
        _display->write( 3, 3,  1 );
        _display->write( 3, 4,  1 );
        _display->write( 3, 5,  1 );
        _display->write( 4, 5,  1 );
        _display->write( 5, 5,  1 );
        _display->write( 5, 4,  1 );
        _display->write( 5, 3,  1 );
      } else if (dif < 4000) {
        _display->write( 1, 3,  1 );  //2
        _display->write( 1, 4,  1 );
        _display->write( 1, 5,  1 );
        _display->write( 2, 5,  1 );
        _display->write( 3, 3,  1 );
        _display->write( 3, 4,  1 );
        _display->write( 3, 5,  1 );
        _display->write( 4, 3,  1 );
        _display->write( 5, 5,  1 );
        _display->write( 5, 4,  1 );
        _display->write( 5, 3,  1 );
      } else if (dif < 5000) {
        _display->write( 1, 5,  1 );  //1
        _display->write( 2, 5,  1 );
        _display->write( 3, 5,  1 );
        _display->write( 4, 5,  1 );
        _display->write( 5, 5,  1 );
      } else {
        _isCountdown  = 0;
      }
      
    }
    
    void _runObastacles(){
      /*move os obstáculos*/
      byte contRemove = 0;
      for (int i=0; i<_lengthObstacles; i++) {
        if (_obstacles_positions[i].col == 0 ) { contRemove++;}  //B1111 --> linha inexistente no display. significa que será removido do array
        _obstacles_positions[i].col--;
      }
      
      if (contRemove > 0){
        for (int i=0; i<_lengthObstacles-contRemove; i++) {
          _obstacles_positions[i] = _obstacles_positions[i+contRemove];
        }
        _lengthObstacles -= contRemove; 
      }
        
      _generateObstacles();
      
      _countUpdate++;
      
      if (_countUpdate >= 5){
        _inc_speed();
        _countUpdate = 0;
      }
    }
    
    void _runCar() {
      if (_direction == DIR_TOP )    { _position.lin--;  }
      if (_direction == DIR_BOTTOM ) { _position.lin++;  }
      if (_direction == DIR_LEFT )   { _position.col--;  }
      if (_direction == DIR_RIGHT )  { _position.col++;  }
      
      _direction = DIR_STOP; //depois de mover, mantem parado.
      
      //verifica se ultrapassou o limite do display
      if (_position.lin < 0)                     { _gameOver(); }
      if (_position.lin >= _display->rows() )    { _gameOver(); }
      if (_position.col < 0)                     { _gameOver(); }
      if (_position.col >= _display->columns() ) { _gameOver(); }
        
      //verifica se colidiu nos obstáculos
      for (int i=0; i<_lengthObstacles; i++){
        if (_obstacles_positions[i].lin == _position.lin && _obstacles_positions[i].col == _position.col) {
          _gameOver();
        }  
      }
        
      //update display
      for (int lin=0; lin<_display->rows(); lin++) {
        for (int col=0; col<_display->columns(); col++) {
          for (int p=0; p<_lengthObstacles; p++){
            boolean val = _obstacles_positions[p].col==col && _obstacles_positions[p].lin==lin;
            _display->write( lin, col,  val );
            if (val) {break;}
          }
        }
      }
      _display->write(_position.lin, _position.col, HIGH);
    }
  public:
    RaceGame(BitArray2D * display){ 
      _display = display;
      _ur = new UniqueRandom( _display->rows() * _display->columns() );
      _urObstacles = new UniqueRandom( _display->rows() );
      start();
    }
      
    void start(){
      _score  = 0;
      _time = RACE_TIME_INIT;
      _last_millis = 0;
      _position.lin = _display->rows() / 2;
      _position.col = 0;
      _direction = DIR_STOP;
      _lengthObstacles = 0;
      _raceGameStatus = RACE_GAME_ON;
      _countUpdate = 0;
      _generateObstacles();
      _isCountdown = 1;
      _countdown = 5;
      _millisCountdown = millis();
      _last_millis_car = millis();
    }
      
    void left()   { _direction = DIR_LEFT;   }
    void right()  { _direction = DIR_RIGHT;  }
    void top()    { _direction = DIR_TOP;    }
    void bottom() { _direction = DIR_BOTTOM; }
      
    int getScore(){ return _score; }
     
     
    int update(){
      int r = false;
      
      if (_isCountdown){
        if (millis() - _last_millis_car > 20){
          _last_millis_car = millis();
          
          _runCountdown();
          return true;
        }
        
        
        return false;
        
      }
        
      if (millis() - _last_millis > _time) {
        r = true;
        _last_millis = millis();
        
        if (_raceGameStatus == RACE_GAME_ON)   { _runObastacles(); } 
        if (_raceGameStatus == RACE_GAME_OVER) { _runGameOver(); }
      }
      
      if (millis() - _last_millis_car > 20){
        r = true;
        _last_millis_car = millis();
        
        if (_raceGameStatus == RACE_GAME_ON)   { _runCar();         } 
        if (_raceGameStatus == RACE_GAME_OVER) { _runGameOver(); }
      }
        
      return r; //r-->indica se houve mudança no display
    }
};
  
/*************************************************************************************************************
*******************************FIM CLASSE RACE GAME***********************************************************
**************************************************************************************************************/
  
/*************************************************************************************************************
************************************CLASSE ROTARY ENCODER*****************************************************
mais informações: http://fabianoallex.blogspot.com.br/2016/05/arduino-rotary-encoder.html
*************************************************************************************************************/
#define ROTARY_NO_BUTTON     255
     
struct RotaryEncoderLimits{
  int min;
  int max;
};
     
class RotaryEncoder {
  private:
    byte _pin_clk;
    byte _pin_dt;
    byte _pin_sw;
    volatile byte _num_results;
    volatile int _result;
    volatile int * _results;
    byte _index_result;
    RotaryEncoderLimits * _limits;
         
    boolean _a;
    boolean _b;
  public:
    RotaryEncoder(byte pin_clk, byte pin_dt, byte pin_sw = ROTARY_NO_BUTTON, byte num_results=1, RotaryEncoderLimits * limits=0){   //parametro do botao opcional
      _pin_clk = pin_clk;
      _pin_dt = pin_dt;
      _pin_sw = pin_sw;
      pinMode(_pin_clk, INPUT);
      pinMode(_pin_dt, INPUT);
      if (_pin_sw != ROTARY_NO_BUTTON){ 
        pinMode(_pin_sw, INPUT); 
        digitalWrite(_pin_sw, HIGH);
      }
      if (num_results == 0) { num_results = 1; }
      _num_results = num_results;
      _results = new int[_num_results];
      for (int i; i<_num_results; i++){ _results[i] = (limits) ? limits[i].min : 0; }
      _index_result = 0;
      _limits = limits;
      _a = false;
      _b = false;
    }
    byte getIndex() { return _index_result; }
    void next()     { _index_result++; if (_index_result >= _num_results) { _index_result = 0; } } 
    void update_a() {
      _result = 0;
      delay (1);
      if( digitalRead(_pin_clk) != _a ) { 
        _a = !_a;
        if ( _a && !_b ) { _result = -1; }
      }
      if (_results[_index_result]+_result >= _limits[_index_result].min && 
          _results[_index_result]+_result <= _limits[_index_result].max ) {
        _results[_index_result] += _result;
      }
    }
    void update_b() {
      _result = 0;
      delay (1);
      if( digitalRead(_pin_dt) != _b ) {  
        _b = !_b;
        if ( _b && !_a ) { _result = +1; }
      }
      if (_results[_index_result]+_result >= _limits[_index_result].min && 
          _results[_index_result]+_result <= _limits[_index_result].max ) {
        _results[_index_result] += _result;
      }
    }
    int read(){ return _result; }                                        //retorn -1, 0 ou 1.
    int getValue(int index=-1) {                                         //retorna o valor da variável corrente ou a passada como parametro
      if (index < 0 ){ return _results[_index_result]; }
      return _results[index];
    }
    void setValue(int value, int index=-1){ _results[ (index==-1) ? _index_result : index] = value; }         //caso a variável inicializa em determinado valor diferente de zero, utilizar esse método.
    int buttonRead(){ return (_pin_sw == ROTARY_NO_BUTTON) ? LOW : digitalRead(_pin_sw); }
};
/*************************************************************************************************************
************************************FIM CLASSE ROTARY ENCODER*************************************************
*************************************************************************************************************/
   
   
/*************************************************************************************************************
*******************************CLASSE RACE GAME LCD***********************************************************
**************************************************************************************************************/
/*
  0        1        2        3        4        5        6
 ***                        ***      ***               ***                 
 ***                        ***      ***               ***          
                                                             
 ***      ***               ***               ***             
 ***      ***               ***               ***             
                                                               
 ***      ***      ***               ***                        
 ***      ***      ***               ***                            
*/
byte c0[8] = {B11111,  B11111,  B00000,  B11111,  B11111,  B00000,  B11111,  B11111 };
byte c1[8] = {B00000,  B00000,  B00000,  B11111,  B11111,  B00000,  B11111,  B11111 };
byte c2[8] = {B00000,  B00000,  B00000,  B00000,  B00000,  B00000,  B11111,  B11111 };
byte c3[8] = {B11111,  B11111,  B00000,  B11111,  B11111,  B00000,  B00000,  B00000 };
byte c4[8] = {B11111,  B11111,  B00000,  B00000,  B00000,  B00000,  B11111,  B11111 };
byte c5[8] = {B00000,  B00000,  B00000,  B11111,  B11111,  B00000,  B00000,  B00000 };
byte c6[8] = {B11111,  B11111,  B00000,  B00000,  B00000,  B00000,  B00000,  B00000 };
 
class RaceGameLCD {
  private:
    LiquidCrystal * _lcd;  //ponteiro para um objeto lcd
  public:
    void createChars() { 
      _lcd->createChar(0, c0);  
      _lcd->createChar(1, c1);  
      _lcd->createChar(2, c2); 
      _lcd->createChar(3, c3);  
      _lcd->createChar(4, c4);
      _lcd->createChar(5, c5);
      _lcd->createChar(6, c6);
    }
    RaceGameLCD(LiquidCrystal * lcd) {  _lcd = lcd;  }
     
    void write(byte col, byte row, byte val){
      _lcd->setCursor(col, row);
      if (val == B000) { _lcd->print(" ");  }
      if (val == B111) { _lcd->write((byte)0);  }
      if (val == B011) { _lcd->write((byte)1);  }
      if (val == B001) { _lcd->write((byte)2);  }
      if (val == B110) { _lcd->write((byte)3);  }
      if (val == B101) { _lcd->write((byte)4);  }
      if (val == B010) { _lcd->write((byte)5);  }
      if (val == B100) { _lcd->write((byte)6);  }
    }
};
/*************************************************************************************************************
*******************************FIM CLASSE RACE LCD***********************************************************
**************************************************************************************************************/
 
  
/*************************************************************************************************************
*******************************DECLARACAO DOS OBJETOS*********************************************************
**************************************************************************************************************/
 
/*
 
  tamanho display real
 
 * * * * * * * * * * * * * * * *
 * * * * * * * * * * * * * * * *
 * * * * * * * * * * * * * * * *
 * * * * * * * * * * * * * * * *
 * * * * * * * * * * * * * * * *
 * * * * * * * * * * * * * * * *
*/
 
const int LINHAS  = 6;
const int COLUNAS = 16;
  
LiquidCrystal lcd(12, 11, 10, 9, 8, 7);
RaceGameLCD raceGameLcd(&lcd);
  
BitArray2D ba(LINHAS, COLUNAS); //8 linhas e 20 colunas, usada para armazenar o estado do display
RaceGame race(&ba);
 
RotaryEncoderLimits lim[] = { {-1000,1000} };  //limites máximos e mínimos que as variaveis podem atingir
RotaryEncoder       re(A0, A1, 4, 1, lim);  //pino clk, pino dt, pino sw, variaveis, limites
 
/*************************************************************************************************************
*******************************FIM DECLARACAO DOS OBJETOS*****************************************************
**************************************************************************************************************/
   
/*************************************************************************************************************
*******************************TRATAMENTO DAS INTERRUPÇÕES****************************************************
**************************************************************************************************************/
//interrupções dos pinos A0 e A1 via Pin Change Interrupt
ISR(PCINT1_vect) {
  volatile static byte lastVal_a0 = LOW;
  volatile static byte lastVal_a1 = LOW;
  byte val_a0 = digitalRead(A0);
  byte val_a1 = digitalRead(A1);
  if (lastVal_a0 != val_a0){ re.update_a(); lastVal_a0 = val_a0; }
  if (lastVal_a1 != val_a1){ re.update_b(); lastVal_a1 = val_a1; }
}
  
void setup_interrupts(){
  //-----PCI - Pin Change Interrupt ----
  pinMode(A0,INPUT);   // set Pin as Input (default)
  digitalWrite(A0,HIGH);  // enable pullup resistor
  pinMode(A1,INPUT);   // set Pin as Input (default)
  digitalWrite(A1,HIGH);  // enable pullup resistor
  cli();
  PCICR |= 0b00000010; // habilita a porta C - Pin Change Interrupts
  PCMSK1 |= 0b00000011; // habilita interrupção da porta c nos pinos: PCINT8 (A0) e PCINT9(A1)
  sei();
}
  
/*************************************************************************************************************
*******************************FIM TRATAMENTO DAS INTERRUPÇÕES****************************************************
**************************************************************************************************************/
    
void update_display() {
  for (int col=0; col<ba.columns(); col++) {
    byte lin_lcd = 0;
    byte cont = 0;
    byte val = 0;
     
    for (int lin=0; lin<ba.rows(); lin++) {
      if (cont == 0){ val = 0; }
      val = val << 1; 
      val = val | ba.read(lin, col);
      cont++;
      if (cont == 3) {
        raceGameLcd.write(col, lin_lcd, val) ;
        cont = 0;
        lin_lcd++;
      }
    }
  }
}
    
void setup() { 
  setup_interrupts();
  lcd.begin(16, 2);
  raceGameLcd.createChars();
  randomSeed(analogRead(A2));
  re.setValue(0, 0);  //inicializa o rotary em 0
}
    
void loop() {
  static int val_encoder = 0;
  static boolean change = false;
   
  if (re.getValue(0) < val_encoder && !change){ 
    race.bottom();
     
    change = true;
  }
   
  if (re.getValue(0) > val_encoder && !change ){
    race.top();
     
    change = true;
  }  
   
  val_encoder = re.getValue(0);
  if ( race.update() ) { 
    change = false;
    update_display(); 
  }  
   
  //controla o click do botao do enconder
  static byte b = HIGH; //pra ler apenas uma vez o botao ao pressionar
  if( re.buttonRead() == LOW && b != re.buttonRead() ) {
    re.next();           //passa para a próxima variável (index)
    delay(200);          //debounce meia boca
  }
  b = re.buttonRead();
} 

Nenhum comentário:

Postar um comentário