Version 4 is here, and it is not a patch. The whole player was rebuilt from the decoder up. The old firmware had a bug hiding in it since day one that made anything but a tiny GIF stutter, tear, or freeze. That bug is gone. The badge now runs 17 to 23 frames per second with zero missed frames, no tearing, and flat memory over a full day of uptime.
If you already have an UltraJewel64 in your console, skip to the upgrade notes. Your SSID changed.
What the UltraJewel64 is
The jewel on the front of a Nintendo 64 is a flat piece of plastic. This replaces it with a 1.69 inch IPS panel that loops animated GIFs, changes them when you shake the console, and hosts its own WiFi access point so you can load new animations from your phone with nothing installed.
I wrote the firmware, designed the 3D printed mount, and worked out the install. Waveshare makes the board. Everything I wrote is now open source.
What changed in v4
| GIF decoder | Replaced entirely. v1.3 used a hand rolled decoder whose file position counter was a 16 bit integer, so it lost its place partway through any file over 32,767 bytes. That is every GIF worth putting on a badge. v4 uses Larry Bank’s AnimatedGIF library, which handles 32 bit offsets, transparency, and all four disposal methods correctly. |
| Artifacts and rainbow edges | Fixed. The old frame canvas was never cleared before the first frame, so every pixel the animation did not cover displayed whatever was left in memory. On a fresh boot that looked fine. On PSRAM it never did. |
| Smoothness | Panel push went from 32.75 ms per frame to 8.2 ms. Decode went from 9.3 ms and jittery to 5.2 ms and rock steady. SPI now runs at 80 MHz, only the part of the screen that changed gets sent, and the whole frame goes out in one transaction, which is what kills the tearing. |
| Shake detection | Rewritten. The old thresholds were unreachable: the gyro was configured for a 64 degree per second range with a trip point down around 3, so typing at the desk set it off and no sensible threshold was even possible. v4 uses the 512 dps range, treats rotation as the main signal, and requires two consecutive samples. Sitting on a desk it reads 4 to 5 dps against a 220 trip. |
| Upload progress bar | Actually works now. It was polling the badge for status, and a badge busy writing your upload cannot answer. It reads the browser’s own upload progress instead. |
| Uploads onto a full badge | Fixed. The free space check was done in unsigned math, so once the badge got full the number wrapped around to about four billion and it happily said yes. If you ever had a file that would not upload for no reason, that was why. |
| Staying alive | New. Watchdog timer, a memory floor before every load, an access point liveness check, recovery from a stuck upload, and a clean restart if the library ever becomes unloadable. The target was 24 hours with no crash and no slowdown, and it holds. |
| Set as default | Works on any file in your library now, not just the one currently playing. |
Downloads
- UltraJewel64 Firmware v4.0.0 (ZIP, 1.4 MB) – ready to flash binaries plus flashing and GIF instructions
- N64 Screen Mount V2 (STL in a ZIP, 14 KB) – print at 100% infill
- Full source on GitHub – PlatformIO project, pinned toolchain, and the complete write up of how the old bug was finally cornered
Older version, kept for reference: UltraJewel64 v1.3. There is no reason to use it.
The firmware ZIP has two files in it:
UltraJewel64-v4.0.0-full.bin– flash this at0x0. Use it on a new board, on a board coming from v1.3, or any time something is behaving strangely. It contains the bootloader, the partition table and the app.UltraJewel64-v4.0.0-app-only.bin– flash this at0x10000. Only for a badge already running v4 that you want to update without disturbing anything.
Neither file contains GIFs. That is deliberate. A blank board formats its own storage on first boot and then waits for you to load whatever you want. An existing badge keeps its library.
What you need
- A Waveshare ESP32-S3-LCD-1.69, V2 board, 16 MB flash and 8 MB PSRAM. Look for
V2QCprinted on the PCB. This model only. Other 1.69 inch boards will not work. - The 3D printed mount, printed at 100% infill.
- A USB C cable that carries data, not just power.
- USB drivers, if Windows does not see the board. Board variants ship different USB chips: Silicon Labs CP210x or WCH CH341SER.
If you have a V1 board, the buzzer is on GPIO 33 instead of 42. Change BUZZER_PIN in src/pin_config.h and build it yourself, or the badge will be silent.
Flashing it
- Install the driver if you need one, and plug the board in.
- Put it in programming mode. Hold BOOT, tap RESET, release BOOT.
- Flash the file. Two ways, pick either:
The easy way, in your browser. Go to esptool.spacehuhn.com in Chrome or Edge, connect to the board, choose UltraJewel64-v4.0.0-full.bin, and set the offset to 0x0. Program.
The command line way, with esptool:
esptool --chip esp32s3 --port COM11 --baud 460800 --before default_reset --after hard_reset write_flash -z 0x0 UltraJewel64-v4.0.0-full.bin
Swap COM11 for your port. If esptool complains about the arguments, you have version 5, which wants hyphens instead of underscores: --before default-reset --after hard-reset write-flash.
First boot takes about five minutes while it formats its 10 MB of storage. There is a warning on screen the whole time. Do not unplug it. It reboots itself when it is done and then shows NO GIFS, which is correct. Now go load some.
Loading GIFs
- On your phone or laptop, join the WiFi network
UltraJewel64, passwordultra64pw. - Open http://192.168.4.1.
- Upload. Delete. Pick a default. Watch the live frame timings if you are curious what your file is costing.
GIF rules:
- 280 x 240 pixels or smaller. Anything bigger is rejected rather than squashed. Smaller files are centered and letterboxed automatically, so there is no need to pad your artwork.
- 1.5 MB per file maximum, up from 1 MB in v1.3.
- 10 MB of storage total, and up to 64 files.
The most common problem by far is a file exported at 240 x 280 when it needs to be 280 x 240. Swap the numbers and re-export.
One thing worth knowing: the badge plays your GIF at the speed the GIF was authored at, a little quicker. If an animation looks choppy on the badge it is almost always choppy in the file, and the fix is more frames and a shorter delay when you export it, not anything in the firmware.
Shake the console to change animation. Give it a real shake, not a nudge. That is on purpose, so bumping the shelf does not shuffle your jewel.
Upgrading from v1.3
Your access point name and password changed. The old firmware announced itself as Zelda64Jewel with the password triforce. v4 uses UltraJewel64 and ultra64pw. If you flash v4 and then go looking for the old network, you will think the badge is dead. It is not.
Flash UltraJewel64-v4.0.0-full.bin at 0x0. Your GIFs survive, since neither image touches the storage partition. If the access point never shows up afterward, see the note below.
If something goes wrong
- No WiFi network at all, and nothing you flash fixes it. The board’s settings partition is corrupt. It happens after enough resets during startup. Run
esptool --chip esp32s3 --port COM11 erase_flash, then flash the full image again. This wipes your GIFs and costs you another five minute format, and it does work. - Screen dark, board otherwise alive. Bad flash. Reflash the full image at
0x0. - NO GIFS on screen. Working as intended on an empty badge. Go upload something.
- It sat on a warning screen for five minutes. That is the first boot format. Let it finish.
- Sparkle or dropped columns on the panel. Rare, and it means your panel does not like 80 MHz. Drop
SPI_HZto60000000insrc/main.cppand rebuild.
Source code
The complete firmware is on GitHub under the MIT license: the PlatformIO project, the pinned toolchain, the hardware notes, the HTTP API, and a full write up of the two bugs that survived three rewrites and the four confident theories that turned out to be wrong. If you have ever lost a weekend to a bug that was not where everyone said it was, that document is for you.
Build it, change it, put it in something that is not an N64. The one thing I ask is below.
About the mount
The STL is for you and your own console. Please do not share the file around, do not print copies for other people, and do not re-sell it in either digital or physical form. If somebody wants one, send them here.
This project has eaten a lot of evenings. If it made your console better and you want to throw something in the jar, there is Ko-fi and Patreon, and I build this stuff live on Twitch. And if you put one in your console, I would love to see it.
