Publicidade:

terça-feira, 29 de dezembro de 2015

Arduino - Programando o Pro mini com o Uno ou com o Mega

Uma das coisas legais dos Arduinos, é que existem várias versões e cada versão com suas vantagens, seja em tamanho, custo, capacidade de processamento, etc. 


Uma das versões que eu gosto é o Pro mini que consegue reunir quase todos os itens mencionados anteriormente, pequeno, barato e poderoso, tudo em uma pequena plaquinha de 3,4 x 1,9 cm, 8 pinos analógicos, 14 pinos digitais, sendo 6 pwm, botão de reset, led no pino 13 e outro led indicador de energizado e opera a velocidade de 16MHz. Quanto a memória, o Pro Mini disponibiliza 16Kb para memória flash, sendo 2Kb utilizados pelo bootloader, 1Kb de SRAM e 512 bytes de EEPROM. Sem dúvida é uma boa candidata para tirar do papel aquele projeto que você já vem querendo implementar há algum tempo.

Um detalhe importante é que os conectores não vêm soldados na placa, ficando a cargo de quem for utilizá-la escolher qual o melhor tipo de conector a ser utilizado, o que lhe dá a vantagem de poder soldar apenas os pinos que serão de fato necessários de acordo com o projeto a ser implementado. Uma dica quando for soldar os conectores, é usar um pouco de pasta de solda nos terminais, o que facilita bastante conseguir uma solda mais uniforme sem que sobre solda.





Na imagem acima pode-se notar que os 4 primeiros conectores ficaram com a solda "grossa". Como não gostei muito do resultado tentei passar um pouco de pasta de solda, e o resultado foi bem melhor como pode ser observado nos pinos mais à direita.

Em geral os conectores a serem soldados são aqueles que permitem conectar a placa na protoboard ou ainda usar jumper, mas caso vc tenha em mente utilizar alguma outro tipo de conector, tenha certeza que você conseguirá fazer o upload do código após a soldagem, pois dependendo do conector escolhido, o processo de upload pode ficar um pouco mais complicado. Se Notar que será complicado fazer o upload depois de conectar os fios, tente fazer o upload do programa antes de soldar os conectores.

Para mais detalhes sobre as funcionalidades da plaquinha, vale a pena uma boa olhada na imagem abaixo:



Quanto a alimentação pode ser feita diretamente pelo pino Vcc com tensão já regulada em 5V ou através do pino RAW em até 12V pois a placa conta com um regulador de tensão.

Ele não é necessariamente o candidato ideal pra prototipagem, já que nesse quesito o Arduino Uno e o Mega ou outros são os mais indicados, por isso na hora de programá-lo, você irá precisar contar com alguns itens adicionais.

Conversor FTDI

Uma das formas de programa-lo é utilizando um conversor FTDI, o qual possui um conector USB. Porém nesse artigo não irei abordar o seu uso (mas talvez futuramente eu inclua aqui explicações de como utilizá-lo).


Arduino Uno com o Atmega Desencaixado

Outra possibilidade é utilizar um Arduino Uno sem o Atmega conectado na placa. Para isso siga as conexões mostradas abaixo mas com o Atmega desconectado.



Montado na protoboard com os pinos já soldados e com o Atmega removido:


Depois de tudo conectado é hora de configurar a IDE do Arduino para enviar o código para o Pro Mini. Para isso selecione a placa, conforme imagem a baixo.



Feito isso, selecione então o ATmega utilizado, no caso, ATMega328 (5V, 16MHz), como na imagem abaixo:



Agora é só enviar o código. No Exemplo que utilizei, liguei um potenciômetro ao pino A1, um led no pino 13 e outro led no pino 5. O led no pino 13 irá ficar piscando enquanto que o led no pino pwm será controlado através da leitura do pino analógico A1.

O Código possui uma classe temporizadora, a qual ainda irei fazer um outro artigo explicando melhor os detalhes de como ela funciona, mas já fiz outro artigo que abordo sobre temporização, que vale a pena dar uma olhada: http://fabianoallex.blogspot.com.br/2015/09/arduino-como-substituir-delay-pelo.html

Mas o código aqui pode ser qualquer outro que esteja programado para o que você deseja executar.

Código Utilizado:



