2

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?

14

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.

Implementation

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.

Notes

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.

Apple IIgs

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.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.