This is the engineering reference for the model: what it implements, register by register, for people writing firmware against it. For using the board in the editor, start with The Raspberry Pi Pico W and the board page.

Raspberry Pi Pico W board contract (Phase 9)

The Pico W is the Raspberry Pi Pico with a CYW43439 on it. The CYW43439 is not modeled. Everything else about the board is the Pico's, and docs/pico.md is the contract for all of it: the same RP2040, the same forty-pin header in the same order, the same memory map, the same peripheral registers, the same time model and the same runtime. One kernel component serves both (pico in crates/parts, with a variant flag), because below the wireless chip there is nothing to tell apart.

The catalog type is picow. This document says only what differs: three GPIOs and where the LED really is, and one block that is Mokxi's own.

Read section 6 before you plan anything around this board. There is no radio here. What there is, since docs/wifi.md, is a simulated network behind a mailbox at an address the RP2040 leaves unmapped, and the word simulated is the whole of it: no 802.11, no CYW43 firmware, no real internet. The on-board LED does work, because it is reached over that same mailbox, which is where it is reached on the hardware too.

1. What differs: three GPIOs and the LED

Three of the RP2040's GPIOs never reach the header on either board, and they are wired to different things:

GPIO on a Pico on a Pico W
GP23 SMPS_MODE, the regulator's mode pin WL_ON, the CYW43439's power
GP24 VBUS_SENSE WL_D, the CYW43439's half-duplex data line
GP25 the green LED WL_CS, the CYW43439's chip select
GP29 ADC3, the VSYS divider WL_CLK, and the VSYS divider

The LED on a Pico W hangs off the CYW43439's own GPIO 0, reached over the link the radio uses. So:

  • LED_BUILTIN is not a GPIO number on this board. It is MOKXI_WL_LED, a pin number above every real one, and pinMode, digitalWrite and digitalRead send it to the wireless part through the mailbox of section 3.8 rather than to a pad. That is the path the Arduino core takes on real hardware, where LED_BUILTIN goes through the CYW43 driver. digitalWrite(LED_BUILTIN, HIGH) therefore lights the board's lamp here, exactly as it does on a desk, and probe reports it as bit 2, which comes back over the mailbox and not off a pad.
  • GP25 is not the LED. Driving it high here drives WL_CS, which is connected to a chip that is not modeled, and on a desk it would do the same. A sketch that writes GP25 expecting a light gets nothing, in Mokxi and on the bench. The Pico's own blink ELF, which writes GP25, lights nothing on this board, and crates/parts' gp25_is_wl_cs_here_and_lights_nothing holds it that way.
  • There is no PWM behind that lamp. The link carries on and off, not a duty cycle, so analogWrite(LED_BUILTIN, …) does nothing on a Pico W. fade says so and fades GP14 instead.
  • GP24 is not VBUS sense. The Pico model holds it high because that board is always USB powered; here nothing drives it and it reads low.

The alternative (calling GP25 the LED and hoping) was considered and rejected. It would make a sketch that lights up in the simulator do nothing on a desk, which is the one kind of wrong this project will not ship. Having no lamp at all was the first answer and it was not the right one either: the lamp is there on the board, and the path to it is a command interface this model now has. So it is modeled where it really is. DECISIONS.md, 2026-09-17.

Everything else in docs/pico.md section 1 holds: the same forty pins in the same catalog order, the same names, the same RUN and 3V3_EN behavior, the same rails at the same voltages, the same 55 k pad pulls and the same 1.65 V input threshold.

2. Memory map, 3. Peripheral registers, 4. Host channel

As docs/pico.md sections 2, 3 and 4, with one block added that is not the chip's and nothing taken away. It is the same RP2040: the same XIP flash window, the same 264 KB of SRAM, the same RESETS, IO_BANK0, PADS_BANK0, TIMER, UART0, PWM and SIO at the same addresses with the same bits, and UART0 as the host channel.