/*********************************************************************************************************
************************************CLASSE MYTIMER********************************************************
**********************************************************************************************************/
struct MyTimerSequence{
  unsigned long * times;
  unsigned int size;
  unsigned int repeat;
};
class MyTimer{
  private:
    boolean          _enable;
    unsigned long    _mref;
    unsigned long    _mref_disable;
    unsigned long    _time_disable;
    MyTimerSequence *_sequences;
    long             _lag;
    int              _quantidade;
    int              _index_time;
    int              _last_index_time;
    int              _index_sequence;
    int              _last_index_sequence;
    void             (*_onChanging)( int index_sequence, int index_time ); //ponteiro para funcao do evento onChanging
    void             (*_onEnable)();
    void             (*_onDisable)();
  public:
    MyTimer(MyTimerSequence * sequences, int quantidade, long lag, unsigned long mref){
      _lag = lag;
      _mref = mref;
      _sequences = sequences;
      _quantidade = quantidade;
      _time_disable = 0;
      _enable = false;
      _undetermine();
    }
    
    void _undetermine(){
      _index_time          = -1;
      _last_index_time     = -1;
      _index_sequence      = -1;
      _last_index_sequence = -1;
      update();
    }
    void setSequences(MyTimerSequence * sequences, int quantidade){
      _sequences = sequences;
      _quantidade = quantidade;
      _undetermine();
      update();
    };
    void setMillisRef(unsigned long mref)                  { _mref    = mref;        _undetermine();    }
    void setLag(long lag)                                  { _lag     = lag;         _undetermine();    }
    boolean isChanging()                                   { return (( _index_time != _last_index_time)||( _index_sequence != _last_index_sequence))&&(_enable); }
    boolean isIndexTime(int index_sequence, int index_time){ return ( _index_time == index_time)&&(_index_sequence == index_sequence)&&(_enable);   }
    void setOnChanging( void (*onChanging)(int, int) )     { _onChanging = onChanging;                  }
    void setOnEnable  ( void (*onEnable)() )               { _onEnable   = onEnable;                    }
    void setOnDisable ( void (*onDisable)() )              { _onDisable  = onDisable;                   }
    void update();
    
    void enable()  { _enable = true;  if ( _onEnable )  { (*_onEnable )(  ); }  } 
    void disable() { _enable = false; if ( _onDisable ) { (*_onDisable)(  ); }  } 
    
    void setTimeDisable(unsigned long time){ _time_disable = time; _mref_disable = millis(); } 
};
void MyTimer::update(){
  if ((millis() - _time_disable > _mref_disable) && (_time_disable > 0)) { disable(); }  //verifica se está configurado pra desabilitar por time
  if (!_enable) { return; }
  
  unsigned long s = 0;
  for (int i=0; i<_quantidade;i++){ 
    for (int j=0; j<_sequences[i].repeat; j++){
      for (int k=0; k<_sequences[i].size; k++){
        s += _sequences[i].times[k]; 
      }
    }
  }
  long adjustment      = _mref % s;
  long rest            = (millis() + s - adjustment - _lag) % s;
  _last_index_time     = _index_time;
  _last_index_sequence = _index_sequence;
  
  
  boolean ind_break = false;
  s = 0;
  for (int i=0; i<_quantidade;i++){ 
    for (int j=0; j<_sequences[i].repeat; j++){
      for (int k=0; k<_sequences[i].size; k++) {
        s += _sequences[i].times[k]; 
        if (rest < s) {  
          _index_time = k; 
          _index_sequence = i;
          ind_break = true;
          break; 
        } 
      }
      if (ind_break) { break; }
    }
    if (ind_break) { break; }
  }
  if ( isChanging() && _onChanging ) { (*_onChanging)(  _index_sequence, _index_time  ); }
}
/*********************************************************************************************************
************************************FIM CLASSE MYTIMER****************************************************
**********************************************************************************************************/

unsigned long seq01[] = {750};
unsigned long seq02[] = {60, 60};
unsigned long seq03[] = {200};

MyTimerSequence sequences[] = { 
                                 { seq01, sizeof(seq01)/sizeof(unsigned long), 1},
                                 { seq02, sizeof(seq02)/sizeof(unsigned long), 10},
                                 { seq03, sizeof(seq03)/sizeof(unsigned long), 1},
                                 { seq02, sizeof(seq02)/sizeof(unsigned long), 6},
                                 { seq03, sizeof(seq03)/sizeof(unsigned long), 1},
                                 { seq02, sizeof(seq02)/sizeof(unsigned long), 3}
                              } ;

 
