Magstripe project

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
Current state: Both readers successfully interfaced to joystick port. Software
written that will decode BCD information from Track 2 successfully, compatible
with both readers.

Current software is available here: magstripe.c

Format of UCB ID card: [start]0{STUDENT_ID}[field]0140[end]

How it works:

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 NamePanasonic ZU-M2121S451SR&D MCR-175-1R-0106Gameport (15 pin D-sub)Port 0x201
VCCPin 1REDPin 1
DATAPin 2BLUEPin 2 - Joystick A, Button 1Bit 4
CLOCKPin 3GREENPin 7 - Joystick A, Button 2Bit 5
CARD MOTIONPin 4not presentnot connected
CARD DETECTPin 5YELLOWPin 10 - Joystick B, Button 1Bit 6
END STOPPin 6not presentPin 14 - Joystick B, Button 2Bit 7
GROUNDPin 7BLACKPin 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."). We'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 palce. 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.

Change log:


12/09 -- Longitudinal Redundancy Check implemented

12/09 -- 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 -- Twelve hours of exams!

12/07 -- Couldn't resist: wrote code to decode the BCD data on track two. It
         works.

12/06 -- Cooked up software in Linux to read bits from the reader.  It's alive!

12/06 -- Soldered together gameport cable 15 pin male DSub to insertion reader
         in 140 Cory instead of studying. Voltage levels check out. 

12/05 -- Realized I was out of solder. No progress.

12/04 -- 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 -- Ordered lots of toys from BGMicro