SearchA-ZA › Arduboy

Arduboy

2015 MIT (avr8js + games) Online

This runs the Arduboy open-source handheld in your browser. A real ATmega32u4 (AVR) drives a 128x64 OLED, six buttons and a piezo speaker, running real MIT-licensed homebrew game .hex files. The AVR core is Wokwi's avr8js, and it is wired to a full AVR debugger for single-stepping the machine code, reading and writing the registers, and watching flash, SRAM and I/O.

Visit the official site ↗

Runs on: Web browser

Arduboy Online Emulator

Play Arduboy using JavaScript directly in your browser.

Configurations

ConfigurationEmulatorMachineOSLegal
Dash (Arduboy Edition)ArduboyArduboyopenOpen ⛶
MinesweeperArduboyArduboyopenOpen ⛶
Dot (Arduboy Edition)ArduboyArduboyopenOpen ⛶
Crate ConfusionArduboyArduboyopenOpen ⛶

Machines emulated

Chips

Notes

Embedding

The Arduboy is an Atmel ATmega32u4 (8-bit AVR at 16 MHz) with a 128x64 1-bit OLED, six buttons and a piezo. It runs on avr8js — Wokwi's TypeScript AVR simulator, bundled once to a browser global (window.avr8js) with esbuild; nothing in the core is patched. We build the Arduboy board around the core and own the run loop so the debugger can drive it.

Boot. Parse the game's Intel-HEX into a little-endian Uint16Array of flash words, construct a CPU with 2560 bytes of SRAM (so SP resets to 0x0AFF, the real 32u4 RAMEND), build the peripherals, then run avrInstruction(cpu) + cpu.tick() to exactly 16 MHz worth of cycles per video frame:

var cpu = new avr8js.CPU(flashWords, 2560);      // SP -> 0x0AFF (32u4 RAMEND)
var board = ARDUBOY.buildBoard(cpu, avr8js);       // ports B-F, SPI, Timer0/1/3, OLED, speaker
var CYCLES = 16000000 / 60;                    // one video frame of AVR time
for (var s = 0; s < SAMPLES; s++) {              // SAMPLES = round(sampleRate/60)
  var target = start + Math.round((s + 1) * CYCLES / SAMPLES);
  while (cpu.cycles < target) { avr8js.avrInstruction(cpu); cpu.tick(); }
  buf[s] = board.speakerLevel();                   // sample the piezo pins at audio rate
}

Everything is plain JavaScript state — no wasm heap. cpu.pc is a flash word index; cpu.data is the whole data space (R0-R31 at 0x00-0x1F, I/O registers, SP at 0x5D/0x5E, SREG at 0x5F, then SRAM); cpu.progBytes is flash as bytes. Interrupts (Timer0 millis, Timer3 audio, SPI) are serviced inside cpu.tick(), so a plain instruction+tick loop runs the whole machine, and the debugger single-steps it with one avrInstruction.

Debugger integration

The plug-in (arduboy-debug.js) describes the ATmega32u4 to the shared debugger and reuses the AVR disassembler added for the Arduino build:

  • Decoder. Reuses /debugger/src/cpus/avr.js (decoder 'avr') — the full 8-bit AVR set: ALU ops, the LDI/CPI/SUBI immediates, MOV/MOVW, the X/Y/Z LD/ST forms and LDD/STD, the 2-word LDS/STS/JMP/CALL, the BRxx family, IN/OUT, the bit/skip ops and the SREG ops.
  • Registers read live each refresh: R0-R31, the pointer pairs X/Y/Z, SP, PC (as a flash byte address so it lines up with the disassembly + breakpoints) and SREG broken out into I T H S V N Z C. Each has a set() writing straight back into cpu.data.
  • Memory is three side-effect-free chips: Flash (cpu.progBytes, disassembled), the Data space (regs + I/O + SRAM) and a focused I/O-register window over 0x20-0xFF.
  • Single step is one avrInstruction(cpu). Breakpoints are host-side checks of cpu.pc*2 against a set of flash byte addresses. Write-watchpoints wrap cpu.writeData and pause the loop the moment a watched data-space address (e.g. PORTC at 0x28, the speaker) is written.

Architecture

The Arduboy (2015, Kevin Bates) is a credit-card-sized open-source handheld built on a single microcontroller — no external logic. This build models it faithfully:

  • ATmega32u4 — 8-bit AVR at 16 MHz, 32 KB flash (word-addressed), 2.5 KB SRAM (SP resets to 0x0AFF). avr8js ships 328P peripheral configs; the 32u4's GPIO ports (B,C,D,E,F) and SPI sit at the same addresses, so those are reused — but the interrupt vectors differ, so the timer configs are cloned and re-vectored, and a Timer3 config the 328P lacks is built for the audio timer.
  • SSD1306 128x64 OLED over hardware SPI (SCK=PB1, MOSI=PB2, CS=PD6, DC=PD4, RST=PD7). The command/data protocol is decoded from the SPI byte stream (DC selects command vs data); the Arduboy streams the whole 1024-byte page buffer each frame in horizontal-addressing mode, and the framebuffer is rendered 1:1 to the canvas.
  • Six buttons on their real port pins — LEFT=PF5, RIGHT=PF6, UP=PF7, DOWN=PF4, A=PE6, B=PB4 — active-low with pull-ups (released = high, pressed = low), driven from the keyboard / on-screen pad.
  • Piezo speaker on PC6 (OC3A) + PC7, driven by Timer3 (ArduboyTones' CTC compare-match ISR, or Arduboy2's hardware OC3A toggle). A small EEPROM and avr8js's ADC are added so score-saves and random-seed reads behave.

The pin map is taken directly from the open-source Arduboy2 library (Arduboy2Core.h). Because the games are real AVR machine code, every instruction single-steps in the debugger.

Sound

Pattern S (authored chip, from the pins). The Arduboy has no sound chip — the piezo is toggled directly, either by Timer3 in CTC mode driving the OC3A hardware pin (Arduboy2 beep) or by the Timer3 compare-match ISR toggling the port in software (the ArduboyTones library). Both are emulated: a 32u4 Timer3 config (register block at 0x90, OC3A = PC6, compare/overflow vectors re-mapped to the 32u4 table) is added to avr8js, so the CPU actually toggles the speaker pins.

Sound is captured straight off those pins: each audio sample, the machine is run to the corresponding cycle count (real 16 MHz timing, so pitch is exact — no resampling) and the differential level of PC6 vs PC7 is read as -1 / 0 / +1 and scaled to an Int16. Exactly round(EmuAudio.sampleRate/60) interleaved-stereo samples are EmuAudio.push()-ed per video frame.

The mute contract is the standard one: transport.setMute / transport.isMuted delegate to EmuAudio, and it starts muted until the Sound button's real click. Caveat: the piezo is silent until a game plays a tone (many Arduboy games beep on menu / jump / collision events), and single-pin square-wave audio is intentionally buzzy — that is how the hardware sounds.