SPC
SPC
OVERVIEW
Uwaoo! Hi. I'm Gau. Me have a shiny thing. Very shiny... shiny... shiny... A
little *shiny* box inside my SNES. It's called an SPC-700. This little shiny
thing makes noise when told to do so by code inside the SNES. This little gizmo
is the key to making any sound whatsoever with your SNES program.
WHAT IS THE SPC
The SPC-700 is a co-processor. This means it is a separate CPU inside the SNES
with its own memory and instruction set. The SNES communicates with the SPC
through four 8-bit I/O ports. When the appropriate information is sent on the
I/O ports, the program in the SPC will recognize the signals and perform some
action (it might be nothing at all).
The SPC has its own memory, instruction set, etc. You cannot put a program into
the SPC simply by storing it into RAM. The SPC must be sent the program via it's
I/O locations. When you first turn on the SNES, the SPC has an internal kernal
of sorts that gives it the ability to bootstrap to a program sent over the I/O
port. Provided you have such a program, you can send it to the SPC. When the SPC
recieves your code it will jump to the execution address in its memory that you
specify (it's not required to be the start of code). This is where your program
gets control of the SPC, DSP and I/O ports.
The memory on the SPC is 64K (kilobytes that is, not kilobits) in size. The DSP
is 16-bit and supports eight stereo channels, each independantly pannable Left
to Right. Samples sent into the DSP aren't the normal raw smapling used on an
Amiga or PC. The samples in the SPC are encoded in a compressed format (I will
get more to that later). The designers only placed 32K of RAM into the SPC
however, so the upper 32K is unusable.
GETTING A PROGRAM INTO THE SPC
When the SPC starts, it waits for a startup value to appear in location 2141,
This is hexadecimal $CC. However, you must do this last or else the kernal will
get incorrect location and size information. Before I go too much further with
this, I should indicate the block structure the SPC recognizes as part of the
transfer protocol:
WORD Length
WORD Location
BYTES Data
The SPC kernal will let you send any number of these blocks (ie: a MIDI
implementation might use this to load appropriate patches for a song about to be
played and then send the song).
When you've sent all blocks and want to start running code in the SPC, you send
a zero-length block. There won't be any data. This block which contains only a 0
for length and a location will tell the kernal in the SPC that you're done
sending and that it should jump to the program you just sent. The section below
refers to this as the terminator block.
SENDING THE BLOCKS
The transfer protocol is fairly straightforward.
Get the length and place it into a counter
Get the location and store it to $2142-3 (a 16-bit operation will work
fine).
If this is the first chunk, place $CC into $2140 and start an 8-bit
sent-count at zero.
Send $01 to $2140 unless this is the terminator block, in which case you
store a zero here.
You send the rest of the block one byte at a time. Store the byte into
$2140.
To tell the SPC you've sent it the next value, you tell it the curent
location. The SPC will set it to one less than the number of the curent byte
sent (SPC starts this location at $FF and will wait for this location to go
zero). Use the 8-bit sent-count. Store the sent-count to $2141
Wait for $2140 to mimic the value you just put into $2141. Please compare
this with your counter and NOT the I/O port.
Bump the sent-count
Go back to step 5 until all bytes are sent.
Add 3 to your sent-count
If this makes the count 0, add 3 again.
If this wasn't a terminator block, go back to step 4 to continue sending
blocks.
You are done and the SPC should now be doing your code.
Final notes:
If speed is important, you'll want to move your blocks into RAM before
sending.
To enusre reliability during tranfers, disable the NMI while
transferring blocks to the SPC.
ONCE YOUR PROGRAM IS IN
Okay, so you've got an SPC program in the SPC, now what? First your program
should have some means of accepting input from the SNES when it wants to
change game music or play a sound effect. To do this, you need to be able to
read or write values from or onto the I/O port from the SPC. On the SNES
65816 side, the I/O ports $2140-$2143. Inside the SPC, they're zero page
locations $f4-$f7. Location $f4 is $2140, $f5 is $2141, etc.
Your code will want to check these ports. Your SPC program should detect
values by writing a sentinel value to the port and wait for it to change.
Then call the appropriate routine or effect the appropriate change. The
reciever should have the task of using the sentinel to detect a change. If
all the values are important, use two ports, send the byte out both ports
and wait for the exclusive-or complement of the value sent to show up at one
of the ports. This will be the reciever telling you it has recieved the next
byte ok. The reciever should wait for both locations to become equivalent
before attempting to get the next byte. This will ensure the reciever that
the sender has placed a value onto the port.
Next, your program will want to make some noise to tell the world (and you)
that it's there. To do this, you need to know about the SPC's samples and
registers. I'll start with the registers, then talk about the samples.
THE SPC-700 REGISTERS
These registers are directly accessible from your SPC code. They are the
zeropage locations $f1-$ff.
$F1 SPCCON1 bits 0-2 timer enables (1=on), bits 4-5
are I/O port clear bits (11=clear all)
ASL A 1C 1 NOP 1
ASL *,X 1B 2 NOP 1
ASL * 0C 3 NOP 1
ASLZ * 0B 2 NOP 1
LSR A 5C 1 NOP 1
LSR *,X 5B 2 NOP 1
LSR * 4C 3 NOP 1
LSRZ * 4B 2 NOP 1
ROL A 3C 1 NOP 1
ROL *,X 3B 2 NOP 1
ROL * 2C 3 NOP 1
ROLZ * 2B 2 NOP 1
ROR A 7C 1 NOP 1
ROR *,X 7B 2 NOP 1
ROR * 6C 3 NOP 1
RORZ * 6B 2 NOP 1
BPL * 10 2 R1 1
BRA * 2F 2 R1 1
BMI * 30 2 R1 1
BVC * 50 2 R1 1
BVS * 70 2 R1 1
BCC * 90 2 R1 1
BCS * B0 2 R1 1
BNE * D0 2 R1 1
BEQ * F0 2 R1 1
CLR0 * 02 2 NOP 1
CLR1 * 22 2 NOP 1
CLR2 * 42 2 NOP 1
CLR3 * 62 2 NOP 1
CLR4 * 82 2 NOP 1
CLR5 * A2 2 NOP 1
CLR6 * C2 2 NOP 1
CLR7 * E2 2 NOP 1
SET0 * 12 2 NOP 1
SET1 * 32 2 NOP 1
SET2 * 52 2 NOP 1
SET3 * 72 2 NOP 1
SET4 * 92 2 NOP 1
SET5 * B2 2 NOP 1
SET6 * D2 2 NOP 1
SET7 * F2 2 NOP 1
DECW * 1A 2 NOP 1
INCW * 3A 2 NOP 1
CLRW * 5A 2 NOP 1
ADDW YA,* 7A 2 NOP 1
SUBW YA,* 9A 2 NOP 1
MOVW YA,* BA 2 NOP 1
MOVW *,YA DA 2 NOP 1
MUL YA CF 1 NOP 1
DIV YA,X 9E 1 NOP 1
DEC A 9C 1 NOP 1
DEC X 1D 1 NOP 1
DEC Y DC 1 NOP 1
DEC *,X 9B 2 NOP 1
DEC * 8C 3 NOP 1
DECZ * 8B 2 NOP 1
INC A BC 1 NOP 1
INC X 3D 1 NOP 1
INC Y FC 1 NOP 1
INC *,X BB 2 NOP 1
INC * AC 3 NOP 1
INCZ * AB 2 NOP 1
OR A,(X) 06 1 NOP 1
OR A,[*+X] 07 2 NOP 1
OR A,#* 08 2 NOP 1
OR A,*+X 15 3 NOP 1
ORZ A,*+X 14 2 NOP 1
OR A,*+Y 16 3 NOP 1
OR A,[*]+Y 17 2 NOP 1
OR A,* 05 3 NOP 1
ORZ A,* 04 2 NOP 1
OR (X),(Y) 19 1 NOP 1
OR *,#* 18 3 CSWAP 1
OR *,* 09 3 CSWAP 1
TCALL * 01 1 T1 1 4 F0
TSET1 * 0E 3 NOP 1
TCLR1 * 4E 3 NOP 1
CALL * 3F 3 NOP 1
PCALL * 4F 2 NOP 1
JMP [*+X] 1F 3 NOP 1
JMP * 5F 3 NOP 1
E-Mail: [email protected]
WWW: http://www.netbistro.com/~gau