3.7 KiB
3.7 KiB
| title | description | date | projectPeriod | thumbnail | tags | role | stack | scale | outcome | audience | links | media | |||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| A 50 FPS Game Engine on an 8-Bit Microcontroller | A handheld game built from the PCB up: ATtiny85V, OLED, IR receiver, EEPROM, 8 MHz 8-bit ALU. 50 FPS floor. | 2026-05-06 | Spring 2020 |
|
|
Hardware and firmware author |
|
8 MHz, 8-bit ALU, ~31 mW at full brightness, ~1.5 mA standby, 15–20 ms frame budget | A handheld built from schematic to firmware, with a 50 FPS game on it | technical |
|
|
The short version:
- A handheld game built from the PCB up: ATtiny85V, 0.96" SPI OLED, TSOP4838 IR receiver, 3.3V regulator, battery.
- 8-bit ALU at 8 MHz. Every byte and every cycle has a price tag, and that's the whole point.
- 15–20 ms peak frame time, so gameplay never drops below 50 FPS. Power draw ~31 mW peak, ~1.5 mA standby.
Why the PCB changed the project
I'd done microcontroller work on dev boards before and it always felt like I was renting the hardware. As soon as I had a real board with my soldering on it, bugs stopped feeling like software inconveniences and started feeling like consequences of choices I'd made in KiCad. That shift was most of the value of doing it this way.
The constraint that mattered: an 8-bit ALU at 8 MHz, with no FPU, no SIMD instruction set, and 8 KB of flash. Anything I built had to fit inside that, or I'd be staring at a brick.
The bits worth showing
- SIMD-on-an-8-bit-ALU display driver. The OLED is 128×64 monochrome, 1024 bytes per frame. The driver packs four pixels into a byte and processes them with bit-parallel tricks. That's how the frame budget stayed under 20 ms with room for game logic.
- Prototype-based inheritance, in C. Entities share behaviour by pointing at a struct of function pointers. No vtable, no class, no allocator. Cheap dispatch and the whole object model fits on one screen.
- Atomic EEPROM commits. Sprite data and save state both live in EEPROM. The commit path writes a new region, then swaps a tiny header pointer. Pull the battery mid-write and the previous version is intact.
- PNG-to-C sprite pipeline. A Python script turns PNG artwork into static C arrays the firmware can include directly. Asset workflow without ever leaving the source tree.
What I'd change
- A host-side emulator. Debugging firmware directly on hardware was character-building and slow. A small SDL-based simulator linking the same C code would have shortened the iteration loop from "reflash and hope" to "rebuild and run."
- Power numbers I'd actually trust. I have peak and standby draw. I don't have a curve over a real gameplay session, so I honestly can't say how long the battery lasts under load. I can only say it outlasted my patience.
- A development log for the driver. The display driver and the EEPROM commit protocol are the parts I'd still defend. They deserved diagrams and measurements at the time, not the half page of comments I left them with.