The CYW43439 is on SPI-like signals that never leave the module, so nothing in the peripheral map changes to make room for it. What is added instead is Mokxi's own simulated network mailbox, which is not the CYW43 and does not pretend to be:

3.8 Mokxi network mailbox (0x4FFF_E000)

This block is not the RP2040's, and it is not on a plain Pico. Every other address in docs/pico.md is the chip's, taken from the datasheet, and an image built against it runs on a real Pico. This one is Mokxi's own: the simulated network of crates/net, above every APB peripheral (they stop well below 0x4008_0000) and below the AHB-Lite window at 0x5000_0000, so that an image using it faults on silicon rather than reading some other peripheral's register. That fault is the honest answer, because the thing behind this block does not exist on the chip.

Why a mailbox is the honest shape here too. A real Pico W's wireless part is a CYW43439 on the far side of a three-pin SPI-ish link, driven by a closed binary firmware blob. It is not a set of documented registers on the RP2040 any more than an ESP32's radio is a set of documented registers on its own die, and Arduino's WiFi.h on a Pico W really does end up on the far side of a command interface. So this is a command interface: the same one, byte for byte, that the four ESP boards use, at this board's own address.

It is mapped only on a Pico W. firmware/pico/build.sh defines MOKXI_BOARD_PICO_W for that board and not for the Pico, so WiFi.h compiles for one and refuses for the other, and crates/rp2040's Soc::new leaves the address a hole while Soc::new_with_network maps it. On a plain Pico the access faults, which is what a board with no wireless part on it should do.

The block raises no NVIC interrupt. An IRQ number is the chip's, and this block is not the chip's, so it is given none. The firmware polls, and a core parked in WFI is still woken when the network has something to say, the same way it is woken for a PWM edge.

There is no radio, no 802.11 and no real internet here. Nothing in the model transmits, scans, associates, encrypts or opens a socket: a password is compared as a string, a name that is not under .mokxi does not resolve, and the three origins the network serves are functions in crates/net/src/http.rs. The mailbox is the shape of the real seam rather than a fiction about a MAC: on a real ESP32 the WiFi hardware is a closed binary blob with a command interface, and Arduino's WiFi.h ends up on the other side of one of these. docs/wifi.md is the contract for what is behind it; this section is only the registers.

A command register, a status register, a 4096-byte ring each way and an interrupt bit. Writing NET_CMD_REG runs the command; a command that takes simulated time raises BUSY and clears it, with DONE in NET_INT_RAW_REG, when it is finished.

offset name bits
0x00 NET_ID_REG reads 0x494B4F4D, and nothing else does. A sketch that wants to know whether it is on Mokxi reads this; on silicon the access faults
0x04 NET_CMD_REG write a command code (table below); the write runs it
0x08 NET_ARG_REG one argument word, read back: the port for LISTEN, the status for REPLY
0x0C NET_STATUS_REG bit 0 BUSY, bit 1 ERR, bit 2 RX_READY, bit 3 LINK, bit 4 SERVE, bits 8..11 STATE
0x10 NET_RESULT_REG what the last command answered
0x14 NET_INT_RAW_REG bit 0 DONE, bit 1 EVENT
0x18 NET_INT_ENA_REG the same two bits
0x1C NET_INT_CLR_REG write 1 to clear, the same two bits
0x20 NET_TX_LEN_REG bytes staged in the outbound ring
0x24 NET_TX_DATA_REG write: push one byte (bits 0..7) into the outbound ring
0x28 NET_RX_LEN_REG bytes waiting in the inbound ring
0x2C NET_RX_DATA_REG read: pop one byte from the inbound ring, 0 when it is empty
0x30 NET_RING_LEN_REG capacity of each ring in bytes, so firmware need not hard-code 4096

STATE is where the station is: 0 idle, 1 starting, 2 connecting, 3 connected, 4 failed, 5 no such network. NET_CMD_STATUS turns the same thing into the Arduino wl_status_t code a sketch reads from WiFi.status().

