Publicidade:

terça-feira, 17 de novembro de 2015

Arduino - Leitor de Cartão Magnético (Cartão de Crédito)

Antes de partimos para o código vamos entender como funciona um cartão magnético. Cartões magnéticos, aqueles usados por cartões de crédito, na grande maioria, são cartões que possuem uma banda, geralmente da cor preta (mas podem ser encontrada em outras cores) onde ficam armazenados os dados que são lidos pelos leitores.

Nesses Cartões os dados são divididos em três trilhas de dados medindo aproximadamente 2,5 milimetros cada uma.Existe uma normativa, a ISO/IEC 7811 a qual define o conteúdo e formato de cada uma das trilhas:

Trilha 1: tem formato de 210 bpi (bits por polegar) e contém 79 caracteres cada um composto por 6 bits mais um de paridade. É uma trilha só de leitura.

Trilha 2: tem formato de 75 bpi e contém 40 caracteres cada um composto por 4 bits mais um de paridade.

Trilha 3: tem formato 210 bpi e contém 107 caracteres de 4 bit mais um de paridade.

Em geral a maioria dos cartões de crédito utiliza somente as duas primeiras trilhas. A trilha três contém às vezes PINs, códigos de país, limites autorizados, códigos e números de acordos ou parceiras (milhagens, descontos em lojas), etc. mas o seu uso não é regulado de forma homogênea entre bancos e operadoras. Vale notar que vários dados são repetidos ou duplicados nas trilhas um e dois.

O conteúdo da trilha um ainda não está totalmente padronizado entre as empresas.
O primeiro padrão de escritura da trilha um dos cartões foi desenvolvido pela IATA (Associação Internacional das Empresas Aéreas).
Existe um segundo padrão estabelecido pela ABA (American Bankers Association), que a maioria das empresas de cartões de crédito utiliza, mas podem existir exceções.
As informações que normalmente se encontram na trilha um dos cartões de créditos são as seguintes:

Sinal de Início - um caráter (normalmente "%")
Código de Formato - um caráter alfabético (normalmente "B")
Número de Conta Primário - até 19 caracteres
Separador - um caráter (normalmente "^")
Código de País - três caracteres
Nome - de dois até 26 caracteres
Separador - um caráter
Data de vencimento ou separador - quatro caracteres ou um caráter
Informações livres - número de caracteres até completar a capacidade da trilha (79 caracteres no total)
Sinal de Fim - um caráter (normalmente "?")
Caráter de controle (LRC) - um caráter

A trilha dois foi inicialmente padronizada pela ABA (American Bankers Association).
É a trilha mais padronizada para cartões de crédito e a que todos os sistemas utilizam sempre. Os dados contidos na trilha dois são os seguintes:

Sinal de Início - um caráter (normalmente ";")
Número de Conta Primário - até 19 caracteres
Separador - um caráter (normalmente "=")
Código de País - três caracteres
Data de vencimento ou separador - quatro caracteres ou um caráter
Informações livres - número de caracteres até completar a capacidade da trilha (40 caracteres no total)
Sinal de Fim - um caráter (normalmente "?")
Caráter de controle (LRC) - um caráter


O leitor que estou utilizando para estes testes possui 9 pinos, como pode ser verificado na figura abaixo. Cada uma das trilhas utiliza dois pinos, um pra data e outro pra clock. Além dos pinos Media Detect que é utilizado para indicar que há cartão na leitora e os pinos de vcc e ground. Os Pinos de Data e Clock, devem ser ligados nos pinos de interrupções externas do arduino. A Classe que disponibilizei já faz todo esse trabalho. Apesar de termos 9 pinos, na prática podemos dispensar 4 pinos, pois faremos a leitura de apenas uma trilha, a trilha 1, a trilha 2 ou a trilha 3, o que irá depender do projeto em questão.


Observações: Nos meus testes tentei fazer a leitura simultânea de todos as trilhas mas sem sucesso, por isso disponibilizei a classe de modo que apenas uma trilha seja lida. Basta o programador escolher a trilha que deseja ler.








Vídeo:







código-fonte

o exemplo original foi baseado no código linkado no fim do artigo, a partir do qual criei uma classe, para facilitar o uso do código.

Durante os testes percebi que se passar o cartão apenas até a metade e voltar, o leitor faz uma leitura truncada, não retornando o que realmente está na trilha. A Classe que eu fiz não faz essa validação, ficando a cargo do programador que utilizá-la a tratar desse detalhe, o que irá depender do tipo de projeto ao qual o leitor está sendo usado.


BitArray.h - Classe que gerencia a leitura de bits. 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 ); }
 
};


MyInterrupt.h - Classe base para tratar interrupções dentro da própria classe. Mais informações aqui: http://fabianoallex.blogspot.com.br/2015/11/arduino-como-tratar-interrupcoes.html

 /* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */
 
/*
  Part of the Wiring project - http://wiring.uniandes.edu.co
 
  Copyright (c) 2004-05 Hernando Barragan
 
  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.
 
  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.
 
  You should have received a copy of the GNU Lesser General
  Public License along with this library; if not, write to the
  Free Software Foundation, Inc., 59 Temple Place, Suite 330,
  Boston, MA  02111-1307  USA
   
  Modified 24 November 2006 by David A. Mellis
  Modified 1 August 2010 by Mark Sproul
*/
 
#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <stdio.h>
 
#include "wiring_private.h"
 
class Interrupt;  //declaracao previa
void attachInterrupt(uint8_t interruptNum, Interrupt * interruptObject, int mode); //declaracao previa
 
class Interrupt {
  public:
    void attach(uint8_t interruptNum, int mode) { attachInterrupt(interruptNum, this, mode); };
    void volatile virtual execInterrupt(uint8_t interruptNum);
};
 
Interrupt * interruptObjects[EXTERNAL_NUM_INTERRUPTS];
//static volatile voidFuncPtr intFunc[EXTERNAL_NUM_INTERRUPTS];
// volatile static voidFuncPtr twiIntFunc;
 
void attachInterrupt(uint8_t interruptNum, Interrupt * interruptObject, int mode) {
   
  if(interruptNum < EXTERNAL_NUM_INTERRUPTS) {
    interruptObjects[interruptNum] = interruptObject;
     
    // Configure the interrupt mode (trigger on low input, any change, rising
    // edge, or falling edge).  The mode constants were chosen to correspond
    // to the configuration bits in the hardware register, so we simply shift
    // the mode into place.
       
    // Enable the interrupt.
       
    switch (interruptNum) {
#if defined(__AVR_ATmega32U4__)
 // I hate doing this, but the register assignment differs between the 1280/2560
 // and the 32U4.  Since avrlib defines registers PCMSK1 and PCMSK2 that aren't 
 // even present on the 32U4 this is the only way to distinguish between them.
    case 0:
 EICRA = (EICRA & ~((1<<ISC00) | (1<<ISC01))) | (mode << ISC00);
 EIMSK |= (1<<INT0);
 break;
    case 1:
 EICRA = (EICRA & ~((1<<ISC10) | (1<<ISC11))) | (mode << ISC10);
 EIMSK |= (1<<INT1);
 break; 
    case 2:
        EICRA = (EICRA & ~((1<<ISC20) | (1<<ISC21))) | (mode << ISC20);
        EIMSK |= (1<<INT2);
        break;
    case 3:
        EICRA = (EICRA & ~((1<<ISC30) | (1<<ISC31))) | (mode << ISC30);
        EIMSK |= (1<<INT3);
        break;
    case 4:
        EICRB = (EICRB & ~((1<<ISC60) | (1<<ISC61))) | (mode << ISC60);
        EIMSK |= (1<<INT6);
        break;
#elif defined(EICRA) && defined(EICRB) && defined(EIMSK)
    case 2:
      EICRA = (EICRA & ~((1 << ISC00) | (1 << ISC01))) | (mode << ISC00);
      EIMSK |= (1 << INT0);
      break;
    case 3:
      EICRA = (EICRA & ~((1 << ISC10) | (1 << ISC11))) | (mode << ISC10);
      EIMSK |= (1 << INT1);
      break;
    case 4:
      EICRA = (EICRA & ~((1 << ISC20) | (1 << ISC21))) | (mode << ISC20);
      EIMSK |= (1 << INT2);
      break;
    case 5:
      EICRA = (EICRA & ~((1 << ISC30) | (1 << ISC31))) | (mode << ISC30);
      EIMSK |= (1 << INT3);
      break;
    case 0:
      EICRB = (EICRB & ~((1 << ISC40) | (1 << ISC41))) | (mode << ISC40);
      EIMSK |= (1 << INT4);
      break;
    case 1:
      EICRB = (EICRB & ~((1 << ISC50) | (1 << ISC51))) | (mode << ISC50);
      EIMSK |= (1 << INT5);
      break;
    case 6:
      EICRB = (EICRB & ~((1 << ISC60) | (1 << ISC61))) | (mode << ISC60);
      EIMSK |= (1 << INT6);
      break;
    case 7:
      EICRB = (EICRB & ~((1 << ISC70) | (1 << ISC71))) | (mode << ISC70);
      EIMSK |= (1 << INT7);
      break;
#else  
    case 0:
    #if defined(EICRA) && defined(ISC00) && defined(EIMSK)
      EICRA = (EICRA & ~((1 << ISC00) | (1 << ISC01))) | (mode << ISC00);
      EIMSK |= (1 << INT0);
    #elif defined(MCUCR) && defined(ISC00) && defined(GICR)
      MCUCR = (MCUCR & ~((1 << ISC00) | (1 << ISC01))) | (mode << ISC00);
      GICR |= (1 << INT0);
    #elif defined(MCUCR) && defined(ISC00) && defined(GIMSK)
      MCUCR = (MCUCR & ~((1 << ISC00) | (1 << ISC01))) | (mode << ISC00);
      GIMSK |= (1 << INT0);
    #else
      #error attachInterrupt not finished for this CPU (case 0)
    #endif
      break;
 
    case 1:
    #if defined(EICRA) && defined(ISC10) && defined(ISC11) && defined(EIMSK)
      EICRA = (EICRA & ~((1 << ISC10) | (1 << ISC11))) | (mode << ISC10);
      EIMSK |= (1 << INT1);
    #elif defined(MCUCR) && defined(ISC10) && defined(ISC11) && defined(GICR)
      MCUCR = (MCUCR & ~((1 << ISC10) | (1 << ISC11))) | (mode << ISC10);
      GICR |= (1 << INT1);
    #elif defined(MCUCR) && defined(ISC10) && defined(GIMSK) && defined(GIMSK)
      MCUCR = (MCUCR & ~((1 << ISC10) | (1 << ISC11))) | (mode << ISC10);
      GIMSK |= (1 << INT1);
    #else
      #warning attachInterrupt may need some more work for this cpu (case 1)
    #endif
      break;
     
    case 2:
    #if defined(EICRA) && defined(ISC20) && defined(ISC21) && defined(EIMSK)
      EICRA = (EICRA & ~((1 << ISC20) | (1 << ISC21))) | (mode << ISC20);
      EIMSK |= (1 << INT2);
    #elif defined(MCUCR) && defined(ISC20) && defined(ISC21) && defined(GICR)
      MCUCR = (MCUCR & ~((1 << ISC20) | (1 << ISC21))) | (mode << ISC20);
      GICR |= (1 << INT2);
    #elif defined(MCUCR) && defined(ISC20) && defined(GIMSK) && defined(GIMSK)
      MCUCR = (MCUCR & ~((1 << ISC20) | (1 << ISC21))) | (mode << ISC20);
      GIMSK |= (1 << INT2);
    #endif
      break;
#endif
    }
  }
}
 
