Search › A-Z › E › ESP8266

ESP8266

2014 Open source · CC0 On-board only Online

ESP8266 is Espressif's popular low-cost WiFi microcontroller — the chip that launched the hobbyist WiFi boom — emulated in the browser on a from-scratch single-core Tensilica Xtensa LX106 interpreter. The LX106 is a cut-down Xtensa: variable-length 24-bit and 16-bit (density) instructions, a shift-amount register, and PS/EPC1/EXCCAUSE for exceptions, but no register windows — code uses the plain CALL0 ABI (a0-a15, call0/callx0/ret). The core is wired to an ESP8266 SoC model at the real hardware addresses: internal SRAM (IRAM 0x40100000, DRAM 0x3FFE8000), cache-mapped SPI flash (0x40200000), the GPIO block with the on-board LED on GPIO2 (0x60000300), and UART0 (0x60000000). It runs REAL bare-metal Xtensa firmware, including an interactive UART REPL you can type into. Because the whole chip is ordinary JavaScript it plugs into the site's shared debugger (the same from-scratch Xtensa disassembler used by the ESP32): live a0-a15 + PC + the special registers, side-effect-free memory, single-instruction step, execution breakpoints and write-watchpoints. WiFi has no radio here and is an HLE stub, and the module is silent.

ESP8266 at Espressif ↗

Runs on: Web browser

ESP8266 Online Emulator

Play ESP8266 using JavaScript directly in your browser.

Configurations

ConfigurationEmulatorMachineOSLegal
MicroPython v1.23.0ESP8266ESP8266openOpen ⛶
Mini-REPL (UART0)ESP8266ESP8266openOpen ⛶
Blink (on-board LED)ESP8266ESP8266openOpen ⛶
Hello UART (serial)ESP8266ESP8266openOpen ⛶

Chips

Notes

Embedding

The CPU and SoC are written from scratch for this site (public domain / CC0) — nothing here wraps a third-party core. xtensa-esp8266.js shares the site's Xtensa interpreter (the same one that drives the ESP32 LX6) but wires it to an ESP8266 SoC model at the real hardware addresses (from the ESP8266 Technical Reference and the SDK eagle_soc.h). The ESP8266's LX106 is a single-core, CALL0-only Xtensa (no register windows), so the bundled firmware uses the plain CALL0 ABI. It is all inspectable JavaScript state, which is exactly what lets the shared debugger drive it.

var sys = window.ESP8266.createSystem();        // Xtensa LX106 core + ESP8266 SoC
sys.loadImage(bytes, 0x40100000);            // flat app image into IRAM
sys.setEntry(0x40100000);                    // enter at the image start
sys.onUartTx = function(b){ /* UART0 TX -> console */ };
sys.feedRx(byte);                              // keyboard -> UART0 RX FIFO
sys.step();                                    // execute exactly one instruction

We own the run loop so the debugger can drive it. Each frame runs a time-boxed budget of sys.step() (one Xtensa instruction each); breakpoints stop it by PC, write-watchpoints by bus address. Everything is plain JavaScript state:

MemberKindWhat it does
sys.step()methodFetch → decode width (16-bit density vs 24-bit) → execute one instruction, advancing pc. The single-step primitive.
sys.ar(i) / sys.setAr(i,v)methodThe a0-a15 registers. sys.getPc()/setPc() and sys.sr (sar/ps/epc1/exccause) complete the programmer's model — all readable and writable.
sys.mem.sramI / .sramD / .flash / .rtcfieldThe real memories as typed arrays — read side-effect-free for the hex + disassembly views.
sys.read8/16/32, sys.write8/16/32methodThe bus, decoding the ESP8266 memory map. The write path notes watched addresses.
sys.ledOn()methodTrue when GPIO2 is driven high (output enabled) — polled each frame to draw the on-board LED.
sys.feedRx(b)methodSerial RX in — the keyboard drives the mini-REPL through UART0.

Debugger integration

The plug-in (esp8266-debug.js) describes the ESP8266 to the shared debugger and nothing more — the core is not patched:

  • Shared Xtensa decoder. The ESP8266 reuses the site's from-scratch Xtensa disassembler at /debugger/src/cpus/xtensa.js (registered as xtensa, first written for the ESP32), shown here as "Xtensa LX106 (ESP8266)". It decodes the variable-length ISA (24-bit when the low nibble op0 is 0-7, 16-bit density forms when 8-13) — the ALU/shift/load-store/branch set, L32R, the CALL0 calls (CALL0, CALLX0, RET), RSR/WSR/XSR and the narrow forms — and matches the interpreter's encodings exactly.
  • Registers are read live each refresh: a0-a15, the pc, and the special registers SAR, PS, EPC1/EXCCAUSE. Each has a set() that writes straight back through the core. Because the LX106 is CALL0-only, there is no register window or zero-overhead LOOP to surface.
  • Memory is exposed as four chips, all read side-effect-free directly off the typed arrays, each with its real base so the disassembly lines up with PC: IRAM at 0x40100000 (disassembled), DRAM at 0x3FFE8000, XIP flash at 0x40200000 (disassembled), and RTC RAM at 0x60001000. The UART/GPIO peripheral window is deliberately not a chip, because reading the UART FIFO pops a byte — memory reads stay side-effect-free.
  • Single step is one sys.step(). Breakpoints are host-side checks of the PC (in the running code chip's offset space, so a click in the disasm gutter halts) run instruction-by-instruction when any are set. Write-watchpoints are a set the SoC's write path checks, pausing the loop the moment a watched bus address is written.

Architecture

The Espressif ESP8266 (2014) is the low-cost WiFi microcontroller that launched the hobbyist WiFi boom, built on a single 32-bit Tensilica Xtensa LX106:

  • Xtensa LX106 — one 32-bit core at 80 MHz (boostable to 160). The LX106 is a cut-down Xtensa: it has the variable-length 24-bit and 16-bit (density) instructions, a shift-amount register (SAR), and PS / EPC1 / EXCCAUSE for exceptions, but it omits the register-window and zero-overhead-LOOP options — code uses the plain CALL0 ABI (a0-a15, call0/callx0/ret). This emulator implements the LX106 core from scratch — the ALU / SAR shifts / loads-stores / branches, L32R, the 32-bit multiply, RSR/WSR/XSR and a basic trap — sharing the interpreter with the site's ESP32 (LX6) machine.
  • Memory — internal SRAM reachable from the instruction bus at 0x40100000 (IRAM, 32 KiB) and the data bus at 0x3FFE8000 (DRAM, 80 KiB); code also executes in place (XIP) from the external SPI flash cache-mapped at 0x40200000; a small RTC RAM sits at 0x60001000. All are at their real hardware addresses.
  • On-board LED — common modules (ESP-12 / NodeMCU) expose a user LED on GPIO2, driven through the GPIO block at 0x60000300 (GPIO_ENABLE / GPIO_OUT and their W1TS/W1TC set/clear registers).
  • Serial — UART0 at 0x60000000 is the console the ROM bootloader and apps log to; its TX FIFO drives the serial panel and its RX FIFO (read through UART_STATUS' RXFIFO_CNT) is fed by the keyboard.

The default is stock, unmodified MicroPython v1.23.0 (the official esp8266 port, MIT) — taken byte-for-byte from micropython.org and booted through the real Espressif NONOS SDK to a live Python >>> REPL. The image goes into a 4 MiB flash the way esptool write_flash --flash_size detect does it, which sets the flash-size field in its header, and esp.check_fw() confirms it with the firmware's own MD5. To get there the emulator clean-room HLE-s the ESP8266 mask ROM (from the public eagle.rom.addr.v6.ld addresses — SPIRead/flash, the UART funcs, ets_printf, mem*/str*, the soft-float and 64-bit libgcc routines, MD5, GPIO), models the eFuse block, the FRC2/WDEV/RTC timers and the UART0 and timer interrupts. The boot runs the real chip-ID/eFuse check, flash-config load, RF/PHY calibration, clock bring-up and UART0 bring-up, then the task loop hands off to MicroPython's REPL. It auto-runs print(2+2) and 6*7 on arrival and you can type more; every instruction still single-steps in the debugger, interrupt handlers included. Three original CC0 bare-metal programs (mini-REPL, Blink, Hello UART) remain in the picker.

This is the chip's build, not the ESP32's. Floats are 32-bit with the two lowest mantissa bits dropped to fit in an object pointer, so they print 6 digits: math.pi is 3.14159 and struct.pack('<f', math.pi).hex() is d80f4940, which is the constant in the stock image. The random module has only getrandbits and seed; hashlib has sha1 and sha256. Files live in littlefs on the flash and last until the page reloads.

Honest limits. The from-scratch core runs real Xtensa LX106 and the ROM HLE is clean-room from public addresses, but WiFi has no RF: its register windows (and the modem-sleep hooks) are RAM-backed HLE stubs, so network.WLAN starts and reports its state, but no packets move, a scan fails with OSError and a connection never completes. machine.PWM accepts its settings and drives no output (the FRC1 timer's interrupt is not modelled). machine.ADC(0).read() always gives 1024. machine.SPI(1) finishes every transfer with nothing attached, reading zeros. Pin.irq never fires, as GPIO interrupts are not modelled. machine.deepsleep() never returns, so use Reboot. SoftI2C needs Pin.PULL_UP on its pins, as a real bus needs pull-ups: then scan() gives [], and without them it waits out its timeouts for a long while (Ctrl-C works). The hardware watchdog never fires. The panel lights the LED when GPIO2 is high, where a real module's LED is wired active-low, and it shows no NeoPixel colour, as the module has none on board: np.write() just clocks the bits out of the pin. Timing is instruction-approximate, not cycle-exact: emulated time is held to the wall clock, so sleeps and timers run in real time, but code runs slower than on the real chip. The bare module has no on-board audio, so this emulator is naturally silent.