Chapter 02 · Firmware

Python on bare metal

MicroPython is a lean implementation of Python 3 that runs directly on the chip — no OS, no virtualenv, no container. Your while loops, dicts and imports all work; what changes is where the code lives and how it starts.

Recalibrate

Five mental-model shifts

If you internalize these, everything else in embedded Python is just details.

Shift 1

The board has its own filesystem

A small flash drive inside the chip. Your code runs there, not on your laptop — you edit locally, then copy files over USB. pip and .venv never touch the board.

Shift 2

main.py runs at power-on

No terminal, no python main.py. Whatever is named main.py on the board's filesystem runs automatically every time power arrives. That's what makes it an appliance.

Shift 3

The REPL is a live wire into the chip

Over the same USB cable you get a Python prompt running on the board. Poke pins, test ideas, read tracebacks — interactively.

Shift 4

Two kinds of reset

Soft reset (Ctrl+D in the REPL) restarts your program in place. Hard reset (the RESET button, or re-plugging) restarts the whole chip. You'll use both, constantly.

Shift 5

import searches the board

Not your project folder. If main.py needs a driver or a font, that file must already be on the board's flash — which is why deploys copy the whole src/ directory.

Bonus

Batteries (mostly) included

MicroPython ships hardware modules like machine (pins, SPI), network (Wi-Fi) and ntptime — plus familiar ones like time and json, slimmed down.

Kit

Two tools, installed once

Flasher

esptool

Espressif's utility that writes firmware images into the chip's flash. You'll use it exactly twice: erase_flash, then write_flash.

Daily driver

mpremote

MicroPython's official Swiss-army CLI: opens the REPL, copies files, mounts folders, resets the board. Every day-to-day command in this project wraps it.

$ uv tool install esptool
$ uv tool install mpremote

One-time surgery

Flash MicroPython onto the board

New boards ship with CircuitPython (Adafruit's cousin of MicroPython). This project uses MicroPython, so the first job is replacing the firmware. Do it once and forget it.

Download the firmware

Grab the latest Standard .bin from the ESP32_GENERIC_S3 download page.

Enter bootloader mode

Hold D0, tap RESET, release D0. The screen goes dark and the board re-appears as a bare ROM bootloader, ready to accept firmware. (This is why D0 is also labeled BOOT.)

Erase, then write

Find your serial port, wipe the old firmware, and write the new one at address 0:

# macOS: ls /dev/cu.usbmodem*   ·   Linux: ls /dev/ttyACM*
$ esptool.py --chip esp32s3 --port /dev/cu.usbmodem1101 erase_flash
$ esptool.py --chip esp32s3 --port /dev/cu.usbmodem1101 --baud 460800 \
      write_flash 0 ESP32_GENERIC_S3-*.bin

Press RESET

The board reboots into MicroPython. Nothing shows on screen yet — there's no program installed. The proof of life is in the REPL, next.

Two traps

Don't download the “Octal-SPIRAM” build — this board has quad PSRAM and the octal build won't boot. And note the write address for the ESP32-S3 is 0, not the 0x1000 you'll see in older ESP32 tutorials.

First contact

Say hello over the REPL

Connect to the board and you're typing Python into the chip. Light the NeoPixel — note how the power-gate pin from chapter 01 shows up immediately:

$ mpremote
Connected to MicroPython at /dev/cu.usbmodem1101
>>> print("hello from the board")
hello from the board
>>> import machine, neopixel
>>> machine.Pin(21, machine.Pin.OUT, value=1)   # switch on the NeoPixel's power rail
>>> np = neopixel.NeoPixel(machine.Pin(33), 1)
>>> np[0] = (0, 40, 0); np.write()              # and it glows green

Three keystrokes run the whole show in the REPL:

Ctrl + C

Interrupt the running program and drop to the prompt — your escape hatch from any loop.

Ctrl + D

Soft reset: re-runs boot.py then main.py without cutting power.

Ctrl + ]

Exit mpremote and hand the serial port back to your terminal.

Field tip

Only one program at a time can hold the serial port. If mpremote says the port is busy or “could not enter raw repl”, close Thonny / other REPLs and retry.

Rhythm

The dev loop

1 · Edit on the laptop src/*.py in your editor, with autocomplete from stubs 2 · Copy to the board mpremote cp src/*.py : (wrapped as `make deploy`) 3 · It runs on boot main.py auto-starts; watch prints in the REPL tweak → repeat (seconds, not minutes)

Two shortcuts collapse the loop even further while you iterate:

No install

mpremote run file.py

Pipe one script straight into RAM and run it — nothing written to the board's flash. Great for quick experiments.

No copy at all

mpremote mount src

Make the board see your laptop folder as its filesystem. Edit locally, run import main in the REPL, repeat — zero copying while you iterate.

Go deeper

Bookmark these

Back · Chapter 01← Meet the board