Step 1: The Equipment List
- 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
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
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
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
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:
( ((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!