Skip to content

MIDI I‐O Driver

Giorgioggì edited this page Mar 4, 2026 · 1 revision

The following code constitutes a MIDI I/O driver that works with both SoundX V1 and V2.

All code is in ACME Assembler syntax.

Usage is as follows:

!src "soundx.asm".              ; Include the driver

detect:    
    jsr midi_init               ; Call this to detect and initialize the board, if present
    bcc board_detected.         ; If carry is cleared, board was detected

midi_not_detected:
    ; ...
    rts                         ; Stop here

board_detected:
    ; Set board version in version string...
    lda #'1'
    bvs +                        ; If overflow flag is set, board is V1, if cleared it's V2
    lda #'2'
+   jsr CHROUT
    ; ...

After midi_init is called, the other subroutines can be called as desired, please see the contents of soundx.asm below to see what's available.

In order to be as small as possible, the driver uses self-modifying code and the features it actually provides can be controlled by some definitions. Set these as desired before including soundx.asm:

  • ENABLE_MIDI_RECV
  • ENABLE_MIDI_SEND
  • ENABLE_MIDI_NONBLOCK

The driver is contained in three files:

soundx.asm:

!source "hw_v1.inc"
!source "hw_v2.inc"

!zone {
;-----------------------------------------------------------------------------------------------------------------------
midi_init:
;-----------------------------------------------------------------------------------------------------------------------
; Initialize the MIDI hardware
; OUT: Carry=Set if board was not detected, clear otherwise, Overflow=Set if found V1 HW, cleared for V2
;-----------------------------------------------------------------------------------------------------------------------
	jsr midi_init_v1
	bcs .try_v2

	; V1 HW detected, patch the trampolines
!if ENABLE_MIDI_RECV {
	lda #<midi_recv_v1
	sta .midi_recv_ptr
	lda #>midi_recv_v1
	sta .midi_recv_ptr + 1
!if ENABLE_MIDI_NONBLOCK {
	lda #<midi_recv_nonblock_v1
	sta .midi_recv_nonblock_ptr
	lda #>midi_recv_nonblock_v1
	sta .midi_recv_nonblock_ptr + 1
}
}

!if ENABLE_MIDI_SEND {
	lda #<midi_send_v1
	sta .midi_send_ptr
	lda #>midi_send_v1
	sta .midi_send_ptr + 1
!if ENABLE_MIDI_NONBLOCK {
	lda #<midi_send_nonblock_v1
	sta .midi_send_nonblock_ptr
	lda #>midi_send_nonblock_v1
	sta .midi_send_nonblock_ptr + 1
	lda #<midi_trysend_v1
	sta .midi_trysend_ptr
	lda #>midi_trysend_v1
	sta .midi_trysend_ptr + 1
}
}

	lda #$40		; Set overflow flag to indicate V1
	adc #$40
	clc				; Clear carry to report that the board was found
	rts

.try_v2:
	; Check if V2 HW is available
	jsr midi_init_v2
	bcs .not_detected

	; Yes, the trampolines default to V2 so we have nothing to do, carry is already cleared
	clv				; Clear overflow flag to indicate V2
	rts
	

.not_detected:
	;~ sec			; Not necessary as we get here with carry set already
	rts

!if ENABLE_MIDI_RECV {
;-----------------------------------------------------------------------------------------------------------------------
midi_recv:
;-----------------------------------------------------------------------------------------------------------------------
; Wait for a byte from the MIDI interface
; WARNING: This function blocks until a byte arrives!
; OUT: A=Received byte
;-----------------------------------------------------------------------------------------------------------------------
.midi_recv_ptr = * + 1
    jmp midi_recv_v2

!if ENABLE_MIDI_NONBLOCK {
;-----------------------------------------------------------------------------------------------------------------------
midi_recv_nonblock:
;-----------------------------------------------------------------------------------------------------------------------
; Receive a byte from the MIDI interface without blocking
; OUT: A=Received byte, Carry=Set if no byte was available, clear otherwise
;-----------------------------------------------------------------------------------------------------------------------
.midi_recv_nonblock_ptr = * + 1
    jmp midi_recv_nonblock_v2
}
}

!if ENABLE_MIDI_SEND {
;-----------------------------------------------------------------------------------------------------------------------
midi_send:
;-----------------------------------------------------------------------------------------------------------------------
; Send a byte through the MIDI interface
; WARNING: This function blocks until the byte can actually be sent!
; IN: A=Byte to be sent
;-----------------------------------------------------------------------------------------------------------------------
.midi_send_ptr = * + 1
    jmp midi_send_v2

!if ENABLE_MIDI_NONBLOCK {
;-----------------------------------------------------------------------------------------------------------------------
midi_send_nonblock:
;-----------------------------------------------------------------------------------------------------------------------
; Send a byte through the MIDI interface without blocking
; WARNING: Behaviour is undefined if the previous byte hasn't been transmitted yet!
; IN: A=Byte to be sent
;-----------------------------------------------------------------------------------------------------------------------
.midi_send_nonblock_ptr = * + 1
    jmp midi_send_nonblock_v2

;-----------------------------------------------------------------------------------------------------------------------
midi_trysend:
;-----------------------------------------------------------------------------------------------------------------------
; Send a byte through the MIDI interface if possible
; WARNING: This function might not actually send anything, check carry upon return if you mind!
; IN: A=Byte to be sent
; OUT: Carry=Set if byte could not be sent, clear otherwise
;-----------------------------------------------------------------------------------------------------------------------
.midi_trysend_ptr = * + 1
    jmp midi_trysend_v2
}
}

}

