For a long time I wondered what information was stored on the magnetic stripes of all those little cards we all carry around -- credit cards, ATM cards, BART tickets, AAA card, student ID card, etc. In October 1999 I found several magnetic stripe reader units advertised for really cheap prices in the BGMicro catalog. I ordered a bunch and began to experiment. It proved to be a simple matter to connect the magnetic stripe reader assemblies to the PC game (joystick) port and subsequently to write software to decode the data.
Description | Vendor | Cost | Remarks |
---|---|---|---|
SRD MCR-175-1R swipe magstripe reader | BGMicro | $3.95 | |
Panasonic ZUM2121A451 insertion magstripe reader | BGMicro | $1.95 | |
16-conductor ribbon cable (2 meters) | Fry's electronics | $1.05 | sold in 100 foot spools for $16 |
15-pin male D-Sub connector | Fry's electronics | $1.00 |
The two magnetic stripe reader assemblies I acquired each operate on TTL logic levels, so they can pretty much be connected directly to a PC without any additional circuitry. Either the PC parallel port or joystick port will work very well for this. The joystick port has the advantage that it includes designated +5V and ground signals, from which we can power the magstripe unit.
The output from the magnetic stripe reader consists primarily of two signals: CLOCK and DATA. Also available on some readers are a combination of CARD_PRESENT, CARD_MOTION, and/or CARD_ENDSTOP. We provide the card reader with supply voltage (5V) and ground. All signals are conveniently TTL levels, which makes interfacing to the game port extremely easy. Connections are outlined in the following table:
Connections | ||||
---|---|---|---|---|
Signal Name | Panasonic ZU-M2121S451 | SR&D MCR-175-1R-0106 | Gameport (15 pin D-sub) | Port 0x201 |
VCC | Pin 1 | RED | Pin 1 | |
DATA | Pin 2 | BLUE | Pin 2 - Joystick A, Button 1 | Bit 4 |
CLOCK | Pin 3 | GREEN | Pin 7 - Joystick A, Button 2 | Bit 5 |
CARD MOTION | Pin 4 | not present | not connected | |
CARD DETECT | Pin 5 | YELLOW | Pin 10 - Joystick B, Button 1 | Bit 6 |
END STOP | Pin 6 | not present | Pin 14 - Joystick B, Button 2 | Bit 7 |
GROUND | Pin 7 | BLACK | Pin 4 - Ground | |
note: signals may be inverted; this is easily overcome in software. The game port appears on PC's as a 15 pin female D-sub connector, so you'll want to get a male connector for your cable. |
The magnetic stripe reader module handles decoding of the physical layer (magnetic strip) into a data layer (bits). I will not explain how this works here, yet. (See Count Zero's "A Day In the Life of a Flux Reversal."). I'll just skip to how we read the bits from the magnetic stripe reader.
In a loop, we read the gameport status byte (IO port 0x201), in which the status of the DATA and CLOCK signals each appear as an individual bit. Each time we read a byte from the port, we compare to the previous value read. If the values differ, we know something has changed -- most likely a bit is available to us. A new bit is indicated when the CLOCK line goes from 0 to 1. If we notice that the clock has gone high (1) since the previous reading of the port, then a new bit has been read from the magnetic stripe, and its value is the state of the DATA line (0 or 1). This is how individual bits are read from the magstripe -- that's all there is to it.
int foo;
int oldfoo;
oldfoo = readbyte(); /* get a byte from the port */
while (something) {
foo = readbyte();
if ( (foo & CLOCK) && (! (oldfoo & CLOCK)) ) {
/* We have received a bit. Do something about it. */
}
oldfoo = foo; /* save this value */
}
The next step is to assemble the bits into meaningful information. There are many different schemes, but here I will cover the most common scheme for encoding track 2. It is effectively Binary Coded Decimal and provides a sixteen symbol character set: the digits "0" through "9", "START", "END", a field separator, and finally two "CONTROL" symbols which do not appear to be commonly used. There are sixteen symbols, so each symbol requires four bits in order to be uniquely identified. Furthermore, there is an additional bit, a parity bit, which brings the total number of bits per symbol to 5. The fifth bit is set such that there shall be an odd number of 1's in the representation, and is used to detect errors. The data bits (the bits excluding the parity bit) arrive least-significant-bit first (eg, first we get the 1's place, then the 2's place, then the 4's place, then the 8's place).
The main loop of our program is governed by a state machine with three states: OFF, ON, and ONEMORE. The initial state is OFF.
The first thing we have to do is find the START symbol -- eg, we have to look for a specific sequence of bits. The way we do this is to initialize a five-bit FIFO (first-in, first-out buffer). Each time we receive a bit, the five bits in the fifo get shifted right. What was the right-most falls off the edge and is lost, and the newly received bit is stuck into the 16's place. After this is done, the value of the fifo is compared with the START symbol. If there is a match, then we've hit the beginning of the encoded data on the card (and we go to state ON). Otherwise, we just repeat this process (state OFF).
unsigned char c;
switch (state) {
case OFF:
/* This next line is a bit tricky. Remember that foo is
the byte just read from the port, and DATA is a mask which specifies
which bits contain the DATA signal. This implements a simple 5-bit
FIFO. Upon entry to the OFF state, c is set to 0. */
c = (c >> 1) | ( (foo & DATA) ? 16 : 0 );
if (c == START) {
state = ON;
i = 0; /* This is a counter we'll use in the ON state.*/
}
break;
...
}
Once we have found the START symbol, we read bits in chunks of five. (The way we do this is as follows: First, upon entering this state [ON], we set a counter to zero. Then, whenever a bit is received, we increment the counter by one, and test to see whether or not the counter is divisible by 5. If it is divisible by five, then we've read a complete word [symbol], otherwise we still need to read more bits.) For each chunk, we first test to make sure that the parity bit is proper -- ie, we count the total number of 1's in the 5 bit string, and verify that this is an odd number. If this condition is not satisfied, then we announce the error and revert to state OFF (look for another START symbol). Assuming the parity checks out, we just examine the four data bits to see what symbol is represented, and we display that symbol. In the special case that the END symbol is encountered, we move to state ONEMORE.
After the END symbol there is what is called the Longitudinal Redundancy Check, or LRC. This is one last parity check which helps catch errors that occur when multiple bits are read incorrectly in a word such that the parity doesn't change. The LRC is pretty simple: each of the four bits is a parity check on the column of bits above it. For example, the first LRC bit is a parity bit such that the total number of 1's in the first bit position of all words after and including the last START symbol is even. Finally, the fifth bit in the LRC symbol is, as usual, a parity check on the LRC itself. After the LRC is decoded we go to state OFF. To compute parity, the XOR operation is particularly useful.
The most recent version of the software is available here:
magstripe.c
This software was developed to run under Linux, although it would be trivial to adapt to any other operating system on the x86 platform, and very easy to adapt to any other platform as long as you can read in the bits from the magnetic stripe reader. Under Linux it must be run as root so that it will be able to access the IO port directly. Also, since the program polls the port, it may need to be run at a high priority so as to not drop bits.
Current state: Both readers successfully interfaced to joystick port. Software written that will decode BCD information from Track 2 successfully, compatible with both readers.
Format of UCB ID card: [start]0{STUDENT_ID}[field]0140[end]
12/09/99 | Longitudinal Redundancy Check implemented |
12/09/99 | Swapped in swipe reader in place of insertion reader. Existing software is totally compatible with both readers. (cool!) The new reader seems to be more reliable, and can read all of the track data, unlike the insertion reader, which only reads 3/4 of the track before endstop. |
12/08/99 | Twelve hours of exams! |
12/07/99 | Couldn't resist: wrote code to decode the BCD data on track two. It works. |
12/06/99 | Cooked up software in Linux to read bits from the reader. It's alive! |
12/06/99 | Soldered together gameport cable 15 pin male DSub to insertion reader in 140 Cory instead of studying. Voltage levels check out. |
12/05/99 | Realized I was out of solder. No progress. |
12/04/99 | Purchased 100 feet 16 conductor ribbon cable, DB-15(Male) connector at Fry's in Palo Alto. Caused fear on the Stanford campus. |
10/26/99 | Ordered lots of toys from BGMicro |
This is an email I sent to someone who wrote to me with some questions. Perhaps it will be useful to others too:
I located the reference of which I was thinking. It is titled ``Serial Programming Guide for POSIX Operating Systems, 5th Edition'' and is written by Michael R. Sweet. I think you will find it astonishingly useful. I sure did! I searched with Google and found the URL again:
http://www.easysw.com/~mike/serial/
There is another very useful resource describing how to interface to the parallel port, serial port, joystick port, etc. The URL for this one is:
http://www.senet.com.au/~cpeacock
I printed them both out and keep them in a notebook for easy reference.
The nice thing about the joystick port is that it provides a 5 volt power source. Neither the serial port nor the parallel port provides a power source, so you'll have to have an external supply of some kind. (Power can be "stolen" from the serial or parallel interfaces in some cases, but it's always out-of-spec and thus somewhat dangerous -- it's possible to damage the interface, and it won't always work).
The POSIX serial programming interface (what you'll use to talk to the serial port in Linux) is a little odd because it allows for all kinds of different modes that you might think are totally bizarre and unnecessary. It was developed mostly for use connecting terminals to mainframes (etc), so there's a lot of legacy support for features useful in such applications. You'll probably want to put the interface into its most basic, byte-by-byte "raw" mode.
My magnetic stripe reader project just polled the joystick port to get the bits from the reader. This is very inefficient because it requires that the bits be checked very frequently so as to not miss any transitions. Also it requires a fairly fast computer and an unloaded system (no other CPU-hogging programs). This was fine for experimentation but it's a very poor design. A much much better thing to do would be to build some kind of hardware shift register, so that the program running on the PC can periodically ask the magstripe reader whether there are any new bits, and at that time transfer them. This would be far more efficient and shouldn't be too hard.
If you were so motivated you could also without too much difficulty put together a custom ISA card to interface to the magstripe reader. This would be fun & educational, but a serial interface is more practical for production systems. If you're interested in this, or in bypassing the POSIX serial programming interface to talk to the serial port directly, there is a Linux IOPORT programming HOWTO that tells part of the story on how to do this.