void detachInterrupt(uint8_t interruptNum) {
  if(interruptNum < EXTERNAL_NUM_INTERRUPTS) {
    // Disable the interrupt.  (We can't assume that interruptNum is equal
    // to the number of the EIMSK bit to clear, as this isn't true on the 
    // ATmega8.  There, INT0 is 6 and INT1 is 7.)
    switch (interruptNum) {
#if defined(__AVR_ATmega32U4__)
    case 0:
        EIMSK &= ~(1<<INT0);
        break;
    case 1:
        EIMSK &= ~(1<<INT1);
        break;
    case 2:
        EIMSK &= ~(1<<INT2);
        break;
    case 3:
        EIMSK &= ~(1<<INT3);
        break; 
    case 4:
        EIMSK &= ~(1<<INT6);
        break; 
#elif defined(EICRA) && defined(EICRB) && defined(EIMSK)
    case 2:
      EIMSK &= ~(1 << INT0);
      break;
    case 3:
      EIMSK &= ~(1 << INT1);
      break;
    case 4:
      EIMSK &= ~(1 << INT2);
      break;
    case 5:
      EIMSK &= ~(1 << INT3);
      break;
    case 0:
      EIMSK &= ~(1 << INT4);
      break;
    case 1:
      EIMSK &= ~(1 << INT5);
      break;
    case 6:
      EIMSK &= ~(1 << INT6);
      break;
    case 7:
      EIMSK &= ~(1 << INT7);
      break;
#else
    case 0:
    #if defined(EIMSK) && defined(INT0)
      EIMSK &= ~(1 << INT0);
    #elif defined(GICR) && defined(ISC00)
      GICR &= ~(1 << INT0); // atmega32
    #elif defined(GIMSK) && defined(INT0)
      GIMSK &= ~(1 << INT0);
    #else
      #error detachInterrupt not finished for this cpu
    #endif
      break;
 
    case 1:
    #if defined(EIMSK) && defined(INT1)
      EIMSK &= ~(1 << INT1);
    #elif defined(GICR) && defined(INT1)
      GICR &= ~(1 << INT1); // atmega32
    #elif defined(GIMSK) && defined(INT1)
      GIMSK &= ~(1 << INT1);
    #else
      #warning detachInterrupt may need some more work for this cpu (case 1)
    #endif
      break;
#endif
    }
       
    interruptObjects[interruptNum] = 0;
  }
}
 
/*
void attachInterruptTwi(void (*userFunc)(void) ) {
  twiIntFunc = userFunc;
}
*/
 
#if defined(__AVR_ATmega32U4__)
ISR(INT0_vect) {
 if(interruptObjects[EXTERNAL_INT_0])
  interruptObjects[EXTERNAL_INT_0]->execInterrupt(EXTERNAL_INT_0);
}
 
ISR(INT1_vect) {
 if(interruptObjects[EXTERNAL_INT_1])
  interruptObjects[EXTERNAL_INT_1]->execInterrupt(EXTERNAL_INT_1);
}
 
ISR(INT2_vect) {
    if(interruptObjects[EXTERNAL_INT_2])
  interruptObjects[EXTERNAL_INT_2]->execInterrupt(EXTERNAL_INT_2);
}
 
ISR(INT3_vect) {
    if(interruptObjects[EXTERNAL_INT_3])
  interruptObjects[EXTERNAL_INT_3]->execInterrupt(EXTERNAL_INT_3);
}
 
ISR(INT6_vect) {
    if(interruptObjects[EXTERNAL_INT_4])
  interruptObjects[EXTERNAL_INT_4]->execInterrupt(EXTERNAL_INT_4);
}
 
#elif defined(EICRA) && defined(EICRB)
 
ISR(INT0_vect) {
  if(interruptObjects[EXTERNAL_INT_2])
    interruptObjects[EXTERNAL_INT_2]->execInterrupt(EXTERNAL_INT_2);
}
 
ISR(INT1_vect) {
  if(interruptObjects[EXTERNAL_INT_3])
    interruptObjects[EXTERNAL_INT_3]->execInterrupt(EXTERNAL_INT_3);
}
 
ISR(INT2_vect) {
  if(interruptObjects[EXTERNAL_INT_4])
    interruptObjects[EXTERNAL_INT_4]->execInterrupt(EXTERNAL_INT_4);
}
 
ISR(INT3_vect) {
  if(interruptObjects[EXTERNAL_INT_5])
    interruptObjects[EXTERNAL_INT_5]->execInterrupt(EXTERNAL_INT_5);
}
 
ISR(INT4_vect) {
  if(interruptObjects[EXTERNAL_INT_0])
    interruptObjects[EXTERNAL_INT_0]->execInterrupt(EXTERNAL_INT_0);
}
 
ISR(INT5_vect) {
  if(interruptObjects[EXTERNAL_INT_1])
    interruptObjects[EXTERNAL_INT_1]->execInterrupt(EXTERNAL_INT_1);
}
 
ISR(INT6_vect) {
  if(interruptObjects[EXTERNAL_INT_6])
    interruptObjects[EXTERNAL_INT_6]->execInterrupt(EXTERNAL_INT_6);
}
 
ISR(INT7_vect) {
  if(interruptObjects[EXTERNAL_INT_7])
    interruptObjects[EXTERNAL_INT_7]->execInterrupt(EXTERNAL_INT_7);
}
 
#else
 
ISR(INT0_vect) {
  if(interruptObjects[EXTERNAL_INT_0])
    interruptObjects[EXTERNAL_INT_0]->execInterrupt(EXTERNAL_INT_0);
}
 
ISR(INT1_vect) {
  if(interruptObjects[EXTERNAL_INT_1])
    interruptObjects[EXTERNAL_INT_1]->execInterrupt(EXTERNAL_INT_1);
}
 
#if defined(EICRA) && defined(ISC20)
ISR(INT2_vect) {
  if(interruptObjects[EXTERNAL_INT_2])
    interruptObjects[EXTERNAL_INT_2]->execInterrupt(EXTERNAL_INT_2);
}
#endif
 
#endif
 
/*
ISR(TWI_vect) {
  if(twiIntFunc)
    twiIntFunc();
}
*/

Sketch

estou utilizando o pino 5 no media direct e as interrupções INT.0 para o DATA e o INT.1 para o CLOCK.


 #include "MyInterrupt.h"  /*  mais informações: http://fabianoallex.blogspot.com.br/2015/11/arduino-como-tratar-interrupcoes.html*/
#include "BitArray.h"     /*  mais informações: http://fabianoallex.blogspot.com.br/2015/09/arduino-array-de-bits.html*/

#define MCARD_TAMANHO_TRILHA 40

struct MagneticCardInterrupts{
  int intData;
  int intClock;
};
    
class MagneticCardReader : Interrupt {
  private:
    int _pin_status;
    byte _trails;
    MagneticCardInterrupts _interrupts;
    char _cardData[MCARD_TAMANHO_TRILHA];          
    int _charCount;          
    char _readedCode[MCARD_TAMANHO_TRILHA];
    BitArray * _buffer;
    volatile int _buffer_cont = 0;
    volatile int _global_bit = 0; 
    
    char _decodeByte(int thisByte[]) {
      if (thisByte[0] == 0 & thisByte[1] == 0 & thisByte[2] == 0 & thisByte[3] == 0 & thisByte[4] == 1) { return '0'; }
      if (thisByte[0] == 1 & thisByte[1] == 0 & thisByte[2] == 0 & thisByte[3] == 0 & thisByte[4] == 0) { return '1'; }
      if (thisByte[0] == 0 & thisByte[1] == 1 & thisByte[2] == 0 & thisByte[3] == 0 & thisByte[4] == 0) { return '2'; }
      if (thisByte[0] == 1 & thisByte[1] == 1 & thisByte[2] == 0 & thisByte[3] == 0 & thisByte[4] == 1) { return '3'; }
      if (thisByte[0] == 0 & thisByte[1] == 0 & thisByte[2] == 1 & thisByte[3] == 0 & thisByte[4] == 0) { return '4'; }
      if (thisByte[0] == 1 & thisByte[1] == 0 & thisByte[2] == 1 & thisByte[3] == 0 & thisByte[4] == 1) { return '5'; }
      if (thisByte[0] == 0 & thisByte[1] == 1 & thisByte[2] == 1 & thisByte[3] == 0 & thisByte[4] == 1) { return '6'; }
      if (thisByte[0] == 1 & thisByte[1] == 1 & thisByte[2] == 1 & thisByte[3] == 0 & thisByte[4] == 0) { return '7'; } 
      if (thisByte[0] == 0 & thisByte[1] == 0 & thisByte[2] == 0 & thisByte[3] == 1 & thisByte[4] == 0) { return '8'; }
      if (thisByte[0] == 1 & thisByte[1] == 0 & thisByte[2] == 0 & thisByte[3] == 1 & thisByte[4] == 1) { return '9'; }
      if (thisByte[0] == 0 & thisByte[1] == 1 & thisByte[2] == 0 & thisByte[3] == 1 & thisByte[4] == 1) { return ':'; }
      if (thisByte[0] == 1 & thisByte[1] == 1 & thisByte[2] == 0 & thisByte[3] == 1 & thisByte[4] == 0) { return ';'; }
      if (thisByte[0] == 0 & thisByte[1] == 0 & thisByte[2] == 1 & thisByte[3] == 1 & thisByte[4] == 1) { return '<'; }
      if (thisByte[0] == 1 & thisByte[1] == 0 & thisByte[2] == 1 & thisByte[3] == 1 & thisByte[4] == 0) { return '='; }
      if (thisByte[0] == 0 & thisByte[1] == 1 & thisByte[2] == 1 & thisByte[3] == 1 & thisByte[4] == 0) { return '>'; }
      if (thisByte[0] == 1 & thisByte[1] == 1 & thisByte[2] == 1 & thisByte[3] == 1 & thisByte[4] == 1) { return '?'; }
    }
    
    void _printMyByte(int thisByte[]) {
      _cardData[_charCount] = _decodeByte(thisByte);
      _charCount++;
    }
    
    int _getStartSentinal() {
      int queue[5];
      int sentinal = 0;
      for (int j = 0; j < 400; j = j + 1) {
        queue[4] = queue[3];  queue[3] = queue[2]; queue[2] = queue[1]; queue[1] = queue[0]; queue[0] = _buffer->read(j) ? 1 : 0 ;
        if (queue[0] == 0 & queue[1] == 1 & queue[2] == 0 & queue[3] == 1 & queue[4] == 1) { sentinal = j-4; break; }
      }
      return sentinal;
    }
    
    void _decode(){
      int sentinal = _getStartSentinal();
      int i=0, thisByte[5];
      for (int j = sentinal; j < 400 - sentinal; j = j + 1) {
        thisByte[i] = _buffer->read(j) ? 1 : 0 ;
        i++;
        if (i % 5 == 0) {
          i = 0;
          if (thisByte[0] == 0 & thisByte[1] == 0 & thisByte[2] == 0 & thisByte[3] == 0 & thisByte[4] == 0) { break; }
          _printMyByte(thisByte);
        }
      }
      _cardData[_charCount] = '\0';
      for (int i=0; i<MCARD_TAMANHO_TRILHA; i++){  _readedCode[i] = _cardData[i];  }
    }
    
  public:
    MagneticCardReader(int pin_status, MagneticCardInterrupts ints ) {
      _interrupts = ints;
      _buffer = new BitArray(400);
      _pin_status = pin_status;
      _charCount = 0;
      _interrupts.intData  = ints.intData; 
      _interrupts.intClock = ints.intClock;
      attach(_interrupts.intData, CHANGE);
      attach(_interrupts.intClock, FALLING);
    }
    
    void volatile execInterrupt(uint8_t interruptNum) {   
      if (interruptNum == _interrupts.intData )  { _global_bit = (_global_bit == 0) ? 1 : 0;                  }
      if (interruptNum == _interrupts.intClock ) { _buffer->write(_buffer_cont, _global_bit); _buffer_cont++; }
    }
    
    boolean read() {
      for (int i=0; i<MCARD_TAMANHO_TRILHA; i++){ _readedCode[i] = '\0'; }
      int r = 0;  
      
      while(digitalRead(_pin_status) == LOW){ 
        r = 1; 
      }
     
      if(r == 1) {
        _decode();
        _buffer_cont = 0;
        for (int i=0; i<MCARD_TAMANHO_TRILHA; i++) { _cardData[i] = ' '; }
        for (int i=0; i<200; i++) { _buffer->write(i, 0); }  
        _charCount = 0;
        return true;
      }
      return false;
    }
    
