Controlling a Pinball Machine Using Linux
The AS2518 MPU is based on the Motorola 6800 microprocessor. 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.
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 anything. 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 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 computer 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 momentary 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 provide 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 snapshot 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 operation 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 executes at a high frequency (120 Hertz) led me away from user space to the kernel.