NET_CMD_REG name what it does
0 NOP nothing
1 RESET disconnect, drop both rings, drop any request
2 CONNECT the outbound ring holds ssid\0password; raises BUSY for one to three seconds
3 DISCONNECT drop the association and the address
4 STATUS NET_RESULT_REG = the wl_status_t code
5 IP NET_RESULT_REG = the leased address, first octet in the low byte
6 RSSI NET_RESULT_REG = the signal in dBm, sign extended
7 SSID the inbound ring gets the access point's name
8 MAC the inbound ring gets the board's six MAC bytes
9 REQUEST the outbound ring holds method\0url\0headers\0body; raises BUSY
10 POLL NET_RESULT_REG = 0 in flight, then the HTTP status, or a negative error with ERR up; the body lands in the inbound ring
11 LISTEN listen on the port in NET_ARG_REG
12 ACCEPT NET_RESULT_REG = 1 and the inbound ring holds method\0path\0query\0body, or 0 when nothing is waiting
13 REPLY answer the accepted request with the status in NET_ARG_REG; the outbound ring holds content-type\0body
14 STOP stop listening
15 WL_GPIO_SET drive the wireless part's own GPIOs from NET_ARG_REG, bit 0 first. Bit 0 is this board's LED
16 WL_GPIO_GET read them back into NET_RESULT_REG

Commands 15 and 16 are this board's alone. The four ESP boards map the same mailbox and their radios have no such pin, so on them both set ERR and drive nothing: inventing a pin would be inventing hardware. Here they are the path LED_BUILTIN takes (section 1).

The negative POLL codes: -1 the name does not resolve, -2 the station is not associated, -3 https:// and TLS is not simulated, -4 a body longer than the ring, -5 a URL this network cannot parse, -6 a request is already in flight. firmware/include/HTTPClient.h turns them into the Arduino-shaped codes a sketch compares against, and docs/wifi.md lists the mapping.

Timing, all of it this model's own and all of it stated in docs/wifi.md: an association takes between one and three seconds, decided from the SSID so a lesson takes the same time twice; a wrong password becomes WL_CONNECT_FAILED after five seconds; a request to a simulated origin comes back 60 ms later; a request into the sketch's own server that the sketch never answers is closed by the network with a 504 after ten seconds.

5. Time model and the part

As docs/pico.md section 5, with one difference in probe.

probe is a bitmask: 1 running, 2 parked in WFI, 4 the on-board LED, 8 serial transmit in the last 20 ms, 16 serial receive, 32 BOOTSEL held; 0 with no program. Bit 2 means the same thing as on a Pico and comes from somewhere else: on a Pico it is the GP25 pad, and here it is the CYW43439's GPIO 0, read back through the mailbox. The catalog entry's own probe description says so, so a tool reading the catalog learns it without reading this document.

poke is the Pico's: 1 presses BOOTSEL, 0 releases it, 2 holds RUN low, 3 releases it.

6. Wireless

The radio is simulated. There is no 802.11 anywhere in Mokxi, no CYW43439, no real internet and no request that ever leaves the browser tab. docs/wifi.md is the contract and it wins over this section; what follows is the short version and what is particular to this board.

A sketch can use WiFi.h, WiFiClient.h, WiFiClientSecure.h, HTTPClient.h and WebServer.h (the same Arduino-shaped API a Pico W owner copies out of a tutorial), and it will join a network, fetch a page and serve one. Behind those calls is section 3.8's mailbox and crates/net: a station state machine held to a one-to-three-second envelope, one DHCP lease at 192.168.4.2, and four made-up origins under .mokxi. The access point is three properties of the board (wifiSsid, wifiPassword, wifiRssi), because with no radio there is nothing to scan.

