All SDK docs

SD Card & PSRAM

Reading, writing and streaming files on the SD card, loading arcade romsets, and what the 8 MB of PSRAM is good for.

The cartridge's launcher loads your image off the SD card and then steps aside: it serves nothing else. So the SDK carries its own SD driver, built on FatFs: FAT12/16/32 and exFAT, long names, MBR or GPT, read and write.

The API

int      uvm2_sd_init(void);
uint32_t uvm2_sd_read(const char *path, unsigned char *dst, uint32_t max);
uint32_t uvm2_sd_read_from(const char *path, unsigned char *dst, uint32_t max, uint32_t from);
int      uvm2_sd_create(const char *path, const unsigned char *data, uint32_t n);
int      uvm2_sd_overwrite(const char *path, const unsigned char *data, uint32_t n);
int      uvm2_sd_write(const char *path, const unsigned char *data, uint32_t n);
 
extern int uvm2_sd_error;   /* OK / NO_CARD / NO_INIT / NO_FAT / MISSING / TOO_BIG / IO_ERROR */
extern struct uvm2_sd_diag uvm2_sd_diag;

Paths are relative to the root, any depth, long names allowed, matched case-insensitively. uvm2_sd_create and uvm2_sd_write create missing folders and replace an existing file. uvm2_sd_read treats "does not fit in max" as a failure; uvm2_sd_read_from does not.

Streaming a large file

For anything read in slices (audio, captures), open it once and keep reading:

uvm2_sd_file f;
if (uvm2_sd_open("audio/music.pcm", &f)) {     /* 1 = found */
    uint32_t n = uvm2_sd_next(&f, buf, 8192);  /* 0 = end */
    /* ... one slice per frame ... */
    uvm2_sd_close(&f);                         /* optional */
}

Don't use uvm2_sd_read_from in a loop: it re-mounts and seeks from the start on every call, which is O(n²). Loading 1.8 MB of audio that way took minutes. uvm2_sd_next carries on from where the last slice ended.


Things worth knowing

  • Speed. The card runs on the RP2350's SPI0 peripheral at 12.5 MHz, half what the SD spec allows. That's deliberate: nothing checks the CRC yet, so a corrupt byte would look like a corrupt file system. In practice FatFs plus writing into PSRAM gives about 700 KB/s.
  • There is no card-detect pin. "No card" and "did not start" are told apart by uvm2_sd_error.
  • Every call mounts afresh, which is the only way a swapped card is noticed. The exception is while a file is open through uvm2_sd_open, because a remount would invalidate it. A streaming game can still read or save other files in between.
  • uvm2_sd_diag says what was mounted: fs_type (2 FAT16, 3 FAT32, 4 exFAT), the raw FatFs result behind the last error, and reads/writes block counters. If those don't move, the card was never touched.
  • IO_ERROR is its own code. A block that fails mid-file returns an error, not a silently short read.
  • Large cards come formatted exFAT and are fully supported.
  • The console's calibration lives on the card in config/uvm2.cfg. See Calibration.

Debug Cart: its BIOS can only overwrite existing files, not create them. Files it writes (config/uvm2.cfg, DEBUG/LIST.BIN) must already exist on the card at the right size.


Romsets

For arcade ports, uvm2_romzip.c reads roms/<game>.zip from the card into a buffer, and the unzipper extracts members by name and checks the CRC-32 the zip itself carries. A wrong ROM says so instead of breaking halfway through a game.

The buffer size is derived from the zip's size at build time; you don't write it in the Makefile. If the romset is missing or too big, the game paints the failure and the path it tried, and keeps painting it. It never emulates over zeros, which would draw nothing and look exactly like a hang. See Porting an AAE game.


PSRAM

The UVMC2 has 8 MB of PSRAM on the QSPI bus that the launcher firmware doesn't map. uvm2_psram_init() resets the chip, checks its ID and maps it at 0x11000000.

int  uvm2_psram_init(void);               /* 1 if present */
#define UVM2_PSRAM_NO_CACHE 0x15000000u   /* uncached alias: verify through THIS */

Three things to know before using it:

  1. Writes are discarded silently unless the window is marked writable (uvm2_psram_init does that). Silent is the worst failure mode there is.
  2. Verify through the uncached alias, 0x15000000. The XIP cache is 16 KB: a 64 KB buffer verifies fine right after it's written (hot cache) and reads back wrong later.
  3. Use it for write-once, read-once data: a romset, sampled audio, a staging buffer. Don't use it for anything timing-critical. The command list stays in SRAM for exactly that reason.

Build options that use it:

variabledoes
UVM2_ROMZIP_IN_PSRAMputs the romset buffer in PSRAM (on by default when a zip is found)
UVM2_CMDS_IN_PSRAMputs the command list in PSRAM: frees SRAM but costs determinism

Remember the ceiling the PSRAM is relieving: an image loaded by the menu has about 496 KB of SRAM for code, data and the command list together.