Extend the spice-driven input path (already live for AVR/ESP32) to RP2040 and
STM32 so digitalRead() of an INPUT pin reflects the actual wiring: a pin tied
to a rail reads that rail, and an INPUT_PULLUP button-to-GND reads idle-HIGH /
pressed-LOW instead of floating or inverted.
RP2040 (rp2040js, frontend-only): the GPIO listener now splits input vs output
mode. Input pins report their pad pull (InputPullUp/Down) via setPinPull and
seed the pull's idle level (rp2040js does not auto-apply the pad pull to the
readable input register); the SPICE solve then overrides via connectDigital-
InputsToMcu when the net is actually sourced. Output pins drive as before.
spiceDrivenInputs = true.
STM32 (backend QEMU): the worker now forwards a new gpio_pull event (from the
libqemu-arm picsimlab_pull_pin callback) so the netlist stamps the matching
weak resistor; Stm32Bridge surfaces it, Stm32BridgeShim opts into
spiceDrivenInputs, and collectPinStates maps PA0/PC13 names to the linear pin
so the pull is read. STM32 outputs stay on the part layer (unchanged).
Event-driven parts with no SPICE model (rotary encoder, keypad) remain
protected by the existing sourcedNets gate in the connector.
The spiceDrivenInputs change (e81450e + f4401cc) fixed plain-INPUT-wired-to-rail
reads but BROKE the far more common INPUT_PULLUP + button-to-GND pattern: the
internal pull-up is not modeled in the netlist, so the input floated LOW and read
as permanently pressed (verified live on the stm32-bluepill-button example).
Revert all the spice-driven-input changes to the pre-fix part-seed behaviour,
which handles INPUT_PULLUP correctly. Proper fix (model the internal pull-up per
board so BOTH patterns work) is a follow-up. Keeps the Pi LED fix.
Extend the source-backed SPICE-driven input fix to the Pico (RP2040) and STM32:
a GP/PA pin wired to a rail or button now reads the right level from the solve,
while floating event-part nets (encoder/keypad/dialer/dip/stepper) stay on the
part layer. RP2040 just opts in (spiceDrivenInputs); STM32 opts in via the
Stm32BridgeShim and connectDigitalInputsToMcu maps PA0/PC13 names to the linear
pin setPinState expects (stm32PinNameToLinear).
Two robustness fixes for the paid-WiFi open-core split:
1. A pi-pico-w board now boots the RPI_PICO_W firmware variant (which has the
`network` module) based on its BOARD KIND, not on whether the WiFi
peripheral happens to be attached. Previously the variant was
`pioPeripheral ? 'pico-w' : 'pico'`, so any moment the peripheral was
absent (see #2) booted the plain Pico firmware and a Pico W sketch crashed
with "ImportError: no module named 'network'". Store boardKind in
attachPioPeripheral and pick the variant from it. (OSS: 'pico-w' isn't
registered, so firmwareConfig falls back to 'pico' — a self-hosted Pico W
has no WiFi engine anyway.)
2. Re-attach the PIO peripheral in loadMicroPythonProgram before loading
firmware. An example deep-link adds the board during render, which races the
pro overlay's async mountPro that installs the CYW43 factory — so the
board-add attach returned null and a PAID user's Pico W booted plain
firmware too. attachPioPeripheral is idempotent; by run time the factory is
installed, so a paid user gets the W peripheral and real WiFi.
Move the CYW43439 (Pico W) WiFi emulation out of the open-source tree so it
can ship as a paid feature in a private overlay. OSS keeps a plain Pico W
(no WiFi); the overlay registers the cyw43 protocol + backend network stack
at runtime via generic seams.
Frontend:
- Add simulation/PioPeripheral.ts: a generic "PIO bus peripheral" seam
(feedWord / inDiscardableWriteData / resetFraming / hostWakeLevel /
onHostWake / onSimulationStart). No factory is installed in OSS, so
createPioPeripheral() returns null and a Pico W simulates as a plain Pico.
- RP2040Simulator: keep the fragile PIO-FIFO plumbing (it must re-run after
loadMicroPython swaps the chip) but drive it through PioPeripheral instead
of an inlined cyw43 import (attachCyw43 -> attachPioPeripheral, etc.).
- useSimulatorStore: generic attach/detach + setBoardWifiStatus; drop the
cyw43 bridge map.
- MicroPythonLoader: add registerFirmwareVariant() so an overlay can add the
RPI_PICO_W build; remove the OSS pico-w config + bundled .uf2.
- Delete simulation/cyw43/ (moved to the overlay).
Backend:
- core/hooks.py: add generic register_ws_sim_handler / dispatch_ws_sim_message
and register_gateway_proxy / dispatch_gateway_proxy seams.
- simulation.py: route start_picow / stop_picow / picow_packet_out through the
ws_sim_handler hook (the overlay handles + gates them).
- iot_gateway.py: resolve the Pico W gateway through the gateway_proxy hook.
- Delete services/picow_net/ + picow_net_bridge.py (moved to the overlay).
Tests: move the cyw43/picow suites to the overlay; update RP2040Simulator
mock stubs to attachPioPeripheral.
Wi-Fi sketches on the emulated Pico W associate via the chip's built-in
virtual net (DHCP/ARP answered locally), but outbound traffic had no
route, so DNS/MQTT/HTTP failed with OSError -2.
Wire the emulator's outbound DATA path to the backend picow_net bridge:
- Cyw43Emulator forwards every outbound Ethernet frame EXCEPT DHCP/ARP
(still answered locally) to firePacketOut -> the WS bridge, which NATs
DNS/TCP/UDP to the real internet and injects replies back.
- The virtual net stays ON unconditionally and shares the backend's
subnet, gateway and gateway MAC (10.13.37.0/24, gw 10.13.37.1). Nothing
is mutually exclusive, so an absent or flaky bridge can never break the
Wi-Fi association -- it just falls back to no-internet, as before.
- useSimulatorStore opens the bridge (cyw43.connect()) for Wi-Fi sketches.
Validated end to end against a running backend: WiFi connect + DHCP, DNS
resolves example.com, TCP connect + HTTP GET returns 200 OK. Gated e2e in
picow-bridge-e2e.investigate.test.ts (CYW43_BRIDGE_E2E=1).
For the first deploy, keep the chip emulator's built-in virtual DHCP/ARP
net ON and leave the backend internet bridge dormant (not validated end
to end yet). A Pico W board now associates and gets a link-local IP
locally (isconnected True); outbound internet (MQTT/HTTP) has no route
until the picow_net bridge is wired. Revert is a one-liner in the store
(cyw43.wifiEnabled = hasWifi; cyw43.connect()) + setVirtualNet(null).
Headless test that drives the REAL RP2040Simulator (attachCyw43 +
installCyw43PioHooks + lockstep PIO stepping in runFrameForTime), boots
the Pico W firmware, injects a WiFi-connect snippet over the raw REPL, and
asserts isconnected(). Result:
PYBOOT
ACTIVE False (this fw's active() getter reports link status)
CONN_OK 192.168.4.2 (DHCP-leased IP, isconnected() == True)
MAINPY_DONE
Reaches link-up in ~31s wall — the production lockstep PIO stepping is
faster than the harness's setTimeout-cranked PIO.
Also fixes a real production bug: the RP2040 logger was
ConsoleLogger(LogLevel.Error) which THROWS on rp2040js unaligned-read
warnings — lwIP reads the IPv4 header at ethernet offset 14 on every
received packet, so WiFi would have crashed on the first DHCP reply.
Now constructed with throwOnError=false.
Gated behind CYW43_PROD_HARNESS=1 (boots real firmware, ~30s).
Port the gSPI wiring proven in the boot harness into the real simulator
so WiFi works in the browser, not just the test:
- Non-dropping TX FIFO (head-pointer queue) in installCyw43PioHooks, so
the 260-word F2 IOCTL writes aren't truncated, with the firmware/
backplane bulk-write fast-path (inDiscardableWriteData) keeping the
~224 KB download cheap. Fully restorable on detach.
- Drive WL_HOST_WAKE (GPIO24) from emu.onHostWake, and re-sync the pin
level after installCyw43PioHooks (loadMicroPython resets GPIO while the
chip's queue persists).
- With a backend bridge attached, disable the built-in DHCP/ARP net so
the bridge owns the network.
Production steps the PIO in lockstep with the CPU (pioStepAccum), so no
PIO-rate crank is needed (that was harness-only). The emulator-side fixes
(host-wake, F2 byte order, join events, BDC header, virtual DHCP/ARP) are
already shared. Not yet exercised in a browser e2e — the headless harness
is the verification today.
cyw43_spi_transfer calls pio_sm_restart before each transfer's count words, so
hooking restart() to reset the sniffer makes framing deterministic across the
firmware-stream fast-path (no phantom-transfer carryover). Verified: restarts
fire 3625x (once per transfer), F1 phantom count drops, and the CLM IOCTL write
now frames correctly (cmd decodes to F2, 'clmload' payload). Wired into
RP2040Simulator + the harness.
Remaining (next session): the CLM/IOCTL write doesn't complete its payload and
wifi_on still fails (active()=False) — bus_init stalls at/around clm_load with
only 2 STATUS reads and goes idle. Next: trace the CLM write's DMA/PIO drain and
the SDPCM IOCTL response path. See findings.md F-13.
Brings the Pico W CYW43439 gSPI emulation from "fails at the first register
read" to "the chip boots fully and MicroPython's network.WLAN().active(True)
returns" — validated end-to-end against the real RPI_PICO_W firmware via a
headless boot harness.
What now works (Phases 1-2):
- PioBusSniffer rewritten to the real cyw43_bus_pio_spi framing
[out_bits][in_bits][cmd][write_data], skipping the two PIO loop-counter
words. Self-healing: validates count1 (= tx_length*8-1, 4-aligned, <=2052)
and skips non-conforming words — re-syncs after the extra word rp2040js
pushes on large writes AND fast-paths the ~224 KB firmware stream.
- Dual word-order regime: boot 16-bit-LE (swap16x2 / swap16) flips to 32-bit
big-endian (bswap32) at the SPI_BUS_CONTROL write. Calibrated empirically
against the firmware. Sniffer reads the mode via setModeProvider().
- Cyw43Emulator: encodeReadWord (per-regime), readBytes-sized backplane reads
with the value in the last word (response-delay pad), ALP+HT clocks and F2
always ready, AI core registers (IOCTRL/RESETCTRL), interrupt register
reports no errors, f1Mem echo store, SDPCM bus-credit granting + initial
frame.
- RP2040Simulator: serves chip responses on rxFIFO.pull (on-demand) instead of
racing the async DMA/PIO; passes readBytes through.
Not done yet (Phase 3+): connect() runs but stalls in the power-management /
save-restore phase before any F2/IOCTL traffic; packet transport (Tier 2) and
firmware-clocking perf are open. See project/picow-wifi-emulation/ for the full
research, phases, and findings.
The boot harness (picow-cyw43-boot-harness.investigate.test.ts) is gated behind
CYW43_HARNESS=1 so it stays out of the normal test run.
The RP2040 MicroPython loader always fetched the plain RPI_PICO build, which
ships no `network` module and no CYW43 WiFi driver. Every Pico W WiFi/MQTT
example therefore failed at `import network` ("no module named 'network'"),
which surfaced as a compile/run error in the editor.
- getFirmware()/loadUserFiles() are now variant-aware. pi-pico-w boards load
RPI_PICO_W-20230426-v1.20.0 (network/socket/ssl + the CYW43439 driver) and
write the LittleFS at the W board's flash offset (0x12c000, 212 blocks)
instead of the plain Pico's 0xa0000/352. The W firmware spans flash to
~0xab000 and would otherwise be clobbered by the filesystem. Each variant
gets its own IndexedDB cache key.
- The variant is selected by the presence of the already-wired CYW43 emulator
(attachCyw43 runs for pi-pico-w boards only).
- loadMicroPython swaps in a fresh RP2040 each run, so the CYW43 PIO-FIFO hooks
are re-installed on the new instance; otherwise the driver's gSPI traffic
never reaches the emulator and WiFi never comes up.
- Bundle micropython-rp2040w.uf2 as the offline fallback.
- Point the ThingsBoard example at the simulator's Velxio-GUEST network.
The RP2040 core (125 MHz Cortex-M0) is ~8x heavier to emulate than the
AVR. The run loop used a FIXED per-frame cycle budget, and arduino-pico
delay() busy-waits the timer (no WFI), so a host that cannot sustain
125M instr/s rendered a 1s blink every 4-5s (sim ran in slow motion).
- Derive the frame budget from the MEASURED wall-clock delta (mirrors
AVRSimulator) instead of assuming a perfect 60fps.
- Add IdleSpinDetector: recognise a side-effect-free busy-wait spin and
advance the clock over it (capped at the next timer alarm / scheduled
pin change) instead of executing every idle cycle - the same idea the
WFI fast-path already uses for sleep(). Conservative: a bit-bang loop,
an input-poll that just saw its pin move, or a loop that calls out are
never elided; a false positive only ever advances time up to the
wall-clock budget, never past the next event.
- Bound WFI sleeps to the wall-clock budget so they advance in real
time across frames rather than leaping ahead.
Cuts emulation work for a delay-bound sketch ~1900x (125M -> ~65k
instructions per simulated second) so it tracks wall-time even on hosts
that cannot emulate 125 MHz in real time. Public API unchanged;
step()/stepCycles() untouched.
Adds rp2040-realtime.test.ts: IdleSpinDetector unit tests plus
end-to-end scheduler tests driving a real rp2040js core through a
hand-assembled busy-wait loop (no firmware fixture needed).
Same gap as the AVR USART: rp2040js's UART fires `onByte(value)` per
transmitted byte but never toggles the corresponding GPIO, so an
oscilloscope on GP0 (UART0 TX, default for Arduino-Pico's Serial1) sees
nothing during `Serial.print`. Real silicon drives the pin with the
full UART frame at the configured baud rate, and Velxio should match.
`emitUartTxFrame(uartIdx, byte)` derives:
* `txPin` via FUNCSEL inspection: walk GP0 / GP12 / GP16 / GP28 (the
four candidates for UART0 TX per RP2040 datasheet) and pick the
first whose `functionSelect == 2` (FUNCTION_UART). Same for UART1.
Fall back to GP0 / GP4 when nothing is mapped (firmware hasn't
called `Serial1.begin()` properly).
* `baudRate` and `bitsPerChar` directly from the UART peripheral
(rp2040js already exposes these as live getters).
* Time from the RP2040 IClock's `nanos` counter, matching the
existing `setupGpioListeners` path — UART waveforms therefore stack
consistently with PIO / SIO traces on the same scope.
Both `uart[0].onByte` and `uart[1].onByte` get hooked. The seed-idle-
HIGH baseline is pushed once per UART per simulation run; `stop()`
clears the flag so a re-run gets a fresh seed (matching how the scope
buffer is cleared on restart).
End-to-end pipeline fixes uncovered while auditing the /examples gallery.
Each bug shipped past green unit + snapshot tests because none of those run
firmware + render LEDs. Added scripts/visual-led-test.mjs as a CDP-driven
visual harness that loads each example, runs the simulator, samples
`wokwi-led.brightness`, and asserts toggle / gradient / initial-off
invariants — exits non-zero on any regression.
Frontend simulator
- PinManager.updatePort: new optional ddrMask param. A pin is added to
`outputPins` only if the DDR bit is set, so the PORTx write that
enables INPUT_PULLUP (DDR=0, PORT=1) no longer falsely marks the pin
as MCU output. AVRSimulator now reads DDRB/C/D (0x24/0x27/0x2A on
Uno/Nano, 0x37 on ATtiny85, per-port table on Mega) and forwards it.
- AVRSimulator: pass DDR mask alongside every port-listener fire.
- BasicParts pushbutton{,-6mm}: seed pin HIGH in attachEvents so
`digitalRead()` returns HIGH while idle. avr8js doesn't auto-simulate
INPUT_PULLUP — without this the firmware reads LOW from boot and
thinks the button is permanently pressed (the "LED is always on,
pressing does nothing" UX bug).
- connectMcuEdgesToService: suppress synthetic digital edges on pins
with active PWM, AND subscribe to onPwmChange to re-tick the netlist
on duty changes. Fade-LED now produces a true gradient (6 distinct
brightness levels across a fade cycle) instead of a binary 0/full
toggle.
- CircuitSimulationService.handleMcuEdge: replace single-slot
pendingMcuEdge with a per-pin Map. Multiple pins toggling during the
same in-flight tick used to overwrite each other; now every pin's
most-recent edge replays after the tick. Fixes Traffic-Light RED→
YELLOW→GREEN sequencing.
- NetlistBuilder: new sanitizeSpiceId() helper replaces hyphens with
underscores in V-source names. ngspice's interactive `alter` command
treats `-` as an operator and silently no-ops on hyphenated source
names, so mid-simulation MCU pin transitions stopped propagating
after the first solve. MixedModeScheduler.onMcuPinChange and
CircuitSimulationService self-heal use the same sanitizer so names
stay consistent across emit/alter/lookup. Also added a regex-based
fallback in step 2 so any board pin matching `GND.\d+` canonicalises
to net "0" — ESP32-C3 dev kits expose up to 10 GND pins and the
per-board `groundPinNames` list missed several, leaving wires
floating instead of grounded.
- collectPinStates: emit V-sources only for pins in `outputPins`, not
every wired board pin. Leaves INPUT pins (analog sensors on A0,
pull-down dividers, etc.) free for the SPICE solver instead of being
shorted to 0 V by an ideal MCU V-source.
- start.ts: extended __spiceDebug to also expose outputPinsByBoard +
nodeVoltages + pinNetMapEntries for the visual harness.
- ESP32 / RP2040 / RISC-V / C3 simulators: pass `'mcu'` source flag to
triggerPinChange / setPinState so the new outputPins tracking fires
on those boards too (was AVR-only before).
- useSimulatorStore: stopBoard/resetBoard call pm.resetPinStates() so
outputPins clears between runs; Esp32Bridge.onPinChange passes the
`'mcu'` flag in all three places it's wired.
- types/board.ts: ATtiny85 FQBN `clock=internal16mhz` →
`clock=16pll` (ATTinyCore 1.5.2 renamed the option).
Backend
- esp-idf-template/main/CMakeLists.txt: skip the
`-DLED_BUILTIN=2` fallback for esp32c3 and esp32s3 targets. Both
variants already define LED_BUILTIN in pins_arduino.h via a
self-define macro (`#define LED_BUILTIN LED_BUILTIN` + `static const
uint8_t LED_BUILTIN = ...;`). Pre-defining the symbol from the
command line expanded the static-const declaration to
`static const uint8_t 2 = ...;` — a syntax error that broke every
ESP32-C3 / S3 build (`expected unqualified-id before numeric
constant`).
Examples
- examples.ts: bulk-fix 72 wire endpoints that referenced
`componentId: 'nano-rp2040'` / `'esp32-c3'` etc. (boards that don't
exist on the canvas). Replaced with `'arduino-uno'` (the canvas
board-id convention) and converted `D<n>` pin names to `GP<n>` for
Pico-style boards. Affects pico-blink, pico-i2c-scanner,
pico-i2c-rtc-read, pico-spi-loopback, c3-blink and others.
Tests
- scripts/visual-led-test.mjs: CDP-driven harness. Default suite covers
Blink (single-pin), Button (idle-OFF invariant — catches the
INPUT_PULLUP regression), Traffic-Light (multi-pin sequencing),
Fade-LED (PWM gradient — ≥3 distinct levels), RGB-LED (≥3 PWM pins
driven). Run via `npm --prefix frontend run test:visual` against a
Chrome on `:9222` + vite on `:5174` + backend on `:8001`.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Long-standing latent bug: SPI parts (ILI9341, custom chips, etc.)
register a handler on simulator.spi.onByte via the lazy adapter, but
the actual rp2040.spi[0].onTransmit assignment in initMCU was a pure
loopback that never consulted the adapter. The MicroPython init path
(initMicroPython) had the adapter-aware version since day one;
the Arduino path (initMCU) didn't.
Symptom: Pico Doom + every other Arduino sketch driving an ILI9341
on the RP2040 saw an empty SPI bus. The ILI9341 emulator's onByte
handler was wired up correctly — it just never received a single
byte. Pantalla negra.
Fix: copy the adapter-aware handler from initMicroPython (line 219)
into initMCU (line 441). Each byte the firmware writes to SPI0 now
checks `_spiAdapter.onByte` first; if a part is registered, it gets
the byte; otherwise we keep the original loopback as the fallback
so plain "echo MOSI back as MISO" sketches still work.
Combined with the earlier MADCTL fix (commit 6edc715) and the
power+MISO wiring fix (8440836), Pico Doom should now render its
title screen + the raycaster.
Build verified (vite OSS+pro, 285 SEO pages).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Implement HD44780Decoder for decoding I2C commands to HD44780-compatible LCDs.
- Add bmp280_bridge_reader.ino to read BMP280 chip_id and status registers via I2C.
- Create i2c_scanner_multi.ino to scan I2C addresses and report responding devices.
- Introduce lcd_i2c_hello.ino to demonstrate basic LCD functionality with I2C.
- Implement pcf8574_bidirectional.ino to test bidirectional communication with PCF8574.
- Add pico_i2c_master_reader.ino for reading BMP280 from a Raspberry Pi Pico.
- Create rtc_lcd_clock.ino to display time from a DS1307 RTC on an I2C LCD.
Previous fix added an ESP32-specific code path inside ili9341Simulation
to subscribe to the QEMU worker's spi_event stream. That made the LCD
work on ESP32-CAM but left the underlying issue unsolved: every other
SPI part (custom chips, future SD-card emulators, the SSD168x ePaper
already in the codebase) would also need its own per-board branching.
The right shape: every simulator exposes a `.spi` member matching the
SAME SpiBusLike interface, and SPI parts hook .spi.onByte without
caring which board they're attached to. AVRSimulator already had
this — now everything else does too.
frontend/src/simulation/SpiBus.ts (new)
Defines the contract — `onByte: (mosi) => void | null` plus
optional `completeTransfer(miso)`. Documents the single-listener
semantics that AVR has had since day one.
frontend/src/store/useSimulatorStore.ts
Esp32BridgeShim gets a lazy `.spi` getter that wraps
bridge.onSpiByte (the per-byte WS event from the QEMU worker).
completeTransfer is a no-op because the worker drives MISO via
its own _spi_response global. Covers ESP32 (Xtensa), ESP32-S3,
ESP32-CAM, ESP32-C3 — every kind that routes through Esp32Bridge.
frontend/src/simulation/RP2040Simulator.ts
Adds a lazy `.spi` getter that re-routes rp2040.spi[0].onTransmit
through the adapter. Default loopback (the prior behaviour) is
preserved when no part has accessed `.spi` yet — only consumers
that opt in see their handler invoked. Covers Pico and Pico W.
frontend/src/simulation/parts/ComplexParts.ts
ili9341Simulation no longer has an ESP32 special case. Single
code path: `simulator.spi.onByte = handler`. Works on AVR,
RP2040, all ESP32 variants. Same pattern is now available to
every future SPI part — ssd1306, sd-card, oled, etc.
The Esp32Bridge.ts spi_event field-name fix from 6afa62e (msg.data.event
instead of the non-existent msg.data.data) stays in place — that's what
makes the per-byte stream actually arrive in the bridge.
Verified: ILI9341 + ESP32-CAM gallery example renders the live webcam
preview after a hard refresh. The same simulation code works on Arduino
Uno + ILI9341 (the existing ili9341-test-sketch in example_zip).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Introduced SVG layout dimensions for Phase 1 (B/W mono) and Phase 2 (colour) ePaper panels, detailing active areas, bezels, and pin layouts.
- Developed a phased emulation plan outlining the architecture and deliverables for different panel types, including SSD168x and UC81xx.
- Created a canonical "Hello, World!" sketch for the 1.54" ePaper panel, ensuring compatibility across ESP32, Raspberry Pi Pico, and Arduino Uno.
- Implemented a pure Python SSD168x decoder to validate SPI command sets and framebuffers against specifications.
- Added tests for compiling the hello-world sketch across supported boards and for the SSD168x protocol to ensure correct framebuffer behavior.
- Modified the index file to reflect the new naming convention for Velxio components.
- Changed JSX declarations to use 'velxio-' prefix for various components.
- Updated component overrides to replace 'wokwi-' with 'velxio-' for logic gates and other components.
- Adjusted SVG generation script to use 'velxio-' prefix for BMP280 and Raspberry Pi components.
- Marked submodules as dirty in QEMU and RP2040 libraries.
- Added .prettierignore and .prettierrc.json for consistent code formatting.
- Introduced InstrumentComponent with support for Voltmeter and Ammeter, including pin information handling.
Implements MicroPython emulation for Raspberry Pi Pico boards running
entirely in the browser using rp2040js. Users can toggle between
Arduino C++ and MicroPython modes via a language selector dropdown.
Key changes:
- Add LanguageMode type and BOARD_SUPPORTS_MICROPYTHON to board types
- Create MicroPythonLoader.ts: UF2 firmware parser, LittleFS filesystem
builder (via littlefs-wasm), IndexedDB firmware caching
- Extend RP2040Simulator with loadMicroPython() method using USBCDC for
serial REPL instead of UART
- Add setBoardLanguageMode and loadMicroPythonProgram store actions
- Update EditorToolbar with language toggle and MicroPython compile flow
- Enhance SerialMonitor with REPL label, Ctrl+C/D support
- Bundle MicroPython v1.20.0 UF2 firmware as fallback in public/firmware/
- Update useEditorStore to create main.py default for MicroPython mode
Closes#3
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Added board-agnostic sensor registration methods in RP2040Simulator.
- Enhanced ComplexParts to handle LEDC PWM duty updates for ESP32.
- Updated ProtocolParts to check if the simulator handles sensor protocols natively, delegating to backend if applicable.
- Introduced pre-registration of sensors in useSimulatorStore for ESP32 to prevent race conditions.
- Added tests for ESP32 DHT22 sensor registration flow, ensuring proper delegation and fallback mechanisms.
- Created tests for ESP32 Servo and Potentiometer interactions, verifying PWM subscriptions and ADC handling.
- Add useOscilloscopeStore with ring-buffer sample storage and channel management
- Add onPinChangeWithTime callback to AVRSimulator (fires on every bit transition with cycle-derived timestamp)
- Add onPinChangeWithTime callback to RP2040Simulator (fires on GPIO state change)
- Wire oscilloscope callbacks in useSimulatorStore (initSimulator + setBoardType)
- Create Oscilloscope React component with canvas-based waveform rendering
- Add oscilloscope panel to EditorPage (resizable bottom panel, same as SerialMonitor)
- Add 'Scope' toggle button to SimulatorCanvas toolbar
Co-authored-by: davidmonterocrespo24 <47928504+davidmonterocrespo24@users.noreply.github.com>
- Simplified serial data handling in `useSimulatorStore` for both AVR and RP2040 simulators.
- Introduced `boardPinMapping.ts` to map wokwi-element pin names to simulator GPIO/pin numbers for Arduino Uno and Nano RP2040.
- Added `compilationLogger.ts` to parse compile results into structured log entries for better console output.