Skip to content
FreeDOSDeep Dive Published Updated 7 min readViews unavailable

MPU-401 UART on FreeDOS: Bounded Port I/O, ACKs, and Cleanup

Program a configured FreeDOS MPU-401-compatible UART safely with bounded status polling, mode-entry acknowledgments, MIDI-byte handling, and cleanup.

An MPU-401-compatible UART is a byte-oriented PC-to-MIDI interface. It is not the MIDI synthesizer itself, and its register interface is not the same as a sound card’s DSP-based MIDI path. This distinction matters on Sound Blaster hardware: Creative’s programming guide documents SB-MIDI through DSP ports separately from the MPU-401 UART interface, and identifies the latter as a Sound Blaster 16 feature. A program must use the interface and port assignments actually provided by the target card.

The examples here describe the documented Sound Blaster 16-compatible UART register contract from a 16-bit real-mode DOS program. They are relevant to FreeDOS software when the machine exposes that compatible interface, but they are not a universal MPU-401 driver, an intelligent-mode implementation, or a guarantee for USB MIDI adapters, PC-98 systems, or every clone. The cited Creative programming guide is an archived copy of the vendor-authored manual.

Confirm ownership and the configured base first

The Creative guide documents two consecutive ports: data at base + 0 and status/read or command/write at base + 1. For its SB16 MPU-401 UART, documented base choices are 0x300 and 0x330, with 0x330 as the factory default. The guide lists IRQ choices as well, but a polling test does not need to install an interrupt handler. Read the sound card’s actual configuration or the system’s established driver settings; do not assume that a conventional address is present or available.

Never discover the interface by sending reset or mode commands to every plausible I/O address. Writes to an unowned hardware port are not read-only probes. Keep one driver or test program responsible for the interface at a time, and make the base address an explicit input to the program.

At base + 1, status bit 6 is Output Ready: clear means the interface can accept a data or command byte; set means it is busy. Status bit 7 is Input Ready: clear means a byte is available at the data port; set means there is no byte to read. These are active-low readiness indicators. A loop that tests the wrong polarity can spin forever or drop data.

Enter UART mode and treat the ACK as a protocol byte

Before sending MIDI data, the documented SB16-compatible MPU interface expects command 0x3F at the command port. Poll Output Ready first, write the command to base + 1, then poll Input Ready and read the response from base. The Creative guide specifies 0xFE as the command-acknowledge byte for a successful mode switch. In UART mode, writes of MIDI bytes go to the data port and received bytes are read from that same port after Input Ready indicates data is present.

A bounded transmit-ready routine in MASM/TASM-style 16-bit assembly can avoid a permanent hang if a device is absent or stuck. The counter bounds the number of status reads; it is not a calibrated wall-clock deadline, so the elapsed time varies with processor and wait-state behavior:

; mpuBase is set only after the configured interface is identified.
; Returns with CF clear when Output Ready, set after the finite poll bound.
WaitMpuTxReady PROC NEAR
    mov dx, [mpuBase]
    inc dx                  ; status/command port = base + 1
    mov cx, 0FFFFh
tx_wait:
    in  al, dx
    test al, 40h            ; bit 6 clear means Output Ready
    jz  tx_ready
    loop tx_wait
    stc
    ret
tx_ready:
    clc
    ret
WaitMpuTxReady ENDP

; Returns with CF clear when Input Ready, set after the finite poll bound.
WaitMpuRxReady PROC NEAR
    mov dx, [mpuBase]
    inc dx                  ; status/command port = base + 1
    mov cx, 0FFFFh
rx_wait:
    in  al, dx
    test al, 80h            ; bit 7 clear means data is available
    jz  rx_ready
    loop rx_wait
    stc
    ret
rx_ready:
    clc
    ret
WaitMpuRxReady ENDP

The caller can then wait for output readiness and issue the mode command:

    call WaitMpuTxReady
    jc   device_timeout
    mov  al, 03Fh
    out  dx, al             ; DX still addresses base + 1
    call WaitMpuRxReady
    jc   device_timeout
    dec  dx                  ; data port
    in   al, dx
    cmp  al, 0FEh
    jne  unexpected_ack

For each outgoing MIDI byte, repeat the bounded readiness check, restore DX to the configured base, and write the byte to the data port. For steady-state input, do not spin indefinitely waiting for a player’s next MIDI event: use a bounded poll that returns “no data yet” to the event loop, or use the configured interrupt path. The bounded ACK wait above treats exhaustion as a startup failure; a non-0xFE response must go to an explicit unexpected-response path rather than being mistaken for success. A production routine should preserve the caller’s byte and registers, return an explicit status, and put received bytes into the application’s input queue.

Polling is a useful bring-up path because it avoids IRQ setup, but it is not a timing engine. The guide notes that UART mode supplies no MIDI timing information to the application. The program must schedule its own sends and drain received data quickly enough for its workload. If IRQ-driven reception is required, use the configured IRQ and documented interrupt behavior for the actual card; do not copy an IRQ value from another machine.

Reset is cleanup, not a universal ACK guarantee

The Creative guide says the reset command 0xFF exits UART mode and recommends sending it when the program terminates. A hardware implementation may differ in how it acknowledges reset while already in UART mode: the Analog Devices AD1816A data sheet, for example, says its emulation does not return ACK bytes in UART mode. Therefore, send reset only while the program still owns the interface, wait for Output Ready with a finite poll-count bound, and do not wait forever for a final 0xFE. If the program can be interrupted or terminate abnormally, document that a later owner may need to reinitialize the device.

This cleanup is specific to the MPU UART control port. Wait for Output Ready using a finite poll count before sending reset; do not describe that count as a time deadline. It is not interchangeable with resetting the Sound Blaster DSP, restoring DSP MIDI mode, or shutting down an intelligent MPU-401 sequencer. The user’s configured driver and the specific card manual decide which state must be restored.

Troubleshoot one layer at a time

Record the card model, configured MPU base, interface mode, configured IRQ if used, and whether another driver has claimed the hardware. Then diagnose in protocol order:

  1. No Output Ready transition: confirm the base and interface ownership before deciding that the card is absent. Keep the timeout bounded.
  2. No 0xFE after 0x3F: verify command port, status polling, and whether this particular device implements the SB16-compatible MPU UART command path. Do not substitute DSP-port MIDI commands without confirming the selected interface.
  3. Bytes transmit but no sound: the UART only transfers MIDI bytes. Verify the physical MIDI connection and receiving synthesizer independently.
  4. Input appears stuck: read only when bit 7 is clear. On the documented SB16 interface, reading the data port also clears the UART-mode interrupt signal; an IRQ-based driver must drain the byte and preserve correct interrupt-controller handling.
  5. Another DOS program fails afterward: check whether this program left UART mode active or changed a shared card’s DSP/interface state.

DOSBox-X can provide a separate software control: its documentation describes mpu401=uart as emulating UART-compatible interfaces and configures the MIDI output backend independently through mididevice. A successful emulator test therefore checks the game and emulator route, not physical Sound Blaster port ownership or the target card’s electrical behavior. This distinction follows from the emulator’s separate virtual interface/backend and the card-specific contract above.

Use a loopback fixture or a known MIDI receiver for a functional hardware test. An ACK proves a control exchange, not that a synthesizer is connected, that the instrument interprets the bytes, or that the application meets musical timing requirements.

Related:

Sources:

Comments