    char * getTrail(){ return _readedCode; }
};





/*
https://www.arduino.cc/en/Reference/AttachInterrupt

Board         int.0 int.1 int.2 int.3 int.4 int.5
Uno, Ethernet 2 3        
Mega2560 2 3 21 20 19 18
32u4      3 2 0 1 7  
*/

//                             data   c1ock
MagneticCardReader mcr(  5,  { INT0,  INT1 }  );

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

void loop() {
  if (mcr.read()){ 
    Serial.println(  mcr.getTrail()  ); 
  }
}

Atualização 19/11/2015

Pra que quiser uma versão que não precise criar vários arquivos separados, fiz algumas alterações no código. A principal delas é que tratei das interrupções fora da classe.


Sketch (arquivo único)

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

#define MCARD_TAMANHO_TRILHA 40
   
class MagneticCardReader  {
  private:
    int _pin_status;
    byte _trails;
    char _cardData[MCARD_TAMANHO_TRILHA];          
    int _charCount;          
    char _readedCode[MCARD_TAMANHO_TRILHA];
    BitArray * _buffer;
    volatile int _buffer_cont = 0;
    volatile int _global_bit = 0; 
    
    char _decodeByte(int thisByte[]) {
      if (thisByte[0] == 0 & thisByte[1] == 0 & thisByte[2] == 0 & thisByte[3] == 0 & thisByte[4] == 1) { return '0'; }
      if (thisByte[0] == 1 & thisByte[1] == 0 & thisByte[2] == 0 & thisByte[3] == 0 & thisByte[4] == 0) { return '1'; }
      if (thisByte[0] == 0 & thisByte[1] == 1 & thisByte[2] == 0 & thisByte[3] == 0 & thisByte[4] == 0) { return '2'; }
      if (thisByte[0] == 1 & thisByte[1] == 1 & thisByte[2] == 0 & thisByte[3] == 0 & thisByte[4] == 1) { return '3'; }
      if (thisByte[0] == 0 & thisByte[1] == 0 & thisByte[2] == 1 & thisByte[3] == 0 & thisByte[4] == 0) { return '4'; }
      if (thisByte[0] == 1 & thisByte[1] == 0 & thisByte[2] == 1 & thisByte[3] == 0 & thisByte[4] == 1) { return '5'; }
      if (thisByte[0] == 0 & thisByte[1] == 1 & thisByte[2] == 1 & thisByte[3] == 0 & thisByte[4] == 1) { return '6'; }
      if (thisByte[0] == 1 & thisByte[1] == 1 & thisByte[2] == 1 & thisByte[3] == 0 & thisByte[4] == 0) { return '7'; } 
      if (thisByte[0] == 0 & thisByte[1] == 0 & thisByte[2] == 0 & thisByte[3] == 1 & thisByte[4] == 0) { return '8'; }
      if (thisByte[0] == 1 & thisByte[1] == 0 & thisByte[2] == 0 & thisByte[3] == 1 & thisByte[4] == 1) { return '9'; }
      if (thisByte[0] == 0 & thisByte[1] == 1 & thisByte[2] == 0 & thisByte[3] == 1 & thisByte[4] == 1) { return ':'; }
      if (thisByte[0] == 1 & thisByte[1] == 1 & thisByte[2] == 0 & thisByte[3] == 1 & thisByte[4] == 0) { return ';'; }
      if (thisByte[0] == 0 & thisByte[1] == 0 & thisByte[2] == 1 & thisByte[3] == 1 & thisByte[4] == 1) { return '<'; }
      if (thisByte[0] == 1 & thisByte[1] == 0 & thisByte[2] == 1 & thisByte[3] == 1 & thisByte[4] == 0) { return '='; }
      if (thisByte[0] == 0 & thisByte[1] == 1 & thisByte[2] == 1 & thisByte[3] == 1 & thisByte[4] == 0) { return '>'; }
      if (thisByte[0] == 1 & thisByte[1] == 1 & thisByte[2] == 1 & thisByte[3] == 1 & thisByte[4] == 1) { return '?'; }
    }
    
    void _printMyByte(int thisByte[]) {
      _cardData[_charCount] = _decodeByte(thisByte);
      _charCount++;
    }
    
    int _getStartSentinal() {
      int queue[5];
      int sentinal = 0;
      for (int j = 0; j < 400; j = j + 1) {
        queue[4] = queue[3];  queue[3] = queue[2]; queue[2] = queue[1]; queue[1] = queue[0]; queue[0] = _buffer->read(j) ? 1 : 0 ;
        if (queue[0] == 0 & queue[1] == 1 & queue[2] == 0 & queue[3] == 1 & queue[4] == 1) { sentinal = j-4; break; }
      }
      return sentinal;
    }
    
    void _decode(){
      int sentinal = _getStartSentinal();
      int i=0, thisByte[5];
      for (int j = sentinal; j < 400 - sentinal; j = j + 1) {
        thisByte[i] = _buffer->read(j) ? 1 : 0 ;
        i++;
        if (i % 5 == 0) {
          i = 0;
          if (thisByte[0] == 0 & thisByte[1] == 0 & thisByte[2] == 0 & thisByte[3] == 0 & thisByte[4] == 0) { break; }
          _printMyByte(thisByte);
        }
      }
      _cardData[_charCount] = '\0';
      for (int i=0; i<MCARD_TAMANHO_TRILHA; i++){  _readedCode[i] = _cardData[i];  }
    }
    
  public:
    MagneticCardReader(int pin_status ) {
      _buffer = new BitArray(400);
      _pin_status = pin_status;
      _charCount = 0;
    }
    
    void volatile execInterruptData() {    _global_bit = (_global_bit == 0) ? 1 : 0;                  }
    void volatile execInterruptClock(){    _buffer->write(_buffer_cont, _global_bit); _buffer_cont++; }
    
    boolean read() {
      for (int i=0; i<MCARD_TAMANHO_TRILHA; i++){ _readedCode[i] = '\0'; }
      int r = 0;  
      
      while(digitalRead(_pin_status) == LOW){ 
        r = 1; 
      }
     
      if(r == 1) {
        _decode();
        _buffer_cont = 0;
        for (int i=0; i<MCARD_TAMANHO_TRILHA; i++) { _cardData[i] = ' '; }
        for (int i=0; i<200; i++) { _buffer->write(i, 0); }  
        _charCount = 0;
        return true;
      }
      return false;
    }
    
    char * getTrail(){ return _readedCode; }
};





/*
https://www.arduino.cc/en/Reference/AttachInterrupt

Board         int.0 int.1 int.2 int.3 int.4 int.5
Uno, Ethernet 2 3        
Mega2560 2 3 21 20 19 18
32u4      3 2 0 1 7  
*/

//fisicamente os pinos do leitor estão ligadas nas seguintes interrupções:
//         --> data  clock
//TRILHA 1 --> int.4 int.5
//TRILHA 2 --> int.0 int.1
//TRILHA 3 --> int.2 int.3


MagneticCardReader mcr(  5  );  //pino 5 para detectar a prensença do cartão na leitora
void interruptData_mcr()  { mcr.execInterruptData();  }  
void interruptClock_mcr() { mcr.execInterruptClock(); }  

void setup() {
  Serial.begin(9600);
  
  attachInterrupt(INT0, interruptData_mcr,  CHANGE);   //pino 2
  attachInterrupt(INT1, interruptClock_mcr, FALLING);  //pino 3
}

void loop() {
  if (mcr.read()){ 
    Serial.println(  mcr.getTrail()  ); 
  }
}



fontes:
http://www.fraudes.org/showpage1.asp?pg=108
http://blog.tkjelectronics.dk/2010/02/magnetic-card-lock-with-the-arduino/

segunda-feira, 9 de novembro de 2015

Arduino - Como tratar interrupções externas dentro de objetos

Quem já tentou tratar uma interrupção dentro de uma classe sabe que não é muito simples fazer isso. Nesse vídeo mostro como redefinir a função attachInterrupt para anexar objetos ao invés de funções.




MyInterrupt.h

/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */

/*
  Part of the Wiring project - http://wiring.uniandes.edu.co

  Copyright (c) 2004-05 Hernando Barragan

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General
  Public License along with this library; if not, write to the
  Free Software Foundation, Inc., 59 Temple Place, Suite 330,
  Boston, MA  02111-1307  USA
  
  Modified 24 November 2006 by David A. Mellis
  Modified 1 August 2010 by Mark Sproul
*/

#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <stdio.h>

#include "wiring_private.h"

class Interrupt;  //declaracao previa
void attachInterrupt(uint8_t interruptNum, Interrupt * interruptObject, int mode); //declaracao previa

class Interrupt {
  public:
    void attach(uint8_t interruptNum, int mode) { attachInterrupt(interruptNum, this, mode); };
    void volatile virtual execInterrupt(uint8_t interruptNum);
};

Interrupt * interruptObjects[EXTERNAL_NUM_INTERRUPTS];
//static volatile voidFuncPtr intFunc[EXTERNAL_NUM_INTERRUPTS];
// volatile static voidFuncPtr twiIntFunc;

void attachInterrupt(uint8_t interruptNum, Interrupt * interruptObject, int mode) {
  
  if(interruptNum < EXTERNAL_NUM_INTERRUPTS) {
    interruptObjects[interruptNum] = interruptObject;
    
    // Configure the interrupt mode (trigger on low input, any change, rising
    // edge, or falling edge).  The mode constants were chosen to correspond
    // to the configuration bits in the hardware register, so we simply shift
    // the mode into place.
      
    // Enable the interrupt.
      
    switch (interruptNum) {
#if defined(__AVR_ATmega32U4__)
 // I hate doing this, but the register assignment differs between the 1280/2560
 // and the 32U4.  Since avrlib defines registers PCMSK1 and PCMSK2 that aren't 
 // even present on the 32U4 this is the only way to distinguish between them.
    case 0:
 EICRA = (EICRA & ~((1<<ISC00) | (1<<ISC01))) | (mode << ISC00);
 EIMSK |= (1<<INT0);
 break;
    case 1:
 EICRA = (EICRA & ~((1<<ISC10) | (1<<ISC11))) | (mode << ISC10);
 EIMSK |= (1<<INT1);
 break; 
    case 2:
        EICRA = (EICRA & ~((1<<ISC20) | (1<<ISC21))) | (mode << ISC20);
        EIMSK |= (1<<INT2);
        break;
    case 3:
        EICRA = (EICRA & ~((1<<ISC30) | (1<<ISC31))) | (mode << ISC30);
        EIMSK |= (1<<INT3);
        break;
    case 4:
        EICRB = (EICRB & ~((1<<ISC60) | (1<<ISC61))) | (mode << ISC60);
        EIMSK |= (1<<INT6);
        break;
#elif defined(EICRA) && defined(EICRB) && defined(EIMSK)
    case 2:
      EICRA = (EICRA & ~((1 << ISC00) | (1 << ISC01))) | (mode << ISC00);
      EIMSK |= (1 << INT0);
      break;
    case 3:
      EICRA = (EICRA & ~((1 << ISC10) | (1 << ISC11))) | (mode << ISC10);
      EIMSK |= (1 << INT1);
      break;
    case 4:
      EICRA = (EICRA & ~((1 << ISC20) | (1 << ISC21))) | (mode << ISC20);
      EIMSK |= (1 << INT2);
      break;
    case 5:
      EICRA = (EICRA & ~((1 << ISC30) | (1 << ISC31))) | (mode << ISC30);
      EIMSK |= (1 << INT3);
      break;
    case 0:
      EICRB = (EICRB & ~((1 << ISC40) | (1 << ISC41))) | (mode << ISC40);
      EIMSK |= (1 << INT4);
      break;
    case 1:
      EICRB = (EICRB & ~((1 << ISC50) | (1 << ISC51))) | (mode << ISC50);
      EIMSK |= (1 << INT5);
      break;
    case 6:
      EICRB = (EICRB & ~((1 << ISC60) | (1 << ISC61))) | (mode << ISC60);
      EIMSK |= (1 << INT6);
      break;
    case 7:
      EICRB = (EICRB & ~((1 << ISC70) | (1 << ISC71))) | (mode << ISC70);
      EIMSK |= (1 << INT7);
      break;
#else  
    case 0:
    #if defined(EICRA) && defined(ISC00) && defined(EIMSK)
      EICRA = (EICRA & ~((1 << ISC00) | (1 << ISC01))) | (mode << ISC00);
      EIMSK |= (1 << INT0);
    #elif defined(MCUCR) && defined(ISC00) && defined(GICR)
      MCUCR = (MCUCR & ~((1 << ISC00) | (1 << ISC01))) | (mode << ISC00);
      GICR |= (1 << INT0);
    #elif defined(MCUCR) && defined(ISC00) && defined(GIMSK)
      MCUCR = (MCUCR & ~((1 << ISC00) | (1 << ISC01))) | (mode << ISC00);
      GIMSK |= (1 << INT0);
    #else
      #error attachInterrupt not finished for this CPU (case 0)
    #endif
      break;

    case 1:
    #if defined(EICRA) && defined(ISC10) && defined(ISC11) && defined(EIMSK)
      EICRA = (EICRA & ~((1 << ISC10) | (1 << ISC11))) | (mode << ISC10);
      EIMSK |= (1 << INT1);
    #elif defined(MCUCR) && defined(ISC10) && defined(ISC11) && defined(GICR)
      MCUCR = (MCUCR & ~((1 << ISC10) | (1 << ISC11))) | (mode << ISC10);
      GICR |= (1 << INT1);
    #elif defined(MCUCR) && defined(ISC10) && defined(GIMSK) && defined(GIMSK)
      MCUCR = (MCUCR & ~((1 << ISC10) | (1 << ISC11))) | (mode << ISC10);
      GIMSK |= (1 << INT1);
    #else
      #warning attachInterrupt may need some more work for this cpu (case 1)
    #endif
      break;
    
    case 2:
    #if defined(EICRA) && defined(ISC20) && defined(ISC21) && defined(EIMSK)
      EICRA = (EICRA & ~((1 << ISC20) | (1 << ISC21))) | (mode << ISC20);
      EIMSK |= (1 << INT2);
    #elif defined(MCUCR) && defined(ISC20) && defined(ISC21) && defined(GICR)
      MCUCR = (MCUCR & ~((1 << ISC20) | (1 << ISC21))) | (mode << ISC20);
      GICR |= (1 << INT2);
    #elif defined(MCUCR) && defined(ISC20) && defined(GIMSK) && defined(GIMSK)
      MCUCR = (MCUCR & ~((1 << ISC20) | (1 << ISC21))) | (mode << ISC20);
      GIMSK |= (1 << INT2);
    #endif
      break;
#endif
    }
  }
}

