Publicidade:

quarta-feira, 14 de outubro de 2015

Arduino - LCD Progress Bar - Barra de Progresso

Eventualmente é necessário, em algumas aplicações, mostrar o progresso de determinado processamento, ou valores que variam dentro de um intervalo, como a leitura de potenciômetros ou coisas do gênero. Nesses casos é bem comum o uso de displays LCD.

Pra facilitar a inclusão de barras de progresso em displays LCD, criei uma classe chamada LCDProgressBar, que com poucas linhas é possível incluir uma ou mais barras de progresso, que podem, inclusive, serem mostradas ao mesmo tempo.

A ideia foi criar uma barra customizável, onde o programador indica a posição (linha e coluna) no display e o tamanho que a barra de progresso terá.

A Classe possui um método chamado setPerc(), o qual irá receber um valor que deverá estar entre 0 e 100. A barra será gerada de acordo com o valor passado como parâmetro, sendo que em 0 a barra não aparece e em 100 ela é completamente preenchida.


Vídeo da primeira versão:



vídeo da segunda versão:



código da primeira versão:

/*
Fabiano A. Arndt - 2015
www.youtube.com/user/fabianoallex
www.facebook.com/dicasarduino
fabianoallex@gmail.com
*/

#include <LiquidCrystal.h>

/*************************************************************************************************************
*******************************CLASSE LCD PROGRESS BAR********************************************************
**************************************************************************************************************/
byte c1[8] = {B10000,  B10000,  B10000,  B10000,  B10000,  B10000,  B10000,  B10000};
byte c2[8] = {B11000,  B11000,  B11000,  B11000,  B11000,  B11000,  B11000,  B11000};
byte c3[8] = {B11100,  B11100,  B11100,  B11100,  B11100,  B11100,  B11100,  B11100};
byte c4[8] = {B11110,  B11110,  B11110,  B11110,  B11110,  B11110,  B11110,  B11110};
byte c5[8] = {B11111,  B11111,  B11111,  B11111,  B11111,  B11111,  B11111,  B11111};

class LCDProgressBar {
  private:
    LiquidCrystal * _lcd;
    int _row;
    int _col;
    int _len;
    int _perc; /*0..100*/
  public:
    void createChars() {
      _lcd->createChar(0, c1);
      _lcd->createChar(1, c2);
      _lcd->createChar(2, c3);
      _lcd->createChar(3, c4);
      _lcd->createChar(4, c5);
    }
  
    LCDProgressBar(LiquidCrystal * lcd, int row, int col, int len) {
      _lcd = lcd;      _row = row;      _col = col;      _len = len;
    }
    
    void setPerc(int perc){
      _perc = perc;
      if (perc > 100) { _perc = 100; }
      if (perc < 000) { _perc = 000; }
      
      _lcd->setCursor(_col, _row);
      for (int i=0; i<(_len);i++) { _lcd->print(" "); }
      _lcd->setCursor(_col, _row);
      
      int bars  = 5 * _len * _perc / 100;
      int div   = bars / 5;  //divisao
      int resto = bars % 5;  //resto
      for (int i=0; i<div; i++)  { _lcd->write((byte)4);         }  //pinta todo o quadro
      if (resto > 0 )            { _lcd->write((byte)(resto-1)); }  //pinta o quadro com a quantidade de barras proporcional
    }
};
/*************************************************************************************************************
*******************************FIM CLASSE LCD PROGRESS BAR****************************************************
**************************************************************************************************************/


LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
LCDProgressBar lcdBar1(&lcd, 0, 8,  8); //inclui uma barra no lcd, primeira linha, coluna 8. tamanho 8
LCDProgressBar lcdBar2(&lcd, 1, 12, 4); //inclui outra barra no lcd, segunda linha, coluna 12. tamanho 4

void setup()   {
  Serial.begin(9600);
  lcdBar1.createChars();
  pinMode(44, OUTPUT);
  analogWrite(44, 255/6); //utilizado para aumentar o contraste
  lcd.begin(16, 2);
}

int i = 0;
int perc;

void loop() {
  lcd.setCursor(0, 0);
  int value = i % 100;
  perc = value/100.0 * 100;
  if (value < 010) {lcd.print("00");} else {
  if (value < 100) {lcd.print("0");}       }
  lcd.print(value);
  lcd.print("/");
  lcd.print(100);
  
  lcdBar1.setPerc(perc);  //atualização da primeira barra de progresso
  
  
  lcd.setCursor(0, 1);
  value = (i++) % 200;
  perc = value/200.0 * 100;
  if (value <= 010) {lcd.print("00");} else {
  if (value <  100) {lcd.print("0");}       }
  lcd.print(value);
  lcd.print("/");
  lcd.print(200);
  
  lcdBar2.setPerc(perc); //atualização da segunda barra de progresso
  
  delay(100);  
}




Código 01 da segunda versão :


/*
Fabiano A. Arndt - 2015
www.youtube.com/user/fabianoallex
www.facebook.com/dicasarduino
fabianoallex@gmail.com
*/
 
#include <LiquidCrystal.h>
 
/*************************************************************************************************************
*******************************CLASSE LCD PROGRESS BAR********************************************************
**************************************************************************************************************/
byte c1[8] = {B00000, B00000, B00000, B00000, B00000, B00000, B00000, B11111};
byte c2[8] = {B00000, B00000, B00000, B00000, B00000, B00000, B11111, B11111};
byte c3[8] = {B00000, B00000, B00000, B00000, B00000, B11111, B11111, B11111};
byte c4[8] = {B00000, B00000, B00000, B00000, B11111, B11111, B11111, B11111};
byte c5[8] = {B00000, B00000, B00000, B11111, B11111, B11111, B11111, B11111};
byte c6[8] = {B00000, B00000, B11111, B11111, B11111, B11111, B11111, B11111};
byte c7[8] = {B00000, B11111, B11111, B11111, B11111, B11111, B11111, B11111};
byte c8[8] = {B11111, B11111, B11111, B11111, B11111, B11111, B11111, B11111};
             
 
class LCDProgressBar {
  private:
    LiquidCrystal * _lcd;
    int _row;
    int _col;
    int _len;
    int _perc; /*0..100*/
  public:
    void createChars() {
      _lcd->createChar(0, c1);
      _lcd->createChar(1, c2);
      _lcd->createChar(2, c3);
      _lcd->createChar(3, c4);
      _lcd->createChar(4, c5);
      _lcd->createChar(5, c6);
      _lcd->createChar(6, c7);
      _lcd->createChar(7, c8);
    }
   
    LCDProgressBar(LiquidCrystal * lcd, int row, int col, int len) {
      _lcd = lcd;      _row = row;      _col = col;      _len = len;
    }
     
    void setPerc(int perc){
      _perc = perc;
      if (perc > 100) { _perc = 100; }
      if (perc < 000) { _perc = 000; }
       
      _lcd->setCursor(_col, _row);
      for (int i=0; i<(_len);i++) { _lcd->print(" "); }
      _lcd->setCursor(_col, _row);
       
      int bars  = (8+1) * _len * _perc / 100;
      int div   = bars / 8;  //divisao
      int resto = bars % 8;  //resto
      for (int i=0; i<div; i++)  { _lcd->write((byte)7);         }  //pinta todo o quadro
      if (resto > 0 )            { _lcd->write((byte)(resto-1)); }  //pinta o quadro com a quantidade de barras proporcional
    }
};
/*************************************************************************************************************
*******************************FIM CLASSE LCD PROGRESS BAR****************************************************
**************************************************************************************************************/
 
 
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
LCDProgressBar lcdBar1(&lcd, 0, 8,  8); //inclui uma barra no lcd, primeira linha, coluna 8. tamanho 8
LCDProgressBar lcdBar2(&lcd, 1, 12, 4); //inclui outra barra no lcd, segunda linha, coluna 12. tamanho 4
 
void setup()   {
  Serial.begin(9600);
  lcdBar1.createChars();
  pinMode(44, OUTPUT);
  analogWrite(44, 255/6); //utilizado para aumentar o contraste
  lcd.begin(16, 2);
}
 
int i = 0;
int perc;
 
void loop() {
  lcd.setCursor(0, 0);
  int value = i % 100;
  perc = value/100.0 * 100;
  if (value < 010) {lcd.print("00");} else {
  if (value < 100) {lcd.print("0");}       }
  lcd.print(value);
  lcd.print("/");
  lcd.print(100);
   
  lcdBar1.setPerc(perc);  //atualização da primeira barra de progresso
   
   
  lcd.setCursor(0, 1);
  value = (i++) % 200;
  perc = value/200.0 * 100;
  if (value <= 010) {lcd.print("00");} else {
  if (value <  100) {lcd.print("0");}       }
  lcd.print(value);
  lcd.print("/");
  lcd.print(200);
   
  lcdBar2.setPerc(perc); //atualização da segunda barra de progresso
   
  delay(100);  
}



Código 02 da segunda versão :


/*
Fabiano A. Arndt - 2015
www.youtube.com/user/fabianoallex
www.facebook.com/dicasarduino
fabianoallex@gmail.com
*/
 
#include <LiquidCrystal.h>
 
