65816 Assembly programming for
the SNES and Super Famicom
The Super Nintendo (AKA Super
Famicom) was the successor to the NES... relatively late to
the 16 bit game, the SNES made the unusual choice to not use the
68000 CPU for its processor, favoring the slower and less efficient
65186... this is rumored to be because during development backwards
compatibility with the NES was planned...
The 65186 is a true 16 bit CPU, but it has a 6502 compatibility
mode, in which it works exactly like a 6502, and we can perform
basic SNES programming without even using a 16 bit command!
|
 |
Cpu |
3.58 mhz 65816 |
Ram |
128k |
Vram |
64k |
Tiles |
1024 x 4 or 16 color |
Sprites |
128 onscreen (32 per line) |
Resolution |
512x448 |
Colors |
256 (16 per tile) from 32768 |
Sound
chip |
64k DSP 8 channel SPC700 |
The UK SNES PSU outputs 9V AC the Japanese Super Famicom PSU
outputs 10V DC - CENTER NEGATIVE BE CAREFUL! adapters that
output AC are very rare and will kill most systems - and
Center negative was common for 80's Japanese systems, but is
very rare these days - YOU HAVE BEEN WARNED! |
|
ChibiAkumas Tutorials
Useful Document
SNES Documentation v2.30: Written by Yoshi
nocash SNES hardware specifications
Memory Map
The 65816 has a 16 bit address bus,
it generally works with a 16 bit address, and a 8 bit Data Bank
register (DB)
The ZeroPage and Stack pointer are relocatable, but default to the
6502 normal of $0000-$01FF
Our minimal 32k cartridge will appear in rom from $8000, and
execution will start there, the CPU will default in 8 bit mode.
|
Bank |
16 bit address |
Purpose |
$00-$3F |
$0000-$1FFF |
RAM |
|
$2000-$5FFF |
Hardware Regs |
|
$6000-$7FFF |
Expand (???) |
|
$8000-$FFFF |
Cartridge Rom |
$70 |
$0000-$7FFF |
Battery Backed Up Ram |
$7E |
$0000-$1FFF |
Scratchpad RAM (same as
bank $00 to $3F) |
|
$2000-$FFFF |
RAM |
$7F |
$0000-$FFFF |
RAM |
|
VRAM
VRAM addresses are re-configurable, Here's a sample map which we'll be
working around in these tutorials ... Note the addresses are in WORDS (2
bytes in each address)... so the 64k memory is accessed by addresses
$0000-$7FFF
Address |
Use |
$0000 |
BG1 Tilemap |
$1000 |
Tile Patterns |
$4000 |
Sprite Patterns |
$7FFF |
Last byte of ram |
Cartridge Header
Address |
Bytes |
Category |
Purpose |
Example |
$FFC0
|
21 |
Rom |
Cartridge
title
(Space Padded) |
Test
Rom9012345678901 |
$FFD6
|
1 |
Rom |
ROM/RAM
information
on cart. |
$00 |
$FFD7
|
1 |
Rom |
ROM
size.
|
$01 |
$FFD8
|
1 |
Rom |
RAM
size.
|
$00 |
$FFD9
|
1 |
Rom |
Developer
ID
code. |
$00 |
$FFDB
|
1 |
Rom |
Version
number.
|
$00 |
$FFDC
|
2 |
Rom |
Checksum
complement.
|
$???? |
$FFDE
|
2 |
Rom |
Checksum.
|
$???? |
$FFE0 |
2 |
65816
Mode |
|
$0000 |
$FFE2 |
2 |
65816
Mode |
|
$0000 |
$FFE4 |
2 |
65816
Mode |
COP
Vector |
$0000 |
$FFE6 |
2 |
65816
Mode |
Brk
Vector |
$0000 |
$FFE8 |
2 |
65816
Mode |
Abort
Vector
(Unused) |
$0000 |
$FFEA |
2 |
65816
Mode |
NMI
Vector
(V-blank) |
$0000 |
$FFEC |
2 |
65816
Mode |
Reset
Vector
(Unused) |
$0000 |
$FFEE |
2 |
65816
Mode |
IRQ
Vector
(H/V/External) |
$0000 |
$FFF0 |
2 |
6502
Mode |
|
$0000 |
$FFF2 |
2 |
6502
Mode |
|
$0000 |
$FFF4 |
2 |
6502
Mode |
COP
Vector |
$0000 |
$FFF6 |
2 |
6502
Mode |
BRK
Vector
(unused) |
$0000 |
$FFF8 |
2 |
6502
Mode |
Abort
Vector
(Unused) |
$0000 |
$FFFA |
2 |
6502
Mode |
NMI
Vector
(V-blank) |
$0000 |
$FFFC |
2 |
6502
Mode |
Reset
Vector
(6502 Mode) |
$8000 |
$FFFE |
2 |
6502
Mode |
IRQ/BRK
Vector |
$0000 |
Tile Definitions
Tile definitions use 4 bitplanes for
16 colors, and tile definitions are 8x8 - so 32 bytes ... Data is
transferred in Words, and rather strangely we send bitplane 1+2 of lines,
one at a time... then we do the same for bitplanes 3 and 4
|
Byte 1 |
Byte 2 |
First
16
bytes |
00111100
01111111
01100011
01100011
01111111
01100011
01100011
00000000 |
00222200
02222222
02200022
02200022
02222222
02200022
02200022
00000000 |
Second
16
bytes |
00333300
03333333
03300033
03300033
03333333
03300033
03300033
00000000 |
00444400
04444444
04400044
04400044
04444444
04400044
04400044
00000000 |
Tilemap Data
The Tilemap will typically start from address $0000, each entry contains two
bytes
F |
E |
D |
C |
B |
A |
9 |
8 |
|
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
|
|
V |
H |
L |
P |
P |
P |
T |
T |
|
T |
T |
T |
T |
T |
T |
T |
T |
|
V=vflip
H=hflip L=layer (in
front of sprites) P=palette
T=tile number
(0-1023)
|
Palette Definitions
Palettes are defined by something called 'CGRAM'
there are 256 palette entries...
Because we can only select 8 palettes for Tiles/Sprites, assuming
our sprites and tiles are 16 colors the first 128 colors will be
used by Tiles, and the second 128 will be used by Sprites
To define a palette entry we need to select a palette entry number
by writing it's number as a byte to $2121
We have to write two bytes to $2122... we write them in REVERSE
order, so the low byte is written first!
Address |
Name |
Purpose |
Bits |
Details |
$2121
|
CGADD |
Colour
# (or pallete) selection |
xxxxxxxx |
x=color
(0-255) |
$2122
|
CGDATA |
Colour
data |
-bbbbbgg
gggrrrrr |
Color
Data BGR |
H byte (sent second) |
|
L byte (sent first) |
F
|
E
|
D
|
C
|
B
|
A
|
9
|
8
|
|
7
|
6
|
5
|
4
|
3
|
2
|
1
|
0
|
- |
B |
B |
B |
B |
B |
G |
G |
|
G |
G |
G |
R |
R |
R |
R |
R |
|
Color
Number |
Type |
Palette
Number |
0 |
Background |
0 |
16 |
Background |
1 |
32 |
Background |
2 |
48 |
Background |
3 |
64 |
Background |
4 |
80 |
Background |
5 |
96 |
Background |
6 |
112 |
Background |
7 |
128 |
Sprite |
0 |
144 |
Sprite |
1 |
160 |
Sprite |
2 |
176 |
Sprite |
3 |
192 |
Sprite |
4 |
208 |
Sprite |
5 |
224 |
Sprite |
6 |
240 |
Sprite |
7 |
255 |
� |
� |
|
Sprite Definitions - Overview
Sprites use as special bank of 512 bytes of 'OAM' memory for their
definitions... they also use standard VRAM for the pattern data.
In theory the Pattern data can be relocated... but in practice it's best to
just assume it's at $4000 (address in 16 bit words)
Sprites can be various sizes - a 'default size' is set for all sprites...
and certain selected sprites can be double size...
this is, however a bit tricky... lets say you have the default size as
8x8... and one double size 16,16 sprite
If we point this sprite 'double size' 16x16 sprite to pattern 'Tile
0', the 4 8x8 chunks will be made up of tile numbers:
Lets look at this example of a 16x16
sprite in AkuSprite Editor... Akusprite editor is designed for 8x8
sprites, but we can export a 16x16 one in the following way
If we want to export this quickly, so we can use it as a single
doublesize sprite, one option is to tick the 'FixedSize' tickbox,
and set the size to 128,16
This will export the sprite correctly - of course there will be a
lot of unused space in the exported file... so we would want to
combine all our 16x16 together into a single image
Generally it would be easier to build up a 16x16 sprite from 4 8x8
sprites
|
 |
