The overview, with examples running, is on the board page and The ESP32-C3 model (every register).

The ESP32-C3

A RISC-V core at 160 MHz on the real memory map, with no radio and a simulated network in place of one.

What it is

The ESP32-C3-DevKitM-1: a 32 bit RISC-V chip running on our own RV32IMC core, which passes the riscv-tests suite in the browser. The model uses the real ESP32-C3 memory map and the real register offsets for everything it implements, so firmware built here runs on a real DevKitM-1.

The CPU runs in 10 microsecond slices at 160 MHz, one instruction per cycle, and a pin change inside a slice is placed at the exact instruction that caused it.

The pins

Two 15-pin headers, in the order the real board has them.

Left header, top to bottom: GND, 3V3, 3V3b, 2, 3, RST, GNDb, 0, 1, 10, 4, 5, 6, 7, 8.

Right header: 5V, 5Vb, GNDc, GNDd, 9, 18, 19, 20, 21, then six pins marked NC, which are drawn so the header is complete and are never read.

GPIO 0 to 10, 18, 19, 20 and 21 are usable. This is a 3.3 volt board: an output drives 3.3 V or 0 V, the input threshold is 1.65 V, and the internal pull-up and pull-down are both about 45 k.

RST has a 10 k pull-up; pulling it below 1.65 V holds the chip in reset.

GPIO 8 carries the on-board LED, which is why LED_BUILTIN is 8.

The analog pins

GPIO 0 to GPIO 4 are ADC1 channels 0 to 4, and analogRead() on one of them is a real conversion of the voltage on the pin: twelve bits, 0 to 4095, against a full scale the attenuation picks. The default is 11 dB, about the whole 3.3 V rail, so a potentiometer or a thumb stick across the supply spans the range; analogSetPinAttenuation() takes ADC_0db (1.1 V), ADC_2_5db (1.5 V), ADC_6db (2.2 V) or ADC_11db, and analogReadResolution() shifts the result if you want an Uno's ten bits back.

One read costs about five microseconds of simulated time, which is what the converter takes. A pin with nothing on it reads 0 rather than noise. Any other pin reads 0 too: there are five analog inputs and no more.

PWM

analogWrite(pin, 0..255) works on any GPIO. The output comes from LEDC, the chip's LED PWM controller, and reaches the pin through the GPIO matrix, so there is no fixed set of ~ pins the way there is on an Uno.

The default is eight bits at 1 kHz. When the frequency matters (a servo wants fifty frames a second and a pulse width, not a duty), use the ESP32 spelling:

ledcAttach(4, 50, 14);        // fifty frames a second, 16384 counts each
ledcWrite(4, 1229);           // 1.5 ms of a 20 ms frame: the middle

There are six channels and four timers. Channels asking for the same frequency and resolution share a timer, so six pins fading together cost one of the four; ask for a fifth distinct pair and ledcAttach returns false rather than retuning a timer another pin is riding on. pinMode() and digitalWrite() take a pin back from LEDC.

A duty of 0 is a solid low and a duty of 255 a solid high, out of the hardware, with no edges at all, which also means the simulator has nothing to wake the board for, and a board fading an LED while parked in wfi costs it two wakeups a millisecond and nothing else.

Serial

On the real board, pins 20 and 21 are UART0 and pins 18 and 19 are USB. Here they are plain GPIO, and UART0's bytes go to the serial monitor instead. See run and the serial monitor.

What is modeled

GPIO, IO_MUX, UART0, the SYSTIMER, ADC1's one-shot path and LEDC's low-speed group, at their real addresses with their real bits, plus the machine external interrupt for GPIO and UART.

delay() parks the core on a SYSTIMER alarm rather than spinning, so an idle sketch costs the simulator a few hundred instructions per simulated second instead of a hundred and sixty million.

Some register addresses are unverified

Two of the blocks above carry addresses this model cannot stand behind. The base addresses and the GPIO, IO_MUX, UART0 and SYSTIMER offsets are the technical reference manual's. Inside the SAR ADC, every one-shot register offset except the first two is what this model chose rather than a row quoted from a page of the manual; the same is true of LEDC's four interrupt registers, its global configuration and date registers, and the six GPIO matrix signal numbers LEDC's channels reach a pad through. Firmware built here agrees with the model whether or not it agrees with silicon, and an image being taken to a real DevKitM-1 should check that short list of #defines in firmware/include/esp32c3.h first. Section 7 of the ESP32-C3 model sorts every address into taken from the manual, believed but not verified, and absent.

WiFi, simulated

There is no radio and no Bluetooth. There never will be: an 802.11 MAC is not something this model could reproduce honestly.