void detachInterrupt(uint8_t interruptNum) {
  if(interruptNum < EXTERNAL_NUM_INTERRUPTS) {
    // Disable the interrupt.  (We can't assume that interruptNum is equal
    // to the number of the EIMSK bit to clear, as this isn't true on the 
    // ATmega8.  There, INT0 is 6 and INT1 is 7.)
    switch (interruptNum) {
#if defined(__AVR_ATmega32U4__)
    case 0:
        EIMSK &= ~(1<<INT0);
        break;
    case 1:
        EIMSK &= ~(1<<INT1);
        break;
    case 2:
        EIMSK &= ~(1<<INT2);
        break;
    case 3:
        EIMSK &= ~(1<<INT3);
        break; 
    case 4:
        EIMSK &= ~(1<<INT6);
        break; 
#elif defined(EICRA) && defined(EICRB) && defined(EIMSK)
    case 2:
      EIMSK &= ~(1 << INT0);
      break;
    case 3:
      EIMSK &= ~(1 << INT1);
      break;
    case 4:
      EIMSK &= ~(1 << INT2);
      break;
    case 5:
      EIMSK &= ~(1 << INT3);
      break;
    case 0:
      EIMSK &= ~(1 << INT4);
      break;
    case 1:
      EIMSK &= ~(1 << INT5);
      break;
    case 6:
      EIMSK &= ~(1 << INT6);
      break;
    case 7:
      EIMSK &= ~(1 << INT7);
      break;
#else
    case 0:
    #if defined(EIMSK) && defined(INT0)
      EIMSK &= ~(1 << INT0);
    #elif defined(GICR) && defined(ISC00)
      GICR &= ~(1 << INT0); // atmega32
    #elif defined(GIMSK) && defined(INT0)
      GIMSK &= ~(1 << INT0);
    #else
      #error detachInterrupt not finished for this cpu
    #endif
      break;

    case 1:
    #if defined(EIMSK) && defined(INT1)
      EIMSK &= ~(1 << INT1);
    #elif defined(GICR) && defined(INT1)
      GICR &= ~(1 << INT1); // atmega32
    #elif defined(GIMSK) && defined(INT1)
      GIMSK &= ~(1 << INT1);
    #else
      #warning detachInterrupt may need some more work for this cpu (case 1)
    #endif
      break;
#endif
    }
      
    interruptObjects[interruptNum] = 0;
  }
}

/*
void attachInterruptTwi(void (*userFunc)(void) ) {
  twiIntFunc = userFunc;
}
*/

#if defined(__AVR_ATmega32U4__)
ISR(INT0_vect) {
 if(interruptObjects[EXTERNAL_INT_0])
  interruptObjects[EXTERNAL_INT_0]->execInterrupt(EXTERNAL_INT_0);
}

ISR(INT1_vect) {
 if(interruptObjects[EXTERNAL_INT_1])
  interruptObjects[EXTERNAL_INT_1]->execInterrupt(EXTERNAL_INT_1);
}

ISR(INT2_vect) {
    if(interruptObjects[EXTERNAL_INT_2])
  interruptObjects[EXTERNAL_INT_2]->execInterrupt(EXTERNAL_INT_2);
}

ISR(INT3_vect) {
    if(interruptObjects[EXTERNAL_INT_3])
  interruptObjects[EXTERNAL_INT_3]->execInterrupt(EXTERNAL_INT_3);
}

ISR(INT6_vect) {
    if(interruptObjects[EXTERNAL_INT_4])
  interruptObjects[EXTERNAL_INT_4]->execInterrupt(EXTERNAL_INT_4);
}

#elif defined(EICRA) && defined(EICRB)

ISR(INT0_vect) {
  if(interruptObjects[EXTERNAL_INT_2])
    interruptObjects[EXTERNAL_INT_2]->execInterrupt(EXTERNAL_INT_2);
}

ISR(INT1_vect) {
  if(interruptObjects[EXTERNAL_INT_3])
    interruptObjects[EXTERNAL_INT_3]->execInterrupt(EXTERNAL_INT_3);
}

ISR(INT2_vect) {
  if(interruptObjects[EXTERNAL_INT_4])
    interruptObjects[EXTERNAL_INT_4]->execInterrupt(EXTERNAL_INT_4);
}

ISR(INT3_vect) {
  if(interruptObjects[EXTERNAL_INT_5])
    interruptObjects[EXTERNAL_INT_5]->execInterrupt(EXTERNAL_INT_5);
}

ISR(INT4_vect) {
  if(interruptObjects[EXTERNAL_INT_0])
    interruptObjects[EXTERNAL_INT_0]->execInterrupt(EXTERNAL_INT_0);
}

ISR(INT5_vect) {
  if(interruptObjects[EXTERNAL_INT_1])
    interruptObjects[EXTERNAL_INT_1]->execInterrupt(EXTERNAL_INT_1);
}

ISR(INT6_vect) {
  if(interruptObjects[EXTERNAL_INT_6])
    interruptObjects[EXTERNAL_INT_6]->execInterrupt(EXTERNAL_INT_6);
}

ISR(INT7_vect) {
  if(interruptObjects[EXTERNAL_INT_7])
    interruptObjects[EXTERNAL_INT_7]->execInterrupt(EXTERNAL_INT_7);
}

#else

ISR(INT0_vect) {
  if(interruptObjects[EXTERNAL_INT_0])
    interruptObjects[EXTERNAL_INT_0]->execInterrupt(EXTERNAL_INT_0);
}

ISR(INT1_vect) {
  if(interruptObjects[EXTERNAL_INT_1])
    interruptObjects[EXTERNAL_INT_1]->execInterrupt(EXTERNAL_INT_1);
}

#if defined(EICRA) && defined(ISC20)
ISR(INT2_vect) {
  if(interruptObjects[EXTERNAL_INT_2])
    interruptObjects[EXTERNAL_INT_2]->execInterrupt(EXTERNAL_INT_2);
}
#endif

#endif

/*
ISR(TWI_vect) {
  if(twiIntFunc)
    twiIntFunc();
}
*/






Sketch


#include "MyInterrupt.h"

class Abc : public Interrupt{
  private:
  
  public:
    Abc(){
      attach(0, CHANGE);
      attach(1, CHANGE);
    }
    
    void volatile execInterrupt(uint8_t interruptNum){
      Serial.print("Abc: ");
      Serial.print(interruptNum);
      Serial.print(" - millis: ");
      Serial.println(millis());
    }
};



Abc * abc; 

void setup() {
  Serial.begin(9600);
  abc = new Abc();
}

void loop() {
  // put your main code here, to run repeatedly:
}


quinta-feira, 15 de outubro de 2015

Arduino - LCD Big Numbers

Esse post demonstra vários exemplo de como gerar números grandes em displays LCD. Mais abaixo tem um exemplo de como utilizar mais de 8 caracteres customizáveis e ao final mostro um exemplo de um relógio em display LCD com números grandes.

São vários exemplos diferentes.

Vídeo 01:



Vídeo 02:


Código-fonte 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 BIG NUMBERS*********************************************************
**************************************************************************************************************/
struct LCDNumber {
  byte top;
  byte bottom;
};
 
class LCDBigNumbers {
  private:
    LiquidCrystal * _lcd;
    int _row;
    int _col;
    long _value; /*0..100*/
     
    void _clear(){
      int cont = 1;
      long x = 9;
      while (_value > x){
        cont++;
        x = x * 10 + 9;
      }
      for (int i=0; i<cont; i++) {
        _lcd->setCursor(_col+i, _row);
        _lcd->print( " " );
        _lcd->setCursor(_col+i, _row+1);
        _lcd->print( " " );
      }
    }
  public:
    static byte c0[8];  
    static byte c1[8];  
    static byte c2[8];  
    static byte c3[8];
    static byte c4[8];
    static byte c5[8];  
    static byte c6[8];  
    static byte c7[8];  
    static LCDNumber _lcd_numbers[];
    
    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);
      _lcd->createChar(7, c7);
    }
     
    LCDBigNumbers(LiquidCrystal * lcd, int row, int col) {
      _lcd = lcd;      _row = row;      _col = col;
    }
     
    void setRow(int row){
      _clear();
      _row = row;
      setValue(_value);
    }
    void setCol(int col){
      _clear();
      _col = col;
      setValue(_value);
    }
     
    void setValue(long value){
      _clear();
      _value = value;
       
      int cont = 1;
      long x = 9;
      while (abs(_value) > x){
        cont++;
        x = x * 10 + 9;
      }
       
      for (int i=0; i<cont; i++) {
        int n = value / pow(10, cont-1-i);
        value = value - pow(10, cont-1-i) * n;
         
        _lcd->setCursor(_col+i, _row);
        _lcd->write( _lcd_numbers[n].top );
        _lcd->setCursor(_col+i, _row+1);
        _lcd->write( _lcd_numbers[n].bottom );
      }
       
    }
};

byte LCDBigNumbers::c0[8] = {B11111, B10001, B10001, B10001, B10001, B10001, B10001, B10001};
byte LCDBigNumbers::c1[8] = {B10001, B10001, B10001, B10001, B10001, B10001, B10001, B11111};
byte LCDBigNumbers::c2[8] = {B00001, B00001, B00001, B00001, B00001, B00001, B00001, B00001};
byte LCDBigNumbers::c3[8] = {B11111, B00001, B00001, B00001, B00001, B00001, B00001, B11111};
byte LCDBigNumbers::c4[8] = {B11111, B10000, B10000, B10000, B10000, B10000, B10000, B11111};
byte LCDBigNumbers::c5[8] = {B11111, B00001, B00001, B00001, B00001, B00001, B00001, B00001};    
byte LCDBigNumbers::c6[8] = {B11111, B10001, B10001, B10001, B10001, B10001, B10001, B11111};              
byte LCDBigNumbers::c7[8] = {B00001, B00001, B00001, B00001, B00001, B00001, B00001, B11111};


LCDNumber LCDBigNumbers::_lcd_numbers[] = { 
  {0, 1}, //0
  {2, 2}, //1
  {5, 4}, //2
  {3, 7}, //3
  {1, 2}, //4
  {4, 7}, //5
  {4, 1}, //6
  {5, 2}, //7
  {6, 1}, //8
  {6, 7} // 9
};
 

/*************************************************************************************************************
*******************************FIM CLASSE LCD BIG NUMBERS*****************************************************
**************************************************************************************************************/
   
   
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
LCDBigNumbers lcdNum(&lcd, 0,0); //inclui uma barra no lcd, primeira linha, coluna 8. tamanho 8
   
void setup()   {
  Serial.begin(9600);
  lcdNum.createChars();
  pinMode(44, OUTPUT);
  analogWrite(44, 255/6); //utilizado para aumentar o contraste
  lcd.begin(16, 2);
}
 
long i = 0;
int col = 0;
 
void loop() {
  lcdNum.setValue(i++ * 11);
   
  if (i>=10000) i = 0;
   
  if (i%10 == 0){
    lcdNum.setCol(col++);
    if (col >= 10){
      col = 0;
    }
  }
   
  delay(500);  
}


Código segunda versão:

 /*
Fabiano A. Arndt - 2015
www.youtube.com/user/fabianoallex
www.facebook.com/dicasarduino
fabianoallex@gmail.com
*/
   
#include <LiquidCrystal.h>
   
/*************************************************************************************************************
*******************************CLASSE LCD BIG NUMBERS*********************************************************
**************************************************************************************************************/
struct LCDNumber {
  byte top1;
  byte top2;
  byte top3;
  byte bottom1;
  byte bottom2;
  byte bottom3;
};
 
class LCDBigNumbers {
  private:
    LiquidCrystal * _lcd;
    int _row;
    int _col;
    long _value; /*0..100*/
     
    void _clear(){
      int cont = 1;
      long x = 9;
      while (_value > x){
        cont++;
        x = x * 10 + 9;
      }
      for (int i=0; i<cont; i++) {
        _lcd->setCursor(_col+i, _row);
        _lcd->print( "    " );
        _lcd->setCursor(_col+i, _row+1);
        _lcd->print( "    " );
      }
    }
  public:
    static byte c0[8];  //bottom
    static byte c1[8];  //top
    static byte c2[8];  //fill
    static byte c3[8];
    static byte c4[8];
    static byte c5[8];  //top-bottom 
    static LCDNumber _lcd_numbers[];
    
    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);
      //_lcd->createChar(7, c7);
    }
     
    LCDBigNumbers(LiquidCrystal * lcd, int row, int col) {
      _lcd = lcd;      _row = row;      _col = col;
    }
     
    void setRow(int row){
      _clear();
      _row = row;
      setValue(_value);
    }
    void setCol(int col){
      _clear();
      _col = col;
      setValue(_value);
    }
     
    void setValue(long value){
      _clear();
      _value = value;
       
      int cont = 1;
      long x = 9;
      while (abs(_value) > x){
        cont++;
        x = x * 10 + 9;
      }
       
      for (int i=0; i<cont; i++) {
        int n = value / pow(10, cont-1-i);
        value = value - pow(10, cont-1-i) * n;
         
        _lcd->setCursor(_col+i*4, _row);
        _lcd_numbers[n].top1 == 9 ? _lcd->print(" ") : _lcd->write( _lcd_numbers[n].top1 );
        _lcd_numbers[n].top2 == 9 ? _lcd->print(" ") : _lcd->write( _lcd_numbers[n].top2 );
        _lcd_numbers[n].top3 == 9 ? _lcd->print(" ") : _lcd->write( _lcd_numbers[n].top3 );
        _lcd->setCursor(_col+i*4, _row+1);
        _lcd_numbers[n].bottom1 == 9 ? _lcd->print(" ") : _lcd->write( _lcd_numbers[n].bottom1 );
        _lcd_numbers[n].bottom2 == 9 ? _lcd->print(" ") : _lcd->write( _lcd_numbers[n].bottom2 );
        _lcd_numbers[n].bottom3 == 9 ? _lcd->print(" ") : _lcd->write( _lcd_numbers[n].bottom3 );
      }
    }
};

byte LCDBigNumbers::c0[8] = {B00000, B00000, B00000, B00000, B00000, B11111, B11111, B11111};  //bottom
byte LCDBigNumbers::c1[8] = {B11111, B11111, B11111, B00000, B00000, B00000, B00000, B00000};  //top
byte LCDBigNumbers::c2[8] = {B11111, B11111, B11111, B11111, B11111, B11111, B11111, B11111};  //fill
byte LCDBigNumbers::c3[8] = {B00000, B00000, B00001, B00011, B00011, B00001, B00000, B00000};
byte LCDBigNumbers::c4[8] = {B00000, B00000, B10000, B11000, B11000, B10000, B00000, B00000};
byte LCDBigNumbers::c5[8] = {B11111, B11111, B00000, B00000, B00000, B00000, B11111, B11111};   //top / bottom 

LCDNumber LCDBigNumbers::_lcd_numbers[] = { 
      {2, 1, 2, 2, 0, 2}, //0
      {1, 2, 9, 0, 2, 0}, //1
      {1, 5, 2, 2, 0, 0}, //2
      {1, 5, 2, 0, 0, 2}, //3
      {2, 0, 2, 9, 9, 2}, //4
      {2, 5, 1, 0, 0, 2}, //5
      {2, 5, 1, 2, 0, 2}, //6
      {1, 1, 2, 9, 9, 2}, //7
      {2, 5, 2, 2, 0, 2}, //8
      {2, 5, 2, 0, 0, 2} // 9
    };
/*************************************************************************************************************
*******************************FIM CLASSE LCD BIG NUMBERS*****************************************************
**************************************************************************************************************/
   
   
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
LCDBigNumbers lcdNum(&lcd, 0,0); //inclui uma barra no lcd, primeira linha, coluna 1
   
void setup()   {
  Serial.begin(9600);
  lcdNum.createChars();
  lcdNum.setCol(1); //muda para a coluna 1
  
  pinMode(44, OUTPUT);  //arduino mega - pino de contraste do display
  analogWrite(44, 255/6); //utilizado para aumentar o contraste
  lcd.begin(16, 2);
}
 
long i = 0;
int col = 0;
 
void loop() {
  lcdNum.setValue(i++ * 11);
   
  if (i>=10000) { i = 0; }

  delay(500);  
}


Código terceira e quarta versão:

obs. para inverter entre a terceira e quarta versão verificar o código comentado onde os caracteres customizados são definidos.

/*
Fabiano A. Arndt - 2015
www.youtube.com/user/fabianoallex
www.facebook.com/dicasarduino
fabianoallex@gmail.com
*/
   
#include <LiquidCrystal.h>
   
/*************************************************************************************************************
*******************************CLASSE LCD BIG NUMBERS*********************************************************
**************************************************************************************************************/
struct LCDNumber {
  byte top1;
  byte top2;
  byte bottom1;
  byte bottom2;
};
 
class LCDBigNumbers {
  private:
    LiquidCrystal * _lcd;
    int _row;
    int _col;
    long _value; /*0..100*/
    byte _dist;  /*distancia entre digitos*/
     
    void _clear(){
      int cont = 1;
      long x = 9;
      while (_value > x){
        cont++;
        x = x * 10 + 9;
      }
      for (int i=0; i<cont; i++) {
        _lcd->setCursor(_col+i, _row);
        _lcd->print( "  " );
        for (byte b=0;b<_dist; b++){ _lcd->print( " " ); }
        _lcd->setCursor(_col+i, _row+1);
        _lcd->print( "  " );
        for (byte b=0;b<_dist; b++){ _lcd->print( " " ); }
      }
    }
  public:
    static byte c0[8];  
    static byte c1[8];  
    static byte c2[8];  
    static byte c3[8];
    static byte c4[8];
    static byte c5[8];  
    static byte c6[8];  
    static byte c7[8];  
    static LCDNumber _lcd_numbers[];
    
    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);
      _lcd->createChar(7, c7);
    }
     
    LCDBigNumbers(LiquidCrystal * lcd, int row, int col) {
      _lcd = lcd;      
      _row = row;      
      _col = col;
      _dist = 0;  //distancia entre os numeros
    }
     
    void setRow(int row){
      _clear();
      _row = row;
      setValue(_value);
    }
    void setCol(int col){
      _clear();
      _col = col;
      setValue(_value);
    }
    
    void setDist(int dist){
      _clear();
      _dist = dist;
      setValue(_value);
    }
     
    void setValue(long value){
      _clear();
      _value = value;
       
      int cont = 1;
      long x = 9;
      while (abs(_value) > x){
        cont++;
        x = x * 10 + 9;
      }
       
      for (int i=0; i<cont; i++) {
        int n = value / pow(10, cont-1-i);
        value = value - pow(10, cont-1-i) * n;
         
        _lcd->setCursor(_col+i*(2+_dist), _row);
        _lcd_numbers[n].top1 == 9 ? _lcd->print(" ") : _lcd->write( _lcd_numbers[n].top1 );
        _lcd_numbers[n].top2 == 9 ? _lcd->print(" ") : _lcd->write( _lcd_numbers[n].top2 );
        _lcd->setCursor(_col+i*(2+_dist), _row+1);
        _lcd_numbers[n].bottom1 == 9 ? _lcd->print(" ") : _lcd->write( _lcd_numbers[n].bottom1 );
        _lcd_numbers[n].bottom2 == 9 ? _lcd->print(" ") : _lcd->write( _lcd_numbers[n].bottom2 );
      }
    }
};

//byte LCDBigNumbers::c0[8] = {B11111, B11111, B11000, B11000, B11000, B11000, B11000, B11000}; 
//byte LCDBigNumbers::c1[8] = {B11000, B11000, B11000, B11000, B11000, B11000, B11111, B11111};  
//byte LCDBigNumbers::c2[8] = {B11111, B11111, B00011, B00011, B00011, B00011, B00011, B00011};  
//byte LCDBigNumbers::c3[8] = {B00011, B00011, B00011, B00011, B00011, B00011, B11111, B11111};
//byte LCDBigNumbers::c4[8] = {B11111, B11111, B11000, B11000, B11000, B11000, B11111, B11111};  
//byte LCDBigNumbers::c5[8] = {B11111, B11111, B00011, B00011, B00011, B00011, B11111, B11111};  
//byte LCDBigNumbers::c6[8] = {B11111, B11111, B00000, B00000, B00000, B00000, B00000, B00000};  
//byte LCDBigNumbers::c7[8] = {B11111, B11111, B00000, B00000, B00000, B00000, B11111, B11111};  

byte LCDBigNumbers::c0[8] = {B11111, B11000, B11000, B11000, B11000, B11000, B11000, B11000}; 
byte LCDBigNumbers::c1[8] = {B11000, B11000, B11000, B11000, B11000, B11000, B11000, B11111};  
byte LCDBigNumbers::c2[8] = {B11111, B00011, B00011, B00011, B00011, B00011, B00011, B00011};  
byte LCDBigNumbers::c3[8] = {B00011, B00011, B00011, B00011, B00011, B00011, B00011, B11111};
byte LCDBigNumbers::c4[8] = {B11111, B11000, B11000, B11000, B11000, B11000, B11000, B11111};  
byte LCDBigNumbers::c5[8] = {B11111, B00011, B00011, B00011, B00011, B00011, B00011, B11111};  
byte LCDBigNumbers::c6[8] = {B11111, B00000, B00000, B00000, B00000, B00000, B00000, B00000};  
byte LCDBigNumbers::c7[8] = {B11111, B00000, B00000, B00000, B00000, B00000, B00000, B11111}; 

