How to Write to DSP Registers Without any SPC-700 Code

Note: This tutorial incorrectly uses .smc for ROM that does not have a copier header; could not upload a fixed archive due to https://github.com/uttori/uttori-wiki/issues/34 - jeffythedragonslayer 6/8/23

Usage:

ca65 manual_dsp.s
ld65 -C lorom128.cfg -o manual_dsp.smc manual_dsp.o

lorom128.cfg

# ca65 linker config for 128K SMC

# Physical areas of memory
# Names need not match, but it makes it easier to remember if they do.
MEMORY {
    ZEROPAGE:   start =      0, size =  $100;
    BSS:        start =   $200, size = $1800;
    ROM:        start =  $8000, size = $8000, fill = yes;
    BANK1:      start = $18000, size = $8000, fill = yes;
    BANK2:      start = $28000, size = $8000, fill = yes;
    BANK3:      start = $38000, size = $8000, fill = yes;
}

# Logical areas code/data can be put into.
SEGMENTS {
    ZEROPAGE:   load = ZEROPAGE,    type = zp;
    BSS:        load = BSS,         type = bss, align = $100;

    CODE:       load = ROM,         align = $8000;
    RODATA:     load = ROM;
    HEADER:     load = ROM,         start =  $FFC0;
    ROMINFO:    load = ROM,         start =  $FFD5, optional = yes;
    VECTORS:    load = ROM,         start =  $FFE0;

    # The extra three banks
    BANK1:      load = BANK1,       align = $8000, optional = yes;
    BANK2:      load = BANK2,       align = $8000, optional = yes;
    BANK3:      load = BANK3,       align = $8000, optional = yes;
}

lorom128.inc

; Sets up processor basics and ROM headers/vectors

.p816   ; 65816 processor
.i16    ; X/Y are 16 bits
.a8     ; A is 8 bits

.segment "HEADER"     ; +$7FE0 in file
    .byte ROM_NAME

.segment "ROMINFO"    ; +$7FD5 in file
    .byte $30         ; LoROM, fast-capable
    .byte 0           ; no battery RAM
    .byte $07         ; 128K ROM
    .byte 0,0,0,0
    .word $AAAA,$5555 ; dummy checksum and complement

.segment "VECTORS"
    .word 0, 0, 0, 0, 0, 0, 0, 0
    .word 0, 0, 0, 0, 0, 0, reset, 0

.code

.macro init_cpu
    clc
    xce
    rep #$10        ; X/Y 16-bit
    sep #$20        ; A 8-bit
.endmacro

spc_upload.s

; High-level interface to SPC-700 bootloader
;
; 1. Call spc_wait_boot
; 2. To upload data:
;       A. Call spc_begin_upload
;       B. Call spc_upload_byte any number of times
;       C. Go back to A to upload to different addr
; 3. To begin execution, call spc_execute
;
; Have your SPC code jump to $FFC0 to re-run bootloader.
; Be sure to call spc_wait_boot after that.


; Waits for SPC to finish booting. Call before first
; using SPC or after bootrom has been re-run.
; Preserved: X, Y
spc_wait_boot:
    lda #$AA
@wait:  cmp $2140
    bne @wait

    ; Clear in case it already has $CC in it
    ; (this actually occurred in testing)
    sta $2140

    lda #$BB
@wait2: cmp $2141
    bne @wait2

    rts


; Starts upload to SPC addr Y and sets Y to
; 0 for use as index with spc_upload_byte.
; Preserved: X
spc_begin_upload:
    sty $2142

    ; Send command
    lda $2140
    clc
    adc #$22
    bne @skip       ; special case fully verified
    inc
@skip:  sta $2141
    sta $2140

    ; Wait for acknowledgement
@wait:  cmp $2140
    bne @wait

    ; Initialize index
    ldy #0

    rts


; Uploads byte A to SPC and increments Y. The low byte
; of Y must not changed between calls.
; Preserved: X
spc_upload_byte:
    sta $2141

    ; Signal that it's ready
    tya
    sta $2140
    iny

    ; Wait for acknowledgement
@wait:  cmp $2140
    bne @wait

    rts


; Starts executing at SPC addr Y
; Preserved: X, Y
spc_execute:
    sty $2142

    stz $2141

    lda $2140
    clc
    adc #$22
    sta $2140

    ; Wait for acknowledgement
@wait:  cmp $2140
    bne @wait

    rts

manual_dsp.s

; How to write to DSP registers without any SPC-700 code.
; Makes beep.
;
; ca65 manual_dsp.s
; ld65 -C lorom128.cfg -o manual_dsp.smc manual_dsp.o

.define ROM_NAME "MANUAL DSP"
.include "lorom128.inc"
.include "spc_upload.s"

reset:
    init_cpu

    jsr spc_wait_boot

    ; Upload sample to SPC at $200
    ldy #$0200
    jsr spc_begin_upload
:       lda sample,y
    jsr spc_upload_byte
    cpy #sample_end - sample
    bne :-

    ; Do DSP writes to start playing tone
    ldx #$206C
    jsr write_dsp
    ldx #$004C
    jsr write_dsp
    ldx #$FF5C
    jsr write_dsp
    ldx #$025D
    jsr write_dsp
    ldx #$7F00
    jsr write_dsp
    ldx #$7F01
    jsr write_dsp
    ldx #$0002
    jsr write_dsp
    ldx #$0203
    jsr write_dsp
    ldx #$0004
    jsr write_dsp
    ldx #$C305
    jsr write_dsp
    ldx #$2F06
    jsr write_dsp
    ldx #$CF07
    jsr write_dsp
    ldx #$005C
    jsr write_dsp
    ldx #$003D
    jsr write_dsp
    ldx #$004D
    jsr write_dsp
    ldx #$7F0C
    jsr write_dsp
    ldx #$7F1C
    jsr write_dsp
    ldx #$002C
    jsr write_dsp
    ldx #$003C
    jsr write_dsp
    ldx #$014C
    jsr write_dsp

@forever:
    jmp @forever


sample:
;.org $200
    ;directory:
    .word $204      ; start
    .word $204      ; loop

    ;sample:
    ;sample_loop:
    .byte $B0,$78,$78,$78,$78,$78,$78,$78,$78
    .byte $B3,$78,$78,$78,$78,$78,$78,$78,$78
sample_end:


; Writes high byte of X to SPC-700 DSP register in low byte of X
write_dsp:
    phx
    ; Just do a two-byte upload to $00F2-$00F3, so we
    ; set the DSP address, then write the byte into that.
    ldy #$00F2
    jsr spc_begin_upload
    pla
    jsr spc_upload_byte     ; low byte of X to $F2
    pla
    jsr spc_upload_byte     ; high byte of X to $F3
    rts

manual_dsp.7z

Written by blargg on 2013-08-22.