Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

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.

  • Beginner
  • 20 minutes
  • Examples run with Python 3.14.8 and Pyodide 314.0.7
  • By MySmartCoPilot
Free lesson, Pro answers: 20 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

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:

Interactive · stage by stage From a line of Python to a pin that changes Step through what the firmware does with a main.py that switches GPIO 0 on. The side arrow is the path a precompiled .mpy file takes.
From a line of Python to a pin that changes, step 7 of 7. Showing main.py on the board, Compiler, Bytecode, Virtual machine, machine.Pin, GPIO 0 (high). In focus: GPIO 0..mpy skips the compilermain.py on the boardPython text in flashCompilerinside the firmwareBytecodekept in RAMVirtual machineruns the bytecodemachine.PinC code of the rp2 portGPIO 0high

The drawing shows the whole scene; its 7 steps are listed below.

Every step as text
  1. 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().
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. 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

  1. Question 1 of 2 You copy a main.py file to a Pico and reset it. Where is the Python source turned into bytecode?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: On the Pico, by the compiler inside the MicroPython firmware

    MicroPython's firmware holds a compiler as well as the virtual machine. When main.py runs, the board compiles it to bytecode in RAM and then runs that bytecode, with no build step on your computer.

  2. Question 2 of 2 An app runs out of memory while it imports its modules. What does precompiling them to .mpy files with mpy-cross save on the board?

    Choose one answer.

    A hint

    Think about what the board no longer has to do at import.

    Show the answer: Question 2 of 2

    Answer: The RAM and the time the compiler needs at import; the bytecode itself is still kept in RAM

    MicroPython's guide to small chips names precompiling as the fix when there is not enough RAM left to compile the next module: the compiler and its working memory are no longer needed. Freezing the bytecode into the firmware saves more, because it then runs from flash.

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

  1. Question 1 of 2 Which of these imports fail on a Pico that runs a fresh 1.29.0 firmware, with nothing installed? Choose every one.

    Choose every answer that is right.

    Show the answer: Question 1 of 2

    Answer:

    • import datetime
    • import pathlib

    The library reference lists 29 standard modules in cut-down form, json and asyncio among them, and 12 modules of MicroPython's own, such as machine. datetime and pathlib are not built in; micropython-lib has versions of both.

  2. Question 2 of 2 How do you list the built-in modules of the build on your board?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Type help('modules') at the board's prompt

    The library reference suggests help('modules') at the REPL to see which built-in libraries a build can import, because a port or one firmware file may leave out documented modules.

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:

Interactive · answer and compare Which board should you start this track on? Answer the three questions about your projects. Each answer rules a board out or adds a caveat, with the reason.
Will your projects use Wi-Fi or Bluetooth?
Do you want to install MicroPython by copying one file, with no extra tool on your computer?
Will your programs keep large buffers in RAM, such as a display frame or a long log?

Answer the questions above: the table then marks what fits your case and says why the rest does not.

The Pico, Pico W, Pico 2 W and an ESP32 board against radios, installing firmware, RAM and processor
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

  1. Question 1 of 2 Someone installs the RPI_PICO firmware file on a Pico W. What happens?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: MicroPython runs, but import network fails, because only the Pico W firmware includes the network module and the Wi-Fi chip's driver

    Firmware is built per board. The Pico W's board configuration turns on the network module and the driver of its Wi-Fi chip; the Pico's does not, so on a Pico W the Pico firmware runs without any networking.

  2. Question 2 of 2 What does a Tier 1 port promise that a Tier 2 port does not?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Bug reports handled first, a patch release for regressions and port-specific documentation

    The support tiers table gives Tier 2 a stable API, new features and tests on hardware for releases. Tier 1 adds prioritised bug reports, a patch release for any regression and documentation of its own.

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:

Interactive · line by line Decoding the _mpy number a Pico reports Step through the decoding of 4870. The lit line has just run, and each new variable appears with its value.

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

After line 17: ARCH is [None, 'x86', 'x64', 'armv6', 'armv6m', 'armv7m', 'armv7em', 'armv7emsp', 'armv7emdp', 'xtensa', 'xtensawin', 'rv32imc', …]; mpy is 4870; version is 6; sub is 3; code is 4; arch is 'armv6m'; flags is 0.ARCHNone0'x86'1'x64'2'armv6'3'armv6m'4'armv7m'5'armv7em'6'armv7emsp'7'armv7emdp'8'xtensa'9'xtensawin'10'rv32imc'11'rv64imc'12mpy4870version6sub3code4arch'armv6m'flags0
  • changed in this step