/*************************************************************************************************************
*******************************CLASSE LCD PROGRESS BAR********************************************************
**************************************************************************************************************/
byte c1[8] = {B00000, B00000, B00000, B00000, B00000, B00000, B00000, B11111};
byte c2[8] = {B00000, B00000, B00000, B00000, B00000, B00000, B11111, B11111};
byte c3[8] = {B00000, B00000, B00000, B00000, B00000, B11111, B11111, B11111};
byte c4[8] = {B00000, B00000, B00000, B00000, B11111, B11111, B11111, B11111};
byte c5[8] = {B00000, B00000, B00000, B11111, B11111, B11111, B11111, B11111};
byte c6[8] = {B00000, B00000, B11111, B11111, B11111, B11111, B11111, B11111};
byte c7[8] = {B00000, B11111, B11111, B11111, B11111, B11111, B11111, B11111};
byte c8[8] = {B11111, B11111, B11111, B11111, B11111, B11111, B11111, B11111};
             
 
class LCDProgressBar {
  private:
    LiquidCrystal * _lcd;
    int _row;
    int _col;
    int _len;
    int _perc; /*0..100*/
  public:
    void createChars() {
      _lcd->createChar(0, c1);
      _lcd->createChar(1, c2);
      _lcd->createChar(2, c3);
      _lcd->createChar(3, c4);
      _lcd->createChar(4, c5);
      _lcd->createChar(5, c6);
      _lcd->createChar(6, c7);
      _lcd->createChar(7, c8);
    }
   
    LCDProgressBar(LiquidCrystal * lcd, int row, int col, int len) {
      _lcd = lcd;      _row = row;      _col = col;      _len = len;
    }
     
    void setPerc(int perc){
      _perc = perc;
      if (perc > 100) { _perc = 100; }
      if (perc < 000) { _perc = 000; }
       
      _lcd->setCursor(_col, _row);
      for (int i=0; i<(_len);i++) { _lcd->print(" "); }
      _lcd->setCursor(_col, _row);
      
      int bars  = (8+1) * _len * _perc / 100.0;
      int div   = bars / 8.0;  //divisao
      int resto = bars % 8;  //resto
      Serial.println(div);
      for (int i=0; i<div; i++)  { _lcd->write((byte)7);         }  //pinta todo o quadro
      if (resto > 0 )            { _lcd->write((byte)(resto-1)); }  //pinta o quadro com a quantidade de barras proporcional
    }
};
/*************************************************************************************************************
*******************************FIM CLASSE LCD PROGRESS BAR****************************************************
**************************************************************************************************************/
 
 
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
LCDProgressBar lcdBar1(&lcd, 1, 0,  1); //inclui uma barra no lcd, segunda linha, coluna 0. tamanho 1
LCDProgressBar lcdBar2(&lcd, 1, 2, 1); //inclui outra barra no lcd, segunda linha, coluna 2. tamanho 1
LCDProgressBar lcdBar3(&lcd, 1, 4, 1); //inclui outra barra no lcd, segunda linha, coluna 4. tamanho 1
LCDProgressBar lcdBar4(&lcd, 1, 6, 1); //inclui outra barra no lcd, segunda linha, coluna 6. tamanho 1
LCDProgressBar lcdBar5(&lcd, 1, 8, 1); //inclui outra barra no lcd, segunda linha, coluna 8. tamanho 1
LCDProgressBar lcdBar6(&lcd, 1, 10, 1); //inclui outra barra no lcd, segunda linha, coluna 10. tamanho 1
LCDProgressBar lcdBar7(&lcd, 1, 12, 1); //inclui outra barra no lcd, segunda linha, coluna 12. tamanho 1
LCDProgressBar lcdBar8(&lcd, 1, 14, 1); //inclui outra barra no lcd, segunda linha, coluna 14. tamanho 1

 
void setup()   {
  Serial.begin(9600);
  lcdBar1.createChars();
  pinMode(44, OUTPUT);
  analogWrite(44, 255/6); //utilizado para aumentar o contraste
  lcd.begin(16, 2);
}
 
unsigned int i = 0;
int perc;
 
void loop() {
  lcd.setCursor(0, 0);
  lcd.print("1 2 3 4 5 6 7 8");
  lcd.setCursor(0, 1);
  
  int value = i % 100;
  perc = value/100.0 * 100;
  lcdBar1.setPerc(perc);  //atualização da primeira barra de progresso
  
  
  value = i % 10;
  perc = value/10.0 * 100;
  lcdBar2.setPerc(perc);  //atualização da segunda barra de progresso
  
  
  value = i % 50;
  perc = value/50.0 * 100;
  lcdBar3.setPerc(perc);  //atualização da terceira barra de progresso
  
  
  value = i % 300;
  perc = value/300.0 * 100;
  lcdBar4.setPerc(perc);  //atualização da quarta barra de progresso
   
  
  value = i % 240;
  perc = value/240.0 * 100;
  lcdBar5.setPerc(perc);  //atualização da quinta barra de progresso
  
  
  value = i % 140;
  perc = value/140.0 * 100;
  lcdBar6.setPerc(perc);  //atualização da sexta barra de progresso
  
  
  value = i % 30;
  perc = value/30.0 * 100;
  lcdBar7.setPerc(perc);  //atualização da setima barra de progresso
  
  
  value = (i++) % 180;
  perc = value/180.0 * 100;
  lcdBar8.setPerc(perc); //atualização da oitava barra de progresso
   
  delay(100);  
}


Atualizado 25/05/2016

Em um artigo sobre Rotary Encoder fiz alguns exemplos utilizando as barras de progresso e fiz algumas modificações em uma das barras mostradas aqui.

Veja o artigo aqui: http://fabianoallex.blogspot.com.br/2016/05/arduino-rotary-encoder.html

Vídeo:



Atualizado 27/05/2016

Implementei outra versão das barras de progresso, ela segue o padrão utilizado para mostrar o volume de um som ou a intensidade de um sinal sem fio.

Há duas possibilidades de uso, com altura=1 ou altura=2, que indicará se a barra ocupará uma ou duas linhas do display.

Vídeo:


Código-fonte:


/*
Fabiano A. Arndt - 2016
www.youtube.com/user/fabianoallex
www.facebook.com/dicasarduino
fabianoallex@gmail.com
*/
  
#include <LiquidCrystal.h>
 
/*************************************************************************************************************
************************************CLASSE ROTARY ENCODER*****************************************************
*************************************************************************************************************/
#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){ _results[_index_result] = 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 LCD PROGRESS BAR********************************************************
**************************************************************************************************************/
byte c0[8] = {B00000,  B00000,  B00000,  B00000,  B00000,  B00000,  B00000,  B10000 };
byte c1[8] = {B00000,  B00000,  B00000,  B00000,  B00000,  B00000,  B11000,  B11000 };
byte c2[8] = {B00000,  B00000,  B00000,  B00000,  B00011,  B00011,  B11011,  B11011 };
byte c3[8] = {B00000,  B00000,  B11000,  B11000,  B11000,  B11000,  B11000,  B11000 };
byte c4[8] = {B00011,  B00011,  B11011,  B11011,  B11011,  B11011,  B11011,  B11011 };
byte c5[8] = {B11000,  B11000,  B11000,  B11000,  B11000,  B11000,  B11000,  B11000 };
byte c6[8] = {B11011,  B11011,  B11011,  B11011,  B11011,  B11011,  B11011,  B11011 };
  
class LCDProgressBar {
  private:
    LiquidCrystal * _lcd;  //ponteiro para um objeto lcd
    int _row;
    int _col;
    int _perc; /*0..100*/
    int _heigh;
  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);  }
    LCDProgressBar(LiquidCrystal * lcd, int row, int col, int heigh=1) { 
      _lcd = lcd; 
      _row = row; 
      _col = col; 
      _heigh = (heigh >= 2 && _row > 0) ? 2 : 1;   //heigh assume apenas os valores 2 e 1. e só assume valor = 2 quando estiver posicionado a partir da segunda linha
    }
    void setPerc(int perc) {
      int division = (_heigh == 2) ? 33 / 2 : 33;
      _perc = perc;
      if (perc > 100) { _perc = 100; }
      if (perc < 000) { _perc = 000; } 
      if (_heigh == 2){
        _lcd->setCursor(_col+2, _row-1);
        _lcd->print("  ");
        _lcd->setCursor(_col+2, _row-1);
      }
      _lcd->setCursor(_col, _row);
      _lcd->print((_heigh == 2) ? "    " : "  ");
      _lcd->setCursor(_col, _row);
      if (_perc == 0) {  _lcd->write((byte)0); } else {
        if (_perc > 0 && _perc <= division) { 
          _lcd->write((byte)1); 
        } else { 
          _lcd->write((byte)2); 
          if (_perc > division*2 && _perc < 100/_heigh) { _lcd->write((byte)3); } 
          if (_perc >= 100/_heigh)                      { _lcd->write((byte)4);
          }
        }
      }
      if (_heigh == 2 && _perc > 50){
        if (_perc > 50 && _perc <= (50+division)) { 
          _lcd->write((byte)5); 
        } else { 
          _lcd->write((byte)6); 
          if (_perc > (50+division*2) && _perc < 100) { _lcd->write((byte)5); } 
          if (_perc == 100       )                    { _lcd->write((byte)6); }
        }
        _lcd->setCursor(_col+2, _row-1);
        _lcd->print("  ");
        _lcd->setCursor(_col+2, _row-1);
        if (_perc > 50 && _perc <= (50+division)) { 
          _lcd->write((byte)1); 
        } else { 
          _lcd->write((byte)2); 
          if (_perc > (50+division*2) && _perc < 100 ) { _lcd->write((byte)3); } 
          if (_perc >= 100)                            { _lcd->write((byte)4); }
        }
      }
    }
};
/*************************************************************************************************************
*******************************FIM CLASSE LCD PROGRESS BAR****************************************************
**************************************************************************************************************/

