Saucelet progress

· 9 min read

By Phil · building Legitsauce

A progress update on the Saucelet, the game machine for your keychain.

Until recently a Raspberry Pi drove the prototype screen on my bench. Now the chip that goes in the base Saucelet drives it: an RP2350 on a Seeed XIAO board, wired through a breadboard to the same 1.3 inch, 240 by 240 screen. It has two cores at 150 MHz and 520 KB of RAM, and no operating system.

I've been running small tech demos on it. Each one tests one part of the chip and measures itself while it runs. Here's where things stand.

About the photos

The photos below come from a small camera pointed at the breadboard. The screen has a clear protective cover, and the round white blobs in the photos are reflections of lights on that cover, not part of the picture. The camera also struggles with dark 3D scenes. So each photo sits next to a clean frame of the same demo:

  • For the 3D scene, the clean frame was read straight back from the XIAO over USB. It's exactly what the chip drew.
  • For the other demos, the clean frame comes from my PC model of the same drawing code. When the same code ran on a second chip, the ESP32-P4, its frames matched the PC model pixel for pixel, so this is the picture the chip draws.

60 FPS on a breadboard

Fourteen demos have run on the XIAO so far. Twelve of them run at 60 FPS: all six 2D demos, the first five 3D ones and Mini Warlord. In practice that's about 60.3 frames a second, because the screen refreshes 60.3 times a second and every refresh gets a new frame. The sixth 3D demo runs in a 45 Hz mode, which I'll come back to. The second game demo, Mini TPS, doesn't hit its target yet.

Mode 7

The mode 7 demo is a kart on a tilted floor that turns and scrolls toward the horizon, the classic look of old racing games. Every pixel of the ground needs a texture lookup at a position that shifts across the row, which adds up fast.

The RP2350 has a small piece of hardware for exactly this, called an interpolator. Each core has two. Given a starting point and a step, an interpolator hands back the address of the next texture pixel, wrap-around included, in a single read. My cycle model puts the ground at about 5.5 cycles a pixel with the interpolators, against 85 in plain C.

Measured on the chip, a frame takes 1.8 milliseconds on one core and 1.45 on the other. The same picture drawn in plain C on one core takes 35 milliseconds, which would be 21 FPS.

Two square frames of a yellow kart on a grey track curving right across green grass under a blue sky with red balloons: a blurry camera photo of the XIAO's screen on the left and a clean frame on the right
The mode 7 demo at 60 FPS. Left: the XIAO's screen, photographed. The light blobs are reflections on the screen's cover, not the image. Right: a clean frame from the PC model of the same drawing code. The two show different moments of the same lap.

A small 3D scene

The 3D scene is a short fly-through: Ka'zhurn from Ka'zhurn: The Unbinding at a low level of detail, brick pillars, stacked crates and a tiled floor. About 1,500 triangles go into each frame.

A washed-out camera photo of the screen covered in round reflections next to a clean frame of the horned demon Ka'zhurn holding an axe on a tiled floor, with a brick pillar and stacked crates
The 3D scene. Left: the XIAO's screen, photographed. The light blobs are reflections on the screen's cover, not the image, and the dark scene barely survives the camera. Right: a frame read back from the XIAO over USB, exactly as the chip drew it.

The RP2350 doesn't get this scene to 60 FPS yet. When run at 60 Hz it reaches 55.9 to 58.9 FPS, close but not quite there: the worst frames still take about 22 milliseconds of work, while one refresh lasts 16.6.

So this scene runs in the 45 Hz mode by default. 60 FPS stays the default for every Saucelet game. A heavy scene may drop to 45, and when it does, the screen itself slows to 45 refreshes a second, so every frame stays on screen for the same time and motion stays even. In that mode the scene shows a new frame on every refresh, 45.4 a second. 30 FPS is never allowed.

Closing that last gap is on my list.

The Saucelet menu

This demo is the start of the Saucelet's menus and text: a menu in Open Sans Semibold, Godot's default font, at 10, 12, 16 and 20 pixels, with a focus bar that slides between items.

