Publicidade:

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);
  }  
}





segunda-feira, 28 de setembro de 2015

Arduino - Hack da parte frontal de uma impressora HP Deskjet F4280




Nesse fim de semana peguei uma impressora velha, uma HP Deskjet F4280 e tentei ver o que dava pra fazer com o display. Analisando os componentes que haviam na placa, identifiquei 3 registradores de deslocamento 595 - clique para ver o datasheet - (expansores de saídas) e 1 165 - clique para ver o datasheet - (expansor de entrada). Essa placa se comunicava com outra placa, através de um cabo flat através de 7 vias. Com ajuda de um multímetro identifiquei onde cada uma das vias estava chegando no circuito:
  • 1 - pino 11 do 595 (SH_CP) e pino 2 do 165 (CLOCK) 
  • 2 - gnd
  • 3 - pino 9 do 165 (QH)
  • 4 - pino 14 do 595 (DS)
  • 5 - vcc
  • 6 - pino 12 do 595 (ST_CP) e pino 1 do 165 (LOAD)
  • 7 - botão on/off - ligado diretamente

Com os pinos identificados, cortei com um tesoura um das extremidades do cabo flat e separei cada uma das trilhas. Com as pontas separadas, queimei o plástico que fica em volta do cabo flat para que apenas a trilha ficasse exposta, depois com um bombril removi os resíduos de plástico queimado dos terminais. Depois soldei essas trilhas em uma pequena placa, de modo que fosse possível conecta-las com o arduino através de jumpers. 


--vou colocar uma foto de como foi feito


conforme as trilhas identificadas e numeradas acima, fiz a seguinte ligações no arduino:

  • 1  --> arduino : pino 8
  • 2  --> arduino : gnd
  • 3  --> arduino : pino 2
  • 4  --> arduino : pino 9
  • 5  --> arduino : vcc
  • 6  --> arduino : pino 10
  • 7  --> arduino : não utilizado nesse exemplo. mas poderia ser ligado como qualquer pino e configura-lo como entrada


O próximo passo foi identificar como cada botão e cada led estavam sendo controlados. Os botões são controlados pelo único 165, e ao lado de cada botão (na cor preta) mostro qual o bit que é usado para representá-lo. Os outros 3 595 estão ligados em cascata, e têm suas saídas ligadas nos leds conforme numerados abaixo. O primeiro 595 está na cor Azul, o segundo na cor Vermelha e o terceira na Cor Verde.



Com tudo identificado, o próximo passo foi fazer a programação, pra isso aproveitei uma classe que eu já havia criado pra se comunicar com os registradores 595 e 165 juntos (veja o artigo aqui). Porém, o esquema desse display, não está exatamente da forma que mostrei no artigo citado, mas ainda assim deu pra aproveitar a classe, apenas fazendo algumas modificações. A principal delas, foi que quando é enviado HIGH para acionar um led, os leds são apagados.

Código-fonte:

/*
Criado em 27/09/2015
 Por: 
   Fabiano A. Arndt 
   http://www.youtube.com/fabianoallex
   http://fabianoallex.blogspot.com.br
   fabianoallex@gmail.com
*/
 
/********************************************************************************************
*******************CLASSE Expansor74HC595_74HC165     INICIO*********************************
*********************************************************************************************/
 
class Expansor74HC595_74HC165 {
  private:
    int  _pin_clock;
    int  _pin_latch;
    int  _pin_data;
    byte* _pins_out;
    byte* _pins_in;
    int _num_cis_out;
    int _num_cis_in;
  public:
    Expansor74HC595_74HC165(int pin_clock, int pin_latch, int pin_data, int num_cis_out, int num_cis_in){
      _pin_clock = pin_clock;
      _pin_latch = pin_latch;
      _pin_data  = pin_data;
       
      _num_cis_out = num_cis_out;
      _num_cis_in  = num_cis_in;
       
      _pins_out    = new byte[num_cis_out];
      _pins_in     = new byte[num_cis_in];
       
      pinMode(_pin_clock,OUTPUT);
      pinMode(_pin_latch,OUTPUT);
       
      clear();
    }
    
    void clear(){
      for (int i=0; i<_num_cis_out; i++){ _pins_out[i] = B11111111; }
      update();
    }
    
    void update(){
      digitalWrite(_pin_clock, LOW); 
      digitalWrite(_pin_latch, LOW); 
      digitalWrite(_pin_latch, HIGH);
       
      for(int i=max(_num_cis_in, _num_cis_out) * 8 - 1; i>=0;  i-- ) {   //max -->o for vai até o que tiver mais, ou entradas, ou saidas
        int pos = i / 8;
        int pin = 7-(i % 8);  
         
        if (i < _num_cis_in * 8){
          pinMode(2, INPUT);
         
          if ( digitalRead(2) ){
            _pins_in[pos] |= (1 << pin);  //set a bit HIGH
          } else { 
            _pins_in[pos] &= ~(1 << pin); //set a bit LOW
          }
        }
         
        if (i < _num_cis_out * 8){
          pinMode(_pin_data, OUTPUT);
          digitalWrite(_pin_data,   (_pins_out[pos] & (1 << pin)) != 0   );
        }        
        digitalWrite(_pin_clock, HIGH); 
        digitalWrite(_pin_clock, LOW); 
      }      
      digitalWrite(_pin_latch, LOW); 
      digitalWrite(_pin_latch, HIGH);
      //pinMode(_pin_data, INPUT);   //removido
    }
     