/*************************************************************************************************************
*******************************DECLARACAO DOS OBJETOS*********************************************************
**************************************************************************************************************/
LiquidCrystal lcd(12, 11, 10, 9, 8, 7);
LCDProgressBar lcdBar1(&lcd, 0, 9, 1); //inclui uma barra no lcd, primeira linha, coluna 9. altura 1
LCDProgressBar lcdBar2(&lcd, 1, 11, 2); //inclui outra barra no lcd, segunda linha, coluna 11. altura 2
 
RotaryEncoderLimits lim[] = { {0,30}, {0,20} };  //limites máximos e mínimos que as variaveis podem atingir
RotaryEncoder       re(A0, A1, 4, 2, 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 indicador_rotary(){
  char c = ((re.getIndex() == 0)) ? '>' : ' ';
  lcd.setCursor(7, 0); 
  lcd.print(c);
  c = ((re.getIndex() == 1)) ? '>' : ' ';
  lcd.setCursor(7, 1); 
  lcd.print(c);
}
  
void setup() { 
  setup_interrupts();
  
  lcdBar1.createChars();
  lcd.begin(16, 2);
  indicador_rotary();
}
  
void loop() {
  static int value1 = -1;
  static int value2 = -1;
   
  if (value1 != re.getValue(0)) {
    lcd.setCursor(0, 0);
    value1 = re.getValue(0);
    int perc = value1/30.0 * 100;
    if (value1 <  10) {lcd.print("00");} else {
    if (value1 < 100) {lcd.print("0");}       }
    lcd.print(value1);
    lcd.print("/");
    lcd.print(30);
 
    lcdBar1.setPerc(perc);  //atualização da primeira barra de progresso
  }
    
  if (value2 != re.getValue(1)) {
    lcd.setCursor(0, 1);
    value2 = re.getValue(1);
    int perc = value2/20.0 * 100;
    if (value2 <  10) {lcd.print("00");} else {
    if (value2 < 100) {lcd.print("0");}       }
    lcd.print(value2);
    lcd.print("/");
    lcd.print(20);
     
    lcdBar2.setPerc(perc); //atualização da segunda barra de progresso
  }
    
  //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)
    indicador_rotary();
    delay(200);          //debounce meia boca
  }
  b = re.buttonRead();
    
  delay(100);
}

e nessa outra versão, uma barra que permite informar valores que variam de um número negativo até um número positivo:

vídeo



código-fonte:
/*
Fabiano A. Arndt - 2016
www.youtube.com/user/fabianoallex
www.facebook.com/dicasarduino
fabianoallex@gmail.com
*/
  
#include <LiquidCrystal.h>
 
/*************************************************************************************************************
************************************CLASSE ROTARY ENCODER*****************************************************
*************************************************************************************************************/
#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 LCD PROGRESS BAR********************************************************
**************************************************************************************************************/
/*
  0        1        2        3        4
** **    ** **    ** **    ** **    ** **
           *      **          **    ** **
           *      **          **    ** **
           *      **          **    ** **
           *      **          **    ** **
           *      **          **    ** **
           *      **          **    ** **
** **    ** **    ** **    ** **    ** **

*/
byte c0[8] = {B11011,  B00000,  B00000,  B00000,  B00000,  B00000,  B00000,  B11011 };
byte c1[8] = {B11011,  B00000,  B00000,  B00100,  B00100,  B00000,  B00000,  B11011 };
byte c2[8] = {B11011,  B11000,  B11000,  B11000,  B11000,  B11000,  B11000,  B11011 };
byte c3[8] = {B11011,  B00011,  B00011,  B00011,  B00011,  B00011,  B00011,  B11011 };
byte c4[8] = {B11011,  B11011,  B11011,  B11011,  B11011,  B11011,  B11011,  B11011 };

  
class LCDProgressBar {
  private:
    LiquidCrystal * _lcd;  //ponteiro para um objeto lcd
    int _row;
    int _col;
    int _perc; /*0..100*/
    int _len;
  public:
    void createChars() { 
      _lcd->createChar(0, c0);  
      _lcd->createChar(1, c1);  
      _lcd->createChar(2, c2); 
      _lcd->createChar(3, c3);  
      _lcd->createChar(4, c4);
    }
    LCDProgressBar(LiquidCrystal * lcd, int row, int col, int len) { 
      _lcd = lcd; 
      _row = row; 
      _col = col; 
      _len = len; 
    }
    void setPerc(int perc) {
      _perc = perc;
      if (_perc >  100) { _perc =  100; }
      if (_perc < -100) { _perc = -100; } 
      
      _lcd->setCursor(_col, _row);
      for (int i=0; i<(_len*2-1);i++) { _lcd->write((byte)0); }   //preenche com caracteres vazio
      _lcd->setCursor(_col + (_len-1), _row);
      _lcd->write((byte)1);                                 //preenche com caracter zero (nenhum valor)
      
      if (_perc > 0){
        _lcd->setCursor(_col + (_len-1), _row); 
        int bars  = (2*(_len-1)+1) * _perc / 100;  
        if (bars >= 1) { _lcd->write((byte)3); }
        int div   = (bars-1) / 2;  //divisao
        int resto = (bars-1) % 2;  //resto
        for (int i=0; i<div; i++) { _lcd->write((byte)4);         }  //pinta todo o quadro
        if (resto > 0)            { _lcd->write((byte)2); }
      } else if (_perc < 0) {
        _lcd->setCursor(_col + (_len-1), _row); 
        int bars  = (2*(_len-1)+1) * (-_perc) / 100;  
        if (bars >= 1) { _lcd->write((byte)2); }
        int div   = (bars-1) / 2;  //divisao
        int resto = (bars-1) % 2;  //resto
        int i = 0;
        for (i=0; i<div; i++) {
          _lcd->setCursor(_col + _len/2-i, _row); 
          _lcd->write((byte)4); 
        }
        if (resto > 0) { 
          _lcd->setCursor(_col + _len/2-i, _row); 
          _lcd->write((byte)3); 
        }
      }
    }
};
/*************************************************************************************************************
*******************************FIM CLASSE LCD PROGRESS BAR****************************************************
**************************************************************************************************************/

/*************************************************************************************************************
*******************************DECLARACAO DOS OBJETOS*********************************************************
**************************************************************************************************************/
LiquidCrystal lcd(12, 11, 10, 9, 8, 7);
LCDProgressBar lcdBar1(&lcd, 0, 9, 3); //inclui uma barra no lcd, primeira linha, coluna 9. tamanho 3 (3*2-1 --> 5)
LCDProgressBar lcdBar2(&lcd, 1, 9, 4); //inclui outra barra no lcd, segunda linha, coluna 11. tamanho 4 (4*2-1 --> 7)
 
RotaryEncoderLimits lim[] = { {-30,30}, {-20,20} };  //limites máximos e mínimos que as variaveis podem atingir
RotaryEncoder       re(A0, A1, 4, 2, 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 indicador_rotary(){
  char c = ((re.getIndex() == 0)) ? '>' : ' ';
  lcd.setCursor(7, 0); 
  lcd.print(c);
  c = ((re.getIndex() == 1)) ? '>' : ' ';
  lcd.setCursor(7, 1); 
  lcd.print(c);
}
  
void setup() { 
  setup_interrupts();
  
  lcdBar1.createChars();
  lcd.begin(16, 2);
  
  re.setValue(0, 0);
  re.setValue(0, 1);
  
  indicador_rotary();
}
  
void loop() {
  static int value1 = -1;
  static int value2 = -1;
   
  if (value1 != re.getValue(0)) {
    lcd.setCursor(0, 0);
    value1 = re.getValue(0);
    int perc = value1/30.0 * 100;
    lcd.print("       ");
    lcd.setCursor(0, 0);
    lcd.print(value1);
    lcd.print("/");
    lcd.print(30);
 
    lcdBar1.setPerc(perc);  //atualização da primeira barra de progresso
  }
    
  if (value2 != re.getValue(1)) {
    lcd.setCursor(0, 1);
    value2 = re.getValue(1);
    int perc = value2/20.0 * 100;
    lcd.print("       ");
    lcd.setCursor(0, 1);
    lcd.print(value2);
    lcd.print("/");
    lcd.print(20);
     
    lcdBar2.setPerc(perc); //atualização da segunda barra de progresso
  }
    
  //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)
    indicador_rotary();
    delay(200);          //debounce meia boca
  }
  b = re.buttonRead();
    
  delay(100);
}

terça-feira, 13 de outubro de 2015

Arduino - Snake Game (jogo da cobrinha)

Implementação do jogo Snake Game (jogo da cobrinha)

Para o teclado foi utilizado a serial, através de uma arquivo .bat

Por enquanto as explicações estão no vídeo.






Arquivo .bat

MODE COM6 BAUD=9600 PARITY=n DATA=8