hw_v1.inc:

; Hardware V1 uses a MOS (or Rockwell, or whatever) 6551 ACIA chip with a 3 MHz crystal
;
; The usage of the "overclocked" crystal makes it possible to reach the 31250 MIDI baud rate when the chip is
; configured for 19200 bps using the internal baud rate generator

!addr {
    ACIA_BASE       = $fde0
    ACIA_TX         = ACIA_BASE         ; Write only
    ACIA_RX         = ACIA_BASE         ; Read only
    ACIA_RESET      = ACIA_BASE + 1     ; Write only
    ACIA_STATUS     = ACIA_BASE + 1     ; Read only
    ACIA_CMD        = ACIA_BASE + 2
    ACIA_CTL        = ACIA_BASE + 3
}

!zone {
;-----------------------------------------------------------------------------------------------------------------------
midi_init_v1:
;-----------------------------------------------------------------------------------------------------------------------
; Initialize the MIDI hardware
; OUT: Carry=Set if board was not detected, clear otherwise
;-----------------------------------------------------------------------------------------------------------------------
    ; Reset ACIA for a start
    lda #$00
    sta ACIA_RESET
        
    ; Configure ACIA for No Parity, No Echo, No TX Interrupt, /RTS Low (Unneeded), No RX Interrupt, /DTR Low (Unneeded)
    lda #%00001011
    sta ACIA_CMD
    ; 0 Stop Bits, 8 Data Bits, Internal Baud Rate Generator, 19200 bps (which will actually be 31250)
    lda #%00011111
    sta ACIA_CTL
    
    lda ACIA_CMD
    cmp #%00001011
    bne .not_detected
    lda ACIA_CTL
    cmp #%00011111
    bne .not_detected
    clc
    rts
    
.not_detected:
    sec
    rts

!if ENABLE_MIDI_RECV {
;-----------------------------------------------------------------------------------------------------------------------
midi_recv_v1:
;-----------------------------------------------------------------------------------------------------------------------
; Wait for a byte from the MIDI interface
; WARNING: This function blocks until a byte arrives!
; OUT: A=Received byte
;-----------------------------------------------------------------------------------------------------------------------
    lda #%00001000
-   bit ACIA_STATUS
    beq -
    
    lda ACIA_RX
    rts

!if ENABLE_MIDI_NONBLOCK {
;-----------------------------------------------------------------------------------------------------------------------
midi_recv_nonblock_v1:
;-----------------------------------------------------------------------------------------------------------------------
; Receive a byte from the MIDI interface without blocking
; OUT: A=Received byte, Carry=Set if no byte was available, clear otherwise
;-----------------------------------------------------------------------------------------------------------------------
    lda #%00001000
    bit ACIA_STATUS
    bne +
    sec
    rts
    
+   lda ACIA_RX
    clc
    rts
}
}

!if ENABLE_MIDI_SEND {
;-----------------------------------------------------------------------------------------------------------------------
midi_send_v1:
;-----------------------------------------------------------------------------------------------------------------------
; Send a byte through the MIDI interface
; WARNING: This function blocks until the byte can actually be sent!
; IN: A=Byte to be sent
;-----------------------------------------------------------------------------------------------------------------------
    pha

    lda #%00010000              ; Wait for "Transmit Data Register Empty" bit to be set
-   bit ACIA_STATUS
    beq -

    pla
    sta ACIA_TX

    rts

!if ENABLE_MIDI_NONBLOCK {
;-----------------------------------------------------------------------------------------------------------------------
midi_send_nonblock_v1:
;-----------------------------------------------------------------------------------------------------------------------
; Send a byte through the MIDI interface without blocking
; WARNING: Behaviour is undefined if the previous byte hasn't been transmitted yet!
; IN: A=Byte to be sent
;-----------------------------------------------------------------------------------------------------------------------
    sta ACIA_TX
    rts

;-----------------------------------------------------------------------------------------------------------------------
midi_trysend_v1:
;-----------------------------------------------------------------------------------------------------------------------
; Send a byte through the MIDI interface if possible
; WARNING: This function might not actually send anything, check carry upon return if you mind!
; IN: A=Byte to be sent
; OUT: Carry=Set if byte could not be sent, clear otherwise
;-----------------------------------------------------------------------------------------------------------------------
    pha

    lda #%00010000              ; Wait for "Transmit Data Register Empty" bit to be set
    bit ACIA_STATUS
    beq +

    pla
    sta ACIA_TX
    clc
    rts

+   pla
    sec
    rts
}
}

}