Printed so far

version: 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.

Line run 17
Next line end
Every step as text
  1. Start. No line has run yet, so there are no variables. Python starts with line 1.
  2. Line 1. ARCH = [None, "x86", "x64", "armv6", "armv6m", "armv7m", "armv7em", "armv7emsp", "armv7emdp", "xtensa", "xtensawin", "rv32imc", "rv64imc"] makes ARCH a list of 13 items.
  3. Line 8. mpy = 4870 sets mpy to 4870.
  4. Line 9. version = mpy & 0xFF sets version to 6. 4870 is 0x1306 in hexadecimal. & 0xFF keeps the lowest 8 bits, 0x06, so the board loads .mpy files of version 6.
  5. Line 10. sub = (mpy >> 8) & 3 sets sub to 3. >> 8 drops those 8 bits and & 3 keeps the next 2: 0x13 & 3 is 3, the sub-version. Version 6.3 is the format of every release from v1.23.0 on.
  6. Line 11. code = (mpy >> 10) & 0x0F sets code to 4. Key point Bits 10 to 13 hold the native architecture. 4870 >> 10 is 4, and & 0x0F keeps those 4 bits, so the code is 4.
  7. Line 12. arch = ARCH[code] sets arch to '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.
  8. Line 13. flags = mpy >> 16 sets flags to 0. 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.
  9. Line 14. print("version:", version) prints version: 6.
  10. Line 15. print("sub-version:", sub) prints sub-version: 3.
  11. Line 16. print("arch:", arch) prints arch: armv6m.
  12. Line 17. print("arch flags:", flags) prints arch 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 _mpy number of four builds, worked out and decoded Python · builds/build_table.py