The screen is drawn in 15 bands of 16 rows, and only the bands that change get redrawn, between 0 and 2 of them per frame here. That costs 0.52 milliseconds a frame on one core and 0.02 on the other. Redrawing the whole screen the plain way takes 9.6 milliseconds. It runs at 60 FPS.

It also taught me something useful. At 10 pixels, capital letters come out only 7 pixels tall, under the 8-pixel minimum I've set for readable text on this screen. Better to learn that from a demo than from a finished game.

A camera photo of the menu on the XIAO's screen, with "New game" highlighted, next to a clean frame of the same menu with Continue, New game, Sound, Text size, Brightness and Credits under the title Saucelet
The Saucelet menu demo. Left: the XIAO's screen, photographed. The light blobs are reflections on the screen's cover, not the image. Right: a clean frame from the PC model, with a different item selected. Font: Open Sans by Steve Matteson, SIL Open Font License.

Mini Warlord

Mini Warlord is the first demo with a game running inside it. It's a small battlefield in the style of Yours Truly, The Warlord: 16 by 16 tiles, the game's own units and buildings, enemies that follow a flow field toward your town centre, arrow towers firing homing arrows, and fog of war. The game logic runs on the chip along with the drawing, and the game state peaks at 6,677 bytes.

A camera photo of the screen dominated by round reflections next to a clean top-down frame of a green battlefield with a dirt road, two stone arrow towers and wooden palisades
Mini Warlord. Left: an older build on the XIAO's screen, photographed. The light blobs are reflections on the screen's cover, not the image. Right: a frame of the current build, read back from the XIAO over USB. The current build was too dark to photograph, so the photo shows an older work-in-progress build.

An earlier build of the engine missed 60 FPS on the XIAO. Since then the engine has been optimised, and Mini Warlord now runs at 60.3 FPS, dipping to 53-56 in the biggest fights. The game logic takes at most 0.35 milliseconds a frame, and no frame was at risk of tearing.

Its sibling, Mini TPS, a corridor from Godot's third-person shooter demo, doesn't hit its target yet. It runs in the 45 Hz mode, and its heaviest views drop to 33 to 44 FPS. More work to do there.

No more tearing

A screen like this one redraws itself from top to bottom, 60 times a second. If a new frame gets written while that sweep is halfway down, you see the top of one frame and the bottom of another, split by a line. That's tearing, and on a small screen in a fast game it's hard to miss.

Many screens have a pin that tells the chip when it's safe to write. This one doesn't. So the XIAO asks the screen, over the same wires it sends pictures on, which line it's drawing right now. From those answers it keeps a running model of the sweep and starts each new frame only inside a safe window. Across the measurements of the 2D demos, the first five 3D demos and Mini Warlord, not one frame was at risk of tearing.

Two fixes on the way took the demos from 45 to 60 FPS:

  • SPI mode 3. In SPI mode 0, which the code used before, the chip paused for one and a half clock ticks after every 16 bits it sent, so a full frame took 13.45 milliseconds to send instead of 12.29. Switching to mode 3, which the screen reads just as well, brought it to 12.30.
  • Frame timing. The old code sometimes misread where the sweep was and waited a whole extra refresh, so frames alternated between 16.6 and 28.9 milliseconds. The new model fixed that. Mode 7 went from 45.9 to 60.3 FPS.

The first custom board

The first circuit board for the base Saucelet is fully routed, with zero design-rule errors and zero unrouted connections. The case grew by a couple of millimeters so the board could keep its extra flash for game assets next to a new microSD slot. It now measures 39.05 by 36.30 by 15.25 millimeters. A final review and the files for manufacturing are next.

A 3D render of a small green circuit board with chips, a microSD socket, a USB-C port and a screen connector, next to a render of a small white square case with a dark screen opening and a keyring loop
Renders of the routed base Saucelet board and the case it fits in, 39.05 by 36.30 by 15.25 millimeters.

What's next

  • More off the 3D scene's worst frames, aiming for 60.
  • Mini TPS up to its target.
  • The final board review.

I also ran these same demos on a second chip, the ESP32-P4, to see what more 3D would cost. That comparison gets its own post: RP2350 vs ESP32-P4: choosing the Saucelet's 3D chip.

If there's something you'd like to see running on the Saucelet next, tell me.