hw_v2.asm:

; Hardware V2 uses a Motorola MC6850 ACIA chip with a 500 kHz oscillator

!addr {
    MC6850_BASE		= $fde0
    MC6850_CTL		= MC6850_BASE	; Write only
    MC6850_STATUS	= MC6850_BASE	; Read only
    MC6850_TX		= MC6850_BASE+1	; Write only
    MC6850_RX		= MC6850_BASE+1	; Read only
}


!zone {
;-----------------------------------------------------------------------------------------------------------------------
midi_init_v2:
;-----------------------------------------------------------------------------------------------------------------------
; Initialize the MIDI hardware
; OUT: Carry=Set if board was not detected, clear otherwise
;-----------------------------------------------------------------------------------------------------------------------
    ; Reset ACIA for a start
    lda #%00000011
    sta MC6850_CTL

    ; Configure for No interrupt, /RTS Low (Unneeded), 8 Data bits + 1 Stop bit, No Parity, Clock Divider = 16
    lda #%00010101
    sta MC6850_CTL

    ; Chip detection cannot really be done reliably, but try something
    lda MC6850_STATUS
    and #%01001110		; Only consider Parity Error, /CTS, /DCD and TX Data Empty bits
    cmp #%00000010		; We should have no parity error, /CTS and /CDC low (they are tied to GND on the board) and TX Data empty
    bne .not_detected
    clc
    rts

.not_detected:
    sec
    rts

!if ENABLE_MIDI_RECV {
;-----------------------------------------------------------------------------------------------------------------------
midi_recv_v2:
;-----------------------------------------------------------------------------------------------------------------------
; Receive a byte from the MIDI interface
; WARNING: This function blocks until a byte arrives!
; OUT: A=Received byte
;-----------------------------------------------------------------------------------------------------------------------
    lda #%00000001
-   bit MC6850_STATUS
    beq -

    lda MC6850_RX
    rts

!if ENABLE_MIDI_NONBLOCK {
;-----------------------------------------------------------------------------------------------------------------------
midi_recv_nonblock_v2:
;-----------------------------------------------------------------------------------------------------------------------
; Receive a byte from the MIDI interface without blocking
; OUT: A=Received byte, Carry=Set if no byte was available, clear otherwise
;-----------------------------------------------------------------------------------------------------------------------
    lda #%00000001
    bit MC6850_STATUS
    bne +
    sec
    rts

+   lda MC6850_RX
    clc
    rts
}
}

!if ENABLE_MIDI_SEND {
;-----------------------------------------------------------------------------------------------------------------------
midi_send_v2:
;-----------------------------------------------------------------------------------------------------------------------
; Send a byte through the MIDI interface
; WARNING: This function blocks until the byte can actually be sent!
; IN: A=Byte to be sent
;-----------------------------------------------------------------------------------------------------------------------
    pha

    lda #%00000010              ; Wait for "Transmit Data Register Empty" bit to be set
-   bit MC6850_STATUS
    beq -

    pla
    sta MC6850_TX

    rts

!if ENABLE_MIDI_NONBLOCK {
;-----------------------------------------------------------------------------------------------------------------------
midi_send_nonblock_v2:
;-----------------------------------------------------------------------------------------------------------------------
; Send a byte through the MIDI interface without blocking
; WARNING: Behaviour is undefined if the previous byte hasn't been transmitted yet!
; IN: A=Byte to be sent
;-----------------------------------------------------------------------------------------------------------------------
    sta MC6850_TX
    rts

;-----------------------------------------------------------------------------------------------------------------------
midi_trysend_v2:
;-----------------------------------------------------------------------------------------------------------------------
; Send a byte through the MIDI interface if possible
; WARNING: This function might not actually send anything, check carry upon return if you mind!
; IN: A=Byte to be sent
; OUT: Carry=Set if byte could not be sent, clear otherwise
;-----------------------------------------------------------------------------------------------------------------------
    pha

    lda #%00000010              ; Wait for "Transmit Data Register Empty" bit to be set
    bit MC6850_STATUS
    beq +

    pla
    sta MC6850_TX
    clc
    rts

+   pla
    sec
    rts
}
}

}

Clone this wiki locally