:LOOP
  
  CHOICE /C:1235 /M "1: liga; 2: desliga; 3: pisca; 4: sair " 
  IF errorlevel 4 GOTO TOP
  IF errorlevel 3 GOTO RIGHT
  IF errorlevel 2 GOTO BOTTOM
  IF errorlevel 1 GOTO LEFT

  :RIGHT  
  ECHO b > COM6 
  GOTO END

  :LEFT
  ECHO a > COM6  
  GOTO END

  :TOP
  ECHO c > COM6  
  GOTO END

  :BOTTOM
  ECHO d > COM6  
  
  :END
  CLS
GOTO LOOP 

:SAIR




Código-Fonte:

/*************************************************************************************************************
*******************************LEDCONTROL ALTERADA************************************************************
**************************************************************************************************************
mais informações aqui: http://fabianoallex.blogspot.com.br/2015/09/arduino-alteracoes-na-biblioteca.html
**************************************************************************************************************/

//the opcodes for the MAX7221 and MAX7219
#define OP_DECODEMODE  9
#define OP_INTENSITY   10
#define OP_SCANLIMIT   11
#define OP_SHUTDOWN    12
#define OP_DISPLAYTEST 15

class LedControl {
  private :
    byte spidata[16];
    byte * status;
    int SPI_MOSI;
    int SPI_CLK;
    int SPI_CS;
    int maxDevices;
    int _auto_send;
    
    void spiTransfer(int addr, volatile byte opcode, volatile byte data) {
      int offset   = addr*2;
      int maxbytes = maxDevices*2;
      for(int i=0;i<maxbytes;i++)  { spidata[i]=(byte)0; }
      spidata[offset+1] = opcode;
      spidata[offset]   = data;
      
      digitalWrite(SPI_CS,LOW);
      for(int i=maxbytes;i>0;i--) { shiftOut(SPI_MOSI,SPI_CLK,MSBFIRST,spidata[i-1]); }
      digitalWrite(SPI_CS,HIGH);
    }
      
  public:
    LedControl(int dataPin, int clkPin, int csPin, int numDevices) {
      _auto_send  = true;
      SPI_MOSI    = dataPin;
      SPI_CLK     = clkPin;
      SPI_CS      = csPin;
      maxDevices  = numDevices;
      
      pinMode(SPI_MOSI, OUTPUT);
      pinMode(SPI_CLK,  OUTPUT);
      pinMode(SPI_CS,   OUTPUT);
      digitalWrite(SPI_CS, HIGH);
      
      status      = new byte[maxDevices * 8]; //instancia o array de acordo com a quantia de displays usados
      for(int i=0;i<maxDevices * 8 ;i++) { status[i]=0x00; }
      
      for(int i=0;i<maxDevices;i++) {
        spiTransfer(i, OP_DISPLAYTEST,0);
        setScanLimit(i, 7);               //scanlimit is set to max on startup
        spiTransfer(i, OP_DECODEMODE,0);  //decode is done in source
        clearDisplay(i);
        shutdown(i,true);                 //we go into shutdown-mode on startup
      }
    }
    
    void startWrite() {  _auto_send = false;  };
    
    void send() {
      for (int j=0; j<maxDevices; j++) {
        int offset = j*8;
        for(int i=0;i<8;i++) { spiTransfer(j, i+1, status[offset+i]); }
      }
      _auto_send = true;
    }
    
    int getDeviceCount(){ return maxDevices; }
    
    void shutdown(int addr, bool b){
      if(addr<0 || addr>=maxDevices) return;
      spiTransfer(addr, OP_SHUTDOWN, b ? 0 : 1);
    }
    
    void setScanLimit(int addr, int limit){
      if(addr<0 || addr>=maxDevices) return;
      if(limit>=0 && limit<8) spiTransfer(addr, OP_SCANLIMIT,limit);
    }
    
    void setIntensity(int addr, int intensity) {
      if(addr<0 || addr>=maxDevices)   {  return;                                    }
      if(intensity>=0 && intensity<16) {  spiTransfer(addr, OP_INTENSITY, intensity); }
    }
    
    void clearDisplay(int addr){
      if(addr<0 || addr>=maxDevices) return;
      
      int offset = addr*8;
      for(int i=0;i<8;i++) {
        status[offset+i] = 0;
        if (_auto_send) { spiTransfer(addr, i+1, status[offset+i]); }
      }
    }
    
    void setLed(int addr, int row, int column, boolean state) {
      if(addr<0 || addr>=maxDevices)             { return; }
      if(row<0 || row>7 || column<0 || column>7) { return; }
      
      int offset = addr*8;
      byte val = B10000000 >> column;
      
      if(state) { status[offset+row] = status[offset+row] | val; }
      else {
        val=~val;
        status[offset+row] = status[offset+row]&val;
      }
      
      if (_auto_send) { spiTransfer(addr, row+1, status[offset+row]); }
    }
    
    void setRow(int addr, int row, byte value) {
      if(addr<0 || addr>=maxDevices) return;
      if(row<0 || row>7) return;
      int offset = addr*8;
      status[offset+row] = value;
      if (_auto_send) {
        spiTransfer(addr, row+1, status[offset+row]);
      }
    }
    
    void setColumn(int addr, int col, byte value) {
      if(addr<0 || addr>=maxDevices) return;
      if(col<0 || col>7)             return;
      
      byte val;
      for(int row=0; row<8; row++) {
        val=value >> (7-row);
        val=val & 0x01;
        setLed(addr,row,col,val);
      }
    }
};
/*************************************************************************************************************
*******************************FIM LEDCONTROL ALTERADA********************************************************
**************************************************************************************************************/

/*************************************************************************************************************
*******************************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 estaram 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**************************************************
*************************************************************************************************************/


/*************************************************************************************************************
*******************************DISPLAY************************************************************************
**************************************************************************************************************/

/*
rotation indica qual parte do display estará para cima

         TOP
L  . . . . . . . . R
E  . . . . . . . . I
F  . . . . . . . . G
T  . . . . . . . . H
   . . . . . . . . T
   . . . . . . . .
   . . . . . . . .
   . . . . . . . .   
       BOTTOM
    
*/
enum Rotation {TOP, LEFT, BOTTOM, RIGHT};

struct Position {
  int lin;
  int col;
};

struct Display {
  int index;
  Position position; 
  Rotation rotation;
};

/*************************************************************************************************************
*******************************FIM DISPLAY********************************************************************
**************************************************************************************************************/

/*************************************************************************************************************
*******************************CLASSE SNAKE GAME**************************************************************
**************************************************************************************************************/

const int SNAKE_MAX_LEN   = 50;  //tamanho maximo da cobra
const int SNAKE_TIME_INIT = 500; //tempo entre deslocamento da cobra (velocidade)
const int SNAKE_TIME_INC  = 15;  //incremento da velocidade

enum Direction { DIR_STOP, DIR_TOP, DIR_LEFT, DIR_BOTTOM, DIR_RIGHT};
enum SnakeStatus { SNAKE_GAME_ON, SNAKE_GAME_OVER };

class SnakeGame{
  private:
    BitArray2D * _display;
    Position _snake_positions[SNAKE_MAX_LEN];
    Position _apple;
    int _length;
    Direction _direction;
    unsigned long _last_millis;
    int _time;
    int _score;
    SnakeStatus _snakeStatus;
    
    UniqueRandom * _ur;  //utilizado no game over
    
    void _generateApple() {
      int lin, col;
      boolean random_ok = false;
      
      while (!random_ok) {
        random_ok = true;
        lin = random(0, _display->rows()-1);
        col = random(0, _display->columns()-1);
        
        for (int p=0; p<_length; p++){
          if (_snake_positions[p].col==col && _snake_positions[p].lin==lin){ //verifica se gerou em um local que não seja a cobra
            random_ok = false;
            break;
          }
        }
      }
      _apple.lin = lin;
      _apple.col = col;
    }
    
    void _gameOver(){ 
      _snakeStatus = SNAKE_GAME_OVER; 
      _direction   = DIR_STOP;
      _time = 20;
    }
    
