MicroPython for Microcontrollers Module 1 – Getting started: boards, firmware, the REPL and mpremote
What MicroPython is and where it runs
How MicroPython runs Python 3 inside a microcontroller, what it keeps from CPython, its ports and support tiers, four boards to start on and sys.implementation.
What you will learn
- Explain how MicroPython runs Python source on a chip that has no operating system
- Predict which parts of a CPython program carry over and which need micropython-lib or a change
- Name the Tier 1 ports and say what a port's support tier promises
- Choose a board for this track from the Pico, Pico W, Pico 2 W and an ESP32 board
- Identify a running build from sys.implementation, including the architecture inside _mpy
What you’ll learn
What you will learn
- Explain how MicroPython runs Python source on a chip that has no operating system
- Predict which parts of a CPython program carry over and which need micropython-lib or a change
- Name the Tier 1 ports and say what a port's support tier promises
- Choose a board for this track from the Pico, Pico W, Pico 2 W and an ESP32 board
- Identify a running build from sys.implementation, including the architecture inside _mpy
On this page
A Raspberry Pi Pico has 264 KB of RAM, no operating system and no screen, yet you can type 2 + 3 into it over a
USB cable and it answers 5. MicroPython makes that possible: an implementation of Python 3 small enough to live
inside a microcontroller: A computer on one chip: a processor, a few hundred kilobytes of RAM, flash memory or an interface to it, and pins for the outside world. It runs one program, often with no operating system., which its project says can be as small as 256 KiB of flash
and 16 KiB of RAM.
This lesson shows what runs on the chip when your Python runs there, which parts of the Python you know carry over, which boards and ports MicroPython supports and how well, and how to read the one object that names the build a board is running. By the end you will have picked a board for this track, and you will be able to decode the number that every build reports about the code it can load.
MicroPython compiles and runs your Python on the chip itself
On your computer, Python is a program that the operating system starts. A Pico has no operating system, so MicroPython brings everything with it. Its firmware: The program stored in a microcontroller's flash that starts when it powers on. MicroPython's firmware holds the interpreter, its built-in modules and the drivers for one board., one file you install on the board, holds a compiler, a virtual machine that runs bytecode: Compact instructions that MicroPython's compiler makes from Python source and its virtual machine runs. MicroPython's bytecode format is not CPython's., a memory manager with a garbage collector, the built-in modules and the C code that drives that board’s hardware. MicroPython’s glossary puts it plainly: on a board without an operating system, MicroPython works like a small operating system itself, running your programs and giving you an interactive prompt. The ESP32 is the one exception among the boards of this track: its port runs on ESP-IDF, Espressif’s development framework, which is built on FreeRTOS. Your programs see the same MicroPython on both.
Follow a two-line main.py that switches GPIO 0 on, from the text in the board’s flash to the pin:
The drawing shows the whole scene; its 7 steps are listed below.
Every step as text
- The source. After a reset the firmware finds main.py in the board's flash and opens it. The file holds two lines of plain text: from machine import Pin, and Pin(0, Pin.OUT).on().
- Compile on the chip. MicroPython's compiler is part of the firmware, so the board turns the text into bytecode itself. No computer is needed once the file is on the board.
- Bytecode. The bytecode is a compact list of instructions, built in RAM. Both CPython and MicroPython compile to bytecode, but the two formats are different.
- A precompiled module. A module that main.py imports can be a .mpy file, made on your computer with mpy-cross: it is bytecode already, so the board skips the compiler and the RAM it needs. main.py itself always starts from its text.
- The virtual machine. The virtual machine runs the instructions one after another. One of them looks up Pin in the machine module and calls it with 0 and Pin.OUT.
- Into C. machine.Pin is written in C, in the code of the rp2 port. It sets GPIO 0 to be an output and then drives it high, by writing the chip's registers.
- The pin changes. GPIO 0 is now high, so an LED wired to it through a resistor lights. On a Pico every step from the text to the pin happened on the microcontroller, with no operating system underneath.
If you come from Arduino, notice the second step: the board compiles the source itself, so there is no build step on your computer. The cost is RAM. MicroPython’s guide to small chips says the bytecode of every imported module is kept in RAM, and the compiler needs RAM of its own while it works, so a program with many modules can run out of memory just by importing them. That is why the same code can reach the virtual machine in three ways:
| How the code is stored | Where it becomes bytecode | What it costs the board |
|---|---|---|
A .py file on the board’s filesystem |
On the board, at every import | Compiler time and RAM, then the bytecode in RAM |
| An .mpy file: A file of precompiled MicroPython bytecode, made with mpy-cross on a computer, that a board imports like a .py module without compiling it. It may also hold machine code for one architecture. on the filesystem | On your computer, with mpy-cross | No compiling on the board; the bytecode still goes to RAM |
| A frozen module: A module compiled into the firmware image when the firmware is built. On most ports its bytecode runs straight from flash, so it takes no RAM for the code. inside the firmware | When the firmware is built | On most ports the bytecode runs straight from flash |
You will write plain .py files for most of this track; precompiling and freezing come back in the module on
packages and firmware, when a program outgrows its RAM.
Key idea
MicroPython’s firmware carries a compiler, a virtual machine and the board’s drivers in one file, so the board turns your .py source into bytecode and runs it with nothing else underneath.
Checkpoint
Check: Compiling and running on the chip
Your Python carries over, but CPython’s standard library does not
Take a CPython script that reads a sensor value, formats it with an f-string, stores it as JSON and logs the time. On
MicroPython the language part of it carries over almost untouched. The project’s README says MicroPython implements
the entire Python 3.4 syntax, the async and await keywords of Python 3.5 and selected features of later versions,
with the core types you use every day: str, bytes, bytearray, tuple, list, dict, set, classes and
instances. The library part is different, and that is where most porting work goes:
| In your CPython program | On a MicroPython build |
|---|---|
Python 3.4 syntax, plus async, await and later features |
The 3.4 syntax and async/await; the differences pages list the status of each 3.5 to 3.10 feature |
| The standard library | 29 of its modules, each cut down to its core, for example json, time, re, os and asyncio |
Other standard modules (datetime, logging, pathlib) |
Not built in. micropython-lib has versions of them, installed one at a time with mip |
| Packages from PyPI | mip does not use PyPI; a pure-Python package copied by hand works only if all it imports exists |
| Nothing for hardware | 12 MicroPython modules, among them machine, network and bluetooth |
The library reference calls the 29 cut-down modules “micro-ified”: each keeps the core of its CPython namesake, but
only part of its functions. It also warns that a port, or one firmware file, may leave out a documented module
or function to save space. The 12 MicroPython modules are the reason to use it at all: machine drives pins, timers
and buses, and network joins Wi-Fi on boards that have a radio.
To see what a build has, type help('modules') at its prompt: on the ports that support it, it lists every module
built into the firmware, frozen ones included. The next module of this track, on the differences from CPython, works
through the rest one probe at a time.
Expecting pip install to work on the board
pip install requests installs into the Python on your computer, never onto a board. For a board, install a
micropython-lib package with mip, for example mpremote mip install datetime: it copies the package, as
precompiled .mpy files, into the first folder of sys.path that ends in /lib, which is /lib on a Pico.
Checkpoint
Check: What carries over from CPython
Firmware is built for one board, and each port has a support tier
MicroPython’s download page lists different files for the Pico and the Pico W, although both boards carry the same
RP2040 chip. The Pico W has a Wi-Fi and Bluetooth chip beside the RP2040, and only its firmware switches on the
network module and the driver for that chip, as the board’s configuration in the source shows. That is the general
rule: the glossary says firmware is usually built per board: A circuit board that carries a microcontroller and the parts around it, such as the Raspberry Pi Pico. MicroPython firmware is usually built for one board, with its drivers and pin names., because it holds the board’s
drivers and pin names as well as code for the chip.
The code that adapts MicroPython to one family of chips, or one operating system, is a port: MicroPython adapted to one family of chips or one operating system, such as rp2 for the RP2040 and RP2350, esp32, or unix. Each port builds the firmware for its boards.. The rp2 port covers the RP2040 and the RP2350, the esp32 port Espressif’s ESP32 chips, and the unix and windows ports run MicroPython as a program on a computer. Since version 1.27, each port belongs to a support tier: One of MicroPython's four levels of care for a port. Tier 1 ports such as rp2, esp32 and unix are the most actively maintained, tested and documented. that says how much care it gets:
| Tier | Ports | What the tier promises |
|---|---|---|
| 1 | esp32, mimxrt, rp2, samd, stm32, unix, windows | All of Tier 2, plus bug reports handled first, a patch release for any regression and port-specific documentation |
| 2 | alif, embed, nrf, psoc-edge, renesas-ra, webassembly, zephyr | Actively maintained, a stable Python API, new features, and tests on hardware before each release |
| 3 | cc3200, esp8266, pic16bit | Built and tested by the project’s continuous integration, but not actively maintained |
| M | bare-arm, minimal, qemu | Kept for maintenance, development and testing of MicroPython itself |
The two ports this track uses, rp2 and esp32, are both Tier 1, and so is unix, which runs on your computer. Answer three questions about your projects and the table below marks which of the four boards of this track fit them:
Answer the questions above: the table then marks what fits your case and says why the rest does not.
| Board | Wi-Fi and Bluetooth | Installing firmware | RAM on the chip | Processor |
|---|---|---|---|---|
| Raspberry Pi Pico | Poor none: the board has no radio | Good hold BOOTSEL, plug in, copy the .uf2 file to the drive | Mixed 264 KB of SRAM (RP2040) | Good two Arm Cortex-M0+ cores, 125 MHz under MicroPython |
| Raspberry Pi Pico W | Good Wi-Fi and Bluetooth LE | Good the same .uf2 copy, with the file for the Pico W | Mixed 264 KB of SRAM (RP2040) | Good the same RP2040 as the Pico |
| Raspberry Pi Pico 2 W | Good Wi-Fi and Bluetooth LE | Good the same .uf2 copy; the drive is called RP2350 | Good 520 KB of SRAM (RP2350) | Good two Arm Cortex-M33 or Hazard3 RISC-V cores, up to 150 MHz |
| ESP32 board (WROOM) | Good Wi-Fi and Bluetooth in the chip itself | Mixed esptool over the serial port: erase, then write at 0x1000 | Good 528 KB, part of it kept by the system | Good two Xtensa LX6 cores, up to 240 MHz |
How the answers decide
- “Will your projects use Wi-Fi or Bluetooth?” Yes, some of them: rules out Raspberry Pi Pico. The Pico has no radio at all. The Pico W, the Pico 2 W and an ESP32 board all have Wi-Fi and Bluetooth LE, which the networking module of this track uses.
- “Do you want to install MicroPython by copying one file, with no extra tool on your computer?” Yes, keep it to a file copy: adds a caveat to ESP32 board (WROOM). An ESP32 is installed over its serial port with Espressif's esptool, by erasing the flash and writing the image at 0x1000. The Pico boards only need a .uf2 file copied to a USB drive.
- “Will your programs keep large buffers in RAM, such as a display frame or a long log?” Yes, large buffers: adds a caveat to Raspberry Pi Pico and Raspberry Pi Pico W. The RP2040 of the Pico and the Pico W has 264 KB of SRAM. The RP2350 of the Pico 2 W has 520 KB, and an ESP32 has 528 KB, part of which its system keeps.
In short: a Pico W or a Pico 2 W follows the whole track and installs with a file copy; a plain Pico does everything except the networking module; an ESP32 board brings the fastest cores and installs with a command-line tool. What only one chip can do, such as PIO on the RP2040 and RP2350 or partitions on the ESP32, has a module of its own near the end of the track.
One board, kept for the whole track
Choose by the radio first. The machine module gives pins, timers and buses the same Python API on every port,
and only pin numbers and a few details differ, so what you learn on one board carries over to the others.
Checkpoint
Check: Ports, boards and support tiers
sys.implementation names the build a board is running
Two boards can run the same release and still differ: one has a radio driver, another emits machine code for a
different processor. The object sys.implementation tells you which build you are talking to, and the MicroPython
documentation recommends it as the way to tell MicroPython apart from other Pythons. On a Pico running 1.29.0, its
fields hold these values, which the firmware takes from its own configuration:
| Field | On a Pico, 1.29.0 | On an ESP32 board, 1.29.0 | What it is |
|---|---|---|---|
name |
'micropython' |
'micropython' |
The implementation |
version |
(1, 29, 0, '') |
(1, 29, 0, '') |
The release; the fourth part is 'preview' for a preview build |
_machine |
'Raspberry Pi Pico with RP2040' |
'Generic ESP32 module with ESP32' |
The board and the chip |
_build |
'RPI_PICO' |
'ESP32_GENERIC' |
The board name of the firmware file |
_thread |
'unsafe' |
'GIL' |
Threads exist; whether a global lock protects shared objects |
_mpy |
4870 |
11014 |
The .mpy format and the machine code this build can load |
The last field is a number with three fields packed into it. MicroPython’s page on .mpy files lays out the bits:
the lowest 8 hold the .mpy version, the next 2 the sub-version, and bits 10 to 13 the native architecture, an index
into a fixed list of instruction sets. Step through the decoding of the Pico’s 4870:
Program · decode/decode_one.py
ARCH = [None, "x86", "x64",
"armv6", "armv6m",
"armv7m", "armv7em",
"armv7emsp", "armv7emdp",
"xtensa", "xtensawin",
"rv32imc", "rv64imc"]
mpy = 4870
version = mpy & 0xFF
sub = (mpy >> 8) & 3
code = (mpy >> 10) & 0x0F
arch = ARCH[code]
flags = mpy >> 16
print("version:", version)
print("sub-version:", sub)
print("arch:", arch)
print("arch flags:", flags) Variables
- changed in this step
Printed so far
Nothing printed yetversion: 6 sub-version: 3 arch: armv6m arch flags: 0
Every step was recorded from a run with Python 3.14.8 on macOS 26 arm64.
The drawing shows the variables when the program ends, and the program's 12 steps are listed below.
Change the program and run it here. Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
Every step as text
- Start. No line has run yet, so there are no variables. Python starts with line 1.
- Line 1.
ARCH = [None, "x86", "x64", "armv6", "armv6m", "armv7m", "armv7em", "armv7emsp", "armv7emdp", "xtensa", "xtensawin", "rv32imc", "rv64imc"]makesARCHa list of 13 items. - Line 8.
mpy = 4870setsmpyto4870. - Line 9.
version = mpy & 0xFFsetsversionto6. 4870 is 0x1306 in hexadecimal.& 0xFFkeeps the lowest 8 bits, 0x06, so the board loads .mpy files of version 6. - Line 10.
sub = (mpy >> 8) & 3setssubto3.>> 8drops those 8 bits and& 3keeps the next 2: 0x13 & 3 is 3, the sub-version. Version 6.3 is the format of every release from v1.23.0 on. - Line 11.
code = (mpy >> 10) & 0x0Fsetscodeto4. Key point Bits 10 to 13 hold the native architecture.4870 >> 10is 4, and& 0x0Fkeeps those 4 bits, so the code is 4. - Line 12.
arch = ARCH[code]setsarchto'armv6m'. Code 4 is position 4 of the list, armv6m, the instruction set of the RP2040's Cortex-M0+ cores. Machine code inside a .mpy file must be armv6m to load here. - Line 13.
flags = mpy >> 16setsflagsto0. Nothing is left above bit 15, so this build has no architecture flags. RISC-V builds use these bits to list the processor extensions they need. - Line 14.
print("version:", version)printsversion: 6. - Line 15.
print("sub-version:", sub)printssub-version: 3. - Line 16.
print("arch:", arch)printsarch: armv6m. - Line 17.
print("arch flags:", flags)printsarch flags: 0. That was the last line, so the program ends. The same four lines decode any board's number. Press Edit, put the value your board printed in place of 4870, and run it.
So the Pico loads .mpy files of version 6.3, and machine code inside them must be armv6m. The architecture matters
the day you copy a precompiled module from one board to another: a .mpy file that holds machine code for one
architecture fails to import on a board of another with ValueError('incompatible .mpy arch'). The next program
works out the number for four builds of this track’s release from the same layout, and decodes each one back. Like
every program in this track, it shows under the code what it printed when it ran. The programs on these pages are the
half that runs on your computer, as mpremote and editors do, and they ran on Python 3.14.8; code for the board is what
you type at its prompt or save on it, and a program that stands in for a board says so:
"""The sys.implementation._mpy number of four MicroPython 1.29.0 builds, worked out and decoded.
The number packs three fields: the .mpy version in bits 0 to 7, the sub-version in bits 8 and 9,
and the native architecture in bits 10 to 13 (MicroPython's ".mpy files" page, py/persistentcode.h).
"""
ARCH = (None, "x86", "x64", "armv6", "armv6m", "armv7m", "armv7em",
"armv7emsp", "armv7emdp", "xtensa", "xtensawin", "rv32imc", "rv64imc")
MPY_VERSION = 6 # .mpy 6.3 is the format of v1.23.0 and every later release
MPY_SUB_VERSION = 3
def encode(arch):
"""The _mpy number of a build whose compiler emits machine code for arch (None: no machine code)."""
return MPY_VERSION | (MPY_SUB_VERSION << 8) | (ARCH.index(arch) << 10)
def decode(value):
"""The .mpy version, sub-version and native architecture that an _mpy number holds."""
return value & 0xFF, (value >> 8) & 3, ARCH[(value >> 10) & 0x0F]
BUILDS = [
("RPI_PICO", "armv6m"), # RP2040: two Arm Cortex-M0+ cores
("ESP32_GENERIC", "xtensawin"), # ESP32: two Xtensa LX6 cores
("unix on x86-64", "x64"),
("unix on 64-bit Arm", None), # MicroPython has no emitter for 64-bit Arm
]
print(f"{'Build':<20}{'_mpy':>6} {'hex':>6} .mpy native code")
for name, arch in BUILDS:
value = encode(arch)
version, sub, found = decode(value)
print(f"{name:<20}{value:>6} {value:#06x} {version}.{sub} {found or 'none'}")
# Put the number your own board printed for sys.implementation._mpy here, then run again.
mine = 4870
version, sub, found = decode(mine)
print(f"\nYour board: {mine} is .mpy {version}.{sub}, native code {found or 'none'}") Output
Build _mpy hex .mpy native code RPI_PICO 4870 0x1306 6.3 armv6m ESP32_GENERIC 11014 0x2b06 6.3 xtensawin unix on x86-64 2822 0x0b06 6.3 x64 unix on 64-bit Arm 774 0x0306 6.3 none Your board: 4870 is .mpy 6.3, native code armv6m
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 build_table.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
Every build of the release says .mpy 6.3, because the format belongs to the release, and only the architecture
changes: armv6m for the Pico’s Cortex-M0+ cores, xtensawin for the ESP32, x64 for the unix port on a PC, and none at
all for the unix port on a 64-bit Arm computer, for which MicroPython has no native code emitter.
Decode your own board
Open your board’s prompt (the next lesson shows how), type import sys and then sys.implementation._mpy. Press
Edit on the program above, put your number in place of 4870 on the line mine = 4870, and run it.
Not every build has all six fields. _build was added in version 1.25.0 and _thread in 1.26.0, so a board still
on 1.24 has neither, and _mpy is optional too: a very small port without .mpy support leaves it out. Read these
fields with getattr(sys.implementation, "_build", None), so that a missing one is not an error.
Testing sys.platform to detect MicroPython
sys.platform names the port or the operating system: 'rp2' on a Pico, and 'linux' for the unix port on Linux,
the same value CPython gives there. It cannot tell the two Pythons apart, so test
sys.implementation.name == 'micropython' instead, and keep sys.platform for telling ports apart.
Checkpoint
Check: Reading sys.implementation and _mpy
Practise: describe a build
Write the function that turns a sys.implementation object into the four facts this lesson decoded: the version,
the build name, the .mpy version and the architecture. The sample tests use the builds of the tables above, a
board too old to have _build, and a RISC-V build whose number carries flag bits above bit 15:
Exercise · Easy · Python
Describe the build a sys.implementation object stands for
Write describe_build(impl) in build.py. impl is an object like sys.implementation: it always has name and version, a tuple such as (1, 29, 0, ''), and it may have _build and _mpy. Return a dictionary with four entries:
"version": the version as text,"1.29.0". When the fourth part of the tuple is"preview", add-preview:(1, 30, 0, "preview")gives"1.30.0-preview"."build": the value of_build, orNonewhen the object has no_build(builds older than 1.25.0)."mpy": the.mpyversion and sub-version from_mpyas a pair,(6, 3)for 4870, orNonewithout_mpy."arch": the name of the native architecture in bits 10 to 13 of_mpy, fromARCH;Nonewhen the code is 0 or there is no_mpy, and"unknown"for a code past the end ofARCH.
Bits 16 and up of _mpy hold architecture flags; they do not change any of the four entries. For an object whose name is not "micropython", raise ValueError.
- A Pico on 1.29.0 (
_build"RPI_PICO",_mpy4870) gives{"version": "1.29.0", "build": "RPI_PICO", "mpy": (6, 3), "arch": "armv6m"}. - The unix port on a 64-bit Arm computer (
_mpy774) gives"arch": None.
Starter code · build.py
ARCH = (None, "x86", "x64", "armv6", "armv6m", "armv7m", "armv7em",
"armv7emsp", "armv7emdp", "xtensa", "xtensawin", "rv32imc", "rv64imc")
def describe_build(impl):
"""Describe the MicroPython build that a sys.implementation-like object stands for."""
return {} The sample tests · test_build.py
from types import SimpleNamespace
from build import describe_build
def impl(**fields):
return SimpleNamespace(name="micropython", **fields)
def test_pico():
"""describes a Pico on 1.29.0"""
pico = impl(version=(1, 29, 0, ""), _build="RPI_PICO", _mpy=4870)
assert describe_build(pico) == {"version": "1.29.0", "build": "RPI_PICO", "mpy": (6, 3), "arch": "armv6m"}
def test_esp32_and_unix():
"""finds xtensawin, x64 and no native code"""
assert describe_build(impl(version=(1, 29, 0, ""), _build="ESP32_GENERIC", _mpy=11014))["arch"] == "xtensawin"
assert describe_build(impl(version=(1, 29, 0, ""), _build="standard", _mpy=2822))["arch"] == "x64"
assert describe_build(impl(version=(1, 29, 0, ""), _build="standard", _mpy=774))["arch"] is None
def test_older_build_without_build_name():
"""gives None for a build without _build"""
old = impl(version=(1, 24, 1, ""), _mpy=4870)
assert describe_build(old) == {"version": "1.24.1", "build": None, "mpy": (6, 3), "arch": "armv6m"}
def test_no_mpy():
"""gives None for mpy and arch without _mpy"""
tiny = impl(version=(1, 29, 0, ""), _build="minimal")
assert describe_build(tiny) == {"version": "1.29.0", "build": "minimal", "mpy": None, "arch": None}
def test_preview():
"""marks a preview version"""
assert describe_build(impl(version=(1, 30, 0, "preview"), _mpy=774))["version"] == "1.30.0-preview"
def test_flag_bits_ignored():
"""reads the architecture under the flag bits of a RISC-V build"""
riscv = impl(version=(1, 29, 0, ""), _build="RPI_PICO2-RISCV", _mpy=6 | 3 << 8 | 11 << 10 | 1 << 16)
assert describe_build(riscv) == {"version": "1.29.0", "build": "RPI_PICO2-RISCV", "mpy": (6, 3), "arch": "rv32imc"}
def test_unknown_code():
"""gives "unknown" for a code past the end of ARCH"""
assert describe_build(impl(version=(1, 29, 0, ""), _mpy=6 | 3 << 8 | 15 << 10))["arch"] == "unknown"
def test_not_micropython():
"""refuses another Python"""
cpython = SimpleNamespace(name="cpython", version=(3, 14, 8, "final"))
try:
describe_build(cpython)
except ValueError:
return
raise AssertionError("expected ValueError for cpython") A hint
Start with the name check, then build the version text with ".".join(str(n) for n in impl.version[:3]). Read the optional fields with getattr(impl, "_mpy", None), so a missing field gives None instead of an error. With the number in hand, value & 0xFF is the version, (value >> 8) & 3 the sub-version and (value >> 10) & 0x0F the architecture code, an index into ARCH.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Last, test yourself on the four ideas of this lesson. The self-check picks the idea you are weakest on first, and it explains each choice you make on the spot:
Practice that adapts to you
Practise this lesson
13 questions about 4 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up (fresher): What is MicroPython, and how is it different from CPython?
MicroPython is an implementation of Python 3 for microcontrollers and other small systems, written in C and shipped as firmware for each board. The firmware holds a compiler, a bytecode virtual machine, a garbage-collected heap and the built-in modules, so the board compiles and runs .py files itself, with a few hundred kilobytes of RAM and, on a chip such as the Pico’s, no operating system at all. The language is close to CPython’s: the whole Python 3.4 syntax, async and await, and selected later features. The libraries differ: 29 standard modules exist only in cut-down form, others such as datetime come from micropython-lib through mip, and MicroPython adds modules of its own, such as machine for pins, timers and buses.
Two longer interview questions of this module start from this lesson:
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher to mid
A teammate's precompiled module, sensor.mpy, imports fine on their Pico but fails on your ESP32 board with ValueError('incompatible .mpy arch'). Both boards run MicroPython 1.29.0. What does the error tell you, how do you confirm the cause on both boards, and how do you ship one module that works on both?
-
Mid to senior
Your product runs one MicroPython app on a Pico, a Pico W and an ESP32 board. How does the code tell which board and build it runs on, and how do you stop a feature that one build lacks, such as networking, from crashing the app in the field?
Flashcards · 7 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
What does MicroPython's firmware carry, so that a chip with no operating system can run Python?
A compiler, a virtual machine for bytecode, a memory manager with a garbage collector, the built-in modules and the C drivers of one board. The board turns your .py source into bytecode itself.
-
Where does code become bytecode as a .py file, an .mpy file and a frozen module?
A .py file is compiled on the board at every import; an .mpy file was compiled on your computer by mpy-cross; a frozen module was compiled when the firmware was built and, on most ports, runs straight from flash.
-
Why does pip install never put a package on a board?
pip installs into the Python on your computer. A board gets micropython-lib packages through mip, for example mpremote mip install datetime, which copies .mpy files into /lib on a Pico.
-
Which support tier are the rp2, esp32 and unix ports in, and what does it promise?
Tier 1: on top of Tier 2's active maintenance, stable API and tests on hardware, bug reports are handled first, a regression gets a patch release and the port has documentation of its own.
-
How does code tell MicroPython from CPython?
sys.implementation.name == 'micropython'. sys.platform names the port or the system: 'rp2' on a Pico, but 'linux' for the unix port on Linux, as CPython says there, so it cannot tell the two apart.
-
What three things does sys.implementation._mpy pack into one number?
The .mpy version in the lowest 8 bits, the sub-version in the next 2 and the native architecture in bits 10 to 13. A Pico reports 4870: .mpy 6.3 with armv6m machine code.
-
Why do the Pico and the Pico W have different firmware files when both carry an RP2040?
Firmware is built per board. Only the Pico W's file switches on the network module and the driver for the Wi-Fi and Bluetooth chip beside its RP2040.
Key takeaways
- MicroPython’s firmware holds a compiler, a bytecode virtual machine and the board’s drivers, so the board compiles and runs your .py files itself, on a Pico with no operating system at all.
- The language carries over from CPython (the Python 3.4 syntax, async and await, selected later features), while the standard library shrinks to 29 cut-down modules, with more in micropython-lib.
- Firmware is built per board, and a port’s support tier says how much care it gets: rp2, esp32 and unix, the ports of this track, are all Tier 1.
- A Pico W or a Pico 2 W follows the whole track; a plain Pico has no radio, and an ESP32 board brings more RAM and installs with esptool instead of a file copy.
sys.implementationnames the running build, and its_mpynumber packs the.mpyversion, the sub-version and the native architecture: 4870 means.mpy6.3 with armv6m machine code.
Terms in this lesson
- .mpy file
- A file of precompiled MicroPython bytecode, made with mpy-cross on a computer, that a board imports like a .py module without compiling it. It may also hold machine code for one architecture.
- board
- A circuit board that carries a microcontroller and the parts around it, such as the Raspberry Pi Pico. MicroPython firmware is usually built for one board, with its drivers and pin names.
- bytecode
- Compact instructions that MicroPython's compiler makes from Python source and its virtual machine runs. MicroPython's bytecode format is not CPython's.
- firmware
- The program stored in a microcontroller's flash that starts when it powers on. MicroPython's firmware holds the interpreter, its built-in modules and the drivers for one board.
- frozen module
- A module compiled into the firmware image when the firmware is built. On most ports its bytecode runs straight from flash, so it takes no RAM for the code.
- microcontroller
- A computer on one chip: a processor, a few hundred kilobytes of RAM, flash memory or an interface to it, and pins for the outside world. It runs one program, often with no operating system.
- port
- MicroPython adapted to one family of chips or one operating system, such as rp2 for the RP2040 and RP2350, esp32, or unix. Each port builds the firmware for its boards.
- support tier
- One of MicroPython's four levels of care for a port. Tier 1 ports such as rp2, esp32 and unix are the most actively maintained, tested and documented.
References
- MicroPython documentation (v1.29.0) (MicroPython project)
- MicroPython language and implementation (MicroPython project)
- MicroPython differences from CPython (MicroPython project)
- MicroPython libraries (MicroPython project)
- Glossary (MicroPython project)
- MicroPython on microcontrollers (MicroPython project)
- The MicroPython project (README at v1.29.0) (MicroPython project)
- MicroPython Support Tiers (MicroPython project)
- MicroPython v1.27.0 release notes (port tiers) (MicroPython project)
- General information about the RP2xxx port (MicroPython project)
- Quick reference for the RP2 (MicroPython project)
- General information about the ESP32 port (MicroPython project)
- MicroPython downloads: Pico (RPI_PICO) (MicroPython project)
- MicroPython downloads: Pico W (RPI_PICO_W) (MicroPython project)
- MicroPython downloads: Pico 2 W (RPI_PICO2_W) (MicroPython project)
- MicroPython downloads: ESP32 / WROOM (ESP32_GENERIC) (MicroPython project)
- Board configuration of the Pico W (ports/rp2/boards/RPI_PICO_W, v1.29.0) (MicroPython project)
- micropython-lib (MicroPython project)
- sys – system specific functions (MicroPython project)
- MicroPython .mpy files (MicroPython project)
- The .mpy header and native architectures (py/persistentcode.h, v1.29.0) (MicroPython project)
- sys.implementation in the source (py/modsys.c, v1.29.0) (MicroPython project)
- sys: System-specific parameters and functions (sys.platform) (Python Software Foundation)
- MicroPython differences: Python 3.6 (MicroPython project)
- Package management (mip) (MicroPython project)
- MicroPython license information (MicroPython project)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress