Nesse post vou mostrar como reutilizar uma placa de teclado PS2 de PC para usarmos com um teclado 4x4 no Arduino. Mas qual a vantagem disso? Primeiro que se formos utilizar o teclado 4x4 diretamente no Arduino, precisaríamos de 8 pinos e dependendo da forma de montagem mais alguns resistores e até diodos. Com uma placa de teclado, reduzimos para apenas 2 pinos.
Na imagem abaixo temos a demonstração dos pinos de um conector PS2, tanto macho quanto fêmea.
No exemplo mostrado no vídeo utilizo apenas o conector macho, ligando :
GND-3 ao gnd do Arduino,
4-+5V ao Vcc do Arduino,
CLK-5 ao pino 2 (INT.0); e o
DATA-1 ao pino 3 do Arduino.
Vídeo:
Código-fonte:
/*
2015 - Fabiano A. Arndt
fabianoallex@gmail.com
www.facebook.com/dicasarduino
Alterado para ser utilizado com teclados matriciais 3x4, 4x4 ou outros
originalmente desenvolvida para ser utilizado através de teclado ps2, conforme descrição abaixo:
PS2Keyboard.h - PS2Keyboard library
Copyright (c) 2007 Free Software Foundation. All right reserved.
Written by Christian Weichel <info@32leaves.net>
** Mostly rewritten Paul Stoffregen <paul@pjrc.com>, June 2010
** Modified for use with Arduino 13 by L. Abraham Smith, <n3bah@microcompdesign.com> *
** Modified for easy interrup pin assignement on method begin(datapin,irq_pin). Cuningan <cuninganreset@gmail.com> **
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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#define PS2_KEYPAD_BREAK 0x01
#define PS2_KEYPAD_MODIFIER 0x02
#define PS2_KEYPAD_SHIFT_L 0x04
#define PS2_KEYPAD_SHIFT_R 0x08
#define PS2_KEYPAD_ALTGR 0x10
#define PS2_KEYPAD_BUFFER_SIZE 45
#define makeKeymapChar(x) ((char*)x)
#define makeKeymapByte(x) ((byte*)x)
class PS2Keypad {
private:
volatile byte buffer[PS2_KEYPAD_BUFFER_SIZE];
volatile byte head, tail;
byte DataPin;
byte CharBuffer = 0;
byte UTF8next = 0;
char * _chars;
byte * _codes;
byte _cols;
byte _rows;
inline byte get_scan_code(void) {
byte c, i;
i = tail;
if (i == head) { return 0; }
if (++i >= PS2_KEYPAD_BUFFER_SIZE) { i = 0; }
c = buffer[i];
tail = i;
return c;
}
char get_iso8859_code(void) {
static byte state=0;
byte s;
while (1) {
s = get_scan_code();
if (!s) return 0;
if (s == 0xF0) { state |= PS2_KEYPAD_BREAK; } else
if (s == 0xE0) { state |= PS2_KEYPAD_MODIFIER; } else {
if (state & PS2_KEYPAD_BREAK) {
if (s == 0x12) { state &= ~PS2_KEYPAD_SHIFT_L; } else
if (s == 0x59) { state &= ~PS2_KEYPAD_SHIFT_R; } else
if (s == 0x11 && (state & PS2_KEYPAD_MODIFIER)) { state &= ~PS2_KEYPAD_ALTGR; }
state &= ~(PS2_KEYPAD_BREAK | PS2_KEYPAD_MODIFIER); continue;
}
if (s == 0x12) { state |= PS2_KEYPAD_SHIFT_L; continue; } else
if (s == 0x59) { state |= PS2_KEYPAD_SHIFT_R; continue; } else
if (s == 0x11 && (state & PS2_KEYPAD_MODIFIER)) { state |= PS2_KEYPAD_ALTGR; }
return s;
}
}
}
public:
PS2Keypad(byte dataPin, byte rows, byte cols, char * chars, byte * codes) {
DataPin = dataPin;
digitalWrite(DataPin, HIGH);
head = 0;
tail = 0;
_chars = chars;
_codes = codes;
_cols = cols;
_rows = rows;
}
bool available(){
if (CharBuffer || UTF8next) return true;
CharBuffer = get_iso8859_code();
if (CharBuffer) return true;
return false;
}
int read(){
byte result = UTF8next;
if (result) { UTF8next = 0; }
else {
result = CharBuffer;
if (result) { CharBuffer = 0; }
else { result = get_iso8859_code(); }
if (result >= 128) {
UTF8next = (result & 0x3F) | 0x80;
result = ((result >> 6) & 0x1F) | 0xC0;
}
}
if (!result) { return -1; }
return result;
}
char readChar(){
byte code = read();
for (int i=0; i<_rows;i++){
for (int j=0; j<_cols;j++){
if ( *(_codes+ i*_rows + j) == code) { return *(_chars+ i*_rows + j); }
}
}
return ' ';
}
void execInterrupt(void) {
static byte bitcount = 0;
static byte incoming = 0;
static unsigned long prev_ms = 0;
unsigned long now_ms;
byte n, val;
val = digitalRead(DataPin);
now_ms = millis();
if (now_ms - prev_ms > 250) {
bitcount = 0;
incoming = 0;
}
prev_ms = now_ms;
n = bitcount - 1;
if (n <= 7) { incoming |= (val << n); }
bitcount++;
if (bitcount == 11) {
byte i = head + 1;
if (i >= PS2_KEYPAD_BUFFER_SIZE) { i = 0; }
if (i != tail) { buffer[i] = incoming; head = i; }
bitcount = 0;
incoming = 0;
}
}
};
/*****daqui pra baixo é mostrado como usar a classe PS2Keypad***************/
#define PIN_DATA 3
#define ROWS 4
#define COLS 4
char keys_chars[ROWS][COLS] = { {'1','2','3', 'A'}, {'4','5','6','B'}, {'7','8','9', 'C'}, {'*','0','#','D'} };
byte keys_codes[ROWS][COLS] = { {58, 49, 54, 61}, {26, 103,14, 22}, {33, 19, 6, 38}, {34, 100,5, 30} }; //esse código será diferente de acordo com as linhas e colunas escolhidas
PS2Keypad keypad(PIN_DATA, ROWS, COLS, makeKeymapChar(keys_chars), makeKeymapByte(keys_codes));
void interrupt_keypad(){ keypad.execInterrupt(); }
void setup() {
Serial.begin(9600);
Serial.println("Keypad Test:");
attachInterrupt(INT0, interrupt_keypad, FALLING); //atacha a interrupção que trata o recebimento dos dados. SEMPRE FALLING deve ser usado
}
void loop() {
if (keypad.available()) {
//int c = keypad.read();
char c = keypad.readChar();
Serial.print(c);
}
}
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.
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.
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:
}
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);
}
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:
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.