Transferring data from ROM to the SNES APU

The method of transferring data to the sound module of the SNES is a bit screwy, but it works.  I've read many docs, looked at a lot of code, and nothing I've seen seems to explain the method to this madness in a straightforward way.  I'm hoping I can shed some light on the matter.

During the transfer process, the following ports apply:

$2140 = Status
$2141 = Command / Data
$2142-3 = Address

 
Initializing the transfer

1.  Wait for port $2140 to be $AA and port $2141 to be $BB.  (This means the ROM program is through initializing, and is ready to begin a transfer.)
2. Write any value other than 0 to $2141.  (A value of 0 has a different meaning which will be explained later.)
3. Write the destination address of the transfer to ports $2142 and $2143, with the low byte written to $2142.
4. Write $CC to port $2140.
5. Wait for $2140 to be $CC.

 
Transferring data

1.  Write the first byte to be transferred to port $2141.
2. Write $00 to port $2140.
3. Wait for $2140 to be $00.
4. Write the next byte to be transferred to port $2141.
5. Increase the value in $2140 by 1, and write it back.
6. Wait for $2140 to be the same as the value written.
7. Goto step 4 until all bytes are transferred

 
Beginning another transfer

1.  Write a non-zero value to port $2141.
2. Write the destination address of the transfer to ports $2142 and $2143, with the low byte written to $2142.
3. Increase the value in $2140 by 2.  If it's 0, increase it again.  Write it back.
4. Wait for $2140 to be the same as the value written, then go to the section "Transferring data".

 
Ending transfers

1.  Write 0 to port $2141.
2. Write the address to begin execution to ports $2142 and $2143, with the low byte written to $2142.
3. Increase the value in $2140 by 2, and write it back.

 
Summary

Between transfers, a non-zero value sent to $2141 means to begin a transfer, and a zero value means to end a transfer and begin execution.

16-bit addresses are written to $2142-3.

During transfers, in $2140 a value less than means the APU is busy, a value equal means it's ready for the next byte.  Writing a value one greater means the next byte ($2141) is ready to be stored.  Writing a value two or more greater means to end the transfer and apply the command in $2141.  (Because of the internal workings of the ROM program, the value in $2140 must be non-zero if the command is to start another transfer.)

 
Example




Init:









Start:





Next:



Wait:
;Wait for SPC700 to initialize --
ReP   #$30
LdA   #$BBAA
Cmp   $2140
BNE   Init

;Initialize transfer ------------
LdA   #SPCExec
StA   $2142

LdA   #$1CC
StA   $2140
SeP   #$20
Cmp   $2140
BNE   Start

;Send data ----------------------
LdX   #0
LdY   #SPCSize
LdA   SPCData,X
StA   $2141
TXA
StA   $2140
Cmp   $2140
BNE   Wait
InX
DeY
BNE   Next

;Begin execution ----------------
ReP   #$20
LdA   #SPCExec
StA   $2142

SeP   #$20
LdA   #0
StA   $2141

LdA   $2140
AdC   #2
StA   $2140

;(16-bit A and X Y)





;Send starting address



;Write $CC to 2140 and !0 to 2141
;(8-bit A)
;Wait for SPC700 to give the go



;X indexes the current byte being sent
;Y = Number of bytes that need to be sent
;Send byte



;Wait for transfer






;(16-bit A)
;Starting address of execution


;(8-bit A)
;Jmp to execution address


;Break transfer cycle

Copyright ©2000 Alpha-II Productions