Skip to content

Mag Stripe

Mag Stripe

  • Have three Tracks

Each track starts with byte that is used to figure out the start of the message.
Each track ends with byte that is used to figure out the end of the message.

Visual Graphic of Tracks

Track 1

  • The start byte is 0x45 or "%"
  • The track has 7 bits to each byte. This allows characters 0x20 to 0x9F.
    • Each byte has a parity bit must have a odd number of bits.
      • If this is not so then the data is corrupted
      • The parity bit is the high bit of the data
    • Each bit is added to 0x20 which gets its range of characters

Track 2

  • The start byte is 0x0b
  • The track has 5 bits to each byte. This allows characters 0x30 to 0x50.
    • Each bit is added to 0x30 which gets its range of characters

Track 3

  • The start byte is 0x0b

Reading with an Arduino

// Visual feedback when the card is being read...
#define READ_LED 12
#define ERROR_LED 11

#define MAGSTRIPE_RDT 2  /* (RDT) data pin (blue) */
#define MAGSTRIPE_RCL 3  /* (RCL) clock pin (green) */
#define MAGSTRIPE_CLS 4  /* (CLS) card present pin (yellow) */

#define TRACK_NUM 2

/*
 * Track 3 is the one that can contain the most characters (107).
 * We add one more to accommodate the final '\0', as the data is a C string...
 */
static const byte DATA_BUFFER_LEN = 108;
static char data[DATA_BUFFER_LEN];

// Variables used by the interrupt handlers...
static volatile bool next_bit = 0;                       // next bit to read
static volatile unsigned char bits[DATA_BUFFER_LEN];  // buffer for bits being read
static volatile short num_bits = 0;                      // number of bits already read

void data_callback(){
  next_bit = !next_bit;
}

void clock_callback(){
  // Avoid a crash in case there are too many bits (garbage)...
  if (num_bits >= (DATA_BUFFER_LEN * 8 )) {
    return;
  }

  //Get Character and Bit Index
  int char_index = num_bits / 8;
  int bit_index = num_bits % 8;

  //Add the Bit to the buffer
  bits[char_index] |= (next_bit << bit_index);

  //Increase the bit number
  num_bits++;
}


void setup()
{
  //Setup Read and Error LEDs
  pinMode(READ_LED, OUTPUT);
  pinMode(ERROR_LED, OUTPUT);
  //Set LEDs OFF
  digitalWrite(READ_LED, LOW);
  digitalWrite(ERROR_LED, LOW);
  
  //Setup Serial to Computer
  Serial.begin(9600);

  //Setup Input from Mag Stripe
  pinMode(MAGSTRIPE_RDT, INPUT);
  pinMode(MAGSTRIPE_RCL, INPUT);
  pinMode(MAGSTRIPE_CLS, INPUT);

  //Setup Interrupts
  //Reading is more reliable when using interrupts...
  attachInterrupt(digitalPinToInterrupt(MAGSTRIPE_RDT), data_callback, CHANGE);    // data pin
  attachInterrupt(digitalPinToInterrupt(MAGSTRIPE_RCL), clock_callback, FALLING);  // clock pin

}

short find_start_byte(unsigned char pattern){
    unsigned char bit_accum = 0;
    unsigned char bit_length = (TRACK_NUM == 1 ? 7 : 5);
    unsigned char new_bit;

    for (short i = 0; i < num_bits; i++) {
        bit_accum >>= 1;
        bit_accum |= bitRead(bits[i / 8], (i % 8)) << (bit_length - 1);  // ...and add the current bit

        // Stop when the start sentinel pattern is found...
        if (bit_accum == pattern) {
            return i - (bit_length - 1);
        }
    }

    // No start sentinel was found...
    return -1;
}

short decode_magdata(char *data, unsigned char size){
  unsigned char start_byte = (TRACK_NUM == 1 ? 0x45 : 0x0b);
  unsigned char chars = 0;
  unsigned char bit_length = (TRACK_NUM == 1 ? 7 : 5);
  
  //Find the sentinel that denotes the start of the magstripe data
  short start_bit_index = find_start_byte(start_byte);
  Serial.print("Start Byte: ");
  Serial.println(start_bit_index);
  if(start_bit_index < 0){
    return -2;
  }

  //Convert Bits to data
  for (short bit_count = start_bit_index; bit_count < num_bits; bit_count += bit_length) {
    unsigned char bit_accum = 0;
    
    //Read the first bit_length bytes
    for (short j = 0; j < bit_length; j++) {
      short idx = bit_count + j;
      bit_accum |= (bitRead(bits[idx / 8], (idx % 8)) << j);
    }

    //Check if too long for buffer
    if (chars >= size) {
        return -3;
    }

    // Check if reached the end of the data
    if (bit_accum == 0) {
        break;
    }

    //Do Parity Check
    unsigned char parity = 0;

    for (unsigned char j = 0; j < 8; j++) {
        parity += (bit_accum >> j) & 1;
    }

    // The parity must be odd...
    if(parity % 2 == 0){
      return -4;
    }
    
    //Remove the Parity bit from the bit_accum
    bit_accum &= ~(1 << (bit_length - 1));

    // Convert the character to ASCII...
    data[chars] = (TRACK_NUM == 1 ? bit_accum + 0x20 : bit_accum + 0x30);
    Serial.print(data[chars]);
    chars++;
    }

  return chars;  
}

void reset_data(){
  //Reset Variables
  num_bits = 0;
  next_bit = 0;
  
  //Reset Buffers
  for(short i = 0; i<DATA_BUFFER_LEN; i++ ){
    data[i] = 0;
    bits[i] = 0;
  }
}

void loop()
{
  //Check if card present pin is Low
  if (digitalRead(MAGSTRIPE_CLS) == LOW){
    Serial.println("Error: Card Not Available");
    return;
  }

  //Device Is Detected Set Read LED ON
  Serial.println("Card Available");
  digitalWrite(READ_LED, HIGH);
  
  //Wait for Card Input
  while(digitalRead(MAGSTRIPE_CLS) != LOW){ }
  //Wait for reset of card reader
  while(digitalRead(MAGSTRIPE_CLS) == LOW){ }

  // Show that the card has finished reading...
  digitalWrite(READ_LED, LOW);
  Serial.print("Bits Read: ");
  Serial.println(num_bits);

  //Decode Data
  short ret = decode_magdata(data, DATA_BUFFER_LEN);
  if(ret < 0){
    //Reverse Bits and try again
    //Happens when swipped the opposite way
    for (short i = 0; i < num_bits / 2; i++) {
      bool tmp  = bitRead(bits[i / 8], (i % 8));
      bool tmp2 = bitRead(bits[(num_bits - i) / 8], ((num_bits - i) % 8));
  
      //Swap bits
      bitWrite(bits[i / 8], (i % 8), tmp2);
      bitWrite(bits[(num_bits - i) / 8], ((num_bits - i) % 8), tmp);
    }
    
    //Decode Data
    ret = decode_magdata(data, DATA_BUFFER_LEN);
  }

  // Send the data to the computer...
  Serial.println();
  Serial.print("Return Value: ");
  Serial.println(ret);
  
  // If there was an error reading the card, blink the error LED...
  if (num_bits <= 0) {
    digitalWrite(ERROR_LED, HIGH);
    delay(250);
    digitalWrite(ERROR_LED, LOW);
  }
  else{
    Serial.print("Return Data: ");
    Serial.println(data);
  }
  reset_data();
}