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

The ESP32 DevKit V1

The classic ESP32: an Xtensa LX6 at 240 MHz on the thirty-pin DOIT board, with the LEDC PWM controller, a twelve-bit ADC and the six pins that are input only.

What it is

The DOIT ESP32 DevKit V1, an ESP32-WROOM-32 module on a thirty-pin carrier, running on our own Xtensa core, the first board here whose instruction set is neither RISC-V, AVR nor Arm. Firmware is an Xtensa ELF talking to the real ESP32 addresses.

The board is 51.5 by 28.3 mm and its two rows of fifteen pins are 0.9 inch apart, so it straddles a breadboard's center channel with one row of holes left free on each side.

This is the thirty-pin DOIT board, not the 38-pin Espressif DevKitC. The DevKitC is a full inch between rows and leaves no row free at all, and it brings out six more pins. If your board has 38 pins, the pin numbers below are still the chip's, but their positions are not yours.

The pins

Fifteen down each long edge, the micro-USB socket at the bottom, the module and its PCB antenna at the top.

Left column, top to bottom: EN, VP, VN, D34, D35, D32, D33, D25, D26, D27, D14, D12, GND, D13, VIN.

Right column, top to bottom: D23, D22, TX0, RX0, D21, D19, D18, D5, TX2, RX2, D4, D0, D2, D15, 3V3.

Dn is GPIO n. VP is GPIO 36 and VN is GPIO 39, named on the silkscreen for their function rather than their number. EN is the chip enable, which is this board's reset. VIN is the 5 V rail from USB.

This is a 3.3 volt board and its pins are not 5 V tolerant.

Three things the pinout does to you

These are the hardware, not the model, and each one catches somebody:

  • GPIO 34 to 39 cannot be outputs. Ever. Those six pins have an input buffer and nothing else: no output driver and no internal pull-up or pull-down. pinMode(34, OUTPUT) writes the register, the bit does not stick, and the pin stays high-impedance. Four of them are on this header: D34, D35, VP and VN. Mokxi refuses the write the way the chip does, counts it, and puts a small amber mark on the board rather than letting you believe a pin is driving something. If you need a pull-up on one of these, it has to be a resistor.
  • GPIO 6 to 11 are not on the header at all, because they are the SPI flash the program is running from. On boards that do bring them out, driving one stops the chip.
  • GPIO 0, 2, 5, 12 and 15 are strapping pins. They decide how the chip boots, so what is wired to them at power-on matters. GPIO 2 is also the blue LED, so its state before pinMode runs varies from board to board.

The LED and the button

LED_BUILTIN is GPIO 2, an ordinary blue LED wired the ordinary way around: HIGH lights it. BOOT is GPIO 0, active low, and it is on the header as D0 as well, so you can wire to it; clicking it on the board grounds the pin.

analogRead: eight channels, and 4095 is not where a 3.3 V rail lands

ADC1's eight channels are not GPIO 0 to 7. They are, in order, GPIO 36, 37, 38, 39, 32, 33, 34 and 35, and 37 and 38 are not brought out on a WROOM-32, so what you can actually reach is VP, VN, D32, D33, D34 and D35.

Twelve bits, and at the default 11 dB attenuation the converter's full scale is about 3.9 V, not 3.3. A potentiometer across this board's own 3.3 V rail therefore reads about 0 to 3466 and never reaches 4095. That is the real part, and the knob example prints the raw number so it is not a mystery.

ADC2 is not modeled. On a real ESP32 ADC2 is shared with the WiFi radio and stops working when WiFi starts, which is why nobody uses it; here it is simply absent, so the eight channels above are all of them.

The straight-line conversion here is an approximation of a real converter that bends at both ends and is corrected from a calibration curve burned into the chip. Expect the model to be a little kinder than the silicon near 0 V and near the top of the range.

analogWrite is LEDC, and it costs the core nothing

