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.
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
- Each byte has a parity bit must have a odd number of bits.
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();
}