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

The ESP8266 NodeMCU

An ESP8266 board with simulated WiFi, an Xtensa LX106 at 80 MHz, D0 to D8 pins that differ from GPIO numbers, and software PWM.

What it is

The NodeMCU V1.0 "Amica", an ESP-12E module on a thirty-pin carrier, running on our own Xtensa core in its LX106 configuration. Firmware is an Xtensa ELF talking to the real ESP8266 addresses, with one exception, named below, that the model is upfront about.

The board is 48.3 by 25.6 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. The board people photograph bridging two breadboards is the wider V3 "LoLin", whose rows are a full inch apart. This part is the V1.0.

D0 to D8 are not GPIO numbers

This is the thing this board catches everybody out with, so it is first:

Silkscreen D0 D1 D2 D3 D4 D5 D6 D7 D8 RX TX
GPIO 16 5 4 0 2 14 12 13 15 3 1

digitalWrite(2, HIGH) is D4, not D2. Mokxi's runtime defines D0 to D8, so you can write digitalWrite(D2, HIGH) and mean the pin you are looking at.

The pins

Fifteen down each long edge, the micro-USB socket at the bottom.

Left column, top to bottom: A0, RSV, RSV, SD3, SD2, SD1, CMD, SD0, CLK, GND, 3V3, EN, RST, GND, VIN.

Right column, top to bottom: D0, D1, D2, D3, D4, 3V3, GND, D5, D6, D7, D8, RX, TX, GND, 3V3.

This is a 3.3 volt board and its pins are not 5 V tolerant. VIN is the 5 V rail from USB.

SD0 to SD3, CMD and CLK are the flash chip's six wires. They are on a real NodeMCU's header and they are on this part, and here they are connected to nothing: they neither drive nor read. Driving one on a real board stops the flash your program is running from, so a simulator that let you use them would be teaching you to break a board. The two RSV pins are the same.

Two LEDs, and both are backwards

D4 (GPIO 2) has the ESP-12 module's blue LED and D0 (GPIO 16) has the carrier board's. Both are active low: digitalWrite(LED_BUILTIN, LOW) lights the module's one. LED_BUILTIN is 2.

The blink example writes the on-board LED and a wired one on D1 in opposite directions on purpose, so the two are always the other way up, which is what you see on a desk.

The FLASH button is GPIO 0, which is D3, active low. Clicking it on the board grounds the pin.

D0 is not an ordinary pin

GPIO 16 (D0) is the RTC's pin, not the GPIO block's. Three things follow:

  • It has a pull-down, where every other pad on this part has a pull-up.
  • It cannot raise an interrupt. attachInterrupt(D0, ...) does nothing.
  • On a real board it is the pin you wire to RST for deep-sleep wake, which is not modeled here because deep sleep is not.

analogRead: one pin, ten bits, and a divider

A0 is the only analog pin the ESP8266 has. It is not a GPIO and cannot be written.

The chip's own pin converts 0 to 1.0 V. A NodeMCU puts a 220k/100k divider in front of it, so the A0 on the header is 0 to about 3.2 V and a potentiometer across the 3.3 V rail very nearly spans the range. The divider is part of the board, and Mokxi models it: the pin loads your net with 320 k, which is what a real one does.

Ten bits, 0 to 1023, which is also this board's PWM range, so the knob example feeds the reading straight into the duty.

This is the one thing on this board that is not at a real address. Espressif has never published the ESP8266's ADC registers (the SDK calls system_adc_read() out of a binary blob), so rather than invent an address and let you assume it is the chip's, Mokxi puts three registers of its own there and says so. What that means in practice: if you take a build from here to a real NodeMCU, analogRead is the one call you would have to change. Nothing else.

analogWrite is software, the range is 1023, and it costs the core something

The ESP8266 has no PWM hardware at all. Not a scaled-down LEDC: none. The Arduino ESP8266 core generates its waveforms from a timer handler and so does this runtime: eight pins at once, 1 kHz by default, analogWriteFreq(40..60000).

Two things follow, and both are real:

  • analogWrite takes 0 to 1023, not 0 to 255. analogWrite(pin, 255) is a quarter brightness, not full. That is what the real core does, and copying it was the choice between surprising you here and surprising you on the desk. analogWriteRange(255) makes an Uno sketch behave.
  • The core wakes twice per period to move the pin. On an ESP32 a fading LED is free; here it is not. Both boards are here, and the difference between them is one of the more useful things this pair teaches.

Serial

Serial is UART0 on TX and RX, which is the port the USB bridge is on, and it is wired to the serial monitor. Bytes leave at the baud rate you set.

Serial1 is not modeled. On a real ESP8266 UART1's TX pin is GPIO 2, which is also the module's LED, which is why you cannot have both.

What is modeled

The Xtensa LX106 in the call0 ABI: sixteen registers and no register window, which is what its compiler emits and why an ESP32 image faults on this core rather than misbehaving quietly. Around it: the GPIO block and IO_MUX, the RTC registers that are GPIO 16, UART0, the TOUT converter, and CCOUNT and CCOMPARE0 behind millis(), micros(), delay() and the software PWM.

That is the set pinMode, digitalWrite, digitalRead, analogRead, analogWrite, millis, micros, delay, attachInterrupt and Serial need, at their real addresses, with the ADC as the stated exception.

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 0x6FFF_E000, where a real ESP8266 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.

This is the part with a radio and no documentation for it at all: the ESP8266's WiFi is a binary blob in the SDK and Espressif has never published its registers. This model does not guess at them. What it offers instead is a mailbox that is honestly Mokxi's, at an address that is honestly not the part's.

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. No 802.11, no PHY, no channel, no WPA2, and no register of the part's own radio, because Espressif has never published one. On a part whose whole reason to exist is WiFi that is the biggest thing missing here, and it is said first rather than last, which is why the section above it is the simulated network and not this one.

Also absent: flash and the cache that maps it, so no PROGMEM read from flash, no SPIFFS, no LittleFS and no OTA. FRC1 and FRC2, the two hardware timers. The clock here is the core's own cycle counter, which is a register no model has to guess. Deep sleep and the RTC, its counter and its memory. SPI, I2S and the hardware UART1. There is no I2C peripheral on this chip in the first place; the Arduino core bit-bangs it, and that is not here either. The watchdogs. The eFuses.

Vendor SDK 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. For this chip the compiler is Espressif's own clang, which has the Xtensa back end, so the first build 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.

Floating-point arithmetic is the thing to avoid: the LX106 has no FPU and the runtime links no soft-float library, so float 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 addresses are as real as this part allows, which are believed and which three are invented, is in the ESP8266 NodeMCU 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