MicroPython
MicroPython is a lean and efficient implementation of the Python 3 programming language, designed specifically for microcontrollers and other resource-constrained environments, incorporating a small subset of the Python standard library to enable lightweight scripting on embedded hardware.[1]
Developed as an open-source project under the MIT license, MicroPython was created by Australian programmer Damien George, who launched a successful Kickstarter campaign on November 13, 2013, raising £97,803 to fund the initial development of the pyboard, a compact electronic circuit board that runs the interpreter.[2][3] The project originated from George's frustration with the complexity of C programming for microcontrollers during his university studies, aiming to bring Python's high-level syntax and ease of use to embedded systems.[4] Since its inception, MicroPython has evolved through community contributions on GitHub, achieving compatibility with Python 3.4 syntax and select features from later versions, such as async/await, while maintaining a focus on performance and minimal resource usage; as of September 2025, the latest stable release is v1.26.1.[5][6]
Among its notable features, MicroPython provides an interactive read-eval-print loop (REPL) with capabilities like tab completion and auto-indentation, alongside support for advanced language constructs including closures, list comprehensions, generators, and exception handling.[1] It includes hardware-oriented modules, such as the "machine" module for low-level I/O access, and fits within tight constraints—requiring as little as 256 KB of flash memory and 16 KB of RAM—while offering fast boot times and experimental multithreading support.[1] The interpreter runs on diverse platforms, including the original STM32F405-based pyboard (with a 168 MHz ARM Cortex-M4 processor, 1 MB flash, and 192 KB RAM), as well as ports for popular microcontrollers like ESP8266, ESP32, and Raspberry Pi RP2040, spanning architectures such as ARM, Xtensa, and x86.[1] This versatility has made MicroPython a popular choice for Internet of Things (IoT) applications, robotics, and educational projects, emphasizing rapid prototyping without sacrificing efficiency.[1]
History
Origins and Kickstarter Launch
MicroPython was conceived in early 2013 by Damien P. George, an Australian programmer and robotics enthusiast then pursuing undergraduate studies at the University of Cambridge. Motivated by the challenges of programming microcontrollers with low-level languages like C, George aimed to bring the accessibility and expressiveness of Python to embedded systems, enabling rapid prototyping for robotics and IoT applications. On April 29, 2013, he wrote the first line of code as a private experiment to implement a streamlined version of Python 3 optimized for devices with severe resource constraints, specifically targeting a footprint of 256 KB for code space and 16 KB of RAM.[7][8][1]
To accelerate development and produce affordable hardware, George launched a crowdfunding campaign on Kickstarter on November 13, 2013, titled "Micro Python: Python for microcontrollers." The campaign's primary goal was to raise £15,000 to manufacture the Pyboard—a compact development board featuring an STM32F405 microcontroller, USB connectivity, and Python's interactive REPL—while releasing the software as open-source under the MIT license. Stretch goals included adding Wi-Fi (£40,000), Ethernet (£50,000), and wireless support (£60,000), reflecting ambitions to expand connectivity options. The effort exceeded expectations, securing £97,803 from 1,931 backers over 30 days, which funded initial production and community growth.[2][9][4]
By the campaign's start, George had already built early prototypes of the Pyboard, hand-assembling two iterations of the PCB using services from EuroCircuits, resulting in fully operational boards capable of running basic Python scripts. Public demonstrations were showcased in the Kickstarter video, illustrating practical applications such as blinking an LED with a simple for loop and controlling a servo motor via the board's GPIO pins, highlighting MicroPython's potential to simplify embedded programming without sacrificing performance. These prototypes validated the core concept, paving the way for broader adoption.[2][4]
Key Milestones and Releases
MicroPython's development gained momentum following its successful Kickstarter campaign launched in November 2013, which funded the creation of an open-source Python implementation for microcontrollers and the production of the initial pyboard hardware. The project's first official release, version 1.0, occurred on May 3, 2014, marking the debut of a lean Python 3 interpreter optimized for resource-constrained embedded systems, with initial support for ARM Cortex-M microcontrollers through the stmhal port on the pyboard. This initial version laid the foundation for subsequent ports and enhancements, focusing primarily on ARM-based microcontrollers.
Early milestones centered on expanding hardware compatibility. Version 1.3 in August 2014 provided enhancements to the stmhal port, enabling deployment on a wide range of STM32 boards. The ESP8266 port followed in November 2014 with version 1.3.7, bringing Wi-Fi capabilities to low-cost IoT devices and broadening the project's appeal for networked applications. By 2016, development of the ESP32 port began, with official integration in May 2018 via version 1.9.4, which added support for the more powerful ESP32 SoC with dual-core processing, Bluetooth, and enhanced peripherals.
Significant community-driven variants emerged in the late 2010s. In 2017, Adafruit Industries forked MicroPython to create CircuitPython, emphasizing ease-of-use for beginners with features like live USB reloading and a focus on educational hardware such as SAMD-based boards. In April 2019, LEGO Education released an official MicroPython image for the Mindstorms EV3 kit, built on the ev3dev operating system and Pybricks runtime, allowing Python programming for robotics education without modifying the brick's firmware.
The project continued to evolve with key hardware integrations in the 2020s. Version 1.14, released in February 2021, introduced the RP2 port for the Raspberry Pi RP2040 microcontroller, supporting its dual Cortex-M0+ cores, PIO state machines, and USB capabilities, which quickly became popular for custom embedded projects. Subsequent releases added features like BLE support in 2019 (v1.12) and the mip package manager in 2023 (v1.20.0) for easier library installation.
Recent developments have emphasized advanced connectivity and portability. Version 1.23.0 in May 2024 introduced dynamic USB device support, allowing runtime configuration of USB interfaces on supported ports like RP2040 and ESP32, along with new modules such as openamp for inter-processor communication, tls for secure networking, vfs for virtual file systems, and a revamped WebAssembly port for browser-based execution. The stable release, version 1.26.1 on September 11, 2025, included patches for native USB on ESP32, support for new MCUs like STM32N6 and ESP32-C2, and enhancements to I2C target mode. In February 2025, a presentation at FOSDEM highlighted MicroPython's growing role in embedded Linux environments, showcasing integrations for larger systems beyond traditional microcontrollers.
Core Features
Python Compatibility and Portability
MicroPython implements the core syntax and semantics of Python 3.4, along with select features from later versions such as Python 3.5 and beyond, enabling developers to write code that is largely interchangeable with standard Python (CPython).[10] This includes full support for advanced language constructs like closures via the nonlocal keyword, list comprehensions for concise iterable creation, generators using yield for memory-efficient iteration, and comprehensive exception handling with try/except blocks that mirror CPython behavior.[11] While minor behavioral differences exist—such as context managers in generators not always calling __exit__ if the generator does not complete—these ensure reliable operation in resource-constrained environments without altering fundamental Python idioms.[11]
The standard library in MicroPython provides a subset of CPython's modules, focusing on essential functionality for embedded systems while omitting or simplifying those reliant on a full operating system.[12] Core modules like os for basic file system operations and time for timing functions are included as drop-in replacements, but with reduced scope to fit microcontroller constraints.[12] For hardware interaction, the machine module offers portable abstractions, such as machine.Pin for GPIO control, machine.I2C for inter-integrated circuit communication, and machine.[SPI](/page/SPI) for serial peripheral interface, allowing code to interface with peripherals consistently across ports without low-level hardware specifics.[13]
A key aspect of MicroPython's design is its emphasis on code portability, achieved through a unified codebase that compiles and runs on diverse microcontrollers without requiring operating system modifications.[14] Developers use cross-compilation tools, such as the mpy-cross compiler and platform-specific Makefiles, to build firmware from a single source tree organized into ports (e.g., for STM32, ESP32, or RP2040), ensuring that application-level Python scripts remain hardware-agnostic.[14] This contrasts with full CPython, which assumes a hosted environment; MicroPython is optimized for bare-metal execution, prioritizing real-time responsiveness and minimal memory footprint over complete feature parity.[10]
Modules and Interactive REPL
MicroPython provides a collection of built-in modules that form the core of its runtime environment, offering essential functionality for embedded applications. These include a subset of the Python standard library, adapted for resource-constrained devices, such as json for data serialization, math for mathematical operations, and time for timing and delays.[12] This selection prioritizes lightweight implementations to minimize memory and storage usage while maintaining compatibility with common Python idioms.[12]
In addition to standard library modules, MicroPython includes port-specific and hardware-oriented extensions, such as uasyncio for asynchronous programming, which enables non-blocking I/O operations suitable for real-time tasks on microcontrollers.[12] Other examples encompass bluetooth for low-energy wireless communication and machine for direct hardware control, like pin manipulation and PWM signals.[12] These modules extend Python's capabilities to interact with the underlying hardware without requiring low-level C programming.[12]
The module loading system in MicroPython is optimized for flash-based storage, where the import mechanism searches directories in sys.path for .py files, .mpy bytecode files, or frozen modules embedded in the firmware.[15] Frozen modules store Python source or precompiled bytecode directly in the firmware, allowing seamless imports like import mymodule while reducing runtime compilation overhead.[16] For efficiency, .mpy files—generated via the mpy-cross cross-compiler—contain bytecode or native machine code that loads faster from flash than interpreting raw Python source, conserving RAM on devices with limited resources.[15] Compatibility checks ensure .mpy files match the MicroPython version and architecture, raising errors like ValueError for mismatches.[15]
The interactive Read-Eval-Print Loop (REPL) serves as the primary interface for development and debugging, accessible over USB or serial connections to the microcontroller board.[17] Upon connection, users encounter a prompt (typically >>>) where Python expressions and statements can be entered and executed immediately.[17] Key features include auto-indentation, which adds four spaces after colons in control structures like if or for and executes multiline input upon double Enter; auto-completion via Tab to list module attributes (e.g., machine.<Tab> suggests Pin); and paste mode (entered with Ctrl-E) to disable indentation for bulk code pasting, exiting with Ctrl-D.[17]
For advanced use, raw REPL mode (entered with Ctrl-A) provides low-level access without line buffering, ideal for programmatic interaction or high-speed data transfer, and exits with Ctrl-B.[17] Programs can be interrupted with Ctrl-C, raising a KeyboardInterrupt to return to the prompt, while Ctrl-D performs a soft reset.[17] Special commands like help('modules') list available built-in modules, aiding exploration.[12]
This setup facilitates an efficient development workflow: code is edited on a host machine using tools like text editors or IDEs, uploaded to the device's filesystem via serial tools (e.g., ampy or rshell), and executed instantly in the REPL for testing.[17] Frozen modules or .mpy files can be pre-integrated during firmware builds for production, streamlining deployment on flash-limited hardware.[16]
MicroPython operates within the severe constraints of microcontroller environments, typically requiring a minimum of 256 KB of flash memory for the firmware and 16 KB of RAM for runtime execution.[1] These limits restrict the ability to handle large data structures, such as extensive lists or dictionaries, which would exceed available heap space and lead to memory errors. Additionally, full garbage collection is constrained in low-RAM scenarios, where fragmentation can prevent effective memory reclamation, often necessitating manual calls to gc.collect() that may take several milliseconds and still yield limited results.[16]
The interpreted nature of MicroPython's bytecode execution results in significantly slower performance compared to native C code, particularly in computational tasks like hashing or signal processing, with slowdowns often ranging from tens to hundreds of times or more. For instance, benchmarks on ESP32 show MicroPython up to approximately 1,900 times slower than optimized C implementations for SHA-256 hashing of 1024-byte inputs (as of 2023).[18] While ports like those for ARM architectures include optimizations such as the @micropython.native and @micropython.viper decorators—which compile functions to faster machine-like code—these provide only partial mitigation, achieving roughly 2x speedups with @micropython.native over pure bytecode and greater gains with @micropython.viper for integer-heavy operations, but still falling short of native efficiency.[19]
MicroPython's compatibility with standard Python is partial, targeting a Python 3.4 baseline with selective adoption of later features, leading to omissions in advanced language constructs.[20] Full support for Python 3.5+ asynchronous programming, including comprehensive async/await syntax and coroutine types, is limited to a basic implementation via the uasyncio module, which lacks the depth of CPython's event loop and task management for complex concurrent I/O.[21] Threading is similarly restricted, with the _thread module providing only experimental multithreading that is not fully documented or stable, unsuitable for production use in resource-constrained settings.[22] Furthermore, MicroPython does not include a built-in integrated development environment (IDE) or interactive debugger, relying instead on external tools for code inspection and error tracing.
To address these limitations, developers can employ workarounds such as string interning via QSTRs (accessed through the micropython module's qstr_info), which reduces RAM usage by storing unique strings as indices rather than full copies, speeding up comparisons in performance-critical sections.[23] Avoiding recursion is another key strategy, as deep call stacks quickly exhaust limited resources; iterative alternatives using loops or explicit stacks are recommended to prevent overflows and maintain responsiveness.[19] These techniques, combined with pre-allocation of buffers like bytearray objects, help optimize execution within the available footprint without altering the core runtime.[19]
Language Implementation
Syntax and Semantics
MicroPython's syntax closely mirrors that of Python 3.4, incorporating selected features from later versions while maintaining core rules for readability and structure.[24] This includes the use of significant whitespace for defining code blocks, a hallmark of Python's design philosophy. The language supports standard lexical elements such as keywords, identifiers, literals, and operators, ensuring that most valid Python 3 code parses correctly in MicroPython, subject to resource constraints.[25]
Code blocks in MicroPython are delimited by indentation rather than braces, following Python conventions. Statements that introduce a block—such as if, for, while, function definitions with def, and exception handlers—end with a colon (:), after which the subsequent lines must use consistent indentation, typically four spaces. Inconsistent indentation raises an IndentationError. For example, a simple function definition demonstrates this:
def greet(name):
print("Hello, " + name)
if name == "world":
print("!")
else:
print(".")
def greet(name):
print("Hello, " + name)
if name == "world":
print("!")
else:
print(".")
This structure enforces clear hierarchy without explicit delimiters.[25]
MicroPython supports the full suite of Python operators for arithmetic, comparison, logical operations, and more. Arithmetic operators include addition (+), subtraction (-), multiplication (*), true division (/), floor division (//), and modulo (%). Comparison operators are ==, !=, <, >, <=, and >=, while logical operators are and, or, and not. Floor division (//) always truncates toward negative infinity, consistent with Python 3 semantics; for instance, 5 // 2 yields 2, and -5 // 2 yields -3. An example combining these:
a = 10
b = 3
result = (a + b) * 2 // (a - b) # Evaluates to 9
if result > 5 and b != 0:
[print](/page/Print)("Condition met")
a = 10
b = 3
result = (a + b) * 2 // (a - b) # Evaluates to 9
if result > 5 and b != 0:
[print](/page/Print)("Condition met")
These operations behave identically to Python 3 unless constrained by hardware limits.[26]
Control structures in MicroPython enable conditional execution and iteration, aligning with Python 3 syntax. The if statement evaluates a condition and executes the block if true, optionally followed by elif for additional checks and else for the fallback:
x = 7
if x < 5:
print("Small")
elif x < 10:
print("Medium")
else:
print("Large")
x = 7
if x < 5:
print("Small")
elif x < 10:
print("Medium")
else:
print("Large")
Loops include for, which iterates over iterables like ranges or lists:
for i in range(3):
print(i) # Outputs: 0 1 2
for i in range(3):
print(i) # Outputs: 0 1 2
The while loop continues as long as the condition holds:
count = 0
while count < 3:
print(count)
count += 1 # Outputs: 0 1 2
count = 0
while count < 3:
print(count)
count += 1 # Outputs: 0 1 2
Exception handling uses try, except, else, and finally: the try block attempts code, except catches specified exceptions, else runs if no exception occurs, and finally executes regardless:
try:
value = 10 / 0
except ZeroDivisionError:
print("Division by zero")
else:
print("Success")
finally:
print("Cleanup")
try:
value = 10 / 0
except ZeroDivisionError:
print("Division by zero")
else:
print("Success")
finally:
print("Cleanup")
These structures support breaking flow with break, continue, and return.[24][27]
Semantically, MicroPython diverges from full Python 3 in resource-constrained environments, omitting advanced features like metaclasses, which are not implemented to preserve memory and simplicity.[28] Dynamic introspection is limited; for example, locals() excludes local variables for optimization, treating them as fixed array slots, and eval() operates without local scope access.[11] Integers (int) provide arbitrary precision, matching Python 3's unbounded nature, but practical size is capped by available RAM, typically 16-512 KB on microcontrollers, preventing excessively large computations.[29][16]
Bytecode and Compilation
MicroPython compiles Python source code files (with a .py extension) into bytecode using a cross-compiler tool known as mpy-cross, which runs on the host machine to generate portable .mpy files containing the precompiled bytecode.[16] This process allows modules to be imported directly on the target device without requiring runtime compilation, thereby reducing RAM usage during execution.[30] For efficiency on resource-constrained hardware, bytecode can also be "frozen" directly into the firmware binary during the build process, enabling boot-time loading from flash memory without additional compilation steps.[16]
The bytecode format in MicroPython consists of a sequence of virtual machine opcodes designed for stack-based execution, where operands are pushed and popped from an evaluation stack to perform operations.[31] This format is stored in a compact binary .mpy container that includes metadata such as the API version, source file name, and the bytecode itself, optimized for minimal memory footprint on embedded systems.[15] To further reduce memory overhead, the compiler employs optimizations like constant pooling, where repeated literals are substituted with references to a shared pool using the built-in const() function, avoiding redundant storage of immutable values such as numbers or strings.[16]
During execution, the MicroPython virtual machine (VM) interprets the bytecode instructions sequentially, managing a stack for temporary values and the heap for object allocation.[16] The VM supports inline caching mechanisms within the interpreter to accelerate attribute lookups and method calls by storing recent resolution results directly in the bytecode stream, improving performance on repeated operations without full recompilation.[32] For performance-critical code, MicroPython provides decorators such as @micropython.native and @micropython.viper to generate optimized native machine code ahead-of-time for supported architectures including ARM, Thumb, Xtensa, RISC-V, x86, and x64, as improved in v1.26 (August 2025).[33] The core implementation relies on interpretation across all ports.
Decompiling .mpy files to recover readable source code is challenging due to the binary format, but tools like the official mpy-tool.py script from the MicroPython repository can parse and dump the bytecode opcodes for analysis or reverse engineering. Specialized efforts, such as those presented in security research, have developed custom reconstructors to extract data structures and bytecode from frozen modules, though no standard tool like uncompyle6 (designed for CPython) fully supports MicroPython's variant without modification.
Libraries and Extensions
MicroPython includes a subset of the Python standard library, adapted for resource-constrained environments, providing essential functionality for embedded systems programming. Core modules such as builtins offer fundamental types and exceptions, while sys enables access to system-specific parameters like path manipulation and version information. These core libraries form the foundation for basic script execution and runtime introspection.
For input/output operations, MicroPython provides io for stream handling and file-like objects, and os for basic filesystem interactions such as directory listing and file removal, though limited by the underlying hardware's storage capabilities. Data processing is supported through modules like json for encoding and decoding JSON data, and urllib for simple URL fetching and HTTP requests, facilitating network-enabled applications without full web framework overhead. Hardware interfacing libraries include machine.Pin for GPIO control, allowing pin configuration as inputs or outputs with callbacks, and network for Wi-Fi or Ethernet setup and socket operations on supported ports.[34]
Custom extensions enhance MicroPython's capabilities beyond the standard set, either as pure Python modules or compiled C modules for performance-critical tasks. Pure Python libraries can be written and imported like standard modules, offering portability across ports, while C extensions are compiled into the firmware using build tools like Make or CMake, integrating directly via user-defined modules with functions and classes exposed to Python. For instance, the ujson module provides an optimized JSON implementation in C, offering faster parsing and serialization compared to the pure Python json alternative, suitable for high-throughput data handling in embedded scenarios.[35]
Port-specific libraries address hardware-unique features. On ESP32 ports, the esp32 module includes functions for low-power modes, such as configuring deep sleep wake sources via wake_on_ext0 for external pin triggers or gpio_deep_sleep_hold to retain GPIO states during sleep, optimizing battery life in IoT devices. For RP2040-based boards like the Raspberry Pi Pico, the rp2 module exposes the Programmable I/O (PIO) subsystem through classes like StateMachine, allowing custom assembly programs for high-speed peripherals via the @rp2.asm_pio decorator, enabling efficient protocol implementations without CPU intervention.[36][37]
Unlike full Python, MicroPython lacks a built-in package manager like pip, so libraries are distributed as source .py files, pre-compiled .mpy bytecode files for faster loading and smaller size, or frozen into firmware during build. Custom or third-party libraries are manually uploaded to the device's filesystem using tools such as ampy for command-line file transfer over serial/USB or rshell for interactive shell access and bulk operations, ensuring compatibility with the target port's constraints.[15][38][39]
Hardware Support
Supported Architectures and Ports
MicroPython primarily targets resource-constrained microcontrollers but also includes ports for desktop simulation. The core supported architectures encompass the ARM Cortex-M series, Xtensa processors, RISC-V, and x86 for development environments.[40] These ports enable deployment on a diverse array of 32-bit hardware, from low-end designs to systems with integrated wireless capabilities.
The ARM Cortex-M architecture forms the backbone of many MicroPython ports, supporting variants like Cortex-M0, M0+, M3, M4, M7, and M33. Notable implementations include the STM32 family from STMicroelectronics, which powers various development boards; the RP2040 and RP2350 dual-core Cortex-M0+ and Cortex-M33 in the Raspberry Pi Pico and Pico 2 (RP2350 released 2024);[41] the nRF51 Cortex-M0 in the BBC micro:bit for educational use; and the SAMD21 Cortex-M0+ in boards like the Arduino Zero. These ports run in bare-metal mode, directly on the hardware without an underlying operating system, and require minimal resources: typically 256 KB of flash storage and 16 KB of RAM for the core runtime, with 128 KB of RAM recommended for a functional REPL and basic scripting.[42][43][44][1]
RISC-V support was significantly extended in MicroPython v1.24 (October 2024), including native code generation for RV32IMC and ports for devices like the ESP32-C6 and RP2350 (RISC-V cores).[45]
Xtensa architecture support is provided through dedicated ports for Espressif's ESP8266 and ESP32 microcontrollers, which integrate Wi-Fi and Bluetooth. These ports leverage the Xtensa LX106 and LX6 cores, respectively, and are optimized for IoT applications, offering built-in network modules alongside the standard MicroPython features.
For testing and development, the Unix port runs on x86/x64 systems such as Linux, macOS, and Windows (via WSL or Cygwin), emulating microcontroller behavior with file system access and standard I/O. This port facilitates rapid prototyping without hardware.[46]
A prominent variant is CircuitPython, an education-oriented fork of MicroPython maintained by Adafruit, which emphasizes plug-and-play peripherals, automatic file reloading, and broader support for sensors and displays through its library ecosystem.[47]
Execution Methods and Booting
MicroPython supports multiple methods for loading and executing code on resource-constrained devices. The primary interactive method is the Read-Eval-Print Loop (REPL), an interactive interpreter accessible via serial connection (typically USB or UART) that allows real-time input, evaluation, and printing of Python code. File-based scripts, stored as .py files on the device's filesystem (such as internal flash or SD card), can be executed manually using the exec(open('script.py').read()) command or automatically during boot. Frozen modules consist of pre-compiled Python bytecode embedded directly into the firmware during the build process, enabling efficient execution from ROM without runtime compilation or filesystem dependency, which is particularly useful for devices lacking persistent storage. Code upload occurs primarily via USB using tools like mpremote for transferring files to the filesystem (e.g., mpremote cp local.py :remote.py) or executing scripts in RAM without storage (mpremote run script.py); Device Firmware Upgrade (DFU) mode, supported on certain ports, facilitates firmware flashing and code integration during builds.[17][48][49][50]
The booting process commences with a power-on reset or hard reset, triggering the microcontroller unit (MCU) to load the bootloader from non-volatile flash memory, which handles initial hardware initialization including clock setup and memory mapping. The bootloader then determines the boot mode and loads the MicroPython virtual machine (VM), starting with the execution of a frozen _boot.py script that mounts the filesystem. In normal boot mode, this is followed by running boot.py for custom hardware configuration (which must complete without looping) and then main.py as the primary application script; if main.py is absent or exits cleanly, the REPL launches for interactive use. The process ensures minimal resource overhead, with bytecode for scripts compiled on-the-fly or loaded from frozen storage to initialize the Python environment.[48][49]
MicroPython implements distinct boot modes to support standard operation, recovery, and maintenance. Normal boot mode executes the full sequence of startup scripts (boot.py and main.py) from the filesystem, enabling user-defined initialization. Safe boot mode, available on most ports, bypasses these scripts to prevent faulty code from interfering with recovery, directly entering the REPL with default settings for filesystem editing or diagnostics. Filesystem boot mode, supported on select ports, provides raw access to storage for factory resets or low-level operations, often resetting the filesystem while entering safe mode. Entry into these modes typically involves hardware-specific actions, such as holding a boot button during reset to select the mode based on timing.[48][51][52]
Error handling during execution or boot emphasizes reliability on embedded systems. Faults, such as import errors in startup scripts, may trigger visual indicators like specific LED blink patterns (e.g., three blinks for an import failure on supported hardware) to signal issues without console output. Programmatic recovery is facilitated by the machine.reset() function, which performs a soft reset to restart the Python interpreter and VM while preserving certain state like network connections, avoiding a full hardware reboot. Developers are encouraged to wrap main scripts in try-except blocks to catch exceptions and return to the REPL, ensuring the system remains accessible.[48][13][51]
Specific Devices and Boards
The Pyboard serves as the reference development board for MicroPython, featuring the STM32F405RGT6 microcontroller based on a Cortex-M4F core running at 168 MHz, with 1024 KiB of internal flash memory and 192 KiB of RAM.[53] It includes a micro USB connector for power and serial communication, a micro SD card slot for storage expansion, an integrated Freescale MMA7660 accelerometer for motion sensing, and four servo ports alongside RGBY LEDs for visual feedback. The board provides access to 30 general-purpose input/output (GPIO) pins, enabling versatile interfacing with external components.[54]
Beyond the Pyboard, MicroPython supports a range of other development boards with distinct hardware integrations. The ESP32 port accommodates boards equipped with Espressif's Xtensa dual-core LX6 processor operating up to 240 MHz, 528 KiB of SRAM, integrated WiFi, and Bluetooth Low Energy (BLE) capabilities, facilitating wireless connectivity in IoT applications.[55] The ESP32-C6 uses a RISC-V single-core processor at 160 MHz, 512 KiB of SRAM, and supports Wi-Fi 6 and BLE.[56] The RP2040, as found in the Raspberry Pi Pico, utilizes a dual-core ARM Cortex-M0+ configuration at 133 MHz (overclockable), 264 KiB of SRAM, and eight programmable I/O (PIO) state machines that allow custom peripheral emulation without dedicated hardware.[57] The RP2350, in the Raspberry Pi Pico 2 (released 2024), features dual-core ARM Cortex-M33 or RISC-V Hazard3 at up to 150 MHz, 520 KiB of SRAM, and enhanced PIO.[58] Olimex boards, such as the ESP32-POE series and STM32-H407 variants, extend support to Ethernet-enabled designs and power-optimized STM32 microcontrollers, often incorporating USB, displays, and SD card interfaces for industrial and embedded use cases.[59]
Device setup on these boards begins with pin mapping through the machine.Pin class, which initializes GPIO pins by specifying an identifier (integer or string) and mode, such as input (Pin.IN) or output (Pin.OUT), with options for pull-up resistors and initial values to configure hardware interfaces reliably.[60] Analog-to-digital conversion (ADC) is handled via machine.ADC wrapped around a Pin object for reading sensor voltages, while digital-to-analog conversion (DAC) uses machine.DAC for generating analog outputs on supported pins, and pulse-width modulation (PWM) is enabled through machine.PWM on a Pin to control devices like motors and LEDs by adjusting duty cycles.[13]
For hardware expansion, MicroPython provides machine.I2C and machine.[SPI](/page/SPI) classes to interface with sensors and peripherals, supporting hardware-accelerated two-wire I2C communication (SCL and SDA lines) for multi-device buses or bit-banged software implementations, and four-wire SPI (SCK, MOSI, MISO, CS) for high-speed data transfer, both requiring appropriate pull-up resistors and pin assignments. These boards are designed for compatibility with breadboards, allowing GPIO pins to connect directly to prototyping setups for adding sensors like temperature or motion detectors without soldering.[61][62]
| Board | MCU | Core/Frequency | Memory (Flash/RAM) | Key Features |
|---|
| Pyboard v1.1 | STM32F405RGT6 | Cortex-M4F/168 MHz | 1024 KiB/192 KiB | USB, SD slot, accelerometer, 30 GPIO |
| ESP32 | Xtensa LX6 dual-core | Up to 240 MHz | External/528 KiB | WiFi, BLE |
| ESP32-C6 | RISC-V single-core | 160 MHz | External/512 KiB | Wi-Fi 6, BLE |
| RP2040 (Pico) | ARM Cortex-M0+ dual | 133 MHz (overclockable) | External/264 KiB | PIO state machines |
| RP2350 (Pico 2) | ARM Cortex-M33 or RISC-V dual | Up to 150 MHz | External/520 KiB | Enhanced PIO, security features |
| Olimex (e.g., ESP32-POE) | ESP32/STM32 series | Varies | Varies | Ethernet, USB, display |
Applications and Examples
Programming Examples
MicroPython provides straightforward programming interfaces for interacting with hardware peripherals, allowing developers to write concise scripts for common tasks on embedded devices. The following examples illustrate basic operations using core modules like machine, time, network, and uasyncio, demonstrating how to control GPIO pins, read analog inputs, establish network connections, and manage asynchronous tasks. These snippets are executable on supported boards such as the Raspberry Pi Pico, ESP32, or Pyboard, with pin numbers adjusted according to the specific hardware.
Blinking an LED
A fundamental example in MicroPython involves blinking an LED connected to a GPIO pin, which showcases digital output control using the machine.Pin class and timing with the time module. This operation toggles the pin state in a loop, turning the LED on and off at regular intervals. On boards like the Raspberry Pi Pico, GPIO 25 is often used for an onboard LED; for other devices, connect an external LED with a current-limiting resistor to an appropriate GPIO pin.[60]
The following code creates a Pin object in output mode, sets it high to turn the LED on, waits 500 milliseconds, sets it low to turn the LED off, and repeats indefinitely:
from machine import Pin
from time import [sleep](/page/Sleep)
led = Pin(25, Pin.OUT) # Adjust pin number for your board
while True:
led.[value](/page/Value)(1) # Turn LED on
[sleep](/page/Sleep)(0.5)
led.[value](/page/Value)(0) # Turn LED off
[sleep](/page/Sleep)(0.5)
from machine import Pin
from time import [sleep](/page/Sleep)
led = Pin(25, Pin.OUT) # Adjust pin number for your board
while True:
led.[value](/page/Value)(1) # Turn LED on
[sleep](/page/Sleep)(0.5)
led.[value](/page/Value)(0) # Turn LED off
[sleep](/page/Sleep)(0.5)
This script runs continuously until interrupted (e.g., via Ctrl+C in the REPL). The Pin.OUT mode configures the pin for digital output, and value(1) or value(0) sets the logic level. For alternative toggle methods, led.toggle() can be used instead of explicit value settings, reducing code length while achieving the same effect.[60]
Reading a Sensor with ADC
MicroPython's machine.ADC class enables reading analog sensor values, such as from a potentiometer, by converting continuous voltages (typically 0-3.3V) into discrete digital values (0-65535 for 16-bit resolution). A potentiometer serves as a simple variable resistor example, where rotating the knob changes the input voltage proportionally. Connect the potentiometer's middle pin to an ADC-capable GPIO (e.g., GPIO 26 on Raspberry Pi Pico or GPIO 34 on ESP32), with the outer pins to ground and 3.3V.[63]
The code below initializes the ADC on the specified pin, reads the raw value in a loop, converts it to voltage, and prints the result every second:
from machine import [ADC](/page/ADC)
from time import [sleep](/page/Sleep)
adc = [ADC](/page/ADC)(26) # Adjust pin number for your board (must support [ADC](/page/ADC))
while True:
raw_value = adc.read_u16() # Read 16-bit value (0-65535)
voltage = (raw_value / 65535) * 3.3 # Convert to volts (assuming 3.3V reference)
print(f"Raw: {raw_value}, Voltage: {voltage:.2f}V")
[sleep](/page/Sleep)(1)
from machine import [ADC](/page/ADC)
from time import [sleep](/page/Sleep)
adc = [ADC](/page/ADC)(26) # Adjust pin number for your board (must support [ADC](/page/ADC))
while True:
raw_value = adc.read_u16() # Read 16-bit value (0-65535)
voltage = (raw_value / 65535) * 3.3 # Convert to volts (assuming 3.3V reference)
print(f"Raw: {raw_value}, Voltage: {voltage:.2f}V")
[sleep](/page/Sleep)(1)
The read_u16() method samples the input rapidly, providing a raw integer that scales linearly with the applied voltage. Division by 65535 normalizes the value, multiplied by the reference voltage (3.3V on most MicroPython ports) to obtain the actual voltage; some ports offer read_uv() for direct microvolt readings with built-in calibration. This approach is essential for interfacing with analog sensors like temperature or light detectors, where raw values are mapped to physical units based on sensor specifications.[63]
WiFi Connection on ESP32
On ESP32-based boards, the network module facilitates WiFi connectivity in station mode, allowing the device to join an existing access point for internet access. This is achieved by creating a WLAN interface, activating it, and providing the network credentials (SSID and password). The connection status can be verified via the isconnected() method, enabling scripts to wait until associated before proceeding with network-dependent tasks like HTTP requests.[64]
The example script connects to a specified WiFi network and prints the assigned IP address upon success:
import network
wlan = network.WLAN(network.STA_IF) # Create [station](/page/Station) interface
wlan.active(True) # Activate the interface
wlan.connect('your_ssid', 'your_password') # Replace with actual credentials
while not wlan.isconnected():
pass # Wait for [connection](/page/Connection)
print('Connected. [IP](/page/IP):', wlan.[ifconfig](/page/Ifconfig)()[0])
import network
wlan = network.WLAN(network.STA_IF) # Create [station](/page/Station) interface
wlan.active(True) # Activate the interface
wlan.connect('your_ssid', 'your_password') # Replace with actual credentials
while not wlan.isconnected():
pass # Wait for [connection](/page/Connection)
print('Connected. [IP](/page/IP):', wlan.[ifconfig](/page/Ifconfig)()[0])
Replace 'your_ssid' and 'your_password' with the target network details. The STA_IF constant specifies client mode, and ifconfig()[0] retrieves the IPv4 address post-connection. This setup is foundational for IoT applications, as it enables subsequent use of sockets or higher-level protocols like urequests for data exchange. For security, avoid hardcoding credentials in production; instead, use configuration files or secure input methods.[64]
Asynchronous Task with uasyncio
MicroPython's uasyncio library (aliased as asyncio) supports cooperative multitasking for concurrent operations without blocking the main thread, ideal for handling multiple hardware interactions efficiently on resource-constrained devices. A simple example involves scheduling independent coroutines to blink two LEDs at different rates, demonstrating task creation and non-blocking sleeps. This requires boards with GPIO support, such as the Pyboard or ESP32, using pins configured via machine.Pin.[21]
The code defines an asynchronous blink function, creates tasks for two LEDs, and runs them for 10 seconds:
import asyncio
from [machine](/page/Machine) import Pin
async def blink(led, period_ms):
while True:
led.on()
await asyncio.sleep_ms(5) # Brief on-time for visibility
led.off()
await asyncio.sleep_ms(period_ms)
async def main():
led1 = Pin(1, Pin.OUT) # Adjust pins for your board
led2 = Pin(2, Pin.OUT)
asyncio.create_task(blink(led1, 700)) # Task 1: 700ms period
asyncio.create_task(blink(led2, 400)) # Task 2: 400ms period
await asyncio.sleep_ms(10000) # Run for 10 seconds
asyncio.run(main())
import asyncio
from [machine](/page/Machine) import Pin
async def blink(led, period_ms):
while True:
led.on()
await asyncio.sleep_ms(5) # Brief on-time for visibility
led.off()
await asyncio.sleep_ms(period_ms)
async def main():
led1 = Pin(1, Pin.OUT) # Adjust pins for your board
led2 = Pin(2, Pin.OUT)
asyncio.create_task(blink(led1, 700)) # Task 1: 700ms period
asyncio.create_task(blink(led2, 400)) # Task 2: 400ms period
await asyncio.sleep_ms(10000) # Run for 10 seconds
asyncio.run(main())
The async def keyword defines coroutines, await asyncio.sleep_ms() yields control without halting other tasks, and create_task() schedules them on the event loop. asyncio.run() starts the scheduler. This pattern scales to more complex scenarios, like simultaneous sensor polling and communication, while maintaining low memory overhead compared to threading. Pins 1 and 2 are examples; on Pyboard, pyb.LED(1) and pyb.LED(2) can substitute for built-in LEDs.[21]
Implementations and Use Cases
MicroPython finds extensive application in Internet of Things (IoT) projects, particularly those leveraging the ESP32 microcontroller for its built-in Wi-Fi and Bluetooth capabilities. Weather stations are a common implementation, where ESP32 boards integrate sensors such as the BME280 to monitor temperature, humidity, and atmospheric pressure, often displaying data via a local web server or transmitting it to remote dashboards.[65] For smart home devices, MicroPython enables control of appliances like power outlets through ESP32, allowing local network commands or integration with cloud services for automated responses, such as adjusting lighting based on sensor inputs.[66] Cloud integration is facilitated by libraries like umqtt.simple, enabling ESP32 devices to connect to platforms such as AWS IoT Core for secure data exchange and remote management.[67]
In education, MicroPython powers interactive learning tools, notably the BBC micro:bit, which supports robotics projects where students program simple robots to navigate obstacles or respond to environmental inputs using the device's accelerometer and GPIO pins.[68] This platform is integrated into school curricula worldwide, with resources like Arm's GCSE Computer Science lessons using MicroPython on micro:bit to teach core programming concepts including loops, conditionals, and functions in an accessible, hardware-tied manner.[69] Platforms such as Tynker further extend this by offering MicroPython-based courses on micro:bit for creating wearables like pedometers or communication devices, fostering problem-solving skills among K-12 students.[70]
For embedded systems, MicroPython serves as lightweight firmware in resource-constrained devices, including drones and wearables. In drones, such as the pyDrone based on ESP32-S3, MicroPython handles flight control, sensor fusion, and camera integration, allowing customizable autonomous behaviors through Python scripts.[71] Wearables benefit from its efficiency, with micro:bit projects creating fitness trackers or interactive badges that process motion data and LED outputs on battery-powered hardware.[72] As of 2025, updates to MicroPython ports, including runtime-defined USB device support on RP2040, enable advanced gadget modes like mass storage or HID emulation, simplifying firmware deployment and debugging in these systems.[73]
The MicroPython community thrives through open-source contributions on GitHub, with numerous open issues and pull requests reflecting active development of libraries and ports for diverse hardware.[5] The Unix port facilitates integration with Linux environments, running MicroPython scripts alongside native tools for testing and simulation, as it supports standard Unix-like systems including Linux distributions for rapid prototyping without dedicated hardware.[74]