What is not here, and will not be:

  • No 802.11 in any form. No channel, no scan, no WiFi.scanNetworks(), no beacon, no association frames, no WPA2, no PMK, no encryption. A password is compared as a string. RSSI() is the property, not a path loss.
  • No CYW43439 as a chip. No SPI protocol to it, no firmware blob, no driver, no registers. Its GPIO 0 is modeled, because that is the board's LED and it is a pin a sketch really drives; the two commands that reach it are in section 3.8's table and drive nothing on any other board. None of its other GPIOs are, and there is no PWM on the one that is.
  • No sequencing. On real hardware the CYW43 has to be powered and its firmware blob loaded before its GPIO 0 will do anything, which the Arduino core does at start-up. Here the command works from the first instruction, whether or not the sketch has called WiFi.begin(). That is a simplification and this is where it is written down.
  • No Bluetooth. No LE, no GATT, no pairing, no stub. The CYW43439 does Bluetooth on real hardware; Mokxi does not model it, and there is no header for a sketch to fail on.
  • No real internet. No TCP, no IP stack, no sockets, no DNS protocol. A name that is not under .mokxi fails with one sentence the sketch can read and the Cloud panel prints.
  • No TLS. WiFiClientSecure compiles and then fails, because a TLS that trusted everything would teach a student that https is free.

If real wireless is the point of your project, this board in Mokxi is not the tool for it, and neither is any other: there is no radio in this product. What it is for is learning the shape of the code (the connect loop, the status codes, HTTPClient, a WebServer handler) and everything the Pico is for underneath it.

7. Firmware runtime

firmware/pico/, with one flag: firmware/picow/build.sh runs it with BOARD=picow, which adds -DMOKXI_BOARD_PICO_W. That flag defines MOKXI_NET_BASE in include/rp2040.h and pulls in firmware/include/mokxi_net.h, which is what makes the network headers compile; it also brings the ESP32-C3's wifi.cpp into the link, because the network runtime knows nothing about a board beyond that one address. Everything else (crt0.S, link.ld, src/, arduino.h) is the Pico's, unchanged and not copied.

The catalog program string is elf32 arm rp2040 picow, not the Pico's. The two boards run the same instruction set on the same memory map, but an image built for the W writes to an address a plain Pico leaves unmapped, so it faults there. Two strings mean the editor offers each board its own list, exactly as the Nano's string is kept apart from the Uno's (docs/nano.md section 6), and web/public/firmware/picow/index.json is that list.

The eight examples: blink, button, serial, fade and chaser are read straight out of firmware/pico/examples/ rather than copied; wificonnect, wifiweather and wifilamp live in firmware/picow/examples/, because only this board can compile them. blink drives LED_BUILTIN and GP15, and on this board the first of those goes over the mailbox to the CYW43's GPIO 0, so both lamps blink. fade fades GP14 only, because there is no PWM on the far side of that link. wifilamp drives both, so the page switches one lamp on the board and one on the breadboard.

There is a separate compile family and a separate runtime bundle (picow), because a bundle is keyed by the program string. A sketch written in the editor for a Pico W compiles in the browser, like the Pico's.

8. What is not modeled

Everything in docs/pico.md section 6 (the second core, PIO, DMA, the ADC, SPI, I2C, USB, the watchdog, the RTC, the clock tree, the boot ROM and the second-stage bootloader), plus:

  • The CYW43439 in every particular but one: its SPI protocol, its firmware blob, its registers, its VBUS sense on GPIO 2, 802.11 in any band, Bluetooth, and the antenna. The exception is GPIO 0, the board's LED, which is modeled because a sketch really drives it: through the mailbox, on or off, with no PWM. Section 6's simulated network is not a model of this chip and is not at its address; it is Mokxi's own block, and section 3.8 says so on its first line.
  • GP23, GP24, GP25 and GP29 as wireless signals. They exist in the RP2040 model as ordinary GPIOs, because that is what the chip has, and nothing is listening on the other end.

That is the whole of the difference. Anything this document does not mention is the Pico's, and docs/pico.md is where it is written down.