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/

3 comentários:

  1. Fabiano,

    Leitura das trilhas do chip emv, você tem algum tutorial também?Sabemos que tarjas não irão duram muito no mercado.

    t+

    ResponderExcluir
  2. WHATSAPP: 094 984251207

    Como solicitar Cartão de Crédito ou Informações CC? Com os seguintes Bancos: Bradesco, BANCO DO BRASIL, ITAU, SANTANDER E CAIXA ECONÔMICA. BANDEIRAS: Visa, Mastercard e Elo.

    Conhecidos por serem uns bancos sólidos e com nomes no mercado financeiro, o Banco Bradesco, Brasil, Itaú, Santander e Caixa Econômica possui uma vasta lista de clientes que buscaram essas instituiçães acreditando em seus potenciais econômicos. E na lista de produtos oferecidos, Iremos destacar o cartão de crédito com as bandeiras Visa, Mastercard e Elo. O qual estamos trabalhando em 2020.

    Com essas bandeiras você pode efetivar seus pagamentos bancários e consumo sem sair de casa. Tudo isso via internet. Lembrando que você pode fazer suas vendas digitadas na sua própria máquineta com segurança, e pode também efetivar suas compras online.

    CNH:

    OBTENHA SUA CNH SEM BUROCRACIA

    Obtenha sua habilitação, fácil e rápido e totalmente seguro, autorizada e emitida pelo detran com prontuário e com consulta, entre em contato conosco para maiores informações!

    QUERO COMPRAR MINHA CNH.

     Habilitação sem provas e exames

     Não é necessário provas teoricas

     Não é necessário provas práticas

     Não é necessário provas de simulador

     Dados são inseridos direto no Detran

     Sigilo total de comprador e vendedor

     Documentação totalmente original

     Dados escaneados para facilidade

     Entrega de produtos com rastreamento.

     Formas de pagamentos seguras e facilitadas.

     Consultas de documentos emitidos

    RASTREAMENTO
    Forneceremos um código de rastreio para que você acompanhe seu pedido.


    QUALIDADE
    Não trabalhamos com falsificações, nossos documentos são originais.

    NÃO PERCA AS OPORTUNIDADES

    A carteira de habilitação, já é considerado uma prioridade na vida das pessoas, principalmente dos jovens que precisam ter uma permissão legal para dirigir e garantir também mais espaço no mercado de trabalho possibilitando um leque maior de opções de emprego. Podemos ajudar você! Processo rápido, fácil, sem burocracia e totalmente seguro.

    POR QUE OFERECEMOS A MELHOR SOLUÇÃO?

    Você cliente não terá que pagar taxas. Os valores combinados será feito entre comprador e vendedor contém todos custos, sendo assim o comprador estará livre de taxas extras. Todas suas perguntas serão respondidas com satisfação. Negociação feita de forma segura para ambos os lados.

    Quais taxas são isentas na compra?


     Expedição do documento de habilitação

     Avaliação psicológica

     Exame de aptidão física e mental

     Exame teórico-técnico

     Exame de direção

     Curso teórico-técnico

     Curso de prática de direção veicular e locação do veículo para o exame de direção.

    Escolha sua categoria

    Escolha sua categoria e se informe sobre valores com nosso atendente no WhatsApp: 094 984251207


    .Comprar Habilitação categoria A / AB

    É válida exclusivamente para condutor de veículo motorizado de 2 ou 3 rodas.

    .Comprar Habilitação categoria B / AB
    Categoria designada para condutor de carros de passeio.

    .Comprar Habilitação categoria C

    Essa categoria é utilizada em transporte de carga, peso bruto é de 3,5 ton.

    .Comprar Habilitação categoria D / E

    Essa categoria é utilizada em transporte de carga, peso pesado.

    × EFETIVAMOS PAGAMENTOS DE BOLETOS BANCÁRIOS E CONSUMO.

    * TIRAMOS SEU NOME DA INADIMPLÊNCIA (CPC E SERASA).

    .(EMITIMOS: RG, CPF, RG NASCIMENTO, RESEVISTA, PASSAPORTE E CT).

    WHATSAPP: 094 984251207

    ResponderExcluir