21fx Programming Guide

Register Interface

$21f0  command port [rw]
  00 = set data port address (wait for data busy flag to be cleared after using this command)
    SR[3-0] = address
  01 = set audio track# (wait for audio busy flag to be cleared after using this command)
    SR[1-0] = track#
  02 = set volume level
    SR[1] = left volume (linear: 0 = 0%, 255 = 100%)
    SR[0] = right volume
  03 = play
    SR[0].d1 = pause
    SR[0].d0 = repeat
  d7 = data port busy
  d6 = audio port busy
  d5 = audio playing
  d4 = reserved (currently 0)
  d3-d0 = version# (currently 1)

$21f1  shift register port [w]
  writes to this register perform SR = (SR << 8) | data

$21f2  data port [r]
  reads from this register fetch from the data source and auto-increment the source address

Reading Data

//stream from 21fx data offset $00000001, and wait until it is ready to read
lda #$01; sta $21f1
lda #$00; sta $21f0
-; lda $21f0; bmi -

//copy four bytes into save RAM
lda $21f2; sta $700000
lda $21f2; sta $700001
lda $21f2; sta $700002
lda $21f2; sta $700003

Playing Video

The idea is to use the SNES' native hardware to actually play the video. What you want to do is set Mode 3, which gives us a 256-color background layer. From here, set the palette up such that it represents an RGB332 table. Direct color mode is a bad choice, as the low bits are clamped, making whites appear slightly gray. You can also transfer your own palette data, if you like.

From here, rely on the BG12NBA register. Write the next video frame over the next 2-3 frames to the top half of VRAM, then set the NBA address to point to the top half. Once you've done that, start writing to the bottom half of VRAM, then set the NBA address to point to the bottom half. It's essentially double buffering.

To transfer the data, you will want to use DMA. There are 1324 usable clock cycles per scanline, and there are 262 scanlines per frame. You can write to video RAM whenever the screen is off (you can force the screen off using $2100.d7 even during scanlines 0-224), and you can transfer from the 21fx device while the screen is on.

Each DMA byte transfer takes exactly 8 clock cycles, so if your video was at 240x160, that would give you 102 scanlines with which to write to video RAM. That works out to 16,881 bytes that can be transferred per frame. Please keep some room for DMA setup overhead. Now consider that 240x160 at 8-bits-per-pixel works out to 38,400 bytes per video frame. So if you split the transfer over three frames, you can achieve 240x160 at 20fps.

Undoubtedly the hardest part of playing video is simply getting a source video into 8bpp tiledata, encoded at RGB332. For now, I'm afraid you're on your own, but I do hope to eventually offer tools to assist with this.

One last bit of advice, you'll want to use the cleanest video feed possible. Remember that this device does not use lossy compression, so it would be foolish to not take advantage of that. Of course, sometimes you have no choice, if the video you want was lossy to begin with. Consider at least using a despeckle or smoothing filter over the video at least in that case.

Playing Audio

//select track# 0, and wait until it is ready to play
lda #$00; sta $21f1
lda #$01; sta $21f0
-; lda $21f0; bvs -

//set volume to 100% for both left and right channels
lda #$ff; sta $21f1
lda #$ff; sta $21f1
lda #$02; sta $21f0

//play track once (no repeat)
lda #$00; sta $21f1
lda #$03; sta $21f0

//wait until the track has finished playing
-; lda $21f0; and #$20; bne -

//select track# 1, and wait until it is ready to play
lda #$01; sta $21f1
lda #$01; sta $21f0
-; lda $21f0; bvs -

//play track forever (repeat)
lda #$01; sta $21f1
lda #$03; sta $21f0

Backwards Compatibility

It is easy to detect the 21fx device, and avoid using it if not found; to preserve backwards-compatibility with existing hardware and emulators.

lda $0021f0
bne 21fx_device_detected

Hardware Caveats

While the final hardware design is not set in stone, the software 21fx implementation is overly restrictive. You should keep these points in mind when programming for 21fx:

1) high latency: don't assume you can set the data port address or audio track number and immediately begin reading data or playing audio. Always wait for the respective busy bits to be cleared, and always assume it will take at least a few milliseconds. To stream video and audio at the same time, set both and then wait for both to be ready. It is hoped that the hardware version will have fast seek memory, hopefully fast enough that checking the ready bits won't even be necessary, but we shall see.

2) B-bus limitation: you can't DMA transfer from $0021f2 to $2118, or in other words, you cannot transfer directly from the data device into S-PPU video RAM. You will need to transfer from $21f2 to WRAM, and then from WRAM to video RAM. This is a limitation of using a base unit. The base unit connector can only access the B-bus, and the same is true for S-PPU video RAM.
Video playback resolution is not affected by this, as you can transfer from the 21fx device to WRAM during active display, and then from WRAM to video RAM during the blanking period.
If you are modifying an existing game and do not have WRAM available to write to, consider increasing the amount of save RAM used by the game.
This is another limitation that can hopefully be removed. If the hardware version is implemented as a cartridge or passthru, it can monitor the A-bus for $21fx accesses, which will allow direct data -> VRAM transfers.

Data Files

bsnes currently looks for the data file as 21fx.bin in the current ROM directory. It looks for audio tracks as audio00000.wav - audio65535.wav. The WAV format must be 44.1KHz 16-bit signed stereo PCM. This is subject to change in the future.

To stream video, simply use Mode 3 and page flipping on the tiledata address. Combine with RGB332 and downsample to 30fps @ 224x144, 20fps @ 240x160, etc. Store the video data uncompressed so that DMA can be used for fast transfers.