MyTimer t1(sequences, sizeof(sequences)/sizeof(MyTimerSequence), 0, 0);  //tem um atraso de 100 milissegundos em relação a referencia 0
   
void setup() {
  Serial.begin(9600);
  
  pinMode(13, OUTPUT);
  pinMode(5, OUTPUT);
  
  t1.setOnChanging( onChanging_t1 );
  t1.enable();
}
 
void loop() {  
  t1.update();
  
  analogWrite(5, analogRead(A1)/4);
}
 
void onChanging_t1(int sequence, int index ) {
  digitalWrite(13, LOW); 
  if (index == 0 && (sequence == 1 || sequence == 3 || sequence == 5)){ digitalWrite(13, HIGH);  }
  
}


Via ISP (Arduino Uno e Mega)

Pra quem pretende utilizar o Arduino Pro Mini com uma certa frequencia, ter que retirar o ATMega do conector do Arduino Uno é um trabalho que pode se tornar um pouco chato, além de ter algum dos Arduinos Uno que não tem como desconectar o ATmega. Nesses casos, o mais recomendado é utilizar o a gravação através de ISP. Pra isso é necessário seguir alguns passos. Vou listá-los e mais abaixo demonstro como realizar cada um dos passos:

Obs.: Esses mesmos passos podem ser seguidos utilizando um Arduino Mega.

  - 1º Passo: Fazer o upload do Sketch "ArduinoISP" para o Arduino Uno (ou Mega).
  - 2º Passo: Conectar os Cabos entre o Arduino Uno (ou Mega) e o Pro Mini.
  - 3º Passo: Mudar a Placa para Arduino Pro ou Pro mini.
  - 4º Passo: Mudar o Programador para "Arduino as ISP".
  - 5º Passo: Fazer Uploado via Programador (Ctrl+Shift+U).
  - 6º Passo: Voltar as configurações iniciais. (Esse passo é só para não ter problemas ao voltar a programar o arduino UNO posteriormente).

1º Passo: Fazer o upload do Sketch "ArduinoISP" para o Arduino Uno (ou Mega).

A Sketch ArduinoISP está na própria IDE do Arduino, bastando entrar no menu Arquivo, Exemplos e Abrir "ArduinoISP", conforme imagem abaixo:


Com o Arquivo aberto, faça o upload do arquivo para o Arduino Uno (ou Mega).


2º Passo: Conectar os Cabos entre o Arduino Uno (ou Mega) e o Pro Mini.

Esquema Com o Arduino Uno:

       Pro mini | Arduino Uno
13 | 13
12 | 12
11 | 11
RST | 10   
Vcc |  5V
  Gnd |  Gnd 

Ligar um capacitor de 10uF no Reset do arduino Uno e no Gnd.







Esquema Com o Arduino Mega:

         Pro mini | Arduino Mega
13 | 52
12 | 50
11 | 51
RST | 53   
Vcc |  5V
  Gnd |  Gnd 

Ligar um capacitor de 10uF no Reset do arduino Uno e no Gnd.




3º Passo: Mudar a Placa para Arduino Pro ou Pro Mini.

Menu Ferramentas --> Placa --> Arduino Pro ou Pro Mini


Escolher o processador:

Menu Ferramentas --> Processador --> ATMega328 (5V, 16MHz)



4º Passo: Mudar o Programador para "Arduino as ISP"

Menu Ferrramentas --> Programador --> Arduino as ISP



5º Passo: Fazer Upload via Programador (Ctrl+Shift+U)

Nesse ponto aqui, por falta de atenção tive alguns problemas, pois tentei fazer o upload via comando tradicional, clicando no botão. Até que percebi que o Correto é enviar através de uma outra função, que fica no Menu Arquivo:

Com o Sketch que se deseja upar aberto
vá até o Menu Arquivo --> Carregar Usando Programador



Feito Isso, o Programa deve ser enviado para o Pro Mini.

6º Passo: Voltar as configurações iniciais.

Como esse procedimento exige muitas mudanças de configurações da IDE do Arduino, é recomendável que as configurações oginais sejam restauradas, para evitar da próxima vez que a IDE seja usada, não cause outros problemas, principalmente se for um computador utilizado por outros programadores. Deixe sempre do jeito que estava quando iniciou.




quinta-feira, 24 de dezembro de 2015

Arduino - Fonte capacitiva para Attiny85