Sprite Definitions - Ports Used
Address |
Name |
Purpose |
Bits |
Details |
$2101
|
OBSEL |
OAM
size
(Sprite) |
SSSNNBBB |
S=size
(See below) N=Bame addr B=Base addr |
$2102
|
OAMADDL/L |
OAM
address
|
LLLLLLLL |
a=oam
address
L |
$2103
|
OAMADDL/H |
OAM
address
|
R000000H |
R=
priority Rotation / H=oam address MSB |
$2104
|
OAMDATA |
OAM
data
|
???????? |
|
$212C
|
TM |
Main
screen
designation |
---S4321 |
S=sprites
4-1=enable
Bgx |
$2138
|
OAMDATAREAD |
Read
data
from OAM |
|
|
Sprite Sizes
Sprite size is set for all sprites, but each sprite can either be normal
or large defined by a single bit from address $0100 onwards
SSS
|
Size (Normal / Large)
|
0 %000
|
8x8 / 16x16 |
1 %001 |
8x8 / 32x32 |
2 %010 |
8x8 / 64x64 |
3 %011 |
16x16 / 32x32 |
4 %100 |
16x16 / 64x64 |
5 %101 |
32x32 / 64x64 |
6 %110 |
16x32 / 32x64 (Undocumented) |
7 %111 |
16x32 / 32x32 (Undocumented) |
Sprite Definitions - OAM Data
The SNES has 128 hardware sprites.
Selecting a HL address is done by setting registers $2102 (L) and
$2103 (H)
Each address below $0100 holds Two Bytes (The first table)...each address
$0100 or above holds just one!... All data is written via the $2104
Note, Sprites use Palettes from 128... so the color palette used is the
value in CCC +128
Sprite data should only be written to Vram during Vsync.
Address |
Byte
1 |
Byte
2 |
Meaning |
SprNum |
$0000 |
XXXXXXXX |
YYYYYYYY |
X=Xpos (bits
0-7) Y=Ypos |
0 |
$0001 |
TTTTTTTT |
YXPPCCCT |
T=Tile
Y=yflip X=xflip P=priority compared to BG C=palette +128 |
0 |
$0002 |
XXXXXXXX |
YYYYYYYY |
X=Xpos (bits
0-7) Y=Ypos |
1 |
$0003 |
TTTTTTTT |
YXPPCCCT |
T=Tile
Y=yflip X=xflip P=priority compared to BG C=palette +128 |
1 |
� |
� |
� |
� |
� |
� |
� |
� |
� |
� |
$00FE |
XXXXXXXX |
YYYYYYYY |
X=Xpos (bits
0-7) Y=Ypos |
127 |
$00FF |
TTTTTTTT |
YXPPCCCT |
T=Tile
Y=yflip X=xflip P=priority compared to BG C=palette +128
|
127 |
$0100 |
SXSXSXSX |
(no 2nd byte) |
S=LargeSize
(1=Large size) sprite X=Xpos (bit 8) |
0-3 |
$0101 |
SXSXSXSX |
(no 2nd byte) |
S=LargeSize
(1=Large size) sprite X=Xpos (bit 8) |
4-6 |
� |
� |
|
� |
� |
$011F |
SXSXSXSX |
(no 2nd byte) |
S=LargeSize
(1=Large size) sprite X=Xpos (bit 8) |
124-127 |
SNES Joypad
Presumably because of the planned backwards compatibility, the SNES
actually uses the same ports in the same way as the NES! However, because
the SNES has more buttons, we can do 16 reads rather than 8 to get the
extra buttons.
Mode
|
Port
|
Purpose |
7
|
6
|
5
|
4
|
3
|
2
|
1
|
0
|
Write |
$4016 |
Strobe
(reset) |
- |
- |
- |
- |
- |
- |
- |
Strobe |
Read |
$4016 |
Joypad
1/3 |
- |
- |
- |
- |
- |
Mic |
Pad3 |
Pad1 |
Read |
$4017 |
Joypad
2/4 |
- |
- |
- |
- |
- |
- |
Pad4 |
Pad2 |
F |
E
|
D
|
C
|
B
|
A
|
9
|
8
|
|
7 |
6 |
5 |
4
|
3
|
2 |
1
|
0
|
- |
- |
- |
- |
R |
L |
X |
A |
|
Right |
Left |
Down |
Up |
Start |
Select |
Y |
B |
The SNES firmware also reads in these ports automatically, and stores
these in ram - we can use these versions if we prefer.
Address |
Name |
Purpose |
Bits |
$4218
|
JOY1L |
Joypad #1
status (set during interrupt) |
AXLR---- |
$4219
|
JOY1H |
Joypad #1
status (if $4200 is set) |
BYSTUDLR |
$421A
|
JOY2L |
Joypad #2
status |
AXLR---- |
$421B
|
JOY2H |
Joypad #2
status |
BYSTUDLR |
$421C
|
JOY3L |
Joypad #3
status |
AXLR---- |
$421D
|
JOY3H |
Joypad #3
status |
BYSTUDLR |
$421E
|
JOY4L |
Joypad #4
status |
AXLR---- |
$421F
|
JOY4H |
Joypad #4
status |
BYSTUDLR |
Ports
rw |
Address |
Name |
Purpose |
Bits |
Details |
w |
$2100
|
INIDISP |
Screen
display |
x000bbbb |
x=screen
disable (1=disable) bbbb=brightness (15=max) |
w |
$2101
|
OBSEL |
OAM size
(Sprite) |
sssnnbbb |
sss^size
nn=name addr bb=base addr |
w 2 |
$2102
|
OAMADDL/H |
OAM address |
aaaaaaaa
r000000m |
a=oam address
r=priority m=addr MSB |
wd |
$2104
|
OAMDATA |
OAM data |
????????
???????? |
|
w |
$2105
|
BGMODE |
Screen mode |
abcdefff |
abcd=tile
sizes (8x8 or 16x16) e=pri fff=mode def |
w |
$2106
|
MOSAIC |
Screen
pixelation |
xxxxabcd |
xxxxx=pixel
size abcd=affect background layer |
w |
$2107
|
BG1SC |
BG1 Tilemap
VRAM location |
xxxxxxab |
xxx=address
(* $800 bytes / $400 words)
ab SC size 00=32x32 01=64x32 10=32x64 11=64x64 |
w |
$2108
|
BG2SC |
BG2 Tilemap
VRAM location |
xxxxxxab |
xxx=address
(* $800 bytes / $400 words)
ab SC size 00=32x32 01=64x32 10=32x64 11=64x64 |
w |
$2109
|
BG3SC |
BG3 Tilemap
VRAM location |
xxxxxxab |
xxx=address
(* $800 bytes / $400 words)
SC size 00=32x32 01=64x32 10=32x64 11=64x64 |
w |
$210A
|
BG4SC |
BG4 Tilemap
VRAM location |
xxxxxxab |
xxx=address
(* $800 bytes / $400 words)
SC size 00=32x32 01=64x32 10=32x64 11=64x64 |
w |
$210B
|
BG12NBA |
BG1 & BG2
VRAM location |
aaaabbbb |
aaaa=base
addr for BG1 (* $2000 bytes / $1000 words)
bbbb=base addr for BG2 (* $2000 bytes / $1000 words) |
w |
$210C
|
BG34NBA |
BG3 & BG4
VRAM location |
ccccdddd |
cccc=base
addr for BG3 (*
$2000 bytes / $1000 words)
dddd=base addr for BG4 (* $2000 bytes / $1000
words) |
wd |
$210D
|
BG1HOFS |
BG1
horizontal scroll |
mmmmmaaa
aaaaaaaa |
aaa=horiz
offset, mmm=Mode 7 option |
wd |
$210E
|
BG1VOFS |
BG1 vertical
scroll |
|
Write L then
H byte of offset to this address
|
wd |
$210F
|
BG2HOFS |
BG2
horizontal scroll |
|
Write L then
H byte of offset to this address |
wd |
$2110
|
BG2VOFS |
BG3 vertical
scroll |
|
Write L then
H byte of offset to this address |
wd |
$2111
|
BG3HOFS |
BG3
horizontal scroll |
-
Same as $210D. |
Write L then
H byte of offset to this address |
wd |
$2112
|
BG3VOFS |
BG3 vertical
scroll |
|
Write L then
H byte of offset to this address |
wd |
$2113
|
BG4HOFS |
BG4
horizontal scroll |
|
Write L then
H byte of offset to this address |
wd |
$2114
|
BG4VOFS |
BG4 vertical
scroll |
|
Write L then
H byte of offset to this address |
w |
$2115
|
VMAIN |
Video port
control |
i000abcd |
I 0=inc on
$2118 or $2139 1=$2119 or $213A� abcd=move size |
w 2 |
$2116-$2117 |
VMADDL/H |
Video port address |
LLLLLLLL HHHHHHHH |
Memory address (in bytepairs)$0000-$7FFF |
w 2 |
$2118-$2119 |
VMDATAL/H |
Video port data (Write)
|
LLLLLLLL HHHHHHHH |
Byte Data |
w |
$211A
|
M7SEL |
MODE7
settings |
ab0000yx |
ab=out of
area function x=xflip y=yflip |
w |
$211B
|
M7A |
COS rotate
angle / X Expnsn |
|
|
w |
$211C
|
M7B |
SIN rotate
angle / X Expnsn |
|
|
w |
$211D
|
M7C |
SIN rotate
angle / Y Expnsn |
|
|
w |
$211E
|
M7D |
COS rotate
angle / Y Expnsn |
|
|
wd |
$211F
|
M7X |
Center
position X (13-bit data only) |
|
|
wd |
$2120
|
M7Y |
Center
position Y (13-bit data only) |
|
|
w |
$2121
|
CGADD |
Colour # (or
pallete) selection |
xxxxxxxx |
x=color
(0-255) |
wd |
$2122
|
CGDATA |
Colour data |
-bbbbbgg
gggrrrrr |
Color Data
BGR |
w |
$2123
|
W12SEL |
Window mask
settings |
abcdefghv |
|
w |
$2124
|
W34SEL |
Window mask
settings |
abcdefgh |
|
w |
$2125
|
WOBJSEL |
Window mask
settings |
abcdefgh |
|
w |
$2126
|
WH0 |
Window 1 left
position |
aaaaaaaa |
a=position |
w |
$2127
|
WH1 |
Window 1
right position |
|
|
w |
$2128
|
WH2 |
Window 2 left
position |
-
Same as $2126. |
|
w |
$2129
|
WH3 |
Window 2
right position |
|
|
w |
$212A
|
WBGLOG |
Mask logic
cfg for Win 1 & 2 per scr |
aabbccdd |
aa..dd=BG
4321 |
w |
$212B
|
WOBJLOG |
Mask logic
for Colour Win & OBJ Win |
0000aabb |
a=color
params b=obj window params |
w |
$212C
|
TM |
Main screen
designation |
---S4321 |
S=sprites
4-1=enable Bgx |
w |
$212D
|
TD |
Sub-screen
designation |
*** Same as
$212C |
|
w |
$212E
|
TMW |
Window mask
main screen desig |
*** Same as
$212C |
|
w |
$212F
|
TSW |
Window mask
sub screen desig |
*** Same as
$212C |
|
w |
$2130
|
CGWSEL |
Fixed color
addition or screen addition |
abcd00ef |
ab=main
cd=sub ef=color data |
w |
$2131
|
CGADSUB |
Addition/subtraction
for
screens |
mrgsabcd |
& OBJs
[CGADSUB] |
w |
$2132
|
COLDATA |
Fixed colour
data for fixed colour +/- |
bgrdddddd |
Blue Green
Red dddd=constant |
w |
$2133
|
SETINI |
Screen
mode/video select |
sn00pvshi |
|
r |
$2134
|
MPYL |
Multiplication
result
(low) |
|
|
r |
$2135
|
MPYM |
Multiplication
result
(middle) |
|
|
r |
$2136
|
MPYH |
Multiplication
result
(high) |
|
|
r |
$2137
|
SLHV |
Software
latch for H/V counter |
aaaaaaaa |
a=result |
r |
$2138
|
OAMDATAREAD |
Read data
from OAM |
|
|
r 2 |
$2139-$213A |
VMDATAL/H READ |
Video port data (Read) |
|
|
r |
$213B
|
CGDATAREAD |
Read data
from CG-RAM (colour) |
|
|
r d |
$213C
|
OPHCT |
Horizontal
scanline location |
|
|
r d |
$213D
|
OPVCT |
Vertical
scanline location |
|
|
r |
$213E
|
STAT77 |
PPU status
flag & version number |
trm0vvvv |
|
r |
$213F
|
STAT78 |
PPU status
flag & version number |
fl0mvvvv |
|
rw |
$2140
|
APUI00 |
APUI00 �
Sound Port 0 |
|
|
rw |
$2141
|
APUI01 |
APUI01 �
Sound Port 1 |
|
|
rw |
$2142
|
APUI02 |
APUI02 �
Sound Port 2 |
|
|
rw |
$2143
|
APUI03 |
APUI03 �
Sound Port 3 |
|
|
rw |
$2180
|
WMDATA |
Read/write
WRAM |
|
|
rw |
$2181
|
WMADDL |
WRAM data
(low byte) |
|
|
rw |
$2182
|
WMADDM |
WRAM data
(middle byte) |
|
|
rw |
$2183
|
WMADDH |
WRAM data
(high byte) |
|
|
rw |
$4016 |
JOYWR/JOYA |
Joypad Output
/ JOY 1 Read (bit 0 x12) |
|
|
r |
$4017 |
JOYB |
JOY 2 Read
(bit 0 x12) |
|
|
w |
$4200
|
NMITIMEN |
Interrupt Enable and Joypad Request |
v0yx000j |
V=Vblank NMI
/ YX=HorizVert interrupt / J=Joypad Request
|
w |
$4201
|
WRIO |
Programmable
I/O port (out-port) |
|
|
w |
$4202
|
WRMPYA |
Multiplicand
'A' |
|
|
w |
$4203
|
WRMPYB |
Multiplier
'B� |
|
|
w 2 |
$4204
|
WRDIVL/H |
Dividend C |
|
|
w |
$4205
|
WRDIVB |
Divisor B |
|
|
w 2 |
$4207
|
HTIMEL/H |
Video H IRQ
beam pos/pointer |
0000000x
xxxxxxxx |
x: Beam
position. |
w 2 |
$4209
|
VTIMEL/H |
Video V IRQ
beam pos/pointer |
0000000y
yyyyyyyy |
y: Beam
position. |
w |
$420B
|
MDMAEN |
DMA enable |
76543210 |
|
w |
$420C
|
HDMAEN |
HDMA enable .
|
|
|
w |
$420D
|
MEMSEL |
Cycle speed |
0000000x |
0=2.68 1=3.58 |
r |
$4210
|
RDNMI |
NMI |
x000vvvv |
x=disable NMI
v=version |
rw |
$4211
|
TIMEUP |
Video IRQ |
i0000000 |
i=irq enabled |
rw |
$4212
|
HVBJOY |
Status |
xy00000a |
x=vblank
state y=hblank state a=joypad ready |
r |
$4213
|
RDIO |
Programmable
I/O port (in-port) |
|
|
r 2 |
$4214
|
RDDIVL/H |
Quotient of
divide result |
|
|
r 2 |
$4216
|
RDMPYL/H |
Multiplication
or
divide result |
|
|
r |
$4218
|
JOY1L |
Joypad #1
status (set during interrupt) |
AXLR---- |
|
r |
$4219
|
JOY1H |
Joypad #1
status (if $4200 is set) |
BYSTUDLR |
|
r |
$421A
|
JOY2L |
Joypad #2
status |
AXLR---- |
|
r |
$421B
|
JOY2H |
Joypad #2
status |
BYSTUDLR |
|
r |
$421C
|
JOY3L |
Joypad #3
status |
AXLR---- |
|
r |
$421D
|
JOY3H |
Joypad #3
status |
BYSTUDLR |
|
r |
$421E
|
JOY4L |
Joypad #4
status |
AXLR---- |
|
r |
$421F
|
JOY4H |
Joypad #4
status |
BYSTUDLR |
|
w |
$43x0
|
DMAPX |
DMA Control |
vh0cbaaa |
|
w |
$43x1
|
BBADX |
DMA
Destination |
LLLLLLLL |
H=$21 |
w 2 |
$43x2
|
A1TXL/H |
Source
address |
|
|
w |
$43x4
|
A1BX |
Source bank
address |
|
|
w 2 |
$43x5
|
DASXL/H |
DMA transfer
size & HDMA address |
|
|
w |
$43xA
|
NTRLX |
Number of
lines for HDMA transfer |
cxxxxxxx |
C=continue
(0=yes) x=lines to transfer |
WD=Write Double byte
2=Write 2 byte word
Sound on the SNES via the SPC700
To make sound on the SNES we have to use it:s SPC700
processor! what's the SPC700? well it's an 8 bit dedicated sound CPU with
64k of isolated memory - meaning that it can't be directly accessed from
the main CPU!
The SPC700 is a great sound processor, used by the Super Nintendo and many
other systems like... er.. the super nintendo!
Ok, so nothing else uses it, and it's a total pain! while it's sound
ability is good, its a real hassle to program... it uses it's own special
instruction set, and its bytecode matches no other CPU, and data has to be
transferred to it using a special procedure because we can't access it's
memory directly.
The CPU is 8 bit and Little endian, so $1234 is stored in ram as
$34,$12... it's registers are the same as the 6502, but it has some 16 bit
commands that use YA as a 16-bit pair like the Z80
it has a ZeroPage, but the ZeroPage is referred to as the
DirectPage, as it can be at $00xx or $01xx... $01xx is also used as the
Stack
Most data transfer commands are done with MOVe commands (like the 68000),
but like the z80, the destination is on the left... for example:
mov a, #$00 ;Set A to $00
I'm not planning to go into greater detail than I need on this CPU, so
please see the tutorials below:
Best SP700
reference
Also a good
tutorial
Sound samples must also be held in SPC700 ram...
Pointers to Sound samples are stored in a single block.. this
block must be byte aliened, and has 256 sound definitions - each of
which contains 2 pointers... one for the start of the sample, and
one for the loop
For example, take the example definition, this will be loaded into
SPC700 ram at $300... we would tell the sound chip to use $03 as the
memory position using the "DIR" sound register $5D
The sound samples themselves are made up of 9 byte chunks, the first
byte is a header, and the other 16 nibbles are the sound data... the
final sample in the sound should have the End bit set in the header,
and the Loop bit if you wish |
align
8
SFXBank:
;We're going to load this into $0300
dw SFXBank_Sound1-SFXBank+$300
;Sample 0
main
dw SFXBank_Sound1-SFXBank+$300
;Sample 0
Loop
align 4
SFXBank_Sound1:
;
SSSSFFLE S= bitshift (0-12) FF=Filter L=Loop E=End
db
%11000111,$FF,$F0,$F0,$F0,$F0,$F0,$F0,$F0
;
01 23 45 67 89 AB CD EF
|
The SPC700 Memory Map
Start
|
End |
Purpose |
0000 |
00EF
|
Zero
Page / Direct Page |
00F0 |
00F0 |
Unused |
00F1 |
00F1 |
Control
Port (Timers & reset) |
00F2 |
00F2 |
Sound
Register Select |
00F3 |
00F3 |
Sound
Register Value |
00F4 |
00F4 |
Link to
65816 address $2140 |
00F5 |
00F5 |
Link to
65816 address $2141 |
00F6 |
00F6 |
Link to
65816 address $2142 |
00F7 |
00F7 |
Link to
65816 address $2143 |
0100 |
01FF |
Stack |
0200 |
FFBF |
RAM |
FFC0 |
FFFF |
ROM |
The SPC700 Registers
Address |
Register |
Description |
Bits |
Meaning |
c0 |
VOL (L) |
Left Volume |
-VVVVVVV |
Volume |
c1 |
VOL (R) |
Right Volume |
-VVVVVVV |
Volume |
c2 |
P (L) |
Pitch L |
PPPPPPPP |
Pitch |
c3 |
P (H) |
Pitch H |
--PPPPPP |
Pitch |
c4 |
SRCN |
Source number
(references the source directory) |
SSSSSSSS |
Source |
c5 |
ADSR (1) |
If bit7 is
set, ADSR is enabled. If cleared GAIN is used. |
EDDDAAAA |
Enable,
Dr, Ar |
c6 |
ADSR (2) |
These two
registers control the ADSR envelope. |
LLLRRRRR |
sL,sR |
c7 |
GAIN |
This register
provides function for software envelopes. |
GGGGGGGG |
G=Envelope
bits |
c8 |
-ENVX |
(auto
updated) Readable current Envelope Value |
0VVVVVVV |
Value |
c9 |
-OUTX |
(auto
updated) Readable current Waveform Value |
SVVVVVVV |
Signed
Value |
0C |
MVOL (L) |
Main Volume
Left |
-VVVVVVV |
Volume |
1C |
MVOL (R) |
Main Volume
Right |
-VVVVVVV |
Volume |
2C |
EVOL (L) |
Echo Volume
Left |
-VVVVVVV |
Volume |
3C |
EVOL (R) |
Echo Volume
Right |
-VVVVVVV |
Volume |
4C |
KON |
Key On |
CCCCCCCC |
Channel |
5C |
KOF |
Key Off |
CCCCCCCC |
Channel |
6C |
FLG |
DSP Flags.
(used for MUTE,ECHO,RESET,NOISE CLOCK) |
RMENNNNN |
Reset
(0=off) Mute (0=off) Echo (1=0ff) N=Noise clock
|
7C |
-ENDX |
(auto
updated) read to see if channel done |
CCCCCCCC |
Channel |
0D |
EFB |
Echo Feedback |
SFFFFFFF |
Signed
Feedback |
2D |
PMON |
Pitch
modulation |
CCCCCCC- |
Channel
(1-7) |
3D |
NON |
Noise enable |
CCCCCCCC |
Channel (0=off) |
4D |
EON |
Echo enable |
CCCCCCCC |
Channel
(0=off)
|
5D |
DIR |
Offset of
source directory (DIR*100h = memory offset) |
OOOOOOOO |
Offset
$oo00 |
6D |
ESA |
Echo buffer
start offset (ESA*100h = memory offset) |
OOOOOOOO |
Offset
$oo00 |
7D |
EDL |
Echo delay,
4-bits, higher values require more memory. |
----EEEE |
Echo
delay |
fF |
COEF |
8-tap FIR
Filter coefficients |
SCCCCCCC |
Signed
Coeficcient |
c =
Channel 0-7� f = filter coefficient
0-7 |
|
|
SPC700 and VASM
Vasm doesn't support the SPC700, but we can simulate the commands
with macros, for example, we can set an immediate value with the
macro to the right |
macro
s_mov_a_ii,aval ;Set
A=immediate value
db $E8
db \aval
endm |
If we want to do a relative jump, we can calculate the relative
offset with Destination-(*+1) |
s_bne_r SoundCallPause-(*+1) |
If we want to include our SPC700 code in our main rom, we'll need
to adjust call addresses for the changing location, to do this we
can use the formula [DestinationLabel]- [StartOfProgramInMainRom]+
[DestinationOfProgramInSPC700Ram] |
s_call_addr
SoundCallResume-SoundProgram+SndPrgMemLoc |
| |
Buy my Assembly programming book on Amazon in Print or Kindle!



Available worldwide! Search 'ChibiAkumas' on your local Amazon website!
Click here for more info!
Buy my Assembly programming book on Amazon in Print or Kindle!



Available worldwide! Search 'ChibiAkumas' on your local Amazon website!
Click here for more info!
|