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
RSTfor 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:
analogWritetakes 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
- The board page: /boards/nodemcu
- The contract document: the ESP8266 NodeMCU model
- The other Xtensa board: the ESP32 DevKit
- The chip that replaced it: the ESP32-C3