analogWrite(pin, 0..255) attaches one of LEDC's eight high-speed channels to the pin the first time it is called: eight bits at 1 kHz, on any pin with an output driver, because the channel reaches the pad through the GPIO matrix rather than through a fixed pin. When the frequency matters, ledcAttach(pin, frequency, bits) and ledcWrite(pin, duty) are there, and channels asking for the same frequency and resolution share one of the four timers.

Because LEDC is real hardware, a fading LED here costs the core nothing at all: the sketch can sit in delay() and the edges still land on time. The ESP8266 NodeMCU is the interesting contrast: it has no PWM hardware, so its core does the work. Both boards are honest about it.

Serial

Serial is UART0, which is the port a DevKit's USB bridge is on, and it is wired to the serial monitor. Bytes leave at the baud rate you set, so a sketch that prints faster than 115200 baud can carry waits here exactly as it does on a desk.

Serial1 and Serial2 are not modeled. TX2 and RX2 on the header are ordinary GPIOs here.

One core, not two

A real ESP32 has two Xtensa LX6 cores, and Mokxi runs one. There is no APP CPU, no cross-core interrupt and no FreeRTOS to schedule across them. A sketch written around setup() and loop() never notices. Anything that starts a task on core 1 will not run.

What is modeled

The Xtensa LX6 in its windowed ABI (the only ABI its compiler emits), with the register window, the window overflow and underflow handlers, and level-1 interrupts. Around it: the GPIO matrix and IO_MUX, UART0, timer group 0's timer 0 behind millis() and micros(), ADC1 one-shot through the SENS block, LEDC's high-speed group, and as much of DPORT's interrupt matrix as two sources need.

That is the set pinMode, digitalWrite, digitalRead, analogRead, analogWrite, ledcAttach, ledcWrite, millis, micros, delay, attachInterrupt and Serial need, at their real addresses.

WiFi, simulated

There is no radio. 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 0x3FEF_E000, where a real ESP32 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.

The address is in the gap the chip's data bus leaves between the external memory windows and the peripheral window, so an image that uses it faults on silicon rather than reading some other peripheral's register. Bluetooth is not simulated at all, in any form.

Three examples ship for this board: WiFi: join the network, WiFi: fetch the weather and WiFi: a lamp on a web page.

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

What is not

A radio of any kind, and Bluetooth in every form. There is no 802.11 in the model and no pretense of one; the WiFi above is simulated and says so. The second core. The FPU: every floating-point instruction faults here rather than returning a wrong number, and the runtime is soft float, so float works and hard-float vendor binaries do not. The cache, flash and PSRAM: code runs from internal RAM. The RTC, the ULP coprocessor and deep sleep. ADC2, the touch sensor and the hall sensor. I2C, SPI, I2S and RMT, so no Wire, no SPI and no addressable-LED library that leans on RMT. The timer groups beyond one timer, and both watchdogs.

Vendor ESP-IDF and Arduino-core binaries will not run here: the runtime is ours.

Compiling your sketch

Edit the sketch and press Run: it compiles in the tab, like every other board here, with a real clang compiled to WebAssembly. For this chip that is Espressif's own clang, which has the Xtensa back end, so the first build on an ESP32 downloads a separate toolchain (clang-v3, about 80 MB, once) and later builds reuse it. Nothing is uploaded. An untouched example still runs its prebuilt program; change a line and Run builds yours.

One thing to avoid: floating-point arithmetic. This model has no FPU, so a float multiply faults with a message in the serial monitor rather than returning a wrong number, and double math does not link. Serial.print(1.5) works. Compiling in the browser has the full account.

The full list of what the model implements, and a plain account of which register addresses came out of the technical reference manual and which are this model's belief, is in the ESP32 DevKit model. Section 6 is the one to read before taking a build from here to real silicon.

The stock Arduino libraries the other boards compile (Servo.h, Wire.h, EEPROM.h, Adafruit_SSD1306.h and the rest) are on the include path here too, but they have not been checked on this board yet.

The examples

Blink, Button, Serial, Fade, Knob, and the three WiFi ones: WiFi: join the network, WiFi: fetch the weather and WiFi: a lamp on a web page. Each one runs its prebuilt program until you change it, and then Run builds your version.

Elsewhere