    void _inc_length(){
      _length++; _score++;
      _time -= SNAKE_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 ) {  
        _ur->randomize();
        start(); 
      }
    }
    
    void _run(){
      for (int i=_length-1; i>0; i--){
        _snake_positions[i].lin = _snake_positions[i-1].lin;
        _snake_positions[i].col = _snake_positions[i-1].col;
      }
      
      if (_direction == DIR_TOP )    { _snake_positions[0].lin--;  }
      if (_direction == DIR_BOTTOM ) { _snake_positions[0].lin++;  }
      if (_direction == DIR_LEFT )   { _snake_positions[0].col--;  }
      if (_direction == DIR_RIGHT )  { _snake_positions[0].col++;  }
      
      //verifica se ultrapassou o limite do display
      if (_snake_positions[0].lin < 0)                     { _gameOver(); }
      if (_snake_positions[0].lin >= _display->rows() )    { _gameOver(); }
      if (_snake_positions[0].col < 0)                     { _gameOver(); }
      if (_snake_positions[0].col >= _display->columns() ) { _gameOver(); }
      
      //verifica se colidiu na cobra
      for (int i=_length-1; i>0; i--){
        if (_snake_positions[i].lin == _snake_positions[0].lin && _snake_positions[i].col == _snake_positions[0].col) {
          _gameOver();
        }  
      }
      
      //verifica se comeu a maça
      if (_snake_positions[0].col == _apple.col && _snake_positions[0].lin == _apple.lin){
        _inc_length();
        
        if (_length > SNAKE_MAX_LEN) { _length = SNAKE_MAX_LEN; } else {
          _snake_positions[_length-1].lin = _snake_positions[_length-2].lin;
          _snake_positions[_length-1].col = _snake_positions[_length-2].col;
        }
        _generateApple();
      }
      
      //update display
      for (int lin=0; lin<_display->rows(); lin++) {
        for (int col=0; col<_display->columns(); col++) {
          for (int p=0; p<_length; p++){
            boolean val = _snake_positions[p].col==col && _snake_positions[p].lin==lin;
            _display->write( lin, col,  val );
            if (val) {break;}
          }
        }
      }
      _display->write(_apple.lin, _apple.col, HIGH);
      //--
    }
    
  public:
    SnakeGame(BitArray2D * display){ 
      _display = display;
      _ur = new UniqueRandom( _display->rows() * _display->columns() );
      start();
    }
    
    void start(){
      _length = 1;
      _score  = 0;
      _time = SNAKE_TIME_INIT;
      _last_millis = 0;
      _snake_positions[0].lin = _display->rows() / 2;
      _snake_positions[0].col = _display->columns() / 2;
      _direction = DIR_STOP;
      
      _snakeStatus = SNAKE_GAME_ON;
      
      _generateApple();
    }
    
    void left()   { if (_direction == DIR_RIGHT)  return; _direction = DIR_LEFT;   }
    void right()  { if (_direction == DIR_LEFT)   return; _direction = DIR_RIGHT;  }
    void top()    { if (_direction == DIR_BOTTOM) return; _direction = DIR_TOP;    }
    void bottom() { if (_direction == DIR_TOP)    return; _direction = DIR_BOTTOM; }
    
    int getScore(){ return _score; }
    
    int update(){
      int r = false;
      
      if (millis() - _last_millis > _time) {
        r = true;
        _last_millis = millis();
      
        if (_snakeStatus == SNAKE_GAME_ON)   { _run();         }
        if (_snakeStatus == SNAKE_GAME_OVER) { _runGameOver(); }
      }
      
      return r; //r-->indica se houve mudança no display
    }
};

/*************************************************************************************************************
*******************************FIM CLASSE SNAKE GAME**********************************************************
**************************************************************************************************************/



const int LINHAS  = 16;
const int COLUNAS = 8;


/*
 * * * * * * * *
 * * * * * * * *
 * * * * * * * *
 * * * * * * * *
 * * * * * * * *
 * * * * * * * *
 * * * * * * * *
 * * * * * * * *
 
 * * * * * * * *
 * * * * * * * *
 * * * * * * * *
 * * * * * * * *
 * * * * * * * *
 * * * * * * * *
 * * * * * * * *
 * * * * * * * *
*/

Display displays_8x8[] = {
  {0, {0,0}, TOP},
  {1, {8,0}, BOTTOM}
};

BitArray2D ba(LINHAS, COLUNAS); //8 linhas e 20 colunas
SnakeGame snake(&ba);
 
const int PIN_CLOCK   = 5;
const int PIN_DATA    = 7;
const int PIN_LOAD    = 6;
const int QTD_MAX7219 = 2;
 
LedControl lc = LedControl(PIN_DATA,PIN_CLOCK,PIN_LOAD, QTD_MAX7219);

void update_displays_8x8() {
  lc.startWrite();
  
  for (int lin=0; lin<ba.rows(); lin++) {
    for (int col=0; col<ba.columns(); col++) {
      for (int i=0; i<sizeof(displays_8x8)/sizeof(Display); i++) {
        int l = lin - displays_8x8[i].position.lin;
        int c = col - displays_8x8[i].position.col;
        
        if (l>=0 && l<=7 && c>=0 && c<=7) {
          
          if (displays_8x8[i].rotation == BOTTOM) {            c=7-c; l=7-l;   }
          if (displays_8x8[i].rotation == LEFT)   { int aux=c; c=l;   l=7-aux; }
          if (displays_8x8[i].rotation == RIGHT)  { int aux=l; l=c;   c=7-aux; }

          lc.setLed(displays_8x8[i].index, l, c, ba.read(lin, col) );
        }
      }
    }
  }
  
  lc.send();
}


void setup() {
  Serial.begin(9600);
  
  lc.shutdown(0,false);
  lc.setIntensity(0,8);
  lc.clearDisplay(0);  
  lc.shutdown(1,false);
  lc.setIntensity(1,8);
  lc.clearDisplay(1);  
  
  randomSeed(analogRead(A0));
}

void loop() {
  int c = Serial.read();
   
  if (c == 97)  { snake.left();   } //a -> 
  if (c == 98)  { snake.right();  } //b -> 
  if (c == 99)  { snake.top();    } //c -> 
  if (c == 100) { snake.bottom(); } //d -> 
  
  if ( snake.update() ) { update_displays_8x8(); }
}




quinta-feira, 8 de outubro de 2015

Arduino - Serial através do DOS (arquivo em lote .bat)

sabe quando você quer usar a serial pra enviar um comando para o arduino executar alguma coisa, mas não quer ficar usando o enter ou clicar no botão enviar?

Neste vídeo mostro como contornar esse probleminha através do uso de comando do DOS.



Código-fonte:


/*
mais detalhes sobre essa função: http://fabianoallex.blogspot.com.br/2015/09/arduino-como-substituir-delay-pelo.html
*/
int time(long timeHigh, long timeLow, long atraso, long mref = 0) {
  long ajuste = mref % (timeHigh + timeLow);
  long resto  = (millis() + timeHigh + timeLow - ajuste - atraso) % (timeHigh + timeLow);
  return (resto < timeHigh ? HIGH : LOW);
}


void setup(){
  Serial.begin(9600);
  pinMode(6, OUTPUT);
}

boolean pisca = false;

void loop(){
  int c = Serial.read();
  
  if (c == 97) { digitalWrite(6, HIGH); pisca = false; } //a -> liga
  if (c == 98) { digitalWrite(6, LOW);  pisca = false; } //b -> desliga
  if (c == 99) { pisca = true;                         } //c -> pisca

  if (pisca)   { digitalWrite(6, time(400, 400, 0));   }
}



arquivo .bat:

lembre-se de configurar a porta correta, nesse exemplo, utilizei a porta COM8.


MODE COM8 BAUD=9600 PARITY=n DATA=8

:LOOP
  
  CHOICE /C:1234 /M "1: liga; 2: desliga; 3: pisca; 4: sair " 
  IF errorlevel 4 GOTO SAIR
  IF errorlevel 3 GOTO PISCA
  IF errorlevel 2 GOTO DESLIGA 
  IF errorlevel 1 GOTO LIGA

  :DESLIGA  
  ECHO b > COM8 
  GOTO END

  :LIGA
  ECHO a > COM8  
  GOTO END

  :PISCA
  ECHO c > COM8  
  GOTO END

  :END
  CLS  
GOTO LOOP 

:SAIR


segunda-feira, 5 de outubro de 2015

Arduino - Max7219 e 74HC595 Juntos + Attiny85

Nesse Artigo vou explicar um pouco dos detalhes de como implementei o código para poder controlar o Max7219 junto com o 74HC595, os quais mostrei dois vídeos na página do facebook.



74hc595 e Max7219 juntos no mesmo barramento.



Agora rodando em um attiny85.74hc595 e Max7219 juntos no mesmo barramento.






Apesar de a ideia não ser entrar em detalhes do que são e como funcionam Registradores de Deslocamento, vou deixar aqui um trecho do que a wikipedia diz sobre eles:

Em eletrônica digital um registrador de deslocamento é um conjunto de registradores configurados em um arranjo linear de tal forma que a informação é deslocada pelo circuito conforme o mesmo é ativado.
Os registradores de deslocamento podem possuir uma combinação de entradas e saídas seriais e paralelas, incluindo as configurações entrada serial, saída paralela (SIPO) e entrada paralela, saída serial (PISO). Existem outra configurações possuindo ambas as entradas serial e paralela e outra com saídas serial paralela. Existem também registradores de deslocamento bi-direcionais, os quais permitem que se varie a direção do deslocamento da informação. As entradas e saídas seriais de um registrador podem ser conectadas juntas, de modo a formar um registrador de deslocamento circular. Poderiam também ser desenvolvidos registradores de deslocamento multi-dimensionais, os quais podem realizar processamentos mais complexos. Mais informações, aqui.

Dois exemplos de Registradores de Deslocamento são o Max7219 e o 74HC595, cada um com uma finalidade diferente. Enquanto o 595 é usado apenas para expandir saídas, o Max7219 tem o objetivo de servir como driver para display de 7 segmentos e matriz de Leds 8x8. Apesar de terem suas diferenças, a comunicação entre os dois é basicamente a mesma, com a pequena diferença de que o 595 possui 8 bits de Saída, enquanto que o Max7219 possui 16.

Também não vou entrar em maiores detalhes de como cada um funciona, mas pra quem quiser mais informações, seguem os links para os datasheets:

https://www.sparkfun.com/datasheets/Components/General/COM-09622-MAX7219-MAX7221.pdf
http://www.nxp.com/documents/data_sheet/74HC_HCT595.pdf

Aqui no blog mesmo, há várias postagens sobre o 74HC595: http://fabianoallex.blogspot.com.br/search?q=74HC595