//byte LCDBigNumbers::c0[8] = {B11111, B10000, B10000, B10000, B10000, B10000, B10000, B10000}; 
//byte LCDBigNumbers::c1[8] = {B10000, B10000, B10000, B10000, B10000, B10000, B10000, B11111};  
//byte LCDBigNumbers::c2[8] = {B11111, B00001, B00001, B00001, B00001, B00001, B00001, B00001};  
//byte LCDBigNumbers::c3[8] = {B00001, B00001, B00001, B00001, B00001, B00001, B00001, B11111};
//byte LCDBigNumbers::c4[8] = {B11111, B10000, B10000, B10000, B10000, B10000, B10000, B11111};  
//byte LCDBigNumbers::c5[8] = {B11111, B00001, B00001, B00001, B00001, B00001, B00001, B11111};  
//byte LCDBigNumbers::c6[8] = {B11111, B00000, B00000, B00000, B00000, B00000, B00000, B00000};  
//byte LCDBigNumbers::c7[8] = {B11111, B00000, B00000, B00000, B00000, B00000, B00000, B11111}; 

LCDNumber LCDBigNumbers::_lcd_numbers[] = { 
      {0, 2, 1, 3}, //0
      {2, 9, 3, 1}, //1
      {7, 5, 4, 7}, //2
      {7, 5, 7, 5}, //3
      {1, 3, 6, 2}, //4
      {4, 7, 7, 5}, //5
      {4, 7, 4, 5}, //6
      {6, 5, 9, 2}, //7
      {4, 5, 4, 5}, //8
      {4, 5, 7, 5} // 9
    };
/*************************************************************************************************************
*******************************FIM CLASSE LCD BIG NUMBERS*****************************************************
**************************************************************************************************************/
   
   
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
LCDBigNumbers lcdNum(&lcd, 0,0); //inclui uma barra no lcd, primeira linha, coluna 1
   
void setup()   {
  Serial.begin(9600);
  lcdNum.createChars(); //cria os caracteres especiais na memoria
  lcdNum.setCol(1); //muda para a coluna numero 1
  lcdNum.setDist(1); //1 coluna entre um numero e outro
  
  
  pinMode(44, OUTPUT);  //arduino mega - pino de contraste do display
  analogWrite(44, 255/6); //utilizado para aumentar o contraste
  lcd.begin(16, 2);
}
 
long i = 0;
int col = 0;
 
void loop() {
  lcdNum.setValue(i++ * 11);
   
  if (i>=10000) { i = 0; }

  delay(500);  
}



O Código abaixo é uma versão alterada pra mostrar números e letras, a qual não está no vídeo. Ainda precisa de umas melhorias, mas de qualquer maneira, deixo aqui pra quem quiser testar:



/*
Fabiano A. Arndt - 2015
www.youtube.com/user/fabianoallex
www.facebook.com/dicasarduino
fabianoallex@gmail.com
*/
   
#include <LiquidCrystal.h>
   
/*************************************************************************************************************
*******************************CLASSE LCD BIG TEXT*********************************************************
**************************************************************************************************************/
struct LCDChar {
  byte top1;
  byte top2;
  byte bottom1;
  byte bottom2;
};
 
class LCDBigText {
  private:
    LiquidCrystal * _lcd;
    int _row;
    int _col;
    String _value; /*0..100*/
    byte _dist;  /*distancia entre digitos*/
     
    void _clear(){
      
      for (int i=0; i<_value.length(); i++) {
        //_lcd->setCursor(_col+i, _row);
        _lcd->setCursor(_col+i*(2+_dist), _row);
        _lcd->print( " " );
        _lcd->print( " " );
        for (byte b=0;b<_dist; b++){ _lcd->print( " " ); }
        _lcd->setCursor(_col+i*(2+_dist), _row+1);
        //_lcd->setCursor(_col+i, _row+1);
        _lcd->print( " " );
        _lcd->print( " " );
        for (byte b=0;b<_dist; b++){ _lcd->print( " " ); }
      }
    }
  public:
    static byte c0[8];  
    static byte c1[8];  
    static byte c2[8];  
    static byte c3[8];
    static byte c4[8];
    static byte c5[8];  
    static byte c6[8];  
    static byte c7[8];  
    static LCDChar _lcd_chars[];
    
    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);
      _lcd->createChar(7, c7);
    }
     
    LCDBigText(LiquidCrystal * lcd, int row, int col) {
      _lcd = lcd;      
      _row = row;      
      _col = col;
      _dist = 0;  //distancia entre os numeros
    }
     
    void setRow(int row){
      _clear();
      _row = row;
      setValue(_value);
    }
    void setCol(int col){
      _clear();
      _col = col;
      setValue(_value);
    }
    
    void setDist(int dist){
      _clear();
      _dist = dist;
      setValue(_value);
    }
     
    void setValue(String value){
      _clear();
      _value = value;
             
      for (int i=0; i<_value.length(); i++) {
        char n = value[i];
        
        //tabela ascii
        if (n >= 97 && n<=122){
          n = n - 97 + 10;
        } else if (n >= 65 && n<=90){
          n = n - 65 + 10;
        } else if (n >= 48 && n<=57) {
          n = n - 48;
        } else {
          n = 36;  //space
        }
        
        if (n ==0 ){
          _lcd->setCursor(_col+i*(2+_dist), _row);
          _lcd->print("  ");
          _lcd->setCursor(_col+i*(2+_dist), _row+1);
          _lcd->print("  ");
        } else {
          _lcd->setCursor(_col+i*(2+_dist), _row);
          _lcd_chars[n].top1 == 9 ? _lcd->print(" ") : _lcd->write( _lcd_chars[n].top1 );
          _lcd_chars[n].top2 == 9 ? _lcd->print(" ") : _lcd->write( _lcd_chars[n].top2 );
          _lcd->setCursor(_col+i*(2+_dist), _row+1);
          _lcd_chars[n].bottom1 == 9 ? _lcd->print(" ") : _lcd->write( _lcd_chars[n].bottom1 );
          _lcd_chars[n].bottom2 == 9 ? _lcd->print(" ") : _lcd->write( _lcd_chars[n].bottom2 );
        }
      }
    }
};

//byte LCDBigText::c0[8] = {B11111, B11111, B11000, B11000, B11000, B11000, B11000, B11000}; 
//byte LCDBigText::c1[8] = {B11000, B11000, B11000, B11000, B11000, B11000, B11111, B11111};  
//byte LCDBigText::c2[8] = {B11111, B11111, B00011, B00011, B00011, B00011, B00011, B00011};  
//byte LCDBigText::c3[8] = {B00011, B00011, B00011, B00011, B00011, B00011, B11111, B11111};
//byte LCDBigText::c4[8] = {B11111, B11111, B11000, B11000, B11000, B11000, B11111, B11111};  
//byte LCDBigText::c5[8] = {B11111, B11111, B00011, B00011, B00011, B00011, B11111, B11111};  
//byte LCDBigText::c6[8] = {B11111, B11111, B00000, B00000, B00000, B00000, B00000, B00000};  
//byte LCDBigText::c7[8] = {B11111, B11111, B00000, B00000, B00000, B00000, B11111, B11111};  

//byte LCDBigText::c0[8] = {B11111, B11000, B11000, B11000, B11000, B11000, B11000, B11000}; 
//byte LCDBigText::c1[8] = {B11000, B11000, B11000, B11000, B11000, B11000, B11000, B11111};  
//byte LCDBigText::c2[8] = {B11111, B00011, B00011, B00011, B00011, B00011, B00011, B00011};  
//byte LCDBigText::c3[8] = {B00011, B00011, B00011, B00011, B00011, B00011, B00011, B11111};
//byte LCDBigText::c4[8] = {B11111, B11000, B11000, B11000, B11000, B11000, B11000, B11111};  
//byte LCDBigText::c5[8] = {B11111, B00011, B00011, B00011, B00011, B00011, B00011, B11111};  
//byte LCDBigText::c6[8] = {B11111, B00000, B00000, B00000, B00000, B00000, B00000, B00000};  
//byte LCDBigText::c7[8] = {B11111, B00000, B00000, B00000, B00000, B00000, B00000, B11111}; 

byte LCDBigText::c0[8] = {B11111, B10000, B10000, B10000, B10000, B10000, B10000, B10000}; 
byte LCDBigText::c1[8] = {B10000, B10000, B10000, B10000, B10000, B10000, B10000, B11111};  
byte LCDBigText::c2[8] = {B11111, B00001, B00001, B00001, B00001, B00001, B00001, B00001};  
byte LCDBigText::c3[8] = {B00001, B00001, B00001, B00001, B00001, B00001, B00001, B11111};
byte LCDBigText::c4[8] = {B11111, B10000, B10000, B10000, B10000, B10000, B10000, B11111};  
byte LCDBigText::c5[8] = {B11111, B00001, B00001, B00001, B00001, B00001, B00001, B11111};  
byte LCDBigText::c6[8] = {B11111, B00000, B00000, B00000, B00000, B00000, B00000, B00000};  
byte LCDBigText::c7[8] = {B11111, B00000, B00000, B00000, B00000, B00000, B00000, B11111}; 

LCDChar LCDBigText::_lcd_chars[] = { 
      {0, 2, 1, 3}, //0
      {2, 9, 3, 1}, //1
      {7, 5, 4, 7}, //2
      {7, 5, 7, 5}, //3
      {1, 3, 6, 2}, //4
      {4, 7, 7, 5}, //5
      {4, 7, 4, 5}, //6
      {6, 5, 9, 2}, //7
      {4, 5, 4, 5}, //8
      {4, 5, 7, 5}, //9
      {0, 2, 0, 2}, //a
      {1, 9, 4, 5}, //b
      {0, 6, 1, 95}, //c
      {9, 3, 4, 5}, //d
      {4, 7, 4, 7}, //e
      {4, 6, 0, 9}, //f
      {0, 7, 1, 5}, //g
      {1, 3, 0, 2}, //h
      {3, 9, 3, 1}, //i
      {9, 2, 1, 3}, //j
      {1, '/', 0, '\\'}, //k
      {2, 9, 3, 1}, //l
      {95, 95, 0, 2}, //m
      {95, 9, 0, 2}, //n
      {0, 2, 1, 3}, //o
      {4, 5, 0, 6}, //p
      {4, 5, 6, 2}, //q
      {4, 5, 0, '\\'}, //r
      {4, 7, 7, 5}, //s
      {3, 1, 5, 4}, //t
      {9, 9, 1, 3}, //u
      {1, 3, '\\', '/'}, //v
      {1, 3, 'w', 'w'}, //w
      {'\\', '/', '/', '\\'}, //x
      {1, 3, 6, 2}, //y
      {6, '/', '/', 95}, //z
      {9, 9, 9, 9} //' '
    };
/*************************************************************************************************************
*******************************FIM CLASSE LCD BIG TEXT*****************************************************
**************************************************************************************************************/
   
   
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
LCDBigText lcdText(&lcd, 0,0); //inclui uma barra no lcd, primeira linha, coluna 1
   
void setup()   {
  Serial.begin(9600);
  lcdText.createChars(); //cria os caracteres especiais na memoria
  lcdText.setCol(0); //muda para a coluna numero 1
  lcdText.setDist(1); //1 coluna entre um numero e outro
  
  
  pinMode(44, OUTPUT);  //arduino mega - pino de contraste do display
  analogWrite(44, 255/6); //utilizado para aumentar o contraste
  lcd.begin(16, 2);
  
}
 
long i = 0;
int col = 0;
 
