Many games on the Apple II supported input from the keyboard, joystick, and Sirius Joyport. While reading the keyboard and Joyport are straightforward, getting the joystick's position is not.
How does one read the position of the joystick?
Retrocomputing Stack Exchange is a question and answer site for vintage-computer hobbyists interested in restoring, preserving, and using the classic computer and gaming systems of yesteryear. It only takes a minute to sign up.Sign up to join this community
The typical Apple II joystick appears to the computer as a pair of analog game
paddles and two buttons. The easiest way to read the joystick position
is from Applesoft BASIC, using
PDL(n). The process is detailed in this
post. The joystick
X axis is
PDL(0), the Y axis is
You can do much better with a custom routine. The reason for this requires a bit of background.
As explained in Apple IIe tech note #6, game paddles are analog devices. Reading them requires accessing a memory-mapped I/O location to trigger a capacitor discharge, and then timing how long it takes for the capacitor to recharge. The rate at which the capacitor charges is determined by the position of the paddle or joystick, which has a variable resistor.
Where things start to get tricky is that each paddle has its own capacitor, and they all get triggered at once. If you read the first joystick axis and then immediately start reading the second, you might catch it in a state where the capacitor is partially charged, because hitting the discharge trigger before all capacitors have recharged has no effect. After reading the first paddle, you have to wait for all paddles to finish charging before reading another. This is easy to do, but wastes time.
routine, which is used by the Applesoft
increments a counter every 11 cycles while waiting for the
capacitor to report charged. The charge time will vary between 2 and 3302
microseconds with ideal hardware, but could vary depending on how close
the electronic components are to their specifications. 256 iterations at
11 cycles per iteration requires (256*11/1.0205) = 2760 microseconds, which
will yield reasonable results for components with +/- 10% tolerance.
(Source: Understanding the Apple //e, by Jim Sather, page 7-29.) The
software handles small variations in the range by reporting 255 for any
We can save nearly 3 milliseconds by reading both joystick axes simultaneously. Unfortunately it's not possible to do that in an 11-cycle loop on a 6502, which means we're going to lose some resolution. For many applications this isn't a problem, e.g. many games only care about up-down-left-right-center.
function, which reads a single paddle, is straightforward:
PTRIG EQU $C070 PADDL0 EQU $C064 PREAD LDA PTRIG TRIGGER PADDLES LDY #$00 INIT COUNT NOP COMPENSATE FOR 1ST COUNT NOP PREAD2 LDA PADDL0,X COUNT Y-REG EVERY BPL RTS2D 12 USEC [actually 11] INY BNE PREAD2 EXIT AT 255 MAX DEY RTS2D RTS
Jim Sather presented a 22-cycle dual-axis version in Understanding the Apple //e:
PDL0 EQU $C064 PDL1 EQU $C065 DOIT LDA PTRIG LDX #0 LDY #0 PHA GIVE SOME SPACE FOR COUNT = 0 PLA GOTPDL1 BIT $0 CHKPDL0 LDA PDL0 BPL GOTPDL0 NOP INY LDA PDL1 BMI NOGOTS BPL GOTPDL1 NOGOTS INX JMP CHKPDL0 GOTPDL0 BIT $0 LDA PDL1 BMI NOGOTS RTS
He noted that the routine returned a value between 0 and approximately 160. (In the AppleWin emulator this produces a smooth value from $00 to $7F, then jumps to $8C at the upper limit.)
John Brooks created an implementation that is a bit longer, but returns values from $00 to $FF. See the listing here.
Another variation can be found in Stellar 7. Note the game is only interested in the 8 principal directions.
Some games that expected input from a game paddle rather than a joystick would deliberately exit the read loop early. For example, the paddle read routine in Space Eggs stops after 105 iterations, because the player's ship can only be in that many positions. This allowed the function to execute more quickly with no loss of precision. Unfortunately this makes the game a little harder to play with a joystick, because the joystick's center position (128) is interpreted as the paddle turned all the way right.
The monitor PREAD function exits when the paddle position has been identified. This means that an application will execute slightly slower or faster depending on the current joystick position. In some cases, a routine that executes for a fixed amount of time is more useful.
The Apple IIgs presents an interesting case, because the CPU is faster and has additional capabilities. As discussed here, there are tricks with the 16-bit accumulator that can be used to examine both paddle states with one load, allowing one to achieve 11-cycle polling on both axes. While it might seem like reading the joystick with the CPU in "fast mode" would also help, in practice the relationship between cycles and microseconds is less certain, so switching to 1 MHz mode before reading paddles is necessary.
PREAD function was updated to work correctly regardless of the
speed setting, so code that calls that -- including Applesoft BASIC -- will
Some CPU accelerators automatically de-accelerate when the joystick is read. For example, the ZipGS could be configured to slow for 5ms when PTRIG is accessed.