A grande vantagem dos Registradores de Deslocamento é que eles podem ser ligados em cascata e sem um limite de quantidade. Ambos possuem 3 pinos de comunicação, clock e data, além do Load no max7219 e o seu equivalente Latch para o 74HC595.

Fisicamente não há grandes dificuldades na comunicação entre eles, uma estudada sobre como são as ligações de ambos dará pra entender (instintivamente) como liga-los juntos. Mas no caso específico que mostro aqui, há algumas regras. Foi necessário ligar primeiramente os Max7219, e por último os 595, nunca intercalando um com o outro.

Programação


A parte que deu mais trabalho foi a parte de programação, já que a ideia foi reutilizar as classes já existentes para controle de ambos, no caso do Max 7219 a biblioteca LedControl, e do 74HC595, utilizar uma classe que já disponibilizei aqui no blog ( veja ). Eu poderia criar uma classe do zero e recriar todas as funcionalidades nessa única classe, mas achei que seria retrabalho, então a ideia foi fazer algo que unisse a funcionalidade das duas.

As explicações dadas daqui pra baixo irão exigir um conhecimento básico de conceitos de Orientação a Objetos. Já fiz um vídeo falando algumas coisas sobre o assunto: http://fabianoallex.blogspot.com.br/2014/09/arduino-dicas-de-programacao-04.html

Ambas as classes agora precisam funcionar de maneira unificada. Quando os dados de qualquer uma das duas for atualizado, será necessário envia-los para todos os CIs que estão ligados em cascata e não apenas a que foi alterada. Pra isso foi necessário criar uma terceira classe que controlasse o envio dos dados para os registradores, a qual chamei de ShiftRegisterController.

A finalidade dessa classe é gerenciar o envio dos dados para os CIs. Porém ainda há um outro detalhe, foi necessário criar uma outra classe que fosse a classe-base para LedControl e para a classe Expansor74HC595, de modo que as duas herdem as mesmas funcionalidades básicas.


class ShiftRegisterController; //prototipo

class ShiftRegister{
  protected:
    ShiftRegisterController * _controller;
  public:
    virtual void shiftOutRegister() ;
};

class ShiftRegisterController {
  private:
    int             _pin_latch;  // ou load no max7219
    ShiftRegister * _exp_595;
    ShiftRegister * _exp_lc;
  public:
    ShiftRegisterController(int pin_latch)      { 
      _pin_latch = pin_latch; 
      pinMode(_pin_latch,OUTPUT);
      digitalWrite(_pin_latch, HIGH);
    }  
    
    void setLedControl(ShiftRegister * lc)      { _exp_lc  = lc;          }
    void setExp74HC595(ShiftRegister * exp_595) { _exp_595 = exp_595;     }
    
    void shiftOutRegisters() {
      if (_exp_595 && _exp_lc){
        digitalWrite(_pin_latch, LOW); 
        _exp_595->shiftOutRegister();
        _exp_lc ->shiftOutRegister();
        digitalWrite(_pin_latch, HIGH);
      }
    } 
};


Além da inclusão dessas novas classes, foram feitas alterações nas definições das classes já existentes, indicando que as mesmas, agora serão herdadas de ShiftRegister:

class Expansor74HC595 : public ShiftRegister {...};
class LedControl : public ShiftRegister {...};

Podemos ver que há um atributo chamado _pin_latch na classe ShiftRegisterController, que anteriormente ficava nas classes LedControl e Expansor74HC595, pois é através desse pino que todos os CIs são "notificados" que as alterações estão prontas e devem ser aplicadas as suas respectivas saídas, logo, as duas classes citadas não controlam mais quando os dados são finalizados, ficando essa função para a classe  ShiftRegisterController.

Observação: Pra não ter que fazer essas alterações diretamente na biblioteca LedControl, eu copiei todo o conteúdo da classe dentro da própria sketch e fiz as alterações necessárias, como pode ser visto no código completo mais abaixo. Em outro artigo eu já mostrei como fazer isso, incluindo algumas melhorias na classe LedControl, a quais explico nesse artigo: http://fabianoallex.blogspot.com.br/2015/09/arduino-alteracoes-na-biblioteca.html

O código abaixo mostra como devem ser declarados os objetos, que são 3.

int const PIN_DATA = 10;
int const PIN_CLK = 9;
int const PIN_LATCH = 8;
int const QUANTIDADE_MAX7219 = 2;
int const QUANTIDADE_74HC595 = 1;

ShiftRegisterController exp_src = ShiftRegisterController(PIN_LATCH);
LedControl      exp_lc  = LedControl     (PIN_DATA,PIN_CLK,QUANTIDADE_MAX7219, &exp_src);
Expansor74HC595 exp_595 = Expansor74HC595(PIN_DATA,PIN_CLK,QUANTIDADE_74HC595 , &exp_src);

Primeiramente foi necessário criar o objeto controlador e posteriormente criar os outros dois Objetos, um LedControl e outro Expansor74HC595. Veja que o primeiro objeto recebeu como parâmetro o PIN_LATCH, enquanto que as demais classes não recebem mais esse parâmetro, recebendo apenas o pino de clock e o pino de data, porém recebem como parâmetro o endereço do objeto controlador.

Código-Fonte - Arduino Uno:

/*
Criado em 04/10/2015
 Por: 
   Fabiano A. Arndt 
   http://www.youtube.com/fabianoallex
   http://fabianoallex.blogspot.com.br
   fabianoallex@gmail.com
*/
/*
 *    LedControl.h / LedControl.cpp - A library for controling Leds with a MAX7219/MAX7221
 *    Copyright (c) 2007 Eberhard Fahle
 * 
 *    Permission is hereby granted, free of charge, to any person
 *    obtaining a copy of this software and associated documentation
 *    files (the "Software"), to deal in the Software without
 *    restriction, including without limitation the rights to use,
 *    copy, modify, merge, publish, distribute, sublicense, and/or sell
 *    copies of the Software, and to permit persons to whom the
 *    Software is furnished to do so, subject to the following
 *    conditions:
 * 
 *    This permission notice shall be included in all copies or 
 *    substantial portions of the Software.
 * 
 *    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 *    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 *    OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 *    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 *    HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 *    WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 *    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 *    OTHER DEALINGS IN THE SOFTWARE.
 */
/*************************************************************************************************************
*******************************CLASSES ShiftRegister E ShiftRegisterController********************************
**************************************************************************************************************/

class ShiftRegisterController; //prototipo

class ShiftRegister{
  protected:
    ShiftRegisterController * _controller;
  public:
    virtual void shiftOutRegister() ;
};

class ShiftRegisterController {
  private:
    int             _pin_latch;
    ShiftRegister * _exp_595;
    ShiftRegister * _exp_lc;
  public:
    ShiftRegisterController(int pin_latch)      { 
      _pin_latch = pin_latch; 
      pinMode(_pin_latch,OUTPUT);
      digitalWrite(_pin_latch, HIGH);
    }  
    
    void setLedControl(ShiftRegister * lc)      { _exp_lc  = lc;          }
    void setExp74HC595(ShiftRegister * exp_595) { _exp_595 = exp_595;     }
    
    void shiftOutRegisters() {
      if (_exp_595 && _exp_lc){
        digitalWrite(_pin_latch, LOW); 
        _exp_595->shiftOutRegister();
        _exp_lc ->shiftOutRegister();
        digitalWrite(_pin_latch, HIGH);
      }
    } 
};

/*************************************************************************************************************
***************************FIM CLASSES ShiftRegister E ShiftRegisterController********************************
**************************************************************************************************************/

/*************************************************************************************************************
*******************************EXPANSOR 74HC595***************************************************************
**************************************************************************************************************/
class Expansor74HC595 : public ShiftRegister {
  private:
    int  _pin_clock;
    int  _pin_data;
    int  _num_cis;
    byte* _pins;
    int  _auto_send;
  public:
    Expansor74HC595(int pin_data, int pin_clock, int num_cis,  ShiftRegisterController * controller){
      _pin_clock  = pin_clock;
      _pin_data   = pin_data;
      _num_cis    = num_cis;
      _auto_send  = true;
      _pins       = new byte[_num_cis];
      _controller = controller;
      
      _controller->setExp74HC595(this);
 
      pinMode(_pin_clock,OUTPUT);
      pinMode(_pin_data, OUTPUT);
      //clear();
    };
   
    void startWrite() { _auto_send = false;  };
     
    void clear() { 
      for (int i=0; i<_num_cis; i++){  _pins[i] = 0b00000000; }
      send();  
    };
   
    int read(int pin)  { 
      if (pin >= _num_cis * 8) {return LOW;} 
      int pos = pin / 8;
      pin = pin % 8;
      return (_pins[pos] & (1 << pin)) != 0;
    };
   
    byte readByte(int num_ci) { 
      return _pins[num_ci];
    };
    
    void shiftOutRegister(){  for(int i=_num_cis-1; i>=0; i--) {  shiftOut(_pin_data, _pin_clock, MSBFIRST, readByte(i) );  }  };
    
    void send(){
      _controller->shiftOutRegisters();
      _auto_send = true;
    };
     
    void writeByte(int num_ci, byte b, int first = MSBFIRST) {  
      if (first == MSBFIRST){
        byte reversed;
        for(int i=0;i<8;i++){ reversed |= ((b>>i) & 0b1)<<(7-i); }
        b = reversed;
      }
      _pins[num_ci] = b; 
      if (_auto_send) { send(); } 
    };
     
