-
Notifications
You must be signed in to change notification settings - Fork 2
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_RECVENABLE_MIDI_SENDENABLE_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
}
}
}