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.
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.
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/