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]
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."). 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.
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