451 lines
13 KiB
Plaintext
451 lines
13 KiB
Plaintext
From PARADIS@htu.tu-graz.ac.at Fri Mar 25 08:41:08 1994
|
|
|
|
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
|
|
valid 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.
|