Turn your Arduino into a Magnetic Card Reader!

Everyone has used a magnetic card reader, I believe. I mean, who carries cash these days? They’re not difficult to get your hands on, either, and during a trip to my favorite local electronics shop, I found a bin full of these guys. So….of course, I picked one up and brought it home to see what sort of stuff I could do with it and an AVR.This instructable will show you how to connect a Magtek magnetic card reader to an AVR or Arduino/clone and read data from the first track of card. Buckle your seats; magnetic card readers have a high bit rate!

Step 1: The Equipment List

Here are a few things you’ll need to get started.
    • Magnetic card reader (Mine is a Magetk 90mm dual-head reader. $5.00)
    • AVR, Arduino, or clone (ATmega328p ~ $4.30 from Mouser.com
    • solderless breadboard
    • some wire
    • maybe a header if you like that sorta thing.
    • something to read your serial port. I use AVR Terminal from BattleDroids.net

Turn your Arduino into a Magnetic Card Reader!That’s all you should need to get started. Depending on the magcard reader you end up getting, you may have to modify these instructions, and most assuredly the code, to work with your specific reader. However, the code I’ve written should get you pretty far, I hope.

Step 2: Self-clocking Magnetic Card Readers

Magnetic card readers are “self-clocking,” meaning that they provide a clock called a strobe, against which the connected microcontroller can sync. This is a boon. It means you don’t have to worry about looking for a clocking signal and timing the signal to center directly on the clock pulse, and no bothersome oscillating into the sweet spot of the clock signal. This make sense when you think about card swipes: everyone swipes at a different pace, some slower, some faster than others. Self-clocking allows even my sweet grandma the ability to use her card without breaking her wrist. Reminds me of having to change the setting for her that determines how much time is valid between clicks to register a double-click….This card reader’s data is valid 1.0 us before the strobe is put onto the line, so there’s no worry about delaying to get yourself into the “bit time.” For a dual head reader such as the one I’m using, there are two data tracks available to read. In this ‘ible, I’m going to show reading from the primary first track to get you started.

There are five connections you will need to make (four if you don’t mind giving up more fine tuned control for fewer I/O ports being used). Check out the picture below. The red wire goes to +5V while the black wire goes to ground. The green wire is /CARD_PRESENT; the yellow wire is /STROBE, and the white wire is /DATA1. The forward slash ( / ) means that the data is inverted. A low signal (ie 0) is read as a one, or high. The other connectors are brown for /STROBE2 and orange for /DATA2. We won’t be using these.

If you want, you can forget about /CARD_PRESENT. This data line goes low after about 17 head flux rotations to indicate that a card is present (instead of, say, random noise causing your reader to send bogus data) and is used to validate that the data you’re getting is card data and not junk. You can skip this connection if you check for the start sentinel on the data stream. More on that later.

As you can see below, I used a right angle male header connected to a bread board and connected my reader to that. I connected /STROBE to PIND2 (digital pin 2 on an Arduino), /CARD_PRESENT to PIND3 (for illustration purposes), and /DATA1 to PIND4. Make sure you enable pullups on these pins so your pins don’t float. I also traded out my Arduino for a Bare Bones AVR because I like the way it fits into the breadboard.

Step 3: Magnetic Card Basics

The primary functions that you’ll need to do to read a magnetic card are:
1. Detect when the card has been swiped
2. Read the stream of data
3. Detect when the card has gone
4. Process the data
5. Display the dataFirst, I’ll introduce you to some magnetic card basics that you’ll need to know when you start writing your own code.

Magnetic Card Standards

Magnetic cards are standardized by the ISO in the following documents:

7810 Physical characteristics of credit card size document
7811-1 Embossing
7811-2 Magnetic stripe – low coercivity
7811-3 Location of embossed characters
7811-4 Location of tracks 1 & 2
7811-5 Location of track 3
7811-6 Magnetic stripe – high coercivity
7813 Financial transaction cards

As you can see, financial cards are specified in a separate document and often have different formats than, say, your grocery card or international calling card. You will have to program for these differences. I just had a credit card and insurance card handy, so I programmed for these types (which both happen to be format B).

Card Formats

There are several different formats for magnetic cards. Format A and B are common, with B being the most common I’ve seen, and which is supported in this code. Formats C through M are reserved by the ISO, I believe, while N through ?? are reserved for institutional custom use.

Track 1
For financial cards, the first track is recorded at 210 bits per inch and is the first 0.110″ of the card from the top. The data is encoded as “card data” as 7-bits per character. That’s 6-bits for the character and a bit for parity. There are ~ 79 alphanumeric characters on track 1.
The physical ordering is backwards. That is, data is  but it’s written backwards on the card (and hence, will be read by your firmware) as . The parity is odd.

The card data format looks like this:

[SS] [FC] [Primary Account #] [FS] [Name] [FS] [Additional data] [FS][ES][LRC]where:
SS Start sentinel
FC Format code
FS Field separator
ES End sentinel
LRC Longitudinal Redundancy Check character

Track one SS = ‘%’, FC = one of the formats (going to be B a lot of times), FS is often ”, ES is ‘?’ and the LRC character is commonly ‘<‘ although it’s not specified in the standards. Besides being written on the card backward, the data has an odd parity bit and is 0x20 from ASCII. We’ll handle this when we process the data.

Track 2
Track two is 0.110″ wide and starts 0.110 from the top of the card. It’s recording density is 75 bits per inch. The data is 5-bits per character and consists of around 40 numeric symbols only. You shouldn’t encounter any letters on this track.

The card data format should follow this structure:

[SS] [primary account #] [FS] [additional data | discretionary data] [ES] [LRC]

The SS for track two is the semicolon: ‘;’ and the FS is ‘=’

With this holy knowledge under your belt, continue on to the next steps to see code implementing the procedure outlined above.

Step 4: Detect When a Card is Swiped

1. Detect when a card has been swiped
Formally, one would check the /CARD_PRESENT pin to see if it’s dropped low. Fortunately, this isn’t really necessary. We’ll check for valid card later. Alternately, you could read your strobe pin to see when strobes have been put onto the pin, however, this will net you lots of clocking zero’s. The reader will send about 60-70 leading zero’s to let you know that data is about to be presented. However, we’re going to use the nature of binary data to determine when to start recording bits.The start sentinel (SS) for track one is the percentage sign (%). It’s binary value is 0010 0101 which means it will be stored (and read) as 1010 001 (it’s 7-bits so the 8th bit isn’t transmitted). Now, the astute reader will notice that even though the data is backwards it doesn’t match the binary ASCII value. That’s because it’s 0x20 off of hex. The % symbol is 0x25 and 0100 0101 is 0x05. Card data has 0x20 subtracted from the value. That one hanging out there in the high nibble is the odd parity bit. It’s put there so that there are an odd number of “1”s in the value.So because we know that a valid card will always start with this start sentinel, and because the parity bit is a 1, then when we detect the first HIGH to LOW transition on the data pin, then we know we have just started to receive the start sentinel from a card. Now, this isn’t always going to be true, and a foolproof plan would be to check the /CARD_PRESENT card to see if it’s gone LOW in addition.

The simplest way to detect the start of the SS, is to create an external interrupt triggered on the falling edge of the /STROBE. The data is valid 1.0 us before the falling edge, so when you’ve sampled the falling edge, then you know you can read the /DATA1 pin and get a valid value. Here’s the code to create your external interrupt triggered on a falling edge.

 

voidInitInterrupt(void){    // Setup interrupt    BSET(EIMSK,INT0);     // external interrupt mask    BSET(EICRA,ISC01);    // falling edge    BCLR(EICRA,ISC00);    // falling edge    BSET(SREG,7);         // I-bit in SREG}

In my common.h that I include in all my programs, the definitions of BSET and BCLR can be found. Refer to that file should you have any questions about how to set bits. Now, when the interrupt is triggered, we want to sample the /DATA1 (in my code defined as CARD_DATA) and set a bit in a general purpose IO register. If we’re on the 7th bit, save off the register as a character in our global buffer. I use a GPIOR0 register because it’s spiffy fast access. The pseudo code is something like this:

 

Stop 16-bit timer    Clear timer    If DATA is LOW        Set BIT=1 in REGISTER        Decrement BIT        Set flag so we don't skip any more 0's    else DATA is HIGH        Set BIT=0 in REGISTER         Decrement BIT    If BIT is 0        Add byte to buffer        Increment index        Reset BIT

If you are asking yourself why decrement instead of increment, remember that the data is backwards, so instead of recording the bits as we get them from LSB to MSB, we save them from MSB to LSB so we don’t have to reverse the bits later when processing the data. If you really wanted, you could also add 0x20 hex here, but since it’s about 5us on these strobes, I’m keeping the processing in this interrupt service routine to a minimum.

 

ISR(INT0_vect){	StopTimer();	ClearTimer();	if ( !BCHK(PIND,CARD_DATA1) )	// inverse low = 1	{		BSET(GPIOR0,bit);		--bit;		bDataPresent = 1;	} else if (bDataPresent) {		BCLR(GPIOR0,bit);		--bit;	}	if (bit < 0) {		buff[idx] = (char)GPIOR0;		++idx;		bit = 6;	}			StartTimer();}

If you’re wondering what the timing business is about, that’s covered in the step in determining when the card has left the reader.

Step 5: Read the Stream of Data

Read the stream of data

Well, I’ve already shown you how to read the data, as it’s part of the Interrupt Service Routine for our falling edge external interrupt. An alternative method would be to set a flag in the ISR, and in the main loop poll the flag and read the data that way, but I believe the way I’ve presented it is cleaner. Be your own judge and write yours however your MCU will allow it.

That being said, let’s move on to finding out how to detect when the card pulls an Elvis and has left the building.

Step 6: Detect the Card Leaving the Reader

Detect when a card has gone

Formally, one would sample the /CARD_PRESENT pin to see if it’s gone HIGH again, but we don’t need no steenkin’ /CARD_PRESENT taking up another I/O port. This is where those timers come in.

Every time the interrupt is called because we’ve detected a falling edge on /STROBE, we stop a timer, clear the timer value and start reading. When we’ve finished reading we start the timer again. Repeat ad nauseum, or until the timer reaches a certain value. That means that the last interrupt has been called and no more data has come in, so we assume that’s it and start processing the data we’ve collected.

For timers, we use TIMER1, ie the 16-bit timer. I’m using a 16 Mhz resonator externally to my AVR. If you’re using an arduino, then you probably are, too. So, I’ve chosen a prescaler value of 1024 which means every (16,000,000 / 1024) times the timer will increment. That is to say, it will ‘tick’ 15,625 times a second. The /CARD_PRESENT will go HIGH indicating the card has left the reader about 150ms after the last data bit. Knowing this, I just decided to check about every 1/4 of a second. That would look something like this:

Turn your Arduino into a Magnetic Card Reader Schematic
    ( ((F_CPU) / PRESCALER) / 4 )

which turns out to be around 3900. So, when the timer counter TCNT1 reaches 3900, then I know it’s been about 300ms and I can pretty safely conclude that the card has left the reader. Easy.

#define PRESCALER	     1024#define CHECK_TIME     ( (F_CPU / PRESCALER) / 4 )		// 250 ms#define StartTimer()     BSET(TCCR1B,CS10), BSET(TCCR1B,CS12)	// 1024 prescaler#define StopTimer()      BCLR(TCCR1B,CS10), BCLR(TCCR1B,CS12)#define ClearTimer()     (TCNT1 = 0)

You’ve seen in the ISR where the timer is started, stopped, and cleared on each interrupt. Now, in the main loop we just check to see if the timer counter has reached our target value, and if so, start the data processing.

for (;;){	if( TCNT1 >= CHECK_TIME)	{			
StopTimer();		
ClearTimer();		
ProcessData();		
ReadData();					
idx = 0;		
bit = 6;		
bDataPresent = 0;		
memset(&buff,0,MAX_BUFF_SZ1);	
}	
}

Now it’s safe to process the data.

Step 7: Process the Data

Process the data

The processing phase consists of:

    • checking for a valid SS
    • checking parity
    • converting to ASCII
    • checking for a valid ES
    • checking LRC

Here, I don’t bother with checking parity, as I just set that bit to zero. I also don’t calculate the LRC for this little tutorial. That would be something that a more fully realized firmware might want to do.

Here’s the code to process the data doing the above steps (sans the previously mentioned). Find it in the image below. It’s commented and pretty self-explanatory. A special note on parity and ASCII:
I simply clear the parity bit (7th bit…ie a 1 with 6 zeros behind it) and to convert from “card data” you must add 0x20 to the value. That’s about it.

 

For more detail: Turn your Arduino into a Magnetic Card Reader!


About The Author

Ibrar Ayyub

I am an experienced technical writer holding a Master's degree in computer science from BZU Multan, Pakistan University. With a background spanning various industries, particularly in home automation and engineering, I have honed my skills in crafting clear and concise content. Proficient in leveraging infographics and diagrams, I strive to simplify complex concepts for readers. My strength lies in thorough research and presenting information in a structured and logical format.

Follow Us:
LinkedinTwitter

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top