Skip to content
FreeDOSDeep Dive Published Updated 8 min readViews unavailable

Reading the IBM PC Game Port: Axis Timing, Button Bits, and Calibration

Measure analog joystick axes through port 201h, interpret active-low buttons, calibrate RC timing, and avoid fragile busy-wait assumptions in DOS.

The original IBM PC game-control adapter is a small but revealing example of a device that exposes analog input through a digital register. A program does not receive an X/Y coordinate from port 201h. It starts a timing conversion, repeatedly samples status bits, and estimates the resistance of the joystick potentiometers from how long those bits remain asserted. The same byte also reports digital button inputs. Correct software therefore needs to distinguish a timed analog measurement from a button snapshot, and must treat the port address, electrical circuit, and polling latency as platform-specific assumptions.

This article describes the IBM-compatible game-control interface for a DOS program that owns the device. It is not an installation guide for USB gamepads, and it does not claim that every modern emulator or motherboard maps a physical port at 201h. Verify that the target actually implements the legacy interface before using port I/O.

Register layout and conversion sequence

The IBM adapter uses I/O port 201h for both triggering the analog one-shots and reading the resulting digital state. A write starts the timing cycle; the adapter’s four analog inputs then remain in their asserted state for a duration determined by the attached resistance. The status is read from the same port. Each axis bit changes independently when its timing interval expires, so software can record a separate elapsed time for each axis. The IBM reference gives the circuit relationship as approximately 24.2 microseconds + 0.01 microseconds per ohm for the documented adapter implementation. That formula is useful for understanding the circuit, not a universal calibration equation for every clone or joystick.

The four analog channels are conventionally associated with joystick A X/Y and joystick B X/Y. The four digital channels are the corresponding fire-button inputs. The adapter’s interface is compact, but a single value has two meanings across time: an axis bit is part of a conversion while its button bits report current digital levels. Do not interpret the whole byte as a ready-made joystick structure without applying the mapping and electrical polarity documented for the adapter.

For the usual active-low button wiring, a pressed button reads as zero and a released or pulled-up input reads as one. An open connector can consequently look like every button is released. A short to ground can look permanently pressed. These are electrical states, not proof that a particular controller is attached. Test each button independently and never use a destructive shorting test on a live connector.

A bounded measurement loop

The essential sequence is: trigger once, sample the register, and note when each analog bit clears. Use a monotonic software timer or calibrated instruction timing if the program needs units, and always impose a maximum wait so a missing device cannot hang the machine forever. A low-level example is intentionally shown as pseudocode because DOS compilers expose port I/O through different intrinsics and protected-mode environments may reject it:

/* Pseudocode for a real-mode program that owns a verified port 201h. */
uint8_t pending = 0x0f;
uint16_t elapsed[4] = {0, 0, 0, 0};

outb(0x201, 0);                 /* start all four analog conversions */
for (uint16_t tick = 0; tick < MAX_TICKS && pending; ++tick) {
    uint8_t state = inb(0x201);
    for (unsigned axis = 0; axis < 4; ++axis) {
        uint8_t bit = (uint8_t)(1u << axis);
        if ((pending & bit) && !(state & bit)) {
            elapsed[axis] = tick;
            pending &= (uint8_t)~bit;
        }
    }
}

The code records a count, not milliseconds, unless the loop body has been calibrated. A compiler may emit different instructions, an 8088 and a faster 486 execute them at different rates, wait states change timing, and virtual machines may synthesize the port. If physical travel is the application-facing output, normalize the recorded count between measured minimum and maximum values for each axis. Clamp out-of-range samples before applying dead zones or acceleration curves.

The bounded loop is important. A disconnected, broken, or unsupported axis might never reach the expected state. Without a timeout, a controller-reading routine can become an infinite loop that prevents input handling, sound servicing, or DOS exit. On timeout, preserve an explicit invalid or last-known sample rather than pretending the stick is centered. The caller can then show a diagnostic or disable only that axis.

Calibration is per device and per axis

