Docs / Contact / sdks/micropython/boards
Supported boards
Two things decide whether a board fits your project, and neither is the RAM number printed on the chip:
- How much memory your code actually gets. A classic ESP32 has 520 KB of SRAM and hands MicroPython 157.8 KB of it, or 30%. A Raspberry Pi Pico W has half the SRAM and hands over 178.6 KB, or 68%. The spec sheet is not a ranking.
- What the firmware includes. Some capabilities are compiled into MicroPython and cannot be added afterwards. The local database is the big one: it needs the
btreemodule, which upstream MicroPython enables for the ESP32 port (every ESP32 board, plus larger-flash ESP8266 builds) and leaves off in therp2,stm32,alif,mimxrt,samd,nrfandrenesas-raports. It is a per-port build decision, not a question of RAM: a $2 ESP32-C3 has it and boards with far more memory on the other ports don't.
Everything below is measured on hardware. Tested means we ran the SDK on that board and recorded the numbers. Expected means it's the right architecture and should work, but nobody has put one on a bench. If you're choosing hardware for a deployment, prefer a tested board.
At a glance
| Board | Chip RAM | Free at boot | Free after install | SDK cost | Local database | Camera | Status |
|---|---|---|---|---|---|---|---|
| ESP32-C3 | 400 KB | 169.7 KB | 146.3 KB | 23.4 KB | Yes | No | ✅ Measured |
| ESP32-WROOM (no PSRAM) | 520 KB | 157.8 KB | 133.7 KB | 24.2 KB | Yes | No | ✅ Measured |
| ESP32-WROVER (PSRAM) | 520 KB + PSRAM | 1.99 MB | 1.96 MB | 23.9 KB | Yes | No | ✅ Measured |
| Raspberry Pi Pico W | 264 KB | 178.6 KB | 110.9 KB | 67.8 KB | No | No | ⚠️ Measured, caveats |
| ESP32-S2 / S3 | 320 KB+ | — | — | — | Yes | No | Not yet measured |
| OpenMV AE3 (Alif) | ~4 MB | 3.90 MB | 3.84 MB | 64.8 KB | No | Yes | ✅ Measured |
| OpenMV N6 (STM32) | ~25 MB | 24.42 MB | 24.35 MB | 65.8 KB | No | Yes | ✅ Measured |
| OpenMV H7 / RT1062 | Varies | — | — | — | Unverified | Yes | Not yet measured |
Every ESP32 row above runs the ROMFS install; the Pico W runs a filesystem install because ROMFS isn't available on that port. That single difference is most of the gap in the "SDK cost" column; see What the install path costs.
"Free after install" is what's left for your application with the full client loaded. "SDK cost" is what the client holds resident once loaded. All figures come from tests/measure_board.py on MicroPython 1.28 (1.22 on the Pico W), so they're directly comparable; see How these numbers were measured.
Feature support
Every supported board does MQTT publish/subscribe over TLS and file transfer, including device-to-device; those aren't listed below because they don't vary. What does vary:
| Board | Local database | Buffer survives reboot | Low-RAM install | Camera & vision | Flash from browser |
|---|---|---|---|---|---|
| ESP32-C3 | Yes | Yes | Yes (ROMFS) | No | Yes |
| ESP32-WROOM / WROVER | Yes | Yes | Yes (ROMFS) | No | Yes |
| ESP32-S2 / S3 | Yes | Yes | Yes (ROMFS) | No | Yes |
| Raspberry Pi Pico W | No | No | No | No | No (UF2) |
| OpenMV AE3 / N6 | No | No | Not used | Yes | No (OpenMV IDE) |
| OpenMV H7 / RT1062 | Unverified | Unverified | Not used | Yes | No (OpenMV IDE) |
Reading the columns:
- Local database: MicroTetherDB for on-device storage, TTL and queries. Needs the
btreemodule, which is compiled into the firmware. Measured present on every ESP32 and absent on every other port we've tested. - Buffer survives reboot: follows directly from the database. Every board buffers undelivered messages in RAM and flushes them on reconnect, so a network outage never loses data. Only boards with the database can write that buffer to flash, where it also survives a power cycle.
- Low-RAM install: the ROMFS path, which runs the client from flash instead of loading it into the heap and costs 2.8x less memory. ESP32-only. OpenMV boards have ROM partitions but ship them factory-populated with ML models, so we don't use them there.
- Camera & vision: camera capture, JPEG streaming and on-device detection. Needs OpenMV hardware and firmware; no ESP32 or Pico has it.
- Flash from browser: whether the device console can install MicroPython itself. It speaks the ESP boot-ROM protocol, so ESP32 only. The Pico W uses Raspberry Pi's UF2 drag-and-drop and OpenMV uses the OpenMV IDE; both are one-time steps, and everything after them works identically.
How much database do you need?
Most of the difference between these boards comes down to how much on-device storage your application actually wants. Work out which level you need, and the board choice mostly answers itself.
Level 0: Publish and forget
The device reads something, sends it, and the platform does the rest. No local storage at all.
from tendrl import Client
client = Client(client_db=False)
client.start()
client.publish({"temp_c": 21.4})
Runs on: every supported board, including the Pico W.
Level 1: Survive an outage
Undelivered messages are written to flash and flushed when the connection returns, so a network drop or a reboot normally doesn't lose data. On-device storage is best-effort, not guaranteed: a power cut mid-write can cost the buffered messages (see durability).
client = Client(offline_storage=True)
client.publish({"temp_c": 21.4}, write_offline=True)
Runs on: ESP32 boards only; they're the only ones with btree. On a Pico W or an OpenMV the same call still buffers in RAM and still flushes on reconnect, but the buffer does not survive a power cycle. See MicroTetherDB for the details.
Level 2: Decide locally
Keep a rolling window on the device and query it: thresholds, debounce, rate-of-change, "only alert if it's been high for ten minutes." The device stops being a dumb sensor and starts filtering what's worth sending.
This is what examples/tendrl_dht/simple_dht.py does: a DHT sensor that stores readings with a TTL, queries its own recent history, and only publishes when something is actually worth reporting.
Runs on: ESP32 boards. A C3 handles it comfortably.
Level 3: Learn over time
Weeks of history, structured queries across it, thresholds that adapt to the device's own baseline. examples/tendrl_dht/statistical_examples.py has three worked versions of this: trend detection with cloud sync, long-term pattern learning, and adaptive thresholds.
Runs on: ESP32 boards with room to spare. A C3 does it; PSRAM makes it comfortable. Size your retention with max_records rather than assuming; see MicroTetherDB for the memory characteristics.
Level 4: See
Camera capture, video streaming, and on-device machine vision need buffers measured in megabytes.
Runs on: OpenMV boards only. See Vision.
What the install path costs
The SDK's memory cost depends far more on how it's installed than on which board it runs on. Measured across six boards on four different MicroPython ports:
| Install | Board | Port | Peak during import | Resident after |
|---|---|---|---|---|
| ROMFS | ESP32-C3 | esp32 | 29.9 KB | 23.4 KB |
| ROMFS | ESP32-WROOM | esp32 | 30.8 KB | 24.2 KB |
| ROMFS | ESP32-WROVER | esp32 | 30.5 KB | 24.0 KB |
| Filesystem | OpenMV AE3 | alif | 74.7 KB | 64.8 KB |
| Filesystem | OpenMV N6 | stm32 | 75.9 KB | 65.4 KB |
| Filesystem | Raspberry Pi Pico W | rp2 | 96.2 KB | 67.8 KB |
Both groups cluster tightly, and they don't overlap. ROMFS lands between 23.4 and 24.2 KB resident on boards with anywhere from 158 KB to 1.99 MB of free heap. A filesystem install lands between 64.8 and 67.9 KB across three different architectures: Alif, STM32 and RP2040. The cost is a property of the install path, not of the board.
The reason is mechanical: a ROMFS install executes in place from a read-only flash partition and never copies bytecode into the heap, while a filesystem install has to load it. That's a 2.8× difference in resident memory for identical code.
ROMFS is ESP32-only, which is why every non-ESP32 board sits in the second group.
Peak is the number that decides whether a board can install at all. A transient spike during import can exhaust a small heap even when the steady-state cost would have fit comfortably. Resident is what you pay afterwards.
Board by board
ESP32-C3: the default recommendation
Around $2, RISC-V, 400 KB of SRAM with 169.7 KB free at boot. With the ROMFS install the SDK imports in 163 ms and leaves 146.3 KB free, the most usable memory of any board we've measured without PSRAM, and comfortably more than a classic ESP32 has at boot.
Everything except camera work runs here, up to and including Level 3. If you have no specific reason to pick something else, pick this.
ESP32-WROOM (no PSRAM)
520 KB on the chip, of which MicroPython gets 157.8 KB (30%), the smallest share of any board here. The ROMFS install leaves 133.7 KB free.
Comfortable for Levels 0–3, though a C3 costs less and still gives you more room. Leave margin for TLS either way; handshakes fragment the heap.
ESP32-WROVER (PSRAM)
1.99 MB free at boot and 1.96 MB after the SDK loads. With PSRAM the memory question simply stops applying; the client's ~26 KB is a rounding error. Everything up to Level 3 runs without thought.
ESP32-S2 / S3
Not yet measured. ROMFS firmware ships for 4 MB and 16 MB S2/S3 boards, with or without PSRAM, so the low-RAM install is available.
Raspberry Pi Pico W
264 KB of SRAM with 178.6 KB free at boot (68%), the highest share of any board here. The SDK imports in 640 ms and leaves 110.9 KB for your application. Note that it ends up with the least free memory despite starting with the most: no ROMFS on this port means the bytecode is loaded into the heap rather than run from flash.
Three limitations, all of them firmware rather than memory:
- No local database. MicroTetherDB needs
btree, compiled into the firmware. Every ESP32 build has it; therp2port doesn't, and it can't be added afterwards. Levels 1–3 are unavailable; passclient_db=False. Not unique to this board; OpenMV lacks it too. - No
umqttin firmware. Handled for you: provisioning detects this and installs the MQTT library automatically. Only relevant if you install the SDK entirely by hand. - No ROMFS install, and firmware flashing uses Raspberry Pi's UF2 drag-and-drop rather than the browser flasher. One-time; everything after it works normally.
Good for Level 0 telemetry where connectivity is reliable or short outages are acceptable. Not the right board where losing a buffer on power loss means losing data.
Verified end to end: provisioned from the device console, then heartbeats and a tagged publish both delivered over MQTT and stored.
OpenMV (AE3, N6, H7, RT1062)
Camera boards with megabytes of RAM, purpose-built for Level 4. Measured: the AE3 starts with 3.90 MB free and keeps 3.84 MB once the SDK loads; the N6 starts with 24.42 MB and keeps 24.35 MB. Memory is not a constraint on either.
What they don't have is btree, so the local database is unavailable, confirmed on both the AE3 (Alif port) and the N6 (STM32 port). Their range is Levels 0 and 4: publish, subscribe, transfer files, and do camera and vision work. Level 1's durable offline buffer isn't available; writes fall back to an in-RAM queue that flushes on reconnect but doesn't survive a reboot. H7 and RT1062 are unverified, but with two different ports both lacking it, assume they do too until you've checked.
Two other OpenMV-specific notes: their firmware ships without mip, so use the serial install paths, and flashing is done with the OpenMV IDE rather than from the browser.
Quick answers
| If you… | Choose |
|---|---|
| Are starting fresh and want the best value | ESP32-C3 |
| Need on-device history, trends, or adaptive thresholds | An ESP32; no other board runs the database |
| Need a camera, video, or on-device vision | OpenMV |
| Already own Pico Ws and only need telemetry | Pico W, with client_db=False |
| Are deploying where power is intermittent | An ESP32, so undelivered data survives a reboot |
How these numbers were measured
Every figure on this page comes from one script, tests/measure_board.py, so the rows are directly comparable. Run it against your own board to reproduce them:
mpremote connect /dev/ttyUSB0 soft-reset run tests/measure_board.py
- Free at boot:
gc.collect()thengc.mem_free()in a fresh interpreter, before importing anything of ours. What your application actually starts with. - Peak during import: garbage collection is disabled across the import, so the drop in free heap is the total allocated: transient loader garbage plus what stays. Leaving GC on understates it by whatever happened to be collected mid-import, which varies with heap size and flatters roomy boards.
- SDK cost (resident): after re-enabling GC and collecting. What the client holds.
- Free after install: what's left for your code.
Two things that will skew a measurement if you're reproducing these. Hard-reset the board first if anything has touched the network: a soft reset doesn't tear down the ESP32 WiFi driver, and a single network.WLAN(STA_IF).active(True) earlier in the session cost 12 KB that looked exactly like a real regression. And capability probes have to run after the measurement, not before: importing umqtt to check where it came from moved free-at-boot by ~3 KB on its own.
Board capabilities are detected on the device at connect time rather than read from this table, so custom MicroPython builds are reported accurately. If you compile a Pico W image with btree enabled, the device console will offer you the full install.
Tendrl