Aplicações onde microcontroladores podem atuar são praticamente infinitas, não há limites de onde eles podem ser aplicados, basta uma boa ideia e alguém vai pensar "Será que dá pra fazer com Arduino?" e muitas vezes a resposta é: SIM, Dá! São projetos dos mais simples aos mais complexos, utilizando poucos, nenhum ou muitos sensores, shields ou módulos. Se comunicando ou não com outros microcontroladores ou computadores, smartphones, via rádio, via wi-fi, bluetooth... enfim, há uma gama enorme do que um microcontrolador pode fazer. Porém, qualquer projeto que seja, maior ou menor, com mais ou menos recursos, vai inevitavelmente precisar de uma fonte de alimentação, alguns deles irão exigir mais potência, outros nem tanto.

Assim como são amplas as possibilidades de projetos com microcontroladores, consequentemente devem ser amplos também os meios para que consigamos alimentá-los. Felizmente há diversas formas de conseguirmos energia elétrica, seja com baterias, pilhas, energia solar, energia alternada da rede, além de outras.

Mas a questão é, como escolher a forma de alimentar o circuito que seja mais adequada para o projeto pretendido? Pra isso devem ser levadas em considerações questões como, custo, espaço utilizado, peso, potência necessária, autonomia, riscos de falta de energia, segurança... enfim, cada tipo de alimentação possui suas vantagens e desvantagens. Escolher a mais adequada faz parte de qualquer projeto, e por isso, é importante que o projetista tenha conhecimento suficiente sobre as principais fontes de alimentação a serem utilizadas nesses projetos.

Quando se trata de alimentar circuitos através da rede energia de 127/220V-AC, a solução mais simples é sem dúvida comprar um fonte AC-DC de 5V pronta, que irá ser conectada em uma tomada normal e pronto, basta plugar no seu microcontrolador e ser feliz. Mas nem sempre essa alternativa é a melhor, pois a fonte pode ser cara para determinado projeto, ou pode ocupar muito espaço, ou ainda ser muito pesada. Talvez você não queira ter seu projeto com partes separadas, e o melhor seja ter fonte e microcontrolador todo em uma única placa, bastando ser ligada diretamente a rede de energia. Nesses casos talvez seja necessário projetar sua própria fonte e pra isso é preciso saber de antemão qual o consumo que seu projeto terá, espaço que deverá ocupar, quanto você vai querer gastar com componentes, etc.

Em alguns projetos simples, que não utilizam muita potência, uma fonte com alguns mili Amperes pode ser a solução. Uma opção para esses casos, são fontes AC-DC sem transformadores, como fontes capacitivas ou ainda fontes resistivas que em geral tem baixo custo dos componentes utilizados, dependendo de até quantos mA se deseja conseguir.

Há uma gama enorme de tipos de fontes que podem ser montadas a partir desses princípios. Basicamente a ideia é utilizar um resistor ou um capacitor pra diminuir a tensão ou a corrente. depois retificar a corrente e por fim regular e filtrar a tensão. São esses basicamente os componentes mais elementares de uma fonte.

Como não vou abordar todos os tipos de fontes que podem ser utilizados, vou deixar duas dicas de buscas no google: "Fonte sem Transformador" e "Transformerless".

No vídeo abaixo montei uma pequena fonte capacitiva que fornece em torno de 30 mA, suficiente pra alimentar um attiny85. Mas atenção, isso não significa que qualquer projeto que utilize um attiny85 irá rodar com essa fonte, se ele possuir mais sensores, ou outros componentes que demandem mais corrente ao mesmo tempo, esse projeto de fonte pode não ser suficiente, por isso todo o consumo do projeto deve ser muito bem calculado.

Atenção Novamente: Manusear diretamente 110/127V Oferece altos riscos de acidentes. Nunca faça nada que não tenha certeza que irá funcionar. Sem tiver dúvidas peça ajuda pra alguém com mais experiencia.





Para mais detalhes dos cálculos do capacitor utilizado, veja os dois vídeos abaixo, onde fiz uma fonte bem parecida, mas com a intenção de alimentar leds diretamente em uma rede de 127V-AC.





Conclusão:

Fontes de alimentação podem ser a parte mais simples ou mais complexa de um projeto, portanto dependendo do projeto a ser desenvolvido, saber dimensionar e escolher a fonte a ser utilizada pode evitar muita dor de cabeça.


segunda-feira, 23 de novembro de 2015

Arduino - Teclado PS2 com Keypad 4x4

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




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/