void loop() {
  
  lcd.clear();
  
  lcdText.setValue("ARDUINO");
  delay(3000);
  
  for (int i = 0; i < 20; i++) {
    lcd.scrollDisplayLeft();
    delay(600);
  }
  
  lcd.clear();
  
  lcdText.setValue("01234");
  delay(3000);
  
  lcdText.setValue("56789");
  delay(3000);
   
  lcdText.setValue("abcde");
  delay(3000);

  lcdText.setValue("fghij");
  delay(3000);  
  
  lcdText.setValue("klmno");
  delay(3000);
  
  lcdText.setValue("pqrst");
  delay(3000);
  
  lcdText.setValue("uvx");
  delay(3000);
  
  lcdText.setValue("wyz");
  delay(3000);
    
  lcdText.setValue("ABCDE");
  delay(3000);

  lcdText.setValue("FGHIJ");
  delay(3000);  
  
  lcdText.setValue("KLMNO");
  delay(3000);
  
  lcdText.setValue("PQRST");
  delay(3000);
  
  lcdText.setValue("UVX");
  delay(3000);
  
  lcdText.setValue("WYZ");
  delay(3000);
  
  
}

Nesse outro exemplo, é mostrado como juntar mais de um formato em uma mesma sketch. Por padrão o LCD aceita apenas 8 caracteres customizáveis, mas é possível criar mais de oito, desde que se tenha apenas 8 sendo usado por vez. Ao terminar de usar os primeiros 8, pode-se definir os outros 8 caracteres customizáveis.




/*
Fabiano A. Arndt - 2015
www.youtube.com/user/fabianoallex
www.facebook.com/dicasarduino
fabianoallex@gmail.com
*/
    
#include <LiquidCrystal.h>
    
/*************************************************************************************************************
*******************************CLASSE LCD BIG NUMBERS 1*******************************************************
**************************************************************************************************************/
struct LCDNumber_1 {
  byte top;
  byte bottom;
};
  
class LCDBigNumbers_1 {
  private:
    LiquidCrystal * _lcd;
    int _row;
    int _col;
    long _value; /*0..100*/
      
    void _clear(){
      int cont = 1;
      long x = 9;
      while (_value > x){
        cont++;
        x = x * 10 + 9;
      }
      for (int i=0; i<cont; i++) {
        _lcd->setCursor(_col+i, _row);
        _lcd->print( " " );
        _lcd->setCursor(_col+i, _row+1);
        _lcd->print( " " );
      }
    }
  public:
    static byte c0[8];  
    static byte c1[8];  
    static byte c2[8];  
    static byte c3[8];
    static byte c4[8];
    static byte c5[8];  
    static byte c6[8];  
    static byte c7[8];  
    static LCDNumber_1 _lcd_numbers[];
     
    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);
      _lcd->createChar(7, c7);
    }
      
    LCDBigNumbers_1(LiquidCrystal * lcd, int row, int col) {
      _lcd = lcd;      _row = row;      _col = col;
    }
      
    void setRow(int row){
      _clear();
      _row = row;
      setValue(_value);
    }
    void setCol(int col){
      _clear();
      _col = col;
      setValue(_value);
    }
      
    void setValue(long value){
      _clear();
      _value = value;
        
      int cont = 1;
      long x = 9;
      while (abs(_value) > x){
        cont++;
        x = x * 10 + 9;
      }
        
      for (int i=0; i<cont; i++) {
        int n = value / pow(10, cont-1-i);
        value = value - pow(10, cont-1-i) * n;
          
        _lcd->setCursor(_col+i, _row);
        _lcd->write( _lcd_numbers[n].top );
        _lcd->setCursor(_col+i, _row+1);
        _lcd->write( _lcd_numbers[n].bottom );
      }
        
    }
};
 
byte LCDBigNumbers_1::c0[8] = {B11111, B10001, B10001, B10001, B10001, B10001, B10001, B10001};
byte LCDBigNumbers_1::c1[8] = {B10001, B10001, B10001, B10001, B10001, B10001, B10001, B11111};
byte LCDBigNumbers_1::c2[8] = {B00001, B00001, B00001, B00001, B00001, B00001, B00001, B00001};
byte LCDBigNumbers_1::c3[8] = {B11111, B00001, B00001, B00001, B00001, B00001, B00001, B11111};
byte LCDBigNumbers_1::c4[8] = {B11111, B10000, B10000, B10000, B10000, B10000, B10000, B11111};
byte LCDBigNumbers_1::c5[8] = {B11111, B00001, B00001, B00001, B00001, B00001, B00001, B00001};    
byte LCDBigNumbers_1::c6[8] = {B11111, B10001, B10001, B10001, B10001, B10001, B10001, B11111};              
byte LCDBigNumbers_1::c7[8] = {B00001, B00001, B00001, B00001, B00001, B00001, B00001, B11111};
 
 
LCDNumber_1 LCDBigNumbers_1::_lcd_numbers[] = { 
  {0, 1}, //0
  {2, 2}, //1
  {5, 4}, //2
  {3, 7}, //3
  {1, 2}, //4
  {4, 7}, //5
  {4, 1}, //6
  {5, 2}, //7
  {6, 1}, //8
  {6, 7} // 9
};
  
 
/*************************************************************************************************************
*******************************FIM CLASSE LCD BIG NUMBERS 1***************************************************
**************************************************************************************************************/
    
    
/*************************************************************************************************************
*******************************CLASSE LCD BIG NUMBERS 2*******************************************************
**************************************************************************************************************/
struct LCDNumber_2 {
  byte top1;
  byte top2;
  byte bottom1;
  byte bottom2;
};
  
class LCDBigNumbers_2 {
  private:
    LiquidCrystal * _lcd;
    int _row;
    int _col;
    long _value; /*0..100*/
    byte _dist;  /*distancia entre digitos*/
      
    void _clear(){
      int cont = 1;
      long x = 9;
      while (_value > x){
        cont++;
        x = x * 10 + 9;
      }
      for (int i=0; i<cont; i++) {
        _lcd->setCursor(_col+i, _row);
        _lcd->print( "  " );
        for (byte b=0;b<_dist; b++){ _lcd->print( " " ); }
        _lcd->setCursor(_col+i, _row+1);
        _lcd->print( "  " );
        for (byte b=0;b<_dist; b++){ _lcd->print( " " ); }
      }
    }
  public:
    static byte c0[8];  
    static byte c1[8];  
    static byte c2[8];  
    static byte c3[8];
    static byte c4[8];
    static byte c5[8];  
    static byte c6[8];  
    static byte c7[8];  
    static LCDNumber_2 _lcd_numbers[];
     
    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);
      _lcd->createChar(7, c7);
    }
      
    LCDBigNumbers_2(LiquidCrystal * lcd, int row, int col) {
      _lcd = lcd;      
      _row = row;      
      _col = col;
      _dist = 0;  //distancia entre os numeros
    }
      
    void setRow(int row){
      _clear();
      _row = row;
      setValue(_value);
    }
    void setCol(int col){
      _clear();
      _col = col;
      setValue(_value);
    }
     
    void setDist(int dist){
      _clear();
      _dist = dist;
      setValue(_value);
    }
      
    void setValue(long value){
      _clear();
      _value = value;
        
      int cont = 1;
      long x = 9;
      while (abs(_value) > x){
        cont++;
        x = x * 10 + 9;
      }
        
      for (int i=0; i<cont; i++) {
        int n = value / pow(10, cont-1-i);
        value = value - pow(10, cont-1-i) * n;
          
        _lcd->setCursor(_col+i*(2+_dist), _row);
        _lcd_numbers[n].top1 == 9 ? _lcd->print(" ") : _lcd->write( _lcd_numbers[n].top1 );
        _lcd_numbers[n].top2 == 9 ? _lcd->print(" ") : _lcd->write( _lcd_numbers[n].top2 );
        _lcd->setCursor(_col+i*(2+_dist), _row+1);
        _lcd_numbers[n].bottom1 == 9 ? _lcd->print(" ") : _lcd->write( _lcd_numbers[n].bottom1 );
        _lcd_numbers[n].bottom2 == 9 ? _lcd->print(" ") : _lcd->write( _lcd_numbers[n].bottom2 );
      }
    }
};
 
//byte LCDBigNumbers_2::c0[8] = {B11111, B11111, B11000, B11000, B11000, B11000, B11000, B11000}; 
//byte LCDBigNumbers_2::c1[8] = {B11000, B11000, B11000, B11000, B11000, B11000, B11111, B11111};  
//byte LCDBigNumbers_2::c2[8] = {B11111, B11111, B00011, B00011, B00011, B00011, B00011, B00011};  
//byte LCDBigNumbers_2::c3[8] = {B00011, B00011, B00011, B00011, B00011, B00011, B11111, B11111};
//byte LCDBigNumbers_2::c4[8] = {B11111, B11111, B11000, B11000, B11000, B11000, B11111, B11111};  
//byte LCDBigNumbers_2::c5[8] = {B11111, B11111, B00011, B00011, B00011, B00011, B11111, B11111};  
//byte LCDBigNumbers_2::c6[8] = {B11111, B11111, B00000, B00000, B00000, B00000, B00000, B00000};  
//byte LCDBigNumbers_2::c7[8] = {B11111, B11111, B00000, B00000, B00000, B00000, B11111, B11111};  
 
byte LCDBigNumbers_2::c0[8] = {B11111, B11000, B11000, B11000, B11000, B11000, B11000, B11000}; 
byte LCDBigNumbers_2::c1[8] = {B11000, B11000, B11000, B11000, B11000, B11000, B11000, B11111};  
byte LCDBigNumbers_2::c2[8] = {B11111, B00011, B00011, B00011, B00011, B00011, B00011, B00011};  
byte LCDBigNumbers_2::c3[8] = {B00011, B00011, B00011, B00011, B00011, B00011, B00011, B11111};
byte LCDBigNumbers_2::c4[8] = {B11111, B11000, B11000, B11000, B11000, B11000, B11000, B11111};  
byte LCDBigNumbers_2::c5[8] = {B11111, B00011, B00011, B00011, B00011, B00011, B00011, B11111};  
byte LCDBigNumbers_2::c6[8] = {B11111, B00000, B00000, B00000, B00000, B00000, B00000, B00000};  
byte LCDBigNumbers_2::c7[8] = {B11111, B00000, B00000, B00000, B00000, B00000, B00000, B11111}; 
 
//byte LCDBigNumbers_2::c0[8] = {B11111, B10000, B10000, B10000, B10000, B10000, B10000, B10000}; 
//byte LCDBigNumbers_2::c1[8] = {B10000, B10000, B10000, B10000, B10000, B10000, B10000, B11111};  
//byte LCDBigNumbers_2::c2[8] = {B11111, B00001, B00001, B00001, B00001, B00001, B00001, B00001};  
//byte LCDBigNumbers_2::c3[8] = {B00001, B00001, B00001, B00001, B00001, B00001, B00001, B11111};
//byte LCDBigNumbers_2::c4[8] = {B11111, B10000, B10000, B10000, B10000, B10000, B10000, B11111};  
//byte LCDBigNumbers_2::c5[8] = {B11111, B00001, B00001, B00001, B00001, B00001, B00001, B11111};  
//byte LCDBigNumbers_2::c6[8] = {B11111, B00000, B00000, B00000, B00000, B00000, B00000, B00000};  
//byte LCDBigNumbers_2::c7[8] = {B11111, B00000, B00000, B00000, B00000, B00000, B00000, B11111}; 
 
LCDNumber_2 LCDBigNumbers_2::_lcd_numbers[] = { 
      {0, 2, 1, 3}, //0
      {2, 9, 3, 1}, //1
      {7, 5, 4, 7}, //2
      {7, 5, 7, 5}, //3
      {1, 3, 6, 2}, //4
      {4, 7, 7, 5}, //5
      {4, 7, 4, 5}, //6
      {6, 5, 9, 2}, //7
      {4, 5, 4, 5}, //8
      {4, 5, 7, 5} // 9
    };
/*************************************************************************************************************
*******************************FIM CLASSE LCD BIG NUMBERS 2***************************************************
**************************************************************************************************************/
 
    
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
LCDBigNumbers_1 lcdNum_1(&lcd, 0,0); //inclui uma barra no lcd, primeira linha, coluna 1
LCDBigNumbers_2 lcdNum_2(&lcd, 0,0); //inclui uma barra no lcd, primeira linha, coluna 1
    