    void write(int pin, int value) {
      if (pin >= _num_cis * 8) { return; }
       
      int pos = pin / 8;
      pin     = pin % 8;
 
      if (value){
        _pins[pos] |= (1 << pin);  //set a bit HIGH
      } else {
        _pins[pos] &= ~(1 << pin); //set a bit LOW
      }
 
      if (_auto_send) { send(); }
    };
};

/*************************************************************************************************************
*******************************FIM 74HC595********************************************************************
**************************************************************************************************************/



/*************************************************************************************************************
*******************************LEDCONTROL ALTERADA************************************************************
**************************************************************************************************************/
 
//the opcodes for the MAX7221 and MAX7219
#define OP_DECODEMODE  9
#define OP_INTENSITY   10
#define OP_SCANLIMIT   11
#define OP_SHUTDOWN    12
#define OP_DISPLAYTEST 15
 
class LedControl : public ShiftRegister {
  private :
    byte spidata[16];
    byte * status;
    int SPI_MOSI;
    int SPI_CLK;
    int maxDevices;
    int _auto_send;
     
    void spiTransfer(int addr, volatile byte opcode, volatile byte data) {
      int offset   = addr*2;
      int maxbytes = maxDevices*2;
      for(int i=0;i<maxbytes;i++)  { spidata[i]=(byte)0; }
      spidata[offset+1] = opcode;
      spidata[offset]   = data;
      
      _controller->shiftOutRegisters();
      
    }
  public:
    void shiftOutRegister(){ for(int i=maxDevices*2;i>0;i--) { shiftOut(SPI_MOSI,SPI_CLK,MSBFIRST,spidata[i-1]); } };
  
    LedControl(int dataPin, int clkPin, int numDevices,  ShiftRegisterController * controller) {
      _auto_send  = true;
      SPI_MOSI    = dataPin;
      SPI_CLK     = clkPin;
      maxDevices  = numDevices;
      
      _controller = controller;
      _controller->setLedControl(this);
       
      pinMode(SPI_MOSI, OUTPUT);
      pinMode(SPI_CLK,  OUTPUT);
       
      status      = new byte[maxDevices * 8]; //instancia o array de acordo com a quantia de displays usados
      for(int i=0;i<maxDevices * 8 ;i++) { status[i]=0x00; }
      
    }
    
    void begin(){
      for(int i=0;i<maxDevices;i++) {
        spiTransfer(i, OP_DISPLAYTEST,0);
        setScanLimit(i, 7);               //scanlimit is set to max on startup
        spiTransfer(i, OP_DECODEMODE,0);  //decode is done in source
        clearDisplay(i);
        shutdown(i,true);                 //we go into shutdown-mode on startup
      }
    }
     
    void startWrite() {  _auto_send = false;  };
     
    void send() {
      for (int j=0; j<maxDevices; j++) {
        int offset = j*8;
        for(int i=0;i<8;i++) { spiTransfer(j, i+1, status[offset+i]); }
      }
      _auto_send = true;
    }
     
    int getDeviceCount(){ return maxDevices; }
     
    void shutdown(int addr, bool b){
      if(addr<0 || addr>=maxDevices) return;
      spiTransfer(addr, OP_SHUTDOWN, b ? 0 : 1);
    }
     
    void setScanLimit(int addr, int limit){
      if(addr<0 || addr>=maxDevices) return;
      if(limit>=0 && limit<8) spiTransfer(addr, OP_SCANLIMIT,limit);
    }
     
    void setIntensity(int addr, int intensity) {
      if(addr<0 || addr>=maxDevices)   {  return;                                    }
      if(intensity>=0 && intensity<16) {  spiTransfer(addr, OP_INTENSITY, intensity); }
    }
     
    void clearDisplay(int addr){
      if(addr<0 || addr>=maxDevices) return;
       
      int offset = addr*8;
      for(int i=0;i<8;i++) {
        status[offset+i] = 0;
        if (_auto_send) { spiTransfer(addr, i+1, status[offset+i]); }
      }
    }
     
    void setLed(int addr, int row, int column, boolean state) {
      if(addr<0 || addr>=maxDevices)             { return; }
      if(row<0 || row>7 || column<0 || column>7) { return; }
       
      int offset = addr*8;
      byte val = B10000000 >> column;
       
      if(state) { status[offset+row] = status[offset+row] | val; }
      else {
        val=~val;
        status[offset+row] = status[offset+row]&val;
      }
       
      if (_auto_send) { spiTransfer(addr, row+1, status[offset+row]); }
    }
     
    void setRow(int addr, int row, byte value) {
      if(addr<0 || addr>=maxDevices) return;
      if(row<0 || row>7) return;
      int offset = addr*8;
      status[offset+row] = value;
      if (_auto_send) {
        spiTransfer(addr, row+1, status[offset+row]);
      }
    }
     
    void setColumn(int addr, int col, byte value) {
      if(addr<0 || addr>=maxDevices) return;
      if(col<0 || col>7)             return;
       
      byte val;
      for(int row=0; row<8; row++) {
        val=value >> (7-row);
        val=val & 0x01;
        setLed(addr,row,col,val);
      }
    }
};
/*************************************************************************************************************
*******************************FIM LEDCONTROL ALTERADA********************************************************
**************************************************************************************************************/
 

/*
 pin 10 is connected to the DataIn 
 pin 9 is connected to the CLK 
 pin 8 is connected to LOAD 
 */
 
int const PIN_DATA = 10;
int const PIN_CLK = 9;
int const PIN_LATCH = 8;
int const QUANTIDADE_MAX7219 = 2;
int const QUANTIDADE_74HC595 = 1;
 
ShiftRegisterController exp_src = ShiftRegisterController(PIN_LATCH);
LedControl      exp_lc  = LedControl     (PIN_DATA,PIN_CLK,QUANTIDADE_MAX7219, &exp_src);
Expansor74HC595 exp_595 = Expansor74HC595(PIN_DATA,PIN_CLK,QUANTIDADE_74HC595 , &exp_src);
const int LINHAS  = 8;
const int COLUNAS = 16;
 
int cont=0;
 
 
void update_displays() {
  exp_lc.startWrite();
   
  for (int lin=0; lin<8; lin++) {
    for (int col=0; col<16; col++) {
      for (int i=0; i<2; i++) {
        int l = lin;
        int c = col - (i*8); 
         
        if (l>=0 && l<=7 && c>=0 && c<=7) {
          exp_lc.setLed(i, l, c, (l+c+cont)%2 );
        }
      }
    }
  }
   
  cont++;
  exp_lc.send();
}
  
void setup() {
  //Serial.begin(9600);
  
  exp_lc.begin();
  exp_lc.shutdown(0,false);
  exp_lc.setIntensity(0,8);
  exp_lc.clearDisplay(0);  
  exp_lc.shutdown(1,false);
  exp_lc.setIntensity(1,8);
  exp_lc.clearDisplay(1);  
}
 
void loop() {
  
  for (byte b=0; b<=255; b++) {    //i=0-> B00000000   i=255-> B11111111
    exp_595.startWrite();
    exp_595.writeByte(0, b, LSBFIRST);
    exp_595.send();
  
    update_displays();
    
    delay(500);
  }
}


Código-fonte Attiny85:

/*
Criado em 04/10/2015
 Por: 
   Fabiano A. Arndt 
   http://www.youtube.com/fabianoallex
   http://fabianoallex.blogspot.com.br
   fabianoallex@gmail.com
*/ 
/*
 *    LedControl.h / LedControl.cpp - A library for controling Leds with a MAX7219/MAX7221
 *    Copyright (c) 2007 Eberhard Fahle
 * 
 *    Permission is hereby granted, free of charge, to any person
 *    obtaining a copy of this software and associated documentation
 *    files (the "Software"), to deal in the Software without
 *    restriction, including without limitation the rights to use,
 *    copy, modify, merge, publish, distribute, sublicense, and/or sell
 *    copies of the Software, and to permit persons to whom the
 *    Software is furnished to do so, subject to the following
 *    conditions:
 * 
 *    This permission notice shall be included in all copies or 
 *    substantial portions of the Software.
 * 
 *    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 *    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 *    OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 *    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 *    HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 *    WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 *    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 *    OTHER DEALINGS IN THE SOFTWARE.
 */
/*************************************************************************************************************
*******************************CLASSES ShiftRegister E ShiftRegisterController********************************
**************************************************************************************************************/
class ShiftRegisterController; //prototipo

class ShiftRegister{
  protected:
    ShiftRegisterController * _controller;
  public:
    virtual void shiftOutRegister() ;
};

class ShiftRegisterController {
  private:
    int             _pin_latch;
    ShiftRegister * _exp_595;
    ShiftRegister * _exp_lc;
  public:
    ShiftRegisterController(int pin_latch)      { 
      _pin_latch = pin_latch; 
      pinMode(_pin_latch,OUTPUT);
      digitalWrite(_pin_latch, HIGH);
    }  
    
    void setLedControl(ShiftRegister * lc)      { _exp_lc  = lc;          }
    void setExp74HC595(ShiftRegister * exp_595) { _exp_595 = exp_595;     }
    
    void shiftOutRegisters() {
      if (_exp_595 && _exp_lc){
        digitalWrite(_pin_latch, LOW); 
        _exp_595->shiftOutRegister();
        _exp_lc ->shiftOutRegister();
        digitalWrite(_pin_latch, HIGH);
      }
    } 
};