    int read(int  pin){
      int pos = pin / 8;
      pin     = 7-(pin % 8);  
       
      if (pos > _num_cis_out) {
        pos = pos - _num_cis_out;
        return (  (_pins_in[pos] & (1 << pin)) != 0   );
      } else {
        return (  (_pins_out[pos] & (1 << pin)) != 0   );
      }
    }
     
    byte readByte(int num_ci) { 
      if (num_ci >= _num_cis_out) {
        num_ci = num_ci - _num_cis_out;
        return _pins_in[num_ci];
      } else {
        return _pins_out[num_ci];
      }
    }
     
    void write(int pin, int value){
      if (pin >= _num_cis_out*8) { return; }
       
      int pos = pin / 8;  //pos -> indica qual ci será atualizado.
      pin     = 7-(pin % 8);
  
      if (pos > _num_cis_out) {
        return; //se estiver tentando escrever um pino de entrada, apenas retorna, sem fazer nada.
      } else {
        if (value){
          _pins_out[pos] |= (1 << pin);  //set a bit HIGH
        } else {
          _pins_out[pos] &= ~(1 << pin); //set a bit LOW
        }
      }
    }
     
    void writeByte(int num_ci, byte b, int first = MSBFIRST) {  
      if (num_ci > _num_cis_out) {
        return; //se estiver tentando escrever um pino de entrada, apenas retorna, sem fazer nada.
      }
       
      if (first == LSBFIRST) {
        byte r=0;
        for(int i=0;i<8;i++) {
          r |= ((b>>i) & 0b1)<<(7-i);
        }
        b = r;
      }
        
      _pins_out[num_ci] = b; 
    } ;
};
 
/********************************************************************************************
*******************CLASSE Expansor74HC595_74HC165     FIM ***********************************
*********************************************************************************************/
 
 
const int PIN_CLOCK    = 8; 
const int PIN_LATCH    = 10; 
const int PIN_DATA_595 = 9;
const int PIN_DATA_165 = 2;
 
  
Expansor74HC595_74HC165 * exp1;  //controla as saídas
Expansor74HC595_74HC165 * exp2;  //controla as entradas
   
void setup() {
  exp1   = new Expansor74HC595_74HC165(PIN_CLOCK, PIN_LATCH, PIN_DATA_595, 3, 0);
  exp2   = new Expansor74HC595_74HC165(PIN_CLOCK, PIN_LATCH, PIN_DATA_165, 0, 3);
   
   
  exp1->writeByte(0, B11111111);
  exp1->writeByte(1, B11111111);
  exp1->writeByte(2, B11111111);
   
  Serial.begin(9600);
}


byte digitos[] = {
  B01000000,
  B01011110,
  B00100100,
  B00001100,
  B00011010,
  B10001000,
  B10000000,
  B01011100,
  B00000000,
  B00001000,
  B00010000,
  B10000010,
  B11100000,
  B00000110,
  B10100000,
  B10110000
};

byte leds[] = {20, 21, 22, 23, 4, 19, 18, 17, 16, 5};

unsigned int dig=0;
unsigned int opt=0;
unsigned int led=0;

void loop() {
  
  
  exp2->update(); //primeiro verifica o estado dos botões
  
  
  
  if (!exp2->read(5+16)) { exp1->write(15, !exp1->read(15)); }
  if (!exp2->read(2+16)) { exp1->write(6,  !exp1->read(6)); }
  
  
  if (!exp2->read(0+16)) { dig++; }
  if (!exp2->read(4+16)) { dig--; }
  byte temp = exp1->read(15);
  exp1->writeByte(1, digitos[dig%16]);
  exp1->write(15, temp);
  
  
  if (!exp2->read(6+16)) { opt++; }
  exp1->write(1, opt%3+1==1);
  exp1->write(2, opt%3+1==2);
  exp1->write(3, opt%3+1==3);
  
  
  if (!exp2->read(3+16)) { 
    for (int i=0; i<10; i++){
      exp1->write(leds[i], led%10!=i);
    }
    led++;
  }
  
  
  
  exp1->update();  //agora envia o estado dos leds
  
  
    
  Serial.println(exp1->readByte(0), BIN);  //LE O PRIMEIRO CI (595)
  Serial.println(exp1->readByte(1), BIN);  //LE O SEGUNDO CI (595)
  Serial.println(exp1->readByte(2), BIN);  //LE O TERCEIRO CI (165)
  Serial.println(exp2->readByte(2), BIN);  //LE O QUARTO CI (165)
  Serial.println("");
   
  delay(100);
  
  
}