fix(sim): Pico W MicroPython loads the RPI_PICO_W firmware (network + CYW43)
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.
This commit is contained in:
parent
a19f5940ee
commit
4d80a9d1c3
Binary file not shown.
|
|
@ -6153,10 +6153,10 @@ from machine import Pin
|
|||
from umqttsimple import MQTTClient
|
||||
|
||||
# --- Configuration ---
|
||||
WIFI_SSID = "kritish" #
|
||||
WIFI_PASSWORD = "@pass"
|
||||
WIFI_SSID = "Velxio-GUEST" # Velxio simulator's built-in WiFi network
|
||||
WIFI_PASSWORD = "" # the simulator AP is open
|
||||
THINGSBOARD_HOST = "eu.thingsboard.cloud"
|
||||
ACCESS_TOKEN = "tokenn"
|
||||
ACCESS_TOKEN = "tokenn" # replace with YOUR ThingsBoard device token
|
||||
|
||||
# --- Hardware Setup ---
|
||||
# Using "LED" for Pico W onboard LED, or Pin(15) for an external LED
|
||||
|
|
|
|||
|
|
@ -16,9 +16,7 @@ import littlefsWasmUrl from 'littlefs/dist/littlefs.wasm?url';
|
|||
|
||||
// Flash geometry (matches rp2040js and MicroPython defaults)
|
||||
const FLASH_START_ADDRESS = 0x10000000;
|
||||
const MICROPYTHON_FS_FLASH_START = 0xa0000;
|
||||
const MICROPYTHON_FS_BLOCK_SIZE = 4096;
|
||||
const MICROPYTHON_FS_BLOCK_COUNT = 352;
|
||||
|
||||
// UF2 block constants
|
||||
const UF2_MAGIC_START0 = 0x0a324655;
|
||||
|
|
@ -28,15 +26,48 @@ const UF2_PAYLOAD_SIZE = 256;
|
|||
const UF2_DATA_OFFSET = 32;
|
||||
const UF2_ADDR_OFFSET = 12;
|
||||
|
||||
// Firmware cache key for IndexedDB
|
||||
const FIRMWARE_CACHE_KEY = 'micropython-rp2040-uf2-v1.20.0';
|
||||
/**
|
||||
* MicroPython firmware variant. RP2040 boards split into the plain Raspberry
|
||||
* Pi Pico and the Pico W. The W build is larger because it embeds the CYW43439
|
||||
* WiFi driver + blob and the `network`/`socket`/`ssl` modules the plain build
|
||||
* omits, which also pushes its LittleFS filesystem to a higher flash offset.
|
||||
* Loading the plain firmware onto a Pico W board is what produced
|
||||
* "ImportError: no module named 'network'" on every WiFi/MQTT example.
|
||||
*/
|
||||
export type FirmwareVariant = 'pico' | 'pico-w';
|
||||
|
||||
// Bundled fallback path (placed in public/firmware/)
|
||||
const FIRMWARE_FALLBACK_PATH = '/firmware/micropython-rp2040.uf2';
|
||||
interface FirmwareConfig {
|
||||
/** IndexedDB cache key — MUST differ per variant or the two builds collide. */
|
||||
cacheKey: string;
|
||||
remoteUrl: string;
|
||||
/** Bundled fallback served from public/firmware/. */
|
||||
fallbackPath: string;
|
||||
/** Flash offset of the MicroPython LittleFS region (board-specific). */
|
||||
fsFlashStart: number;
|
||||
/** Number of 4K blocks in the LittleFS region. */
|
||||
fsBlockCount: number;
|
||||
}
|
||||
|
||||
// Remote firmware URL
|
||||
const FIRMWARE_REMOTE_URL =
|
||||
'https://micropython.org/resources/firmware/RPI_PICO-20230426-v1.20.0.uf2';
|
||||
// Geometry mirrors MicroPython rp2 v1.20.0 board configs:
|
||||
// PICO : MICROPY_HW_FLASH_STORAGE_BYTES = 1408K -> FS @ 0x200000-0x160000 = 0xa0000 (352 blocks)
|
||||
// PICO_W: MICROPY_HW_FLASH_STORAGE_BYTES = 848K -> FS @ 0x200000-0x0d4000 = 0x12c000 (212 blocks)
|
||||
// The W firmware spans flash up to ~0xab000, so its FS sits at 0x12c000 (no overlap).
|
||||
const FIRMWARE_CONFIGS: Record<FirmwareVariant, FirmwareConfig> = {
|
||||
pico: {
|
||||
cacheKey: 'micropython-rp2040-uf2-v1.20.0',
|
||||
remoteUrl: 'https://micropython.org/resources/firmware/RPI_PICO-20230426-v1.20.0.uf2',
|
||||
fallbackPath: '/firmware/micropython-rp2040.uf2',
|
||||
fsFlashStart: 0xa0000,
|
||||
fsBlockCount: 352,
|
||||
},
|
||||
'pico-w': {
|
||||
cacheKey: 'micropython-rp2040w-uf2-v1.20.0',
|
||||
remoteUrl: 'https://micropython.org/resources/firmware/RPI_PICO_W-20230426-v1.20.0.uf2',
|
||||
fallbackPath: '/firmware/micropython-rp2040w.uf2',
|
||||
fsFlashStart: 0x12c000,
|
||||
fsBlockCount: 212,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse UF2 binary and write payload blocks into RP2040 flash.
|
||||
|
|
@ -72,9 +103,11 @@ export function loadUF2(uf2Data: Uint8Array, flash: Uint8Array): void {
|
|||
export async function loadUserFiles(
|
||||
files: Array<{ name: string; content: string }>,
|
||||
flash: Uint8Array,
|
||||
variant: FirmwareVariant = 'pico',
|
||||
): Promise<void> {
|
||||
const { fsFlashStart, fsBlockCount } = FIRMWARE_CONFIGS[variant];
|
||||
// Create a backing buffer for the LittleFS filesystem
|
||||
const fsBuffer = new Uint8Array(MICROPYTHON_FS_BLOCK_COUNT * MICROPYTHON_FS_BLOCK_SIZE);
|
||||
const fsBuffer = new Uint8Array(fsBlockCount * MICROPYTHON_FS_BLOCK_SIZE);
|
||||
|
||||
// Initialize the littlefs WASM module.
|
||||
// locateFile redirects Emscripten's internal fetch to the Vite-resolved asset URL.
|
||||
|
|
@ -109,7 +142,7 @@ export async function loadUserFiles(
|
|||
flashProg,
|
||||
flashErase,
|
||||
flashSync,
|
||||
MICROPYTHON_FS_BLOCK_COUNT,
|
||||
fsBlockCount,
|
||||
MICROPYTHON_FS_BLOCK_SIZE,
|
||||
);
|
||||
const lfsInstance = lfs._new_lfs();
|
||||
|
|
@ -133,7 +166,7 @@ export async function loadUserFiles(
|
|||
lfs._free(config);
|
||||
|
||||
// Copy the LittleFS image into RP2040 flash at the filesystem offset
|
||||
flash.set(fsBuffer, MICROPYTHON_FS_FLASH_START);
|
||||
flash.set(fsBuffer, fsFlashStart);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -141,13 +174,16 @@ export async function loadUserFiles(
|
|||
* Checks IndexedDB cache first, then tries remote download, then bundled fallback.
|
||||
*/
|
||||
export async function getFirmware(
|
||||
variant: FirmwareVariant = 'pico',
|
||||
onProgress?: (loaded: number, total: number) => void,
|
||||
): Promise<Uint8Array> {
|
||||
const { cacheKey, remoteUrl, fallbackPath } = FIRMWARE_CONFIGS[variant];
|
||||
|
||||
// 1. Check IndexedDB cache
|
||||
try {
|
||||
const cached = await idbGet(FIRMWARE_CACHE_KEY);
|
||||
const cached = await idbGet(cacheKey);
|
||||
if (cached instanceof Uint8Array && cached.length > 0) {
|
||||
console.log('[MicroPython] Firmware loaded from cache');
|
||||
console.log(`[MicroPython] Firmware (${variant}) loaded from cache`);
|
||||
return cached;
|
||||
}
|
||||
} catch {
|
||||
|
|
@ -156,7 +192,7 @@ export async function getFirmware(
|
|||
|
||||
// 2. Try remote download
|
||||
try {
|
||||
const response = await fetch(FIRMWARE_REMOTE_URL);
|
||||
const response = await fetch(remoteUrl);
|
||||
if (response.ok) {
|
||||
const total = Number(response.headers.get('content-length') || 0);
|
||||
const reader = response.body?.getReader();
|
||||
|
|
@ -182,12 +218,12 @@ export async function getFirmware(
|
|||
|
||||
// Cache for next time
|
||||
try {
|
||||
await idbSet(FIRMWARE_CACHE_KEY, firmware);
|
||||
await idbSet(cacheKey, firmware);
|
||||
} catch {
|
||||
// Cache write failure is non-fatal
|
||||
}
|
||||
|
||||
console.log(`[MicroPython] Firmware downloaded (${firmware.length} bytes)`);
|
||||
console.log(`[MicroPython] Firmware (${variant}) downloaded (${firmware.length} bytes)`);
|
||||
return firmware;
|
||||
}
|
||||
}
|
||||
|
|
@ -196,20 +232,20 @@ export async function getFirmware(
|
|||
}
|
||||
|
||||
// 3. Fallback to bundled firmware
|
||||
const response = await fetch(FIRMWARE_FALLBACK_PATH);
|
||||
const response = await fetch(fallbackPath);
|
||||
if (!response.ok) {
|
||||
throw new Error('MicroPython firmware not available (remote and bundled both failed)');
|
||||
throw new Error(`MicroPython firmware (${variant}) not available (remote and bundled both failed)`);
|
||||
}
|
||||
const buffer = await response.arrayBuffer();
|
||||
const firmware = new Uint8Array(buffer);
|
||||
|
||||
// Cache for next time
|
||||
try {
|
||||
await idbSet(FIRMWARE_CACHE_KEY, firmware);
|
||||
await idbSet(cacheKey, firmware);
|
||||
} catch {
|
||||
// non-fatal
|
||||
}
|
||||
|
||||
console.log(`[MicroPython] Firmware loaded from bundled fallback (${firmware.length} bytes)`);
|
||||
console.log(`[MicroPython] Firmware (${variant}) loaded from bundled fallback (${firmware.length} bytes)`);
|
||||
return firmware;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -280,10 +280,14 @@ export class RP2040Simulator {
|
|||
files: Array<{ name: string; content: string }>,
|
||||
onProgress?: (loaded: number, total: number) => void,
|
||||
): Promise<void> {
|
||||
console.log('[RP2040] Loading MicroPython firmware...');
|
||||
// Pico W needs the RPI_PICO_W build (network + CYW43 driver + bigger,
|
||||
// higher LittleFS). The cyw43 emulator is attached (via attachCyw43) only
|
||||
// for pi-pico-w boards, so its presence selects the firmware variant.
|
||||
const variant = this.cyw43 ? 'pico-w' : 'pico';
|
||||
console.log(`[RP2040] Loading MicroPython firmware (${variant})...`);
|
||||
|
||||
// 1. Get MicroPython UF2 firmware (cached in IndexedDB)
|
||||
const firmware = await getFirmware(onProgress);
|
||||
const firmware = await getFirmware(variant, onProgress);
|
||||
|
||||
// 2. Create fresh RP2040 instance
|
||||
this.rp2040 = new RP2040();
|
||||
|
|
@ -294,8 +298,9 @@ export class RP2040Simulator {
|
|||
loadUF2(firmware, this.rp2040.flash);
|
||||
console.log(`[RP2040] MicroPython UF2 loaded (${firmware.length} bytes)`);
|
||||
|
||||
// 4. Create LittleFS with user files and load into flash
|
||||
await loadUserFiles(files, this.rp2040.flash);
|
||||
// 4. Create LittleFS with user files and load into flash (variant-specific
|
||||
// flash offset — the Pico W FS lives higher than the plain Pico's).
|
||||
await loadUserFiles(files, this.rp2040.flash, variant);
|
||||
console.log(`[RP2040] LittleFS loaded with ${files.length} file(s)`);
|
||||
|
||||
// Keep a flash copy for reset
|
||||
|
|
@ -359,6 +364,16 @@ export class RP2040Simulator {
|
|||
}
|
||||
this.pioStepAccum = 0;
|
||||
|
||||
// Pico W: attachCyw43 installed the gSPI PIO-FIFO hooks on the RP2040
|
||||
// instance that existed at board-creation time. loadMicroPython just swapped
|
||||
// in a fresh RP2040, so those hooks now point at the discarded instance.
|
||||
// Re-install them on the new PIO FIFOs or the cyw43 driver's bit-banged
|
||||
// traffic never reaches the emulator and WiFi never connects.
|
||||
if (this.cyw43) {
|
||||
this.cyw43HookedFifos = [];
|
||||
this.installCyw43PioHooks();
|
||||
}
|
||||
|
||||
this.setupGpioListeners();
|
||||
this.micropythonMode = true;
|
||||
console.log('[RP2040] MicroPython ready');
|
||||
|
|
|
|||
Loading…
Reference in New Issue