/*************************************************************************************************************
***************************FIM CLASSES ShiftRegister E ShiftRegisterController********************************
**************************************************************************************************************/


/*************************************************************************************************************
*******************************EXPANSOR 74HC595***************************************************************
**************************************************************************************************************/
class Expansor74HC595 : public ShiftRegister {
  private:
    int  _pin_clock;
    int  _pin_data;
    int  _num_cis;
    byte* _pins;
    int  _auto_send;
  public:
    Expansor74HC595(int pin_data, int pin_clock, int num_cis,  ShiftRegisterController * controller){
      _pin_clock  = pin_clock;
      _pin_data   = pin_data;
      _num_cis    = num_cis;
      _auto_send  = true;
      _pins       = new byte[_num_cis];
      _controller = controller;
      
      _controller->setExp74HC595(this);
 
      pinMode(_pin_clock,OUTPUT);
      pinMode(_pin_data, OUTPUT);
      //clear();
    };
   
    void startWrite() { _auto_send = false;  };
     
    void clear() { 
      for (int i=0; i<_num_cis; i++){  _pins[i] = 0b00000000; }
      send();  
    };
   
    int read(int pin)  { 
      if (pin >= _num_cis * 8) {return LOW;} 
      int pos = pin / 8;
      pin = pin % 8;
      return (_pins[pos] & (1 << pin)) != 0;
    };
   
    byte readByte(int num_ci) { return _pins[num_ci];  };
    
    void shiftOutRegister(){  for(int i=_num_cis-1; i>=0; i--) {  shiftOut(_pin_data, _pin_clock, MSBFIRST, readByte(i) );  }  };
    
    void send(){
      _controller->shiftOutRegisters();
      _auto_send = true;
    };
     
    void writeByte(int num_ci, byte b, int first = MSBFIRST) {  
      if (first == MSBFIRST){
        byte reversed;
        for(int i=0;i<8;i++){ reversed |= ((b>>i) & 0b1)<<(7-i); }
        b = reversed;
      }
      _pins[num_ci] = b; 
      if (_auto_send) { send(); } 
    };
     
    void write(int pin, int value) {
      if (pin >= _num_cis * 8) { return; }
       
      int pos = pin / 8;
      pin     = pin % 8;
 
      if (value){
        _pins[pos] |= (1 << pin);  //set a bit HIGH
      } else {
        _pins[pos] &= ~(1 << pin); //set a bit LOW
      }
 
      if (_auto_send) { send(); }
    };
};

/*************************************************************************************************************
*******************************FIM 74HC595********************************************************************
**************************************************************************************************************/



/*************************************************************************************************************
*******************************LEDCONTROL ALTERADA************************************************************
**************************************************************************************************************/
 
//the opcodes for the MAX7221 and MAX7219
#define OP_DECODEMODE  9
#define OP_INTENSITY   10
#define OP_SCANLIMIT   11
#define OP_SHUTDOWN    12
#define OP_DISPLAYTEST 15
 
class LedControl : public ShiftRegister {
  private :
    byte spidata[16];
    byte * status;
    int SPI_MOSI;
    int SPI_CLK;
    int maxDevices;
    int _auto_send;
     
    void spiTransfer(int addr, volatile byte opcode, volatile byte data) {
      int offset   = addr*2;
      int maxbytes = maxDevices*2;
      for(int i=0;i<maxbytes;i++)  { spidata[i]=(byte)0; }
      spidata[offset+1] = opcode;
      spidata[offset]   = data;
      
      _controller->shiftOutRegisters();
      
    }
  public:
    void shiftOutRegister(){ for(int i=maxDevices*2;i>0;i--) { shiftOut(SPI_MOSI,SPI_CLK,MSBFIRST,spidata[i-1]); } };
  
    LedControl(int dataPin, int clkPin, int numDevices,  ShiftRegisterController * controller) {
      _auto_send  = true;
      SPI_MOSI    = dataPin;
      SPI_CLK     = clkPin;
      maxDevices  = numDevices;
      
      _controller = controller;
      _controller->setLedControl(this);
       
      pinMode(SPI_MOSI, OUTPUT);
      pinMode(SPI_CLK,  OUTPUT);
       
      status      = new byte[maxDevices * 8]; //instancia o array de acordo com a quantia de displays usados
      for(int i=0;i<maxDevices * 8 ;i++) { status[i]=0x00; }
      
    }
    
    void begin(){
      for(int i=0;i<maxDevices;i++) {
        spiTransfer(i, OP_DISPLAYTEST,0);
        setScanLimit(i, 7);               //scanlimit is set to max on startup
        spiTransfer(i, OP_DECODEMODE,0);  //decode is done in source
        clearDisplay(i);
        shutdown(i,true);                 //we go into shutdown-mode on startup
      }
    }
     
    void startWrite() {  _auto_send = false;  };
     
    void send() {
      for (int j=0; j<maxDevices; j++) {
        int offset = j*8;
        for(int i=0;i<8;i++) { spiTransfer(j, i+1, status[offset+i]); }
      }
      _auto_send = true;
    }
     
    int getDeviceCount(){ return maxDevices; }
     
    void shutdown(int addr, bool b){
      if(addr<0 || addr>=maxDevices) return;
      spiTransfer(addr, OP_SHUTDOWN, b ? 0 : 1);
    }
     
    void setScanLimit(int addr, int limit){
      if(addr<0 || addr>=maxDevices) return;
      if(limit>=0 && limit<8) spiTransfer(addr, OP_SCANLIMIT,limit);
    }
     
    void setIntensity(int addr, int intensity) {
      if(addr<0 || addr>=maxDevices)   {  return;                                    }
      if(intensity>=0 && intensity<16) {  spiTransfer(addr, OP_INTENSITY, intensity); }
    }
     
    void clearDisplay(int addr){
      if(addr<0 || addr>=maxDevices) return;
       
      int offset = addr*8;
      for(int i=0;i<8;i++) {
        status[offset+i] = 0;
        if (_auto_send) { spiTransfer(addr, i+1, status[offset+i]); }
      }
    }
     
    void setLed(int addr, int row, int column, boolean state) {
      if(addr<0 || addr>=maxDevices)             { return; }
      if(row<0 || row>7 || column<0 || column>7) { return; }
       
      int offset = addr*8;
      byte val = B10000000 >> column;
       
      if(state) { status[offset+row] = status[offset+row] | val; }
      else {
        val=~val;
        status[offset+row] = status[offset+row]&val;
      }
       
      if (_auto_send) { spiTransfer(addr, row+1, status[offset+row]); }
    }
     
    void setRow(int addr, int row, byte value) {
      if(addr<0 || addr>=maxDevices) return;
      if(row<0 || row>7) return;
      int offset = addr*8;
      status[offset+row] = value;
      if (_auto_send) {
        spiTransfer(addr, row+1, status[offset+row]);
      }
    }
     
    void setColumn(int addr, int col, byte value) {
      if(addr<0 || addr>=maxDevices) return;
      if(col<0 || col>7)             return;
       
      byte val;
      for(int row=0; row<8; row++) {
        val=value >> (7-row);
        val=val & 0x01;
        setLed(addr,row,col,val);
      }
    }
};
/*************************************************************************************************************
*******************************FIM LEDCONTROL ALTERADA********************************************************
**************************************************************************************************************/
  
 
/*
 pin 2 is connected to the DataIn 
 pin 1 is connected to the CLK 
 pin 0 is connected to LOAD 
 */
 

int const PIN_DATA = 2;
int const PIN_CLK = 1;
int const PIN_LATCH = 0;
int const QUANTIDADE_MAX7219 = 2;
int const QUANTIDADE_74HC595 = 1;
 
ShiftRegisterController exp_src = ShiftRegisterController(PIN_LATCH);
LedControl      exp_lc  = LedControl     (PIN_DATA,PIN_CLK,QUANTIDADE_MAX7219, &exp_src);
Expansor74HC595 exp_595 = Expansor74HC595(PIN_DATA,PIN_CLK,QUANTIDADE_74HC595, &exp_src);

 
const int LINHAS  = 8;
const int COLUNAS = 16;
 
int cont=0;
 
 
void update_displays() {
  exp_lc.startWrite();
   
  for (int lin=0; lin<8; lin++) {
    for (int col=0; col<16; col++) {
      for (int i=0; i<2; i++) {
        int l = lin;
        int c = col - (i*8); 
         
        if (l>=0 && l<=7 && c>=0 && c<=7) {
          exp_lc.setLed(i, l, c, (l+c+cont)%2 );
        }
      }
    }
  }
   
  cont++;
  exp_lc.send();
}
 
 
void setup() {
  //Serial.begin(9600);
  exp_lc.begin();
  exp_lc.shutdown(0,false);
  exp_lc.setIntensity(0,8);
  exp_lc.clearDisplay(0);  
  exp_lc.shutdown(1,false);
  exp_lc.setIntensity(1,8);
  exp_lc.clearDisplay(1);  
}
 
void loop() {
  for (byte b=0; b<=255; b++) {                    //i=0-> B00000000   i=255-> B11111111
    exp_595.startWrite();
    exp_595.writeByte(0, b, LSBFIRST);
    exp_595.send();
 
    update_displays();
    
    delay(500);
  }  
}