The Bloody SPC-700
------------------
A try to stumble into the inner secret of a nasty chip.
By Antitrack exclusively for the FAMIDEV development group.
Chapter 1:
----------
FACTS
* The SPC 700 is a very stupid sound chip with about the worst handling
that you have seen in your lifetime.
* This chip is a co processor. He has a quite large instruction set
(contrary to the Amiga's COPPER, who has a very small one) and
64KB
RAM memory, of which you can use atleast 32KB. (or so)
* All program and data that is supposed to be run by this chip must
be' moved to the SPC's own ram with a small loop that pokes each
byte of
your SPC assembler program and (e.g. sample-)data into four
memory
locations : $2140 - $2143. They are your only chance to communicate
with the SPC.
* These four memory locations have different meanings for read and
write;
if you read (LDA) $2140, you get the data from memory loc. 00f4
(or
so) of the sound chip.
* On power-on, the SPC 700 jumps (much like the main processor) to a
very small ROM area that resides from $ffc0 to $ffff inside the
SPC.
(This chip REALLY follows the black box principle, eh...) This
program at $ffc0 is waiting to get the data in the right format
on his
input ports at $00f4/5/6/7 , which are $2140/1/2/3 from the 65c816's
(e.g.
your's ) point of view.
* Your main program will therefore have to follow the SPC's
conditions and poke all the program and data for the SPC into
2140/1/2/3 in a
special order.
* When transmission is completed, you will also have transmitted the
start address of your SPC code, and the SPC will start to execute
your
program there.
--------------------QUESTIONS.
Q: How do I move my program and data to the SPC then, what format do
I have to use?
A: First, your SPC data/code has to be moved from ROM to the extra
RAM at e.g. $7f0000 . Dont ask me why it has to be in RAM, probably
it doesnt
but all the existing routines that send data to the SPC do something
like
that.
Your data/code has to be in groups which I will call "chunks". A
alid chunk looks like that:
first word: number of bytes to transmit to SPC
-+
sec. word : start address where to move data to the SPC
| one chunk
byte 4-???? : your data/code
-+
You can have as many chunks as you want to , but the last chunk must
be like that:
first word : $0000
second word: Start address of your code.
Q: So if you are right, this means: After I transmitted all my code
and data, and my own SPC code takes over the control, I might encounter
problems if my SPC program has to communicate with the outer world
(the
65c816).What if the main program wants to change sounds? What if a
background
melody shall always play on two voices, and extra two voices will be
used for
sound effects whenever the player sprite e.g. picks up an object?
A: That is sure a point. Your own code will have to look at memory
locations $00f4/00f5/00f6/00f7 , because they are the only accessible
from
outside at $2140/1/2/3. The easiest way would be: As soon as any of
$f4-$f7
change, jump into the Boot ROM at $ffc0 (?) so the SPC is executing
his
receive routine again. Then you *probably* can send another SPC chunk
with new
sound and code to the SPC....
Q: This only helps if a complete new tune is to be played, this
doesnt help if a melody using two voices shall still remain....
A: Thats true. The best approach is to send own command bytes to the
SPC and your SPC code has to check out $f4-$f7 constantly and react
to it.....
A command byte like $00 could mean: sound off,
$01 : play
tune 1
.
.
.
$0f : play tune $0f
$10 : play
jingle (fx) 01
.
.
.
$ff : jump
to $ffc0 (??) the receive
ROM routine
Q: is there another approach?
A: Yes there is. As you probably know, all important addresses of the
SPC 700 reside inside its own RAM's zeropage:
Address / register
/ usage
0000
Volume left
0001
Volume right
0002
Pitch low
0003
Pitch high (The total
14 bits of pitch
height)
0004
SRCN
Designates source number from 0-
255
0005
ADSR 1
0006
ADSR 2
0007
GAIN
Envelope can be freely designated by
your code
0008
ENVX
Present val of envelope with DSP
rewrites
0009
VALX
Present wave height val
(and so on...)
Your approach would be to move only sample data there, and/or (lots
of) very
small chunks of data with a target address in the zeropage, and a
starting
address of e.g. $ffc0. The small chunks would access zeropage
addresses e.g.
for the volume etc and thus result in tones; if this is done every
frame
you might end up with a music player quite similar to the C64 styled
ones.
Q: So anyway, in what format exactly do I have to move data to the
SPC?
A: I have the following source code for you, but let me explain it a
bit
BEFORE you start to dig into it.
I've already mentioned the general "chunk" format. The loop does the
following:
- move ram destination address to $2142/3 (akku: 16 bit)
- move either #$00 or #$01 into 2141, this depends if you have more
than $0100
bytes of data for the SPC;
- first time (first chunk you transmit): move constant #$cc into 2140
- loop: poke each byte that you want to be transmitted into 2140
(word)
the higher 7-15 bits of your accu-word contain the number of
bytes
already
moved (e.g. 00 on the start)
- cmp $2140 with this number of bytes already moved (lower 8 bits of
this
number only!) and wait if its not equal.
- until the loop is over.
- for the next chunk header this is repeated, but not #$cc is moved
into
2140 but "nn" (lobyte of number of bytes moved) +3 or +6 if
it was
00 when
+3 was used.
EXAMPLE:
move #$0400 to 2142 /word access
move #$01 to 2141
move #$cc to 2140
move "gg00" to 2140 where
"gg" is the first real code/data
byte for
the SPC
wait till 2140 is #$00
move hh01 to 2140 where "hh"
is the second byte of code or
data for SPC
wait till 2140 is #$01
move ii02 to 2140 where "ii"
is the 3rd byte of data for the
SPC....
wait till 2140 is #$02
lets say "ii" was the last byte.
Now we add #$04 (3+carry) to
#$02
(#$02 being the number-1 of how
many bytes we moved to the
SPC), we
will push it onto the stack),
now :
fetch the next header , poke target
RAM address into $2142
(word)
poke 00 or 01 into 2141 depending
of how many bytes to send,
poke #$06 into 2140 (06 : number
of bytes sent from last chunk-
1 + 3 )
I think I got this scheme pretty much right this time. Now, is PLEASE
someone
going to donate their home-brewed SPC dis/assemblers to me? Oh pretty
please,
I hate silent SNES's ! :)
Source code follows, reassembled from a PAN/Baseline demo "xmas wish
92/93":
----------------------------------------------------------------------
------
; entry to the code starts here
SEP
#$30 ; x y a set to 8 bit length
LDA #$FF ; ff into audio0w (write)
STA $2140
REP #$10 ; x,y: 16 bit length
LDX #$7FFF
l0DB5B LDA $018000,X ; move rom
music data to ram at $7f0000
STA $7F0000,X
LDA $028000,X ; move rom music data to ram at $7f0000
STA $7F8000,X
DEX
BPL l0DB5B
LDA #$80 ; screen on , probably not
important at all
STA $2100
LDA #$00 ; 00fd/00fe/00ff point to
the data that is
now
STA $00FD ; in ram at $7f0000
LDA #$00
STA $00FE
LDA #$7F
STA $00FF
STZ $4200 ; disable nmi and timer h/v count
SEI ;
disable irq
JSR
l0DBCD ; unknown sub routine, labeled "RESTART"
by PAN/ATX
SEP
#$30 ; all regs 8 bit
l0DB8B LDA $2140
; wait for reply from sound chip ?
BNE l0DB8B
LDA #$E0 ; audio3w ?
STA $2143
LDA #$FF ; send data to sound chip
?
STA $2142 ; $ffe0 this could be an address
within the
; sound chip ROM between $ffc0 and $ffff
in the
; ROM mask.......
LDA #$01 ; send data to sound chip
?
STA $2141
LDA #$01 ; send data to sound chip
?
STA $2140
l0DBA4 LDA $2140
; wait for reply from sound chip ?
CMP #$01 ; what a fuck of a protocol
.... :(
BNE l0DBA4
l0DBAB LDA $2140
; wait again for reply from soundchip ?
CMP #$55
BNE l0DBAB
LDA
$0207 ; aha ... move $0207 to sound
chip ?
STA $2141 ; probably sound number selector
LDA #$07
STA $2140 ; send data to sound chip
l0DBBD LDA $2140
; wait until sound chip accepted data?
CMP #$07
BNE l0DBBD
l0DBC4 LDA $2140
; wait for reply ?
CMP #$55
BNE l0DBC4
CLI
RTS
l0DBCD PHP
; labeled "RESTART" by pan/ATX
JSR l0DBD8 ;
PLP
LDA #$00 ; 00 into audio0w
STA $2140
RTS
l0DBD8 PHP
REP #$30 ; a,x,y 16 bit regs
LDY #$0000 ; needed first time at lda [$fd],y :
pointer to ram
LDA #$BBAA
l0DBE1 CMP $2140
; wait for sound chip $2140/2141 ?
BNE l0DBE1
SEP #$20 ; akku 8 bit
LDA #$CC
BRA l0DC12 ; oh well, another mystery :-)
; jump here if overflow is set e.g. if more than $0100 data to
move
l0DBEC LDA [$FD],Y ; get
data from ram pointer
INY ; the accumulator
is about to get "xx00"
where
XBA ;
/"xx" is the byte from [fd],y (first
data byte)
LDA #$00 ; /and resides
into bit 15-7 of accu,
and 00 is
BRA l0DBFF ; /#$00 (8bit number of
bytes already
sent)
l0DBF4 XBA
; accu is now "nn??" ?? is old data from
last loop
LDA [$FD],Y ; accu is now "nnxx" with xx the newest
data byte
INY ;
/for
the SPC!
XBA ; accu
is now "xxnn"
l0DBF9 CMP $2140
; wait for sound chip to reply with "nn" !!
BNE l0DBF9
INC A ; increment number
of bytes that were
sent...
; accu is now "xxnn" with newest val for
nn:=nn+1
l0DBFF REP #$20
; akku 16 bit
STA $2140 ; poke "xxnn" to soundchip. xx is actual
data,
SEP #$20 ; akku 8 bit ! nn is the 8-bit
cutted
number of bytes
DEX
! which were already sent!!
BNE l0DBF4 ; as many times as xreg says...
l0DC09 CMP $2140
; byte "nn" will be replied from the SPC if
data
BNE l0DC09 ; received correctly!
l0DC0E ADC #$03
; compare accu with #$fb ADC WILL ADD #$04
COZ
; CARRY IS ALWAYS SET AFTER THE CMP!!!
ATTENTION!
BEQ l0DC0E ; if accu was $fb then accu := $03 . (what
for?)
l0DC12 PHA
; push value accu+$04 to stack (or
beginning: #$cc)
REP #$20 ; accu = 16 bit
LDA [$FD],Y ; get ram data 2 bytes
INY ; point
to next word
INY
TAX ; x:=a
: number of bytes to transmit
LDA [$FD],Y ; get ram data
INY
INY
STA $2142 ; audio2w : possibly the dest.
area in the
spc700
SEP #$20 ; accu 8 bit
CPX #$0100 ; set carry if first ram data was >= 0100
lda #$00 ;
ROL ;
STA $2141 ; if ram data >= 0100, poke "1" into
reg 1
otherw 0
ADC #$7F ; SET OVERFLOW FLAG IF X>=$0100
!!!! (nice
trick!)
PLA
STA $2140 ; $cc in the first case , nn+4 on all
later
cases
l0DC32 CMP $2140 ; wait
for snd chip reply
BNE l0DC32
BVS l0DBEC ; if there were more than $0100 data for the
spc's RAM
; move them where they R supposed to belong
to!
PLP
RTS
PLA
STA $2140 ; same shit, never been jumped into
l0DC3F CMP $2140
BNE l0DC3F
BVS l0DBF9
PLP
RTS
; also lets look at 7f0000: the first few bytes at 7f0000 are:
7f0000: b7 0e 00 04 20 cd cf bd e8 00 5d af c8 f0 d0 fb 5d d5 00 01
d5 00 02
b7 0e should be number of bytes to transmit, 0400 the destination
inside the
spc....
at this point I really need an SPC dis/assembler..... :(((
Okay well my first source was incompetent, sure thing. But I think I
could
solve a lot of questions meanwhile.