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?
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?
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 PDL(1)
.
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.
The monitor PREAD
routine, which is used by the Applesoft PDL(n)
function,
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
large value.
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.
The monitor PREAD
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.
The monitor PREAD
function was updated to work correctly regardless of the
speed setting, so code that calls that -- including Applesoft BASIC -- will
behave consistently.
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.