The adapter measures an RC-dependent interval, not a standardized position. Potentiometer tolerances, joystick wear, cable resistance, adapter circuitry, temperature, loop timing, and emulator scaling can all affect observed counts. Calibrate each axis at both physical extremes and at the neutral position. Keep the raw measurement alongside the normalized value so drift and saturation remain diagnosable.

A practical calibration record contains the machine or emulator, adapter type, port base, polling routine build, four endpoint counts, neutral counts, timeout count, and controller model. Record a range only after repeating each position several times. A single read can be distorted by switch bounce, a loose connector, or scheduling delay. Reject a calibration where the minimum and maximum overlap heavily or where a stationary stick produces unstable large variations.

Dead zones should be applied around the measured neutral point, not automatically at the midpoint between extrema. Worn or mechanically asymmetric joysticks often rest off-center. Use separate positive and negative spans around neutral, clamp values to the calibrated range, and make the dead-zone threshold configurable. Avoid smoothing so aggressively that a controller becomes unresponsive; smoothing is a user-experience choice, not a property of the hardware interface.

Buttons, axes, and sampling cadence

Buttons can be sampled while axis conversions are in progress because their input bits are part of the same port read. Capture button state at a defined point in the sample cycle, and debounce according to the controller and game. A press counter is not supplied by the adapter. If a game polls infrequently, a short press can begin and end between reads. Polling once per rendered frame may therefore miss a button edge even though the axis measurement works.

The analog sequence consumes CPU time. On early machines, tight loops were an expected technique; on later systems and emulators, their duration can vary widely. Do not use a fixed number of loop iterations as a portable time unit. If sampling is integrated into a game loop, bound the work and keep the input routine from starving timer or audio service. A very high polling frequency does not improve a noisy or mechanically limited potentiometer.

If a program supports both the BIOS mouse API and the game port, keep them as separate providers. INT 33h returns driver-managed mouse state; port 201h requires direct hardware sampling and calibration. They have different coordinate systems, ownership rules, and failure modes. Do not infer the presence of one controller from the other.

Port ownership and compatibility boundaries

On original IBM-compatible equipment, 201h is conventionally the game-control port, but other adapters and clones can decode overlapping ranges. BIOS setup, a sound card, an emulator configuration, or a virtualized execution environment may affect whether reads and writes have the expected effect. A write to the port is not an innocuous query: it starts the conversion cycle and may affect another device if the address assumption is wrong.

Do not copy a base port or resistance formula from one card manual to unrelated hardware without evidence. The IBM Options and Adapters reference documents the IBM Game Control Adapter. A compatible card may reproduce that behavior, but compatibility should be tested rather than presumed. USB game controllers generally appear through host APIs or emulator mappings, not as a physical IBM game-control adapter.

Direct IN and OUT instructions also require an execution environment that permits I/O privilege. A conventional real-mode DOS program often has that access, while a protected-mode extender may need a DPMI I/O permission or a driver. A failure to access the port under an extender is not an axis-calibration problem. Do not weaken host security settings just to make a hobby routine run; use the environment’s supported device interface where one exists.

Acceptance checks for a joystick reader

Before calling the implementation reliable, validate the interface in layers:

  1. Confirm the target platform documents or emulates a game-control port at the selected address.
  2. Read button bits with the controller disconnected and connected, then press each button independently.
  3. Trigger a conversion and verify that all expected axis bits transition before the timeout.
  4. Record repeated neutral and endpoint measurements for each axis, then check that normalization and dead-zone logic remain bounded.
  5. Test a missing or stuck axis and confirm the application reports invalid input rather than hanging.
  6. Repeat under the actual CPU, extender, emulator, and game-loop cadence that will be supported.

An emulator passing these checks establishes behavior only for its configured virtual device. It does not establish electrical compatibility or timing on an original adapter. Preserve the raw counts and test environment with bug reports so a timing problem can be separated from calibration or controller wear.

The game port is best understood as a timed analog measurement circuit exposed through one digital I/O address. Trigger, measure, normalize, and time out explicitly. That model is safer than treating port 201h as a universal joystick API, and it keeps hardware-specific input from silently corrupting game logic.

Related:

Sources:

Comments