n F E A T U R E H A C K A N Y T H I N G
5 2nN O V E M B E R 2 0 0 5 W W W . L I N U X J O U R N A L . C O M
n F E A T U R E H A C K A N Y T H I N G
Figure 1. Corrosion on an AS2518 MPU Board
The other circuit boards are usually still intact. When you start working on your game, check the voltages at the test points to make sure. I chose to neuter the flaky +5 VDC circuit altogether and use the power supply from the PC. With the MPU removed, you are left with four wire harnesses holding a total of 66 wires. To connect your PC to the pinball machine, you will want to build an interface board with matching header pins. The design goal is to produce the same inputs and outputs on all of the wires that the original MPU has. This may seem like an overwhelming task, but remember, this is 1980s-era technology. I used an iterative, divide, design, build and test approach to reverse engineer one subsystem at a time.
What differentiates this project from the typical emulator is that no reference is made to the original programs encoded on the MPU firmware. Instead, I employed a black box, or clean room, method based on studying their function rather than their internal structure. For me, it made sense to interpret these 66 electrical connections in terms of their purpose in a closed-loop process control model. That is, each is either input, output, part of a feedback circuit or part of the power supply. The four main divisions of the pinball machine control system are the solenoids, switch matrix, feature lamps and digital displays. I intentionally left out the digital displays for the first prototype, which is why the apparatus uses the computer monitor to show the scores. The analysis yielded the process model shown in Figure 2.
Figure 2. Reverse-Engineered Process Model
The Hardware, Part I: the I/O Board
Facing a total of 11 inputs and 20 outputs, and wanting room to grow, I decided to build a 48-port digital I/O board. Designs can be found with a little Web searching, and the components can be ordered from Jameco. The Intel 8255 Parallel Peripheral Interface (PPI) integrated circuit provides two 8-bit ports and two 4-bit ports, each configurable as either input or output. On my board, I hard-wired two of these ICs to addresses 0x280-0x283 and 0x2A0-0x2A3. The first three bytes of each are memory-mapped to the aforementioned ports. The fourth byte is used to control the port settings. I used a ten-foot piece of 25-pair twisted pair cable to connect it to the interface board via screw terminals. It’s definitely a hack, as Figure 3 illus-trates. You may want to use a 50-conductor SCSI cable and header pins.
Figure 3. Homemade 48-Port ISA I/O Board
The Hardware, Part II: the Interface Board
The AS2518 MPU is based on the Motorola 6800 micro-processor. It uses two 6820 Peripheral Interface Adapters (PIAs) to provide I/O to the rest of the system. The Intel 8255s are functionally similar. What must be duplicated on the interface board are the circuit elements between the PIA I/O lines and the header pins. These are determined through direct inspection and study of the electrical schematics accompanying the patents and the operator manuals, and consist mainly of resistors and capacitors. A picture of the board I created is shown in Figure 4. A label maker works great for marking wires and connectors.
The Software, Part I: Basic Operation
First, I tried to make the control system work as an ordinary user-space program. Using the method of divide and conquer, the simplest subsystem of the pinball machine to hack is the continuous solenoids. They are either on or off for long periods of time. On my game, I implemented only the flipper relay, which is turned on during normal game play and off when the game is over or tilted so that the flipper buttons don’t do any-thing. This operation was easily accomplished by a variation of a C program I wrote to test the I/O board. According to the schematic, the flipper relay is enabled by making its output low
5 4nN O V E M B E R 2 0 0 5 W W W . L I N U X J O U R N A L . C O M
n F E A T U R E H A C K A N Y T H I N G
rather than high. This is known as negative logic. I quickly learned something about the PC architecture: even with a pull-up resistor, the port is in a low state from the moment the com-puter is powered up. This had the unintended result of turning on the flippers before the control program was even started. To work around it, I added a 7404 inverter to the interface board.
Now the flippers are enabled when the output is set high.
Next, in order of complexity, comes control of the momen-tary solenoids. These are things like the pop bumpers, chimes, slingshots, saucers and the outhole kicker that are fired for brief bursts throughout the game. The Bally documentation states most are energized for a period of 26 milliseconds;
some, like the drop target reset, for twice as long. To fire one of 16 possible solenoids, five output lines are used to drive a 74LS154 decoder on the solenoid driver board. Four lines pro-vide the binary representation of the desired solenoid, and one line enables or disables the decoder outputs. Each output in turn drives one of the 16 momentary solenoids.
Like the continuous solenoids, the 74LS154 enable uses negative logic. Programming this action seems simple. Start with the enable high. Output the four-bit solenoid number, set the enable low for the desired duration, then set it high again.
Actually, this creates a problem that challenges the ability of an ordinary Linux user process to behave in real time. You cannot depend on usleep(26000) to produce a 26-millisecond delay precisely; it may and often does yield a longer delay, as the man page warns. Leaving a solenoid enabled for much longer than 100 milliseconds can damage it and blow the fuse. One option discussed in the Port Programming HOWTO is using multiple outb() calls, because each one takes approximately a microsecond to execute. However, this amounts to a colossal waste of CPU time spent in a busy loop.
The prospects for a user-space control process diminished
even more as I began to implement the switch matrix. The Bally documentation explains that once every 8.3 milliseconds a snap-shot of the switch matrix is created and then analyzed for changes, such as when the pinball strikes one of the many switches on the play field. It is a matrix because 40 separate switches are wired into five rows of eight columns apiece. The rows are outputs and the columns are inputs. A logical high is output to the first row, also referred to as strobing the row. After a brief delay to allow the voltage to be detected at the other end of the circuit, an input oper-ation reads the eight, single-bit columns as one byte of data. Then the process repeats for the next row, and so on.
Here is where the real-time requirements become critical for correct game operation. If an adequate delay is not created between the row strobe and the column input, you get garbage;
the game’s closed-loop feedback system fails. If too much time elapses between each sample, such as while the process is swapped out by the scheduler, a switch closure might be missed. The challenge of ensuring that the control process exe-cutes at a high frequency (120 Hertz) led me away from user space to the kernel.
The Software, Part II: the Kernel Module
The module I wrote is based on the examples given in the excellent tutorial The Linux Kernel Module Programming Guide. Every kernel module requires an initialization function that is called when the module is installed via insmod. This is where I write out the control words to the two 8255 PPIs defin-ing which ports are for input and which are for output. Here is also a good place to register a character device file, which is a simple means to communicate between kernel space and user space. I created one called /dev/pmrek.
To turn this module into a periodic process, I declared a workqueue for it. Workqueues are a new feature of the 2.6 ker-nel. The function in my device driver I want to call with the workqueue is pmrek_process_io(). The workqueue is defined at the global level of the module code with the statements:
static struct workqueue_struct * pmrek_workqueue;
static struct work_struct pmrek_task;
static
DECLARE_WORK(pmrek_task, pmrek_process_io, NULL);
Then, in the module initialization function pmrek_init(), create the workqueue with:
pmrek_workqueue = create_workqueue(pmrek_WORKQUEUE);
This does not actually schedule the workqueue yet. That happens when the supervisory program activates it. Figure 5 is a flowchart of the low-level hardware I/O operations per-formed by pmrek_process_io().
The first thing it does is read in the switch columns using inb(). If there are any valid switch detections, they are written to a log buffer. This log buffer is consumed by the supervisory process, and game play advances depending on the switches detected. Switch detections are stamped with the exact time they occurred by getting the CPU Real Time Stamp Counter (RTSC) via the inline assembly command:
_ _asm_ _ volatile (".byte 0x0f, 0x31" : "=A" (cpu_time));
Figure 4. Interface Board
0 25( 63$&( / (66 021(<
8 1/,0,7(' $ ))25'$%/( 1 (7:25. 6 725$*(
(YHU\ERG\ QHHGV PRUH VSDFH $QG WKH\ QHHG WR VSHQG OHVV PRQH\ :KDW LI \RX FDQ ERWK KDYH PRUH VSDFH DQG VSHQG OHVV PRQH\"
:KDW LI \RX FRXOG SXW ò WHUDE\WHV LQ RQO\ UDFN XQLWV" :KDW LI WKDW ò WHUDE\WHV FRVW OHVV WKDQ " ,Q
FOXGLQJ WKH 6$7$ GLVN GULYHV ,PDJLQH LI \RX FRXOG JOXH LW DOO WRJHWKHU ZLWK D 5$,' DSSOLDQFH LQWR RQH V\VWHP :KDW LI
\RX FRXOG DGG DV PXFK VWRUDJH DV \RX ZDQWHG RQH VKHOI DW D WLPH DQG QHYHU KDYH WR CIRUNOLIW¶ DQ\WKLQJ"
&RUDLG¶V QHZ 6$7$ (WKHU'ULYH 6WRUDJH DOORZV \RX WR GR MXVW WKDW 8VLQJ LQGXVWU\ VWDQGDUG 6$7$ GLVN GULYHV
(WKHU'ULYH 6WRUDJH FRQQHFWV GLVNV GLUHFWO\ WR \RXU (WKHUQHW QHWZRUN (DFK GLVN DSSHDUV DV D ORFDO GULYH WR DQ\ /LQX[
)UHH%6' RU 6RODULV V\VWHP XVLQJ RXU RSHQ $7$RYHU(WK
HUQHW $R( SURWRFRO 6LQFH WKH GLVNV MXVW DSSHDU DV ORFDO GULYHV \RX DOUHDG\ NQRZ KRZ WR XVH WKHP
7KH (WKHU'ULYH 6$7$ 6WRUDJH 6KHOI LV D 8 UDFN
PRXQW QHWZRUN DSSOLDQFH WKDW FRQWDLQV 6$7$ GULYH VORWV
,WV WULSOH UHGXQGDQW SRZHU VXSSO\ SURWHFWV \RX IURP \RXU PRVW OLNHO\ IDLOXUH ,WV GXDO *E (WKHUQHW LQWHUIDFHV DOORZ
\RXU GDWD WR JR IDVW 0% SHU VHFRQG $QG DW D YHU\ DI
IRUGDEOH SULFH /LVW SULFH IRU WKH (WKHU'ULYH 6WRUDJH 6KHOI
ZLWKRXW GLVNV LV RQO\
2XU FRPSDQLRQ SURGXFW WKH 5$,'%ODGH 5$,' FRQWUROOHU DOORZV D YLUWXDOO\ XQOLPLWHG QXPEHU RI 6WRUDJH 6KHOYHV WR EH FRPELQHG LQWR D VHW RI ORJLFDO $R( VWRUDJH GH
YLFHV
1RZ \RX FDQ KDYH XQOLPLWHG VWRUDJH DW D YHU\ DI
IRUGDEOH SULFH )RU FRPSOHWH LQIRUPDWLRQ YLVLW RXU ZHEVLWH DW ZZZFRUDLGFRP RU FDOO WROOIUHH $QG ZH¶OO VKRZ KRZ ZH¶YH PDGH QHWZRUN VWRUDJH VR DIIRUGDEOH
\RX FDQ KDYH DOO WKH VSDFH \RX ZDQW