void setup()   {
  Serial.begin(9600);
  lcdNum_1.createChars(); //cria os caracteres especiais na memoria
  lcdNum_2.setDist(1);
   
  pinMode(44, OUTPUT);  //arduino mega - pino de contraste do display
  analogWrite(44, 255/6); //utilizado para aumentar o contraste
  lcd.begin(16, 2);
}
  
long i = 0;
int col = 0;
  
int x = 1;
  
void loop() {
  int c = Serial.read();
    
  if (c == 97)  { lcd.clear(); lcdNum_1.createChars(); x = 1;  } //a
  if (c == 98)  { lcd.clear(); lcdNum_2.createChars(); x = 2;  } //b
   
  if (x == 1) { lcdNum_1.setValue(i++ * 11); }
  if (x == 2) { lcdNum_2.setValue(i++ * 11); }
  
  if (i>=10000) { i = 0; }
  delay(500);  
}



Pra demonstrar com um exemplo um pouco mais prático, criei um relógio que mostra a data e hora em um display LCD, utilizando RTC - DS3231




Código-fonte:

 /*
Fabiano A. Arndt - 2015
www.youtube.com/user/fabianoallex
www.facebook.com/dicasarduino
fabianoallex@gmail.com
*/
    
#include <LiquidCrystal.h>
#include <Wire.h>
#include "DS3231.h"


LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
DS3231 RTC; 
    
/*************************************************************************************************************
*******************************CLASSE LCD BIG NUMBERS*********************************************************
para mais detalhes: http://fabianoallex.blogspot.com.br/2015/10/arduino-lcd-big-numbers.html
**************************************************************************************************************
**************************************************************************************************************/
struct LCDNumber {
  byte top1;
  byte top2;
  byte bottom1;
  byte bottom2;
};
  
class LCDBigNumbers {
  private:
    LiquidCrystal * _lcd;
    int _row;
    int _col;
    long _value; /*0..100*/
    byte _dist;  /*distancia entre digitos*/
    byte _min_size; /*tamanho minimo.  3--> 001   002 ;;;;  2 --> 01   02*/
      
    void _clear(){
      int cont = 1;
      long x = 9;
      while (_value > x){
        cont++;
        x = x * 10 + 9;
      }
      
      for (int i=0; i<cont; i++) {
        if (cont <= (_min_size-1) && i <= (_min_size -2) ){ //zeros a esquerda
          cont ++;
        }
        
        _lcd->setCursor(_col+i*(2+_dist), _row);
        _lcd->print( " " );
        _lcd->print( " " );
        for (byte b=0;b<_dist; b++){ _lcd->print( " " ); }
        _lcd->setCursor(_col+i*(2+_dist), _row+1);
        _lcd->print( " " );
        _lcd->print( " " );
        for (byte b=0;b<_dist; b++){ _lcd->print( " " ); }
      }
    }
  public:
    static byte c0[8];  
    static byte c1[8];  
    static byte c2[8];  
    static byte c3[8];
    static byte c4[8];
    static byte c5[8];  
    static byte c6[8];  
    static byte c7[8];  
    static LCDNumber _lcd_numbers[];
     
    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);
      _lcd->createChar(7, c7);
    }
      
    LCDBigNumbers(LiquidCrystal * lcd, int row, int col) {
      _lcd = lcd;      
      _row = row;      
      _col = col;
      _dist = 0;  //distancia entre os numeros
      _min_size = 1;
    }
      
    void setRow(int row){
      _clear();
      _row = row;
      setValue(_value);
    }
    void setCol(int col){
      _clear();
      _col = col;
      setValue(_value);
    }
     
    void setDist(int dist){
      _clear();
      _dist = dist;
      setValue(_value);
    }
    
    void setMinSize(byte min_size){
      _min_size = min_size;
    }
      
    void setValue(long value){
      _clear();
      _value = value;
        
      int cont = 1;
      long x = 9;
      while (abs(_value) > x){
        cont++;
        x = x * 10 + 9;
      }
      
      for (int i=0; i<cont; i++) {
        int n;
        
        if (cont <= (_min_size-1) && i <= (_min_size -2) ){ //zeros a esquerda
          n = 0;
          cont ++;
        } else {
          
          n = value / pow(10, cont-1-i);
          value = value - pow(10, cont-1-i) * n;
        }
          
        _lcd->setCursor(_col+i*(2+_dist), _row);
        _lcd_numbers[n].top1 == 9 ? _lcd->print(" ") : _lcd->write( _lcd_numbers[n].top1 );
        _lcd_numbers[n].top2 == 9 ? _lcd->print(" ") : _lcd->write( _lcd_numbers[n].top2 );
        _lcd->setCursor(_col+i*(2+_dist), _row+1);
        _lcd_numbers[n].bottom1 == 9 ? _lcd->print(" ") : _lcd->write( _lcd_numbers[n].bottom1 );
        _lcd_numbers[n].bottom2 == 9 ? _lcd->print(" ") : _lcd->write( _lcd_numbers[n].bottom2 );
      }
    }
};
 
//byte LCDBigNumbers::c0[8] = {B11111, B11111, B11000, B11000, B11000, B11000, B11000, B11000}; 
//byte LCDBigNumbers::c1[8] = {B11000, B11000, B11000, B11000, B11000, B11000, B11111, B11111};  
//byte LCDBigNumbers::c2[8] = {B11111, B11111, B00011, B00011, B00011, B00011, B00011, B00011};  
//byte LCDBigNumbers::c3[8] = {B00011, B00011, B00011, B00011, B00011, B00011, B11111, B11111};
//byte LCDBigNumbers::c4[8] = {B11111, B11111, B11000, B11000, B11000, B11000, B11111, B11111};  
//byte LCDBigNumbers::c5[8] = {B11111, B11111, B00011, B00011, B00011, B00011, B11111, B11111};  
//byte LCDBigNumbers::c6[8] = {B11111, B11111, B00000, B00000, B00000, B00000, B00000, B00000};  
//byte LCDBigNumbers::c7[8] = {B11111, B11111, B00000, B00000, B00000, B00000, B11111, B11111};  
 
byte LCDBigNumbers::c0[8] = {B11111, B11000, B11000, B11000, B11000, B11000, B11000, B11000}; 
byte LCDBigNumbers::c1[8] = {B11000, B11000, B11000, B11000, B11000, B11000, B11000, B11111};  
byte LCDBigNumbers::c2[8] = {B11111, B00011, B00011, B00011, B00011, B00011, B00011, B00011};  
byte LCDBigNumbers::c3[8] = {B00011, B00011, B00011, B00011, B00011, B00011, B00011, B11111};
byte LCDBigNumbers::c4[8] = {B11111, B11000, B11000, B11000, B11000, B11000, B11000, B11111};  
byte LCDBigNumbers::c5[8] = {B11111, B00011, B00011, B00011, B00011, B00011, B00011, B11111};  
byte LCDBigNumbers::c6[8] = {B11111, B00000, B00000, B00000, B00000, B00000, B00000, B00000};  
byte LCDBigNumbers::c7[8] = {B11111, B00000, B00000, B00000, B00000, B00000, B00000, B11111}; 
 
//byte LCDBigNumbers::c0[8] = {B11111, B10000, B10000, B10000, B10000, B10000, B10000, B10000}; 
//byte LCDBigNumbers::c1[8] = {B10000, B10000, B10000, B10000, B10000, B10000, B10000, B11111};  
//byte LCDBigNumbers::c2[8] = {B11111, B00001, B00001, B00001, B00001, B00001, B00001, B00001};  
//byte LCDBigNumbers::c3[8] = {B00001, B00001, B00001, B00001, B00001, B00001, B00001, B11111};
//byte LCDBigNumbers::c4[8] = {B11111, B10000, B10000, B10000, B10000, B10000, B10000, B11111};  
//byte LCDBigNumbers::c5[8] = {B11111, B00001, B00001, B00001, B00001, B00001, B00001, B11111};  
//byte LCDBigNumbers::c6[8] = {B11111, B00000, B00000, B00000, B00000, B00000, B00000, B00000};  
//byte LCDBigNumbers::c7[8] = {B11111, B00000, B00000, B00000, B00000, B00000, B00000, B11111}; 
 
LCDNumber LCDBigNumbers::_lcd_numbers[] = { 
      {0, 2, 1, 3}, //0
      {2, 9, 3, 1}, //1
      {7, 5, 4, 7}, //2
      {7, 5, 7, 5}, //3
      {1, 3, 6, 2}, //4
      {4, 7, 7, 5}, //5
      {4, 7, 4, 5}, //6
      {6, 5, 9, 2}, //7
      {4, 5, 4, 5}, //8
      {4, 5, 7, 5} // 9
    };
/*************************************************************************************************************
*******************************FIM CLASSE LCD BIG NUMBERS*****************************************************
**************************************************************************************************************/

/*************************************************************************************************************
*******************************FUNÇÃO TIME********************************************************************
mais detalhes: 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);
}
/*************************************************************************************************************
*******************************FIM FUNÇÃO TIME****************************************************************
**************************************************************************************************************/



/*************************************************************************************************************
*******************************FUNÇÕES DO RELOGIO*************************************************************
**************************************************************************************************************/

LCDBigNumbers lcdNum01(&lcd, 0, 1); //inclui um número no lcd, primeira linha, coluna 1
LCDBigNumbers lcdNum02(&lcd, 0, 6); //inclui um número no lcd, primeira linha, coluna 6
LCDBigNumbers lcdNum03(&lcd, 0, 11); //inclui um número no lcd, primeira linha, coluna 11
    
int sec = 0;
int date = 0;
unsigned long mclock = 0;
char dot = ' ';
  
void show_clock(){
  if (millis()-mclock > 500) {
    
    DateTime now = RTC.now(); //get the current date-time
  
    if (sec != now.second()){
      lcdNum01.setValue(now.hour());
      lcdNum02.setValue(now.minute());
      lcdNum03.setValue(now.second());
      
      sec = now.second();
    }
    
    dot = (dot == '.') ? dot = ' ' : dot = '.';

    lcd.setCursor(5, 0);
    lcd.print(dot);
    lcd.setCursor(10, 0);
    lcd.print(dot);
    lcd.setCursor(5, 1);
    lcd.print(dot);
    lcd.setCursor(10, 1);
    lcd.print(dot);
    
    mclock = millis();
  }
}


void show_date(){
  if (millis()-mclock > 500) {
    DateTime now = RTC.now(); //get the current date-time
    
    if (date != now.second()){
      lcdNum01.setValue(now.date());
      lcdNum02.setValue(now.month());
      lcdNum03.setValue(now.year()-2000);
      
      date = now.date();
    }
    
    lcd.setCursor(5, 0);
    lcd.print(' ');
    lcd.setCursor(10, 0);
    lcd.print(' ');
    
    lcd.setCursor(5, 1);
    lcd.print('.');
    lcd.setCursor(10, 1);
    lcd.print('.');
    
    mclock = millis();
  }
}

/*************************************************************************************************************
*******************************FIM FUNÇÕES DO RELOGIO*********************************************************
**************************************************************************************************************/

void setup()   {
  Serial.begin(9600);
  lcdNum01.createChars(); //cria os caracteres especiais na memoria
  
  lcdNum01.setMinSize(2); //duas casas 00 01 02
  lcdNum02.setMinSize(2); //duas casas 00 01 02
  lcdNum03.setMinSize(2); //duas casas 00 01 02
   
  pinMode(44, OUTPUT);  //arduino mega - pino de contraste do display
  analogWrite(44, 255/6); //utilizado para aumentar o contraste
  lcd.begin(16, 2);
  
  Wire.begin();
  RTC.begin();
}

void loop() {
  //mostra por 10 segundos a hora e por 5 segundos a data
  if ( time(10000, 5000, 0) ) {
    show_clock();
  } else {
    show_date();
  }
}


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