"""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

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

  1. Question 1 of 2 An ESP32 build reports sys.implementation._mpy = 11014, which is 0x2B06. What is its architecture code, (11014 >> 10) & 0x0F?

    Type a number.

    A hint

    0x2B06 >> 8 is 0x2B. The architecture sits above the 2 bits of the sub-version.

    Show the answer: Question 1 of 2

    Answer: 10 (architecture code)

    11014 >> 10 is 10, and 10 & 0x0F stays 10. Position 10 of the list is xtensawin, the windowed instruction set of the ESP32's Xtensa LX6 cores. The low byte, 6, and the sub-version, 3, are the same as on every 1.29.0 build.

  2. Question 2 of 2 A .mpy file that holds armv6m machine code imports fine on a Pico. You copy it to an ESP32 board on the same release and import it. What happens?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: It fails with ValueError('incompatible .mpy arch'), because the ESP32 runs xtensawin machine code

    A .mpy file with machine code records its architecture, and a board loads it only if the architecture matches its own, armv6m for the Pico and xtensawin for the ESP32. A .mpy file of bytecode alone imports on both.

Number Base Converter Convert an _mpy number to binary and hexadecimal and see its bit fields.

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, or None when the object has no _build (builds older than 1.25.0).
  • "mpy": the .mpy version and sub-version from _mpy as a pair, (6, 3) for 4870, or None without _mpy.
  • "arch": the name of the native architecture in bits 10 to 13 of _mpy, from ARCH; None when the code is 0 or there is no _mpy, and "unknown" for a code past the end of ARCH.

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", _mpy 4870) gives {"version": "1.29.0", "build": "RPI_PICO", "mpy": (6, 3), "arch": "armv6m"}.
  • The unix port on a 64-bit Arm computer (_mpy 774) 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.

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.

  1. Question 1 of 13 What does a MicroPython firmware file for a board contain?

    Choose one answer.

    Show the answer: Question 1 of 13

    Answer: A compiler, a bytecode virtual machine, a garbage-collected heap, the built-in modules and the board's drivers

    The firmware is MicroPython itself, built for one board: compiler, virtual machine, memory manager, built-in modules and the port's drivers. Your code lives beside it, on the board's filesystem.

  2. Question 2 of 13 A large lookup-table module must use as little RAM as possible. On most ports, which way of storing it saves the most?

    Choose one answer.

    Show the answer: Question 2 of 13

    Answer: Freeze it into the firmware, so its bytecode runs straight from flash

    MicroPython's guide to small chips says frozen bytecode saves the most RAM on most platforms, because it runs from flash. A .mpy file skips the compiler only.

  3. Question 3 of 13 A colleague asks what runs underneath MicroPython on the boards of this track. Which answer is right?

    Choose one answer.

    Show the answer: Question 3 of 13

    Answer: Nothing on a Pico; on an ESP32 it runs on ESP-IDF, Espressif's FreeRTOS-based framework

    On a bare chip such as the RP2040, MicroPython acts as a small operating system itself. The esp32 port is implemented on ESP-IDF, so a FreeRTOS kernel runs underneath, out of your program's sight.

  4. Question 4 of 13 Which Python syntax does MicroPython implement in full, according to its README?

    Choose one answer.

    Show the answer: Question 4 of 13

    Answer: Python 3.4, plus async and await from 3.5 and selected later features

    MicroPython aims at Python 3.4 with selected later features. For example, the Python 3.6 page marks f-strings and underscores in numeric literals complete.

  5. Question 5 of 13 A CPython script uses json, f-strings and the logging module. What has to change before it runs on a Pico?

    Choose one answer.

    Show the answer: Question 5 of 13

    Answer: logging must be installed from micropython-lib with mip; json and f-strings work as they are

    The language features carry over, and json is built in. logging is one of the standard modules that only micropython-lib provides, installed with mip into the board's /lib folder.

  6. Question 6 of 13 The library reference calls modules such as json and time "micro-ified". What does that mean?

    Choose one answer.

    Show the answer: Question 6 of 13

    Answer: Each keeps the core of its CPython module, and some of its functions are missing

    A micro-ified module provides the core of the CPython module with the same name, so most code that uses it carries over, but a function it lacks raises AttributeError.

  7. Question 7 of 13 In MicroPython's terms, what is a port?

    Choose one answer.

    Show the answer: Question 7 of 13

    Answer: MicroPython adapted to one family of chips or one operating system, such as rp2, esp32 or unix

    The rp2 port covers RP2040 and RP2350 boards, the esp32 port ESP32 chips, and the unix port runs MicroPython on Linux, macOS and other Unix systems.

  8. Question 8 of 13 Which of these ports are in Tier 1? Choose every one.

    Choose every answer that is right.

    Show the answer: Question 8 of 13

    Answer:

    • unix
    • esp32
    • rp2

    Tier 1 holds esp32, mimxrt, rp2, samd, stm32, unix and windows. All three ports of this track are among them.

  9. Question 9 of 13 Your first project logs a soil sensor; later it must send its readings over Wi-Fi. You want to install MicroPython by copying one file. Which board fits best?

    Choose one answer.

    Show the answer: Question 9 of 13

    Answer: A Pico W or a Pico 2 W

    The radio rules the Pico out, and the wish to install by copying a file adds a caveat to the ESP32, which leaves the Pico W and the Pico 2 W.

  10. Question 10 of 13 A library must run on CPython and on every MicroPython port. Which test tells it that it is on MicroPython?

    Choose one answer.

    Show the answer: Question 10 of 13

    Answer: sys.implementation.name == 'micropython'

    sys.platform names the port or operating system, and some values are the same for CPython and MicroPython. sys.implementation.name is 'micropython' on every MicroPython build.

  11. Question 11 of 13 The unix port on a Mac with Apple silicon reports _mpy = 774, which is 0x0306. What is its architecture code, (774 >> 10) & 0x0F?

    Type a number.

    Show the answer: Question 11 of 13

    Answer: 0 (architecture code)

    774 is 3 x 256 + 6, so it has no bits above bit 9: the code is 0, the first entry of the list, which means no native architecture. MicroPython has no machine-code emitter for 64-bit Arm, so this build loads bytecode .mpy files only.

  12. Question 12 of 13 A Pico's sys.implementation has no _build field. What does that tell you?

    Choose one answer.

    Show the answer: Question 12 of 13

    Answer: Its firmware is older than version 1.25.0, which added _build

    _build came in 1.25.0 and _thread in 1.26.0, so a board without them runs an older release. Code that reads them should use getattr with a default.

  13. Question 13 of 13 Which field of sys.implementation names the board build of the firmware, such as 'RPI_PICO_W'?

    Choose one answer.

    Show the answer: Question 13 of 13

    Answer: _build

    _build came with version 1.25.0. On the unix, webassembly and windows ports it starts with the variant name, for example 'standard'; on boards, with the board name.

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.

  1. 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?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. 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?

    Model answer, Pro. First step and follow-ups

Flashcards · 7 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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.

  7. 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.implementation names the running build, and its _mpy number packs the .mpy version, the sub-version and the native architecture: 4870 means .mpy 6.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

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.