A port of Doom to the RP2350. The reference board is the Adafruit Fruit Jam (RP2350B); three more HSTX boards build from the same tree — Pico 2 with the Adafruit DVI Breakout, the Murmulator M2, and the Adafruit Feather RP2350 (see Other supported boards).
It runs the full shareware DOOM1.WAD from flash, drives an HDMI display over
HSTX, plays music and sound effects — over that same HDMI link or through an
I2S DAC (TLV320DAC3100 or PCM5100A) to speaker/headphones — and takes input from
USB keyboards, mice, and gamepads, plus NES/SNES pads on the boards that have
those ports.
The port descends from Graham Sanderson's
rp2040-doom — which fits Doom into
a Raspberry Pi Pico — which is in turn derived from
Chocolate Doom. For the deep
technical story of the RP2xxx port (WAD compression, memory tricks, the
whd_gen tool) see README.RP2040.md; for the upstream engine
see README-chocolate.md.
You do not have to build anything. Every
release ships ready-to-flash .uf2 files for all four boards,
plus the converted shareware WAD and the whd_gen host tool. Grab the newest
one and you are playing in a couple of minutes.
-
Download two files from the latest release — the firmware for your board, and
doom1-whx.uf2(the sharewareDOOM1.WAD, already converted). The WAD file is the same for every board.Board Firmware WAD data Adafruit Fruit Jam doom_tiny_fruitjam.uf2doom1-whx.uf2Pico 2 / Pico Plus 2 + Adafruit DVI breakout doom_tiny_adafruitdvisd.uf2doom1-whx.uf2Murmulator M2 (RP2350) doom_tiny_murmulatorm2.uf2doom1-whx.uf2Adafruit Feather RP2350 + TLV320 breakout doom_tiny_featherrp2350.uf2doom1-whx.uf2 -
Plug in your peripherals first — HDMI display, and a USB keyboard, mouse or gamepad. USB hot-plug is not reliable, so the devices have to be connected before the game starts. Optionally insert a FAT32/exFAT microSD card; it is what saved games and settings are written to. The game runs fine without one.
-
Flash both files. Hold BOOTSEL while plugging the board into your computer's USB, so it appears as an
RPI-RP2drive, and drag the firmware.uf2onto it. The board reboots as soon as the copy finishes, so re-enter BOOTSEL and dragdoom1-whx.uf2across as a second step. Order does not matter; both files must be flashed. Once the game is on there, Options → Bootsel Mode gets you back to that drive without unplugging anything. -
Reset the board. Doom starts on the HDMI output, with sound over HDMI and over the board's I2S DAC or headphone jack.
Two variants of the firmware are attached to each release:
doom_tiny_<board>.uf2— the shareware episode, from flash. Pair it withdoom1-whx.uf2; this is what the table above describes.doom_tiny_<board>_full.uf2— plays a registered/Ultimatedoom.wadoff the SD card instead, so all episodes are available. Convert your own WAD with thewhd_genbinary from the same release (whd_gen doom.wad doom.whd -no-super-tiny), copy the result to the card as/roms/doom/doom.whd, and flash only this file — nodoom1-whx.uf2alongside it. Requires PSRAM on the board; see Full game from SD card.
The releases here are for flashing a board directly over USB. If you run the resident pico-bootLoader menu, take Doom's files from that project's release instead — see Running under the pico-bootLoader.
The rest of this README covers building from source, converting WADs, and the per-board details.
Only the hardware is needed to run a prebuilt release; the build host is for compiling from source.
Hardware
- An Adafruit Fruit Jam (RP2350B) board — or one of the three other supported boards.
- An HDMI display and cable.
- At least one USB input device — a keyboard, a mouse, and/or a supported gamepad (see Controls).
- Optional: headphones or a speaker (audio also comes out over HDMI).
- A microSD card (FAT32/exFAT) for saved games and settings. The game runs without one; you just cannot save anything. It is also what the pico-bootLoader route and the full-game build need.
Build host (Linux or macOS)
-
arm-none-eabi-gcc— the CI builds with 13.2.Rel1; other versions may work but binary size is tight, so mismatches can cause link failures. -
cmakeandmake. -
picotool2.2.0 or newer on yourPATH(the build falls back to fetching and building it, but a system install is much faster). -
The Pico SDK, pico-extras and Pico-PIO-USB, pointed at by environment variables — they are not submodules of this repository:
export PICO_SDK_PATH=$HOME/pico/pico-sdk export PICO_EXTRAS_PATH=$HOME/pico/pico-extras export PICO_PIO_USB_PATH=$HOME/pico/Pico-PIO-USB # PIO-USB boards only
The SDK must have its own submodules checked out (
git -C $PICO_SDK_PATH submodule update --init): tinyusb comes from the SDK'slib/tinyusb, not from this repository.PICO_PIO_USB_PATHis only needed by the boards whose USB host runs on PIO — the Fruit Jam and the Feather RP2350; the Pico 2 and Murmulator M2 builds use native USB and pass-DENABLE_PIO_USB=0. Every build script validates these paths up front via pico-env.sh and stops with a clear message if one is wrong. -
tinyusb newer than the 0.20.0 tag, inside that SDK. SDK 2.2.0 pins 0.18.0, and the
0.20.0tag is also too old — this is a post-release master requirement, not a version number you can ask for. The vendored XInput host driver needsusbh_class_driver_t.opento returnuint16_t(bytes of descriptor consumed) rather thanbool, which landed in tinyusb commitef018e364(2025-12-11,0.20.0-103). Anything older warns-Wincompatible-pointer-typesin3rdparty/tusb_xinput/xinput_host.cand claims interfaces by a different rule. These builds use7f146c9ff(0.20.0-843). Check and bump it in place:git -C $PICO_SDK_PATH/lib/tinyusb describe --tags # want 0.20.0-103 or later git -C $PICO_SDK_PATH/lib/tinyusb fetch origin git -C $PICO_SDK_PATH/lib/tinyusb checkout 7f146c9ff # known good
Note that re-running
git -C $PICO_SDK_PATH submodule update --initafterwards resetslib/tinyusbto the SDK's pinned 0.18.0 and undoes this.
git submodule update --init # tusb_xinput + the shared driver library
./fruitjam-build.shThis produces two files:
build_fruitjam/src/doom_tiny.uf2— the game.build_fruitjam/src/doom1-whx.uf2— the shareware WAD data.
Put the Fruit Jam into BOOTSEL mode and copy both .uf2 files to it over
USB. The device reboots after the first file, so you may need to re-enter the
bootloader before copying the second one. If a build is already running on the
board, Options → Bootsel Mode puts it back into BOOTSEL from the game, which
saves reaching for the cable on every rebuild.
There is a single doom_tiny.uf2 that supports all input devices (USB
keyboard, mouse, and gamepads). The old per-input variants
(doom_tiny_usb, doom_tiny_nost, doom_tiny_nost_usb) no longer exist. USB
input is handled by the
pico_shared HID driver, shared
with the pico-infonesPlus family of emulators.
To run Doom under the resident pico-bootLoader — the menu that lives on an SD card and flashes/launches emulators — build with:
./fruitjam-build-forbootloader.shThis links doom_tiny into the bootloader's application partition (and places
the WAD data further up in flash — see Flash map). Outputs land in
a separate tree so the two builds don't clobber each other:
build_bl_fruitjam/src/doom_tiny.uf2build_bl_fruitjam/src/doom1-whx.uf2
Do not drag these onto the Fruit Jam over USB. Instead place both files on
the pico-bootLoader SD card under /emu/<hwconfig>/ and let the menu flash them
(Doom's main image plus its WHX companion). Every board's script emits the WAD
under the fixed name doom1-whx.uf2, which is the name the bootloader looks up;
the per-config SD folders keep the different boards' copies from colliding. See
the pico-bootLoader README for the SD-card layout and emulators.txt.
Selecting Quit Game (or pressing F10 and confirming) does one of two things, decided at runtime — the same binary behaves correctly either way:
- Launched from the pico-bootLoader — the ENDOOM screen is skipped, the quit
sound is allowed to finish, and the board resets back into the emulator
picker. Doom explicitly asks the loader to skip its resume jump (watchdog
scratch register 7, the same handshake as
pico_shared'sFrens::rebootToBootloader()), so you get the picker and not a relaunch. - Standalone — the ENDOOM screen appears with a fake DOS prompt already on
it. With a keyboard,
DIR,CLSandCDwork, and typingDOOM(orDOOM.EXE) and pressing Return restarts the game. With only a gamepad, Start does the same thing.
The UART reports which path was taken. Detection is
watchdog_hw->scratch[6] == 0xB007ED01, set by the loader just before it jumps
to the application — a cold reset or a BOOTSEL flash clears it, so a
BOOTSEL-flashed image correctly counts as standalone.
The exit screen is a true 80×25 VGA text mode: 8×16 glyphs, the full CP437 set and the 16-colour attribute byte including blink, rendered as 640×400 centred in the 640×480 HDMI signal.
Options → Bootsel Mode resets the board into the RP2350's own ROM
bootloader, so it comes back as the RPI-RP2 drive ready for a new .uf2.
It saves unplugging the board and holding the physical BOOTSEL button every
time you want to reflash, which adds up while developing. The equivalent item
in pico-infonesPlus'
settings menu does the same thing.
This is not the same as Quit Game above: quitting is a warm watchdog reset that lands back in the pico-bootLoader picker (or at the DOS prompt when standalone), whereas this hands the chip to the ROM and leaves the game entirely. There is no confirmation prompt — the reset happens the moment the item is picked — which is why it sits at the bottom of the menu, furthest from where the cursor starts. Settings are flushed to the SD card on the way out, so anything you just changed in the Options menu survives the reflash.
To get out again, either copy a .uf2 across, or power-cycle the board.
The default build embeds the shareware episode in flash. The full variant
instead loads a complete registered/Ultimate doom.wad (all episodes, built
with WHD_SUPER_TINY=0) from the SD card into PSRAM at boot and plays it
from there — the in-game New Game → Episode menu then offers every episode,
and the Episode 3 bunny finale works. Saved games
land on the same card and are keyed to the WAD, so saves made with a different
WAD are refused rather than corrupted.
-
Convert your registered/Ultimate
doom.wad(input first, output second):build/src/whd_gen/whd_gen doom.wad doom.whd -no-super-tiny
(See Converting a different WAD for building
whd_gen.) -
Copy
doom.whdto the SD card as/roms/doom/doom.whd(FAT32/exFAT). -
Build and flash the game (standalone or bootloader — no WHX uf2 involved):
./fruitjam-build-full.sh # → build_full_fruitjam/src/doom_tiny_full.uf2 ./fruitjam-build-full-forbootloader.sh # → build_bl_full_fruitjam/src/doom_tiny_full.uf2
Every board has the same script pair (
adafruitdvisd-build-full.sh, …).
At boot the game initializes the PSRAM (QMI CS1, mapped at 0x11000000),
mounts the SD card over SPI and copies the WHD into PSRAM (~2 s), logging
progress on the UART. Any failure — no PSRAM, no card, missing or non-WHD
file — panics with a descriptive UART message.
PSRAM requirement per board:
| Board | PSRAM |
|---|---|
Fruit Jam (fruitjam) |
onboard 8 MB |
Pico 2 + DVI (adafruitdvisd) |
fit a Pimoroni Pico Plus 2 (8 MB PSRAM on GPIO 47) instead of a stock Pico 2 |
Murmulator M2 (murmulatorm2) |
onboard (GPIO 8) |
Feather RP2350 (featherrp2350) |
external APS6404 wired to GPIO 8 |
Both live on the microSD card, on every board and every build:
| What | Where |
|---|---|
| Saved games | /SAVES/doomsav0.dsg … /SAVES/doomsav5.dsg (the six menu slots) |
| Settings | /settings_DOOM.dat |
The /SAVES directory is created on the first save. The naming follows
pico-infonesPlus, so one card
can hold the saves of both without colliding.
Saved games are the compressed format the port has always used, and are still keyed to the WAD — a save made with a different WAD is refused rather than loaded as garbage.
The settings file covers everything this build can actually change: SFX and music volume, messages on/off, screen size, mouse sensitivity, gamma (F11), the FPS overlay (\) and the NES pad layout. Changes are written when you leave the menus, when you quit, and on the way into Bootsel Mode, so gamma or screen size changed with a hotkey mid-game is stored the next time you open and close the menu. A missing or damaged file is ignored and the built-in defaults are used; a file from an older version is read for the settings it does have.
Without a card the game runs normally — the load menu is empty, saving reports the reason, and settings reset at power-off. Insert a card and it starts working within a few seconds; no reset needed.
Nothing is written to flash any more, so the map is entirely static. The build
scripts flash the WAD data (doom1.whx) to a fixed address that
depends on which build you ran. These values come from TINY_WAD_ADDR in
fruitjam_cflags.h and the two build scripts:
| Build | doom_tiny image |
WHX (WAD) data |
|---|---|---|
Standalone (fruitjam-build.sh) |
0x10000000 |
0x10080000 |
Bootloader (fruitjam-build-forbootloader.sh) |
0x10080000 |
0x10400000 |
These addresses are for the Fruit Jam. The other boards share the standalone map
(WHX at 0x10080000); their bootloader maps differ — the 4 MB Pico 2 boards
(adafruitdvisd, murmulatorm2) place the WHX at 0x10200000. See
Other supported boards.
doom1.whx (the compressed shareware WAD) is bundled in this repository, so you
don't need to generate anything for the default build. To run a different WAD you
convert it to the RP2350 WHX/WHD format with the whd_gen host tool.
Every release attaches ready-to-run whd_gen binaries. They are
statically linked — nothing to install, no DLLs, no glibc version to match:
| File | Platform |
|---|---|
whd_gen-win64.exe |
64-bit Windows |
whd_gen-linux-x64 |
x86_64 Linux |
whd_gen-linux-arm64 |
arm64 Linux (Raspberry Pi OS 64-bit) |
On Linux, make it executable first (chmod +x whd_gen-linux-*). The two
conversions that matter, and which firmware each one pairs with:
# WAD lives in flash — pairs with doom_tiny_<board>.uf2
whd_gen DOOM1.WAD doom1.whx
# WAD lives on the SD card as /roms/doom/doom.whd — pairs with doom_tiny_<board>_full.uf2
whd_gen doom.wad doom.whd -no-super-tinyThere is no prebuilt binary for 32-bit Windows or macOS; build from source below
(src/whd_gen/build_native.sh needs only a C/C++ compiler). Output is
byte-identical whichever platform produced the binary.
whd_gen needs none of the game's runtime dependencies — no SDL, no pico-sdk,
just a C/C++ toolchain and CMake. Build only that target from a native build
directory:
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make whd_genThe binary lands at build/src/whd_gen/whd_gen. cmake .. configures even
without the SDL2 packages installed — it just skips the desktop chocolate-doom
build. Use a release build: the debug build deliberately lowers sound-effect
encoding quality for speed.
Then convert a WAD (input first, output second):
# super-compressed WHX — needed to fit DOOM1.WAD on a 2 MB board:
build/src/whd_gen/whd_gen DOOM1.WAD doom1.whx
# larger WADs (Ultimate Doom, Doom II) on 8 MB boards — less compression:
build/src/whd_gen/whd_gen DOOM2.WAD doom2.whd -no-super-tinysrc/whd_gen/build_native.sh builds whd_gen
without CMake, one target per invocation:
src/whd_gen/build_native.sh # -> src/whd_gen/whd_gen (dynamic, for dev)
src/whd_gen/build_native.sh linux-x64 # -> whd_gen-linux-x64 (static)
src/whd_gen/build_native.sh linux-arm64 # -> whd_gen-linux-arm64 (static)
src/whd_gen/build_native.sh win64 # -> whd_gen-win64.exe (static)
src/whd_gen/build_native.sh win32 # -> whd_gen-win32.exe (static)The Windows targets need MinGW-w64 (sudo apt install g++-mingw-w64-x86-64, or
g++-mingw-w64-i686 for 32-bit). The .exe is statically linked: copy it to any
Windows machine and run it from a Command Prompt, no DLLs required. On Windows
itself the same script works in an MSYS2 MinGW shell.
For linux-arm64 you need an aarch64 cross-compiler. Do not
apt install g++-aarch64-linux-gnu — on Ubuntu 24.04 that package
Breaks/Replaces gcc-multilib/g++-multilib, so apt removes them and any -m32
build on the machine stops working. Use a standalone toolchain tarball, such as
the ARM GNU Toolchain aarch64-none-linux-gnu build, and point the script at it
(note the -none- in the prefix):
CC=/opt/arm-gnu/bin/aarch64-none-linux-gnu-gcc \
CXX=/opt/arm-gnu/bin/aarch64-none-linux-gnu-g++ \
src/whd_gen/build_native.sh linux-arm64For the deeper story (WHX vs WHD, caveats about non-id WADs) see README.RP2040.md.
Releases are built by
.github/workflows/BuildAndRelease.yml on
a self-hosted runner (a Raspberry Pi 5) when a v* tag is pushed. The workflow is
a thin wrapper: everything it does is ./buildAll.sh and ./release-notes.sh, so
running those two scripts locally is testing the pipeline.
./buildAll.sh # fills releases/ -- exactly what CI runs
./release-notes.sh v1.0 # writes release-notes.md, the release bodybuildAll.sh builds the standalone and SD-card variant for all four boards,
converts doom1.whx at the standalone flash offset, and copies the prebuilt
whd_gen binaries in — 12 files in all. The pico-bootLoader variants are not
released here: they are built in the
pico-bootLoader repository,
which invokes <board>-build-forbootloader.sh itself and produces its own WHX at
the offset matching each board's flash map.
The runner needs PICO_SDK_PATH, PICO_EXTRAS_PATH and PICO_PIO_USB_PATH (set
in the workflow to /datalocal/pico/...), plus picotool ≥ 2.2.0 on PATH.
The runner is ARM64 and never compiles whd_gen; it only copies
prebuilt/whd_gen/* into releases/. Those binaries are cross-built on an
x86_64 Linux box and committed:
./update-prebuilt-whd_gen.sh # linux-x64, linux-arm64, win64
git add prebuilt/whd_gen && git commitDo this whenever anything whd_gen compiles changes (src/whd_gen/,
src/tiny_huff.c, src/musx_decoder.c, src/image_decoder.c, src/adpcm-xq/).
buildAll.sh compares the commit recorded in prebuilt/whd_gen/VERSIONS.txt
against those paths and prints a warning if the binaries have fallen behind — a
warning rather than an error, so an unrelated commit cannot block a release.
- Add a
## vX.Ysection to CHANGELOG.md — the heading text must be exactly the tag, sincerelease-notes.shextracts that section. - Commit, then dry-run on the runner without publishing anything:
gh workflow run BuildAndRelease.yml(leave thetaginput empty). git tag vX.Y && git push origin vX.Y.
Besides the Fruit Jam, three more RP2350 HSTX boards from the
pico_shared family are
supported. Each has its own pin header (<tag>_cflags.h) and script pair —
<tag>-build.sh (standalone) and <tag>-build-forbootloader.sh
(pico-bootLoader) — producing build_<tag>/src/doom_tiny.uf2 and
build_<tag>/src/doom1-whx.uf2 (bootloader builds use
build_bl_<tag>/).
| Tag | HW_CONFIG | Board | USB host | Audio | Legacy pads |
|---|---|---|---|---|---|
adafruitdvisd |
2 | Pico 2 + Adafruit DVI Breakout + SD breakout (breadboard or PCB) | native USB (OTG adapter) | HDMI + optional PCM5100A on GPIO 26/27 | 2× NES/SNES |
murmulatorm2 |
13 | Murmulator M2 (Pico 2 module) | native USB (OTG adapter) | HDMI + PCM5100A | 2× NES/SNES, Wii (GPIO 0/1) |
featherrp2350 |
14 | Adafruit Feather RP2350 + TLV320DAC3100 breakout | PIO-USB on GPIO 24/25 (USB Host FeatherWing) | HDMI + TLV320 | Wii (STEMMA QT, GPIO 2/3) |
The Fruit Jam itself has no NES/SNES ports, but does have a Wii extension port on its STEMMA QT connector (GPIO 20/21).
Notes:
- USB stack — the Fruit Jam and Feather run the USB host on Pico-PIO-USB;
the other two use the RP2350's native USB controller (plug gamepads in via
an OTG adapter). The transport is fixed at build time: the board header's
HAS_USBPIOdefine and the script's-DENABLE_PIO_USB=value must agree. - Audio — boards without the Fruit Jam's headphone-detect pin play audio on HDMI and the I2S DAC simultaneously instead of switching sinks.
- NES/SNES controllers — configs 2 and 13 poll two legacy controller
ports through the vendored pico_shared
nespadPIO driver; SNES pads are auto-detected. They use the same layout as the USB pads (see Gamepad below), including the optional NES pad layout. - Wii extension port — configs 8, 13 and 14 additionally read a Wii
extension connector over I2C through the vendored pico_shared
wiipaddriver (see Wii pads below). On the Fruit Jam and Feather that bus is shared with the TLV320 codec, so the pad is brought up first, before the codec — an uninitialized pad on the bus would otherwise make every DAC register access time out. - Flash size for bootloader builds — the bootloader map is sized per board.
Fruit Jam (16 MB) and Feather (8 MB) place the WHX at
0x10400000. The two Pico 2 boards (adafruitdvisd,murmulatorm2) cap the map to the 4 MB chip: a 1.5 MB app slot at0x10080000and the WHX at0x10200000(ending ~0x103B7900, comfortably under the 4 MB mark), so their bootloader builds run on a genuine 4 MB Pico 2 as well as larger clone modules. Standalone builds (WHX at0x10080000) fit every board. - Video jitter trade-off — on the native-USB boards
clk_hstxis derived from PLL_SYS (PLL_USB must stay at 48 MHz for the USB controller), so very strict HDMI sinks may show occasional sparkles; the PIO-USB boards keep the dedicated 126 MHz PLL_USB HSTX clock.
Pin assignments live in fruitjam_cflags.h.
- Video — HDMI/DVI output over HSTX on GPIO 13/15/17/19
(
GPIOHSTXCK/D0/D1/D2). Rendered at Doom's classic resolution and upscaled by the pico_sharedpico_hdmidriver. - Audio — soundtrack and effects over I2S to the onboard TLV320DAC3100 codec (speaker and headphone), plus HDMI data-island audio at 48 kHz. The headphone jack is auto-detected by the driver.
- Input — USB host (keyboard, mouse, gamepad). Hot-plug is not reliable: plug your devices in first, then reset. Also a Wii extension pad on the STEMMA QT connector (GPIO 20/21, shared with the codec's I2C bus) — that one is hot-pluggable, see Wii pads.
- Debug — UART on GPIO 44 (TX) / 45 (RX), 115200 baud.
- Clocking — the RP2350 is overclocked to 378 MHz at 1.60 V. The extra CPU headroom fixes the in-game audio slowdown.
Hold a button at power-on to disable audio:
- btn3 — disable music.
- btn2 — disable music and sound effects.
The input mapping is fixed at build time.
Any "boot keyboard" should work.
| Key | Action |
|---|---|
| W A S D | Move (not the arrow keys!) |
| Q E | Turn left / right |
| R F | Cycle weapons |
| Ctrl | Attack |
| Space | Open / activate |
| Shift | Run |
Any "boot mouse" should work.
| Input | Action |
|---|---|
| Move X | Turn |
| Move Y | Forward / back |
| Left button | Attack |
| Right button | Hold to strafe with X movement; also open / activate |
| Wheel | Should switch next/previous weapon (didn't work on the Logitech wheel mice tested) |
Supported controllers:
- Original SNES/NES gamepads.
- SNES/NES "MantaPad" clones, e.g. Adafruit #6285
- Sony DualShock 4, DualSense, PlayStation Classic.
- Xbox 360 / One / Series (XInput), and 8BitDo pads in XInput mode.
- Sega Genesis / Mega Drive Mini, Retro-bit MD Arcade Pad.
- Wii extension pads over I2C: NES Classic Mini, SNES Classic Mini, Wii Classic Controller (Pro) — see Wii pads.
Buttons use SNES naming. On PlayStation pads: triangle = X, square = Y. On Xbox pads: Y = X, X = Y.
| Control | Action |
|---|---|
| D-pad / left stick | Move and turn |
| X | Fire |
| Y | Open / activate |
| A | Hold to turn D-pad rotation into strafing |
| B | Run |
| L / Select | Previous weapon |
| R | Next weapon |
| Start | Open / close the menu |
In menus, A or X selects and B or Y goes back, so the button you reach for first works either way. Start opens and closes the menu, so a keyboard is no longer needed to get into the options.
The shoulder buttons cycle weapons rather than strafing directly; strafe by holding A and pushing left/right, which is how vanilla Doom's strafe button works. A plain NES pad has no shoulders, so there Start opens the menu and Select is the only weapon cycle — it still reaches every weapon.
The layout above wants six buttons and two shoulders. A plain NES controller has four buttons, and with the default layout it ends up without a Use button, so you cannot open a door. Options → Nes Pad switches every pad — the legacy ports and USB alike — to a layout built for those four buttons:
| Control | In game | In a menu |
|---|---|---|
| D-pad | Move and turn | Navigate |
| A | Fire | Select |
| B | Hold to strafe with the D-pad | Back |
| Start | Open / activate | — |
| Select | Next weapon | — |
| Select + Start | Open / close the menu | Open / close the menu |
There is no run button left, so the marine always runs while this is on. The setting is stored on the SD card, and you can reach it with the pad alone: in the default layout Start opens the menu, the D-pad moves and A selects.
It suits the NES-shelled MantaPad and an original NES controller; a SNES pad keeps working, with B/A as NES A and Y/X as NES B.
Boards with a Wii extension connector (Fruit Jam and Feather on their STEMMA QT port, Murmulator M2 on GPIO 0/1 — see Other supported boards) also take a NES Classic Mini, SNES Classic Mini or Wii Classic Controller (Pro) pad, on an adapter such as the Adafruit Wii Nunchuck Adapter #4836. It merges into the same joystick event as everything else, so it works alongside a USB pad and the legacy ports, and it obeys the NES pad layout setting above.
A NES Classic Mini behaves exactly like an original NES controller: A fires, B runs, Start opens the menu, Select cycles weapons. On a pad with the full set of buttons:
| Control | Action |
|---|---|
| D-pad | Move and turn |
| A | Fire |
| B | Run |
| X | Hold to turn D-pad rotation into strafing |
| Y | Open / activate |
| L / Select | Previous weapon |
| R | Next weapon |
| Start | Open / close the menu |
Note that this is not the same face-button arrangement a real SNES pad gets on the legacy ports (there B fires and Y runs). The two Classic Mini pads share the Wii Classic Controller Pro protocol and identity block, so there is no way to tell a NES Classic from a SNES Classic on the wire — only one of them can match its legacy-port equivalent, and matching the NES pad was chosen.
The pad is polled at ~100 Hz and is hot-pluggable: plug one in at any time and it is picked up within a second (unlike USB, see Known issues).
- USB hot-plug is not reliable: plug your devices in first, then reset.
Would be nice: PICO_NET support for deathmatch.