Search › A-Z › E › ESP32-C3

ESP32-C3

2020 Open source · CC0 On-board only Online

ESP32-C3 is Espressif's low cost connected microcontroller, emulated in the browser on a from-scratch RISC-V RV32IMC interpreter (the RV32I base plus the M multiply/divide and C compressed extensions, the machine-mode CSRs and the trap and interrupt machinery). The core is wired to an ESP32-C3 SoC model at the real hardware addresses: internal SRAM, XIP flash, the GPIO block with the on-board LED on GPIO8, UART0, the systimer and a functional interrupt matrix. It runs stock MicroPython, with a Python REPL you can type into, and real bare-metal firmware. Because the whole chip is ordinary JavaScript it plugs into the site's shared debugger: the real RISC-V disassembler, live x0-x31, pc and the machine CSRs, side-effect-free memory, single-instruction step, execution breakpoints and write-watchpoints. WiFi and BLE have no radio here and are HLE stubs, so the emulator is silent.

ESP32-C3 at Espressif ↗

Runs on: Web browser

ESP32-C3 Online Emulator

Play ESP32-C3 using JavaScript directly in your browser.

Configurations

ConfigurationEmulatorMachineOSLegal
MicroPython v1.24.1ESP32-C3ESP32-C3openOpen ⛶
Mini-REPL (UART0)ESP32-C3ESP32-C3openOpen ⛶
Blink (on-board LED)ESP32-C3ESP32-C3openOpen ⛶
Hello UART (serial)ESP32-C3ESP32-C3openOpen ⛶

Chips

Notes

Embedding

The CPU and SoC are written from scratch for this site (public domain / CC0) — nothing here wraps a third-party core. rv32-esp32c3.js is a plain RV32IMC interpreter plus an ESP32-C3 SoC model at the real hardware addresses (from the ESP32-C3 TRM and the esp-idf soc.h / reg_base.h). It is all inspectable JavaScript state, which is exactly what lets the shared debugger drive it.

var sys = window.ESP32C3.createSystem();       // RV32IMC core + ESP32-C3 SoC
sys.loadImage(bytes, 0x4037C000);          // flat app image into IRAM
sys.setEntry(0x4037C000);                  // 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 RV32IMC 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 compressed vs 32-bit) → execute one instruction, advancing pc; services a pending interrupt first. The single-step primitive.
sys.regs (Int32Array 32)fieldx0-x31 (x0 pinned to 0). sys.getPc()/setPc() and sys.csrObj (mstatus/mtvec/mepc/mcause/mie/mip/…) complete the programmer's model — all readable and writable.
sys.mem.sram / .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 ESP32-C3 memory map. The write path notes watched addresses.
sys.ledOn()methodTrue when GPIO8 is driven high (output enabled) — polled each frame to draw the on-board LED.
sys.rgbfield{r, g, b}: the last WS2812 frame the RMT sent to GPIO8.
sys.feedRx(b)methodSerial RX in — the keyboard drives the mini-REPL through UART0.

Debugger integration

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

  • Reused decoder. The core runs RISC-V, so the existing /debugger/src/cpus/riscv.js disassembler (RV32I + M + the C compressed forms) is reused as-is under the display name "RISC-V RV32IMC (ESP32-C3)". No new decoder was written.
  • Registers are read live each refresh: x0-x31 with their ABI names, the pc, and the machine CSRs (mstatus, mtvec, mepc, mcause, mie, mip, mscratch, mtval, misa). Each has a set() that writes straight back into sys.regs / sys.csrObj.
  • 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 0x4037C000 (disassembled), DRAM at 0x3FC80000, XIP flash at 0x42000000 (disassembled), and RTC memory at 0x50000000. The 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 ESP32-C3 (2020) is a low-cost WiFi + Bluetooth LE microcontroller built on a single 32-bit RISC-V core:

  • RISC-V RV32IMC — one 32-bit core at up to 160 MHz implementing the I base, the M (multiply/divide) and C (compressed) extensions, with machine-mode CSRs, a trap unit and an interrupt matrix. This emulator implements all three extensions, the CSR/trap machinery and a functional interrupt matrix from scratch.
  • Memory — 400 KB on-chip SRAM, reachable from the instruction bus at 0x4037C000 (IRAM) and the data bus at 0x3FC80000 (DRAM); code also executes in place (XIP) from external SPI flash mapped at 0x42000000; a small RTC memory sits at 0x50000000. All are at their real hardware addresses.
  • On-board LED — devkits expose a user LED on GPIO8, driven through the GPIO block at 0x60004000, and some fit an addressable WS2812 RGB LED there instead. This model does both: the pin held high lights it as a plain LED, and a WS2812 frame sent over the RMT peripheral (MicroPython's neopixel) shows its colour, decoded from the pulses.
  • Serial — UART0 at 0x60000000 is the console the ROM bootloader and apps log to, and its TX FIFO drives the serial panel. The mini-REPL reads the keyboard from the UART0 RX FIFO; MicroPython reads it through the USB-Serial-JTAG controller at 0x60043000, as a stock build does.

The default firmware is stock MicroPython. ESP-IDF, FreeRTOS and MicroPython run as real RISC-V machine code, and only calls into the mask ROM are handled in JavaScript, including the ROM's soft-float routines, because the core has no FPU. Its floats are 32-bit, as on the real build, so math.pi prints 3.141593. Its filesystem is real littlefs on a modelled flash chip: files survive machine.reset() and the Reboot button, but not a page reload. The other three programs are real machine code authored for this site (CC0), assembled to flat images. Every instruction of all four single-steps in the debugger.

Limits. The WiFi and BLE radios have no RF: their register windows are RAM-backed stubs, and in MicroPython network.WLAN() and bluetooth.BLE().active(True) crash and restart the chip. The SHA and AES accelerators are not modelled, so hashlib.sha1 and hashlib.sha256 crash and restart the chip and cryptolib.aes never returns (hashlib.md5 works). Neither do the hardware I2C and SPI controllers (machine.I2C(0).scan() and machine.SPI(1) never return; machine.SoftI2C runs, with nothing attached) or the power management unit (machine.lightsleep() and machine.deepsleep() never return). The Reboot button recovers from all of these. Some peripherals answer without doing anything real: the MAC reads as zeros, ADC and temperature readings mean nothing, and machine.PWM accepts its settings but reads back duty_u16() as 0. Timing is instruction-approximate, not cycle-exact: sleeps and timers keep to the wall clock, and busy code runs slower than a real chip. The bare devkit has no on-board audio, so this emulator is silent.