Publicidade:

sexta-feira, 5 de junho de 2015

Arduino - 74HC595 e 74HC165 Juntos

Quando se fala em expansão de portas do Arduino, uma das possibilidades é fazer isso através desses dois CIs, o 74HC595 e o 74HC165. Pra quem ainda não os conhece, são dois CIs que com apenas três pinos do Arduino permitem expandir saídas (595) e entradas (165). Tanto um quanto o outro, permitem que sejam conectados vários CIs em cascata, o que dá a possibilidade de infinitas portas.

Datasheets aqui:

http://www.nxp.com/products/logic/shift_registers/74HC595N.html
http://www.nxp.com/products/logic/shift_registers/74HC165N.html

Pra quem quiser ver um exemplo de como expandir apenas saídas, utilizando apenas 74HC595, criei um classe que permite o uso de maneira bem facilitada, que pode ser visto nesse link aqui.

Nesse post de hoje, vou demonstrar como criar uma Classe no Arduino, que permita nos mesmos 3 pinos ligar 595 e 165 juntos, e serem acessados compartilhando os mesmos 3 pinos, Latch, Clock e Data (sim, o pino Data é o mesmo para entradas e saídas :D ).

Como meu foco é mais disponibilizar o código que facilite a programação, não vou entrar muito nos detalhes da implementação, mas vou deixar aqui o link para o site no qual me baseei para chegar nessa solução:

http://homepages.which.net/~paul.hills/Software/ShiftRegister/ShiftRegisterBody.html

A imagem abaixo foi retirado do site acima, e demonstra como fazer a ligação entre os CIs.


Obs: output enable (que é ligado ao pino 13 do 595) não precisa ser ligado ao Arduino. Os Pinos 13 dos 595s devem ser ligado diretamente ao Ground. Outro detalhes é que o pino identificado como Strobe no esquema acima, no arduino é identificado como Latch.

As principais regras ao ligar são:

 - Primeiro devem ser ligados os 595 e após o último 595, ligar os 165.
 - Não ligar os CIs intercalados.
 - Não há limites para quantidades de cada um, podendo ser apenas um ou outro. Mas para ser usado apenas um dos CIs, recomendo utilizar a classe só para 595, que mostrei acima. Em breve criarei uma classe ser usado apenas por 165.
 - Todas as entradas dos CIs 165 devem obrigatoriamente ser ligadas a resistores PULL DOWN ou PULL UP. Caso alguma entrada fique "em aberto" a entrada ficará flutuando, inclusive interferindo nas demais entradas do CI.

Para testar o circuito acima, montei o mesmo em uma protoboard, mas não liguei todas as saídas e entradas, pois deixaria o circuito muito poluído e com muitas conexões, então preferi focar nas ligações de conexões dos próprios componentes.



Mas para testar as saídas liguei apenas um led na protoboard, onde testo uma saída por vez, como poderá ser visto no vídeo que postarei em breve. Nos Caso das entradas, liguei algumas diretamente ao vcc e outras diretamente ao ground.

Identificação dos pinos

No exemplo acima, foi utilizado 4 cis, dois 595 e dois 165.

Cada um dos pinos é identificado por um número, começando de 0 (zero) e indo até a quantidade de CIs multiplicado por 8 menos 1. No nosso exemplo temos 32 pinos adicionais, sendo os pinos de 0 a 15 os pinos de saídas e os pinos de 16 a 31 os pinos de entradas.

Se for ligado um botão no último pino do ultimo CI 165, faremos a leitura da seguinte maneira:

  int valor = exp->read(31);

Já para leitura de todas as entradas de uma única vez de um determinado CI 165, faremos o seguinte:

  byte valor = exp->readByte(3);  //leitura de todos os bits do quarto CI 

Da mesma maneira que é possível fazer a leitura de um bit por vez ou vários de uma vez, é possível ainda fazer a escrita da mesma forma, utilizando os métodos write() e writeByte().   exemplo:

exp->writeByte(1, B00011001); ou 
exp->writeByte(1, B10011000, LSBFIRST); 

exp->read(9, HIGH); ou
exp->read(9, LOW); 

Tanto os métodos write, writeByte, read e readByte, manipulam apenas os dados que estão em cache na classe. Para que as leituras e escritas tenham efeito é necessário chamar o método update(). Ele é quem envia e recebe os dados para os CIs.

o método update() deve ser chamado antes de serem feitas as leituras das entradas, e após serem feitas as escritas nas saídas.

Vídeo:



Código-Fonte:

/*
Criado em 04/06/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] = B00000000;
      }
      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(_pin_data, INPUT);
          
          if ( digitalRead(_pin_data) ){
            _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);
    }
    
    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 = 4; 
const int PIN_LATCH = 7; 
const int PIN_DATA  = 12; 

 
Expansor74HC595_74HC165 * exp1;
  
void setup() {
  exp1   = new Expansor74HC595_74HC165(PIN_CLOCK, PIN_LATCH, PIN_DATA, 2, 2);
  
  Serial.begin(9600);
}
  
void loop() {
  exp1->writeByte(0, B00000000);
  exp1->writeByte(0, B11001101, LSBFIRST);
  
  exp1->write(8, HIGH);
  exp1->write(9, HIGH);
  
  exp1->write(10, HIGH);
  exp1->write(11, HIGH);
  
  exp1->write(14, HIGH);
  exp1->write(15, !exp1->read(15));  //blink
  
  
    
  exp1->update();  //AS ESCRITAS DEVEM SER FEITAS ANTES DO UPDATE. E AS LEITURAS APOS O UPDATE
   
  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(exp1->readByte(3), BIN);  //LE O QUARTO CI (165)
  Serial.println("");
  
  delay(1000);
}

terça-feira, 2 de junho de 2015

Arduino - Classe para 74HC595 Corrigida


Nesse post (clique aqui) foi mostrado como criar uma classe pra facilitar o uso do registrador de deslocamento 74HC595 com o Arduino.

Apesar de funcionar perfeitamente, a classe ocupava muita memória para armazenar o estados de todas as saídas.

o código abaixo, foi reescrito de forma a ocupar menos memória e ser mais simples. Alguns métodos foram renomeados.

segue código-fonte:


class Expansor74HC595 {
  private:
    int  _pin_clock;
    int  _pin_latch;
    int  _pin_data;
    int  _num_cis;
    byte* _pins;
    int  _auto_send;
  public:
    Expansor74HC595(int pin_clock, int pin_latch, int pin_data, int num_cis){
      _pin_clock        = pin_clock;
      _pin_latch        = pin_latch;
      _pin_data         = pin_data;
      _num_cis          = num_cis;
      _auto_send        = true;
      _pins             = new byte[_num_cis];

      pinMode(_pin_clock,OUTPUT);
      pinMode(_pin_latch,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 send(){
      
      digitalWrite(_pin_latch, LOW); 
      for(int i=_num_cis-1; i>=0; i--) {  shiftOut(_pin_data, _pin_clock, MSBFIRST, readByte(i) );  }
      digitalWrite(_pin_latch, HIGH);
      _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(); }
    };
};
 

const int PIN_CLOCK = 2; 
const int PIN_LATCH = 3; 
const int PIN_DATA  = 4; 

Expansor74HC595 *exp1;
 
void setup() {
  exp1   = new Expansor74HC595(PIN_CLOCK, PIN_LATCH, PIN_DATA, 3);
  
  Serial.begin(9600);
}
 
void loop() {
  for (byte b=0; b<=255; b++) {                    //i=0-> B00000000   i=255-> B11111111
    exp1->startWrite();
    exp1->writeByte(0, b);
    exp1->writeByte(1, b, LSBFIRST);
    exp1->write(23, HIGH);
    exp1->send();
  
    delay(500);
  }
  
  exp1->clear();
  delay(500); 
}

sábado, 30 de maio de 2015

Arduino - Digitar Texto com Controle Remoto + LCD

No post anterior mostrei como digitar texto em um display LCD com um teclado matricial 4x3. Nesse vídeo mostro como fazer a mesma coisa, mas utilizando um controle remoto.


Código-Fonte:

obs. 01: O Controle remoto utilizado foi o da Claro TV. Caso for usar outro tipo de controle remoto, é necessário descobrir a codificação de cada uma das teclas e fazer as alterações na função get_tecla. Esse controle remoto, retorna dois códigos diferentes para a mesma tecla (em alguns casos), como pode ser visto no código. 

obs. 02: o controle remoto ao ser mantido pressionado, não fica enviando a informação de maneira continua, então o arduino, por um pequeno período de tempo, entende que o botão foi solto e pressionado várias vezes, causando um  comportamento indesejado, por isso foi criada a variável global time_read = 150;  150, significa que 150 milissegundos após o botão ser "solto" a função get_tecla, ainda vai continuar retornando o ultimo caractere pressionado. é possível que em diferentes controles remotos, esse tempo varie. Não fiz nenhum teste com outros controles, mas que for usar, é interessante testar isso antes.

obs. 03: é preciso baixar e instalar a biblioteca IRRemote. Não vou entrar em detalhes aqui de como instalar e configurar a biblioteca, pois uma rápida busca no google, certamente irá ajudar.

#include <IRremote.h> 
const int RECV_PIN = 11;
unsigned long last = millis();
 
IRrecv irrecv(RECV_PIN);
decode_results results;

unsigned long millis_last_read = 0;
char char_last_read = '\0';
unsigned long time_read = 150;

char get_tecla() {
  char c = '\0';
  
  if ( (millis() - time_read) > millis_last_read ){
    if (irrecv.decode(&results)) {
      if (results.value == 2152998656 || results.value == 2153031424){ c = '0'; }  
      if (results.value == 2152998657 || results.value == 2153031425){ c = '1'; }
      if (results.value == 2152998658 || results.value == 2153031426){ c = '2'; }
      if (results.value == 2152998659 || results.value == 2153031427){ c = '3'; }
      if (results.value == 2152998660 || results.value == 2153031428){ c = '4'; }
      if (results.value == 2152998661 || results.value == 2153031429){ c = '5'; }
      if (results.value == 2152998662 || results.value == 2153031430){ c = '6'; }
      if (results.value == 2152998663 || results.value == 2153031431){ c = '7'; }
      if (results.value == 2152998664 || results.value == 2153031432){ c = '8'; }
      if (results.value == 2152998665 || results.value == 2153031433){ c = '9'; }
      if (results.value == 551538735)                                { c = '*'; }
      if (results.value == 2152998666 || results.value == 2153031434){ c = '#'; }
        
      irrecv.resume(); // Receive the next value
      
      millis_last_read = millis();
      char_last_read = c;
    }
  } else {
    c = char_last_read;
  }
  
  //Serial.println(c);
  
  return c;
}
 
 
const char teclas[][12] = {"1 ", "2abc", "3def", "4ghi", "5jkl", "6mno", "7pqrs", "8tuv", "9wxyz", "*", "0", "#"};
const unsigned long time_char = 1200;  //1200 milissegundos pra desconsiderar a ultima tecla
 
class TecladoTelefone{
  private:
    unsigned long _millis_last_char;
    char _last_char;
    String _palavra;
     
    void _set_last_char(char c, int ind_palavra){
      if ( ind_palavra == 1 && _last_char != '\0' ) {
        _palavra += _last_char;
      }
 
      _last_char = c;
      _millis_last_char = millis();
    }
     
    void _back_space(){
      if (_palavra.length() >= 1){
        _last_char = _palavra[_palavra.length()-1];
        _palavra   = _palavra.substring(0, _palavra.length()-1);
      } else {
        _last_char = '\0';
      }
    }
     
  public:
    TecladoTelefone(){
      _millis_last_char = millis();
      _last_char = '\0';
    }
     
    int is_timing(){
      return ( (millis() - time_char) < _millis_last_char );
    }
   
    void add(char c){
      int pos = -1;
       
      if (c == '1'){ pos = 0;}
      if (c == '2'){ pos = 1;}
      if (c == '3'){ pos = 2;}
      if (c == '4'){ pos = 3;}
      if (c == '5'){ pos = 4;}
      if (c == '6'){ pos = 5;}
      if (c == '7'){ pos = 6;}
      if (c == '8'){ pos = 7;}
      if (c == '9'){ pos = 8;}
      if (c == '*'){ pos = 9;}
      if (c == '0'){ pos = 10;}
      if (c == '#'){ pos = 11;}
       
      if (pos == -1){ return; }
       
      if (pos == 9){
        _back_space();
        return;
      }
       
      const char * t = teclas[pos];
       
      if ( is_timing() ) {
         
        int i = 0;
        while (t[i] != '\0'){
          if (_last_char == t[i]){
            _set_last_char(  (t[i+1] == '\0') ? _last_char = t[0] : _last_char = t[i+1] , 0 );
            return ;
          }
          i++;
        }
      }
       
      _set_last_char ( c, 1);
    }
     
    char get_last_char(){ return _last_char; }
     
    String get_palavra( ) {
       
      if (_last_char != '\0') {
        return _palavra + _last_char;
      }
       
      return _palavra;
    }
};
 
 
#include <LiquidCrystal.h> 
  
LiquidCrystal lcd(9, 8, 10, 7, 6, 5); //Configura os pinos do Arduino para se comunicar com o LCD
 
 
TecladoTelefone * teclado;
 
void setup() {
  Serial.begin(9600);
  irrecv.enableIRIn(); 
  teclado = new TecladoTelefone();
  lcd.begin(16, 2); 
}
 
char tecla_anterior = '\0';
 
void loop() {
  char tecla = get_tecla();
   
  if (tecla != tecla_anterior){
    if (tecla) {
      teclado->add(tecla);
    }
  }
   
  tecla_anterior = tecla;
   
  lcd.clear();
  lcd.setCursor(0, 0); 
  lcd.print(teclado->get_palavra());
  lcd.cursor();
  lcd.setCursor(teclado->get_palavra().length() - (teclado->is_timing() ? 1 : 0 ), 0);
  delay(10);
  lcd.noCursor();    
}

sexta-feira, 29 de maio de 2015

Arduino - Teclado 4x3 + texto + lcd

Esse vídeo é continução do vídeo anterior, onde tinha demonstrado como digitar texto com teclado matricial, parecido com de telefone celular.

Vídeo anterior, com mais detalhes sobre a montagem do teclado:
http://fabianoallex.blogspot.com.br/2015/05/arduino-texto-com-teclado-matricial-4x3.html

Nessa versão implementei a função de backspace e mostrei o resultado num display LCD.



código-fonte:


#define col1 10 
#define col2 11 
#define col3 12 
#define lin1 3 
#define lin2 4
#define lin3 5
#define lin4 6 


char get_tecla(){
  int l[]={lin1, lin2, lin3, lin4}; // Array de 4 posições contendo os 4 pinos de linhas
  int i = 0, k = 0, t = 0;
  
  for (i=0; i<4; i++) {
    digitalWrite(lin1,LOW); 
    digitalWrite(lin2,LOW);
    digitalWrite(lin3,LOW);
    digitalWrite(lin4,LOW);
    digitalWrite(l[i],HIGH); 
    
    if(digitalRead(col1)) { t = i*3+1; break; }
    if(digitalRead(col2)) { t = i*3+2; break; }
    if(digitalRead(col3)) { t = i*3+3; break; }
  }
  
  if (t > 0 ){
    if (t >= 1 && t<=9){ return char(t+48);   }  //48--> ASCII: o charactere '1' na tabela ascii é 49º item, o '2' é o 50º item e assim por diante
    if (t==10)         { return '*'; }
    if (t==11)         { return '0'; }
    if (t==12)         { return '#'; }
  }
  
  return '\0';
}


const char teclas[][12] = {"1 ", "2abc", "3def", "4ghi", "5jkl", "6mno", "7pqrs", "8tuv", "9wxyz", "*", "0", "#"};
const unsigned long time_char = 1200;  //1200 milissegundos pra desconsiderar a ultima tecla

class TecladoTelefone{
  private:
    unsigned long _millis_last_char;
    char _last_char;
    String _palavra;
    
    void _set_last_char(char c, int ind_palavra){
      if ( ind_palavra == 1 && _last_char != '\0' ) {
        _palavra += _last_char;
      }

      _last_char = c;
      _millis_last_char = millis();
    }
    
    void _back_space(){
      if (_palavra.length() >= 1){
        _last_char = _palavra[_palavra.length()-1];
        _palavra   = _palavra.substring(0, _palavra.length()-1);
      } else {
        _last_char = '\0';
      }
    }
    
  public:
    TecladoTelefone(){
      _millis_last_char = millis();
      _last_char = '\0';
    }
    
    int is_timing(){
      return ( (millis() - time_char) < _millis_last_char );
    }
  
    void add(char c){
      int pos = -1;
      
      if (c == '1'){ pos = 0;}
      if (c == '2'){ pos = 1;}
      if (c == '3'){ pos = 2;}
      if (c == '4'){ pos = 3;}
      if (c == '5'){ pos = 4;}
      if (c == '6'){ pos = 5;}
      if (c == '7'){ pos = 6;}
      if (c == '8'){ pos = 7;}
      if (c == '9'){ pos = 8;}
      if (c == '*'){ pos = 9;}
      if (c == '0'){ pos = 10;}
      if (c == '#'){ pos = 11;}
      
      if (pos == -1){ return; }
      
      if (pos == 9){
        _back_space();
        return;
      }
      
      const char * t = teclas[pos];
      
      if ( is_timing() ) {
        
        int i = 0;
        while (t[i] != '\0'){
          if (_last_char == t[i]){
            _set_last_char(  (t[i+1] == '\0') ? _last_char = t[0] : _last_char = t[i+1] , 0 );
            return ;
          }
          i++;
        }
      }
      
      _set_last_char ( c, 1);
    }
    
    char get_last_char(){ return _last_char; }
    
    String get_palavra( ) {
      
      if (_last_char != '\0') {
        return _palavra + _last_char;
      }
      
      return _palavra;
    }
};


#include <LiquidCrystal.h> 
 
LiquidCrystal lcd(9, 8, 13, 2, 1, 0); //Configura os pinos do Arduino para se comunicar com o LCD


TecladoTelefone * teclado;

void setup() {
  //Serial.begin(9600);
  
    // colunas INPUT
  pinMode(col1,INPUT);
  pinMode(col2,INPUT);
  pinMode(col3,INPUT);
  
  // linhas OUTPUT 
  pinMode(lin1,OUTPUT); 
  pinMode(lin2,OUTPUT);
  pinMode(lin3,OUTPUT); 
  pinMode(lin4,OUTPUT); 
  
  teclado = new TecladoTelefone();
  
  lcd.begin(16, 2); 
}

char tecla_anterior = '\0';

void loop() {

  char tecla = get_tecla();
  
  if (tecla != tecla_anterior){
    if (tecla) {
      teclado->add(tecla);
    }
  }
  
  tecla_anterior = tecla;
  
  lcd.clear();
  lcd.setCursor(0, 0); 
  lcd.print(teclado->get_palavra());
  lcd.cursor();
  lcd.setCursor(teclado->get_palavra().length() - (teclado->is_timing() ? 1 : 0 ), 0);
  delay(10);
  lcd.noCursor();    
}