What there is, since September 2026, is a simulated network. A block that is Mokxi's own rather than the chip's (a mailbox at 0x6FFF_E000, where a real C3 has nothing mapped at all) carries WiFi.h, HTTPClient.h and WebServer.h, so the Arduino code a tutorial gives you runs here: the board joins an access point whose name and password are properties of the board, WiFi.status() walks the same states in the same one to three seconds, DHCP leases it 192.168.4.2, and a sketch can fetch a page or serve one.

What is behind it is software, and nothing is hidden about that. There is no 802.11, no channel, no scan, no WPA2, no TCP, no TLS and no real internet: four made-up names under .mokxi resolve and no others, and nothing a sketch does can reach the network your browser is on. WiFiClientSecure compiles and then fails, saying TLS is not simulated. The Web and Cloud panels along the bottom of the editor open with that sentence and do not scroll it away.

WiFi, simulated is how to use it; Simulated WiFi is the contract, limit by limit.

What is not

Absent: SPI and I2C as peripherals, the timer groups and their watchdogs, RMT, USB Serial/JTAG, the full interrupt matrix, and anything from ESP-IDF. Of the SAR ADC only ADC1's one-shot path is modeled, not ADC2 (which the radio shares on real silicon), the DMA pattern table, the digital controller, the filters and threshold monitors, or the calibration curve in the eFuses, so a reading here is the nominal straight line rather than a real part's slight bend, with no noise and no nonlinearity in it at all. The ADC's done flag can be polled but does not reach the interrupt controller. Vendor binaries are out of scope; what runs is firmware built against our own runtime, which is what the editor builds for you.

One instruction is one cycle at 160 MHz, with no cache, no flash wait states and no pipeline stalls, so a program here runs more evenly than it would on silicon. The 3V3 and 5V pins are ideal sources that cannot sag, so nothing in Mokxi ever browns out.

Register by register, the ESP32-C3 model has the lot.

Stock Arduino libraries

A tutorial that includes Servo.h, Wire.h, SPI.h, EEPROM.h, LiquidCrystal_I2C.h, Adafruit_GFX.h with Adafruit_SSD1306.h, Adafruit_NeoPixel.h, ESP32Servo.h and DHT.h compiles here as it stands. Each is Mokxi's own header under the upstream name, written on the drivers for the parts in the bin, and none of it is the upstream library's code.

  • Wire.begin() puts the I2C bus on GPIO 8 (SDA) and GPIO 9 (SCL), the Arduino core's choice for this board; Wire.begin(sda, scl) moves it.
  • EEPROM.h works, but this board's model has no EEPROM or writable flash, so the bytes live in 4 KB of RAM: they read back within a Run and are gone at a reset.

The code editor and compiling lists what each one covers and how it differs from the upstream library.

The examples

Blink, Button, Serial, Fade, PWM fade, Servo sweep, Counter, Interrupt, WiFi: join the network, WiFi: fetch the weather, WiFi: a lamp on a web page, Thumb stick, Rainbow strip, Plasma panel, OLED text and shapes, OLED dice, OLED pong, Pong console, Binary clock, Electronic dice, Knight Rider, Morse sender, Simon, Stacker and Talker.

PWM fade and Servo sweep are the two that use LEDC. The first is the same breathing LED as Fade, made by the hardware rather than by hand, which is the difference between a core that is awake for every microsecond of the waveform and one that sleeps between steps; the second sweeps an SG90 with a 50 Hz channel, where what the servo reads is the pulse width and not the duty.

Thumb stick and OLED pong are the two that read the ADC. The stick demo prints both axes and lights the LED on the side you push towards, brightly or dimly depending on how far; OLED pong is the Uno's stick game on this board, with the paddle following the stick's Y pot on GPIO 0 and the score in the 5x7 font the Uno has no room for.

The WS2812 driver is on the ESP32-C3 only, so the strip and panel demos are this board's.

Stacker is the one on the home page. Two MAX7219 8x8 matrices stand one above the other, which makes a play field eight pixels wide and sixteen tall, and a 30 mm arcade button on GPIO 3 drops the sliding block. They are daisy-chained the way the chip is meant to be: data on GPIO 7 reaches the top module only, its DOUT feeds the bottom module's DIN, and the clock on GPIO 5 and the one chip select on GPIO 6 go to both. The sketch sends two packets between one pair of CS edges, the bottom module's first, because the packet sent first is pushed furthest along the chain. The matrices run off the board's 5V pin rather than 3V3, because a MAX7219 wants four volts or more. With nobody pressing the button for four seconds the sketch plays itself, and one press takes the game back.

Elsewhere