MicroPython Programming Quick Reference
MicroPython Platform and Compatibility
MicroPython is a compact implementation of Python 3 designed for microcontrollers and other resource-constrained systems. It provides Python syntax, built-in data types, exception handling, modules and hardware-control interfaces.
MicroPython is not identical to desktop CPython. Some standard-library modules are absent, reduced or replaced with embedded-specific equivalents.
| Platform | Typical MicroPython support |
|---|---|
| Raspberry Pi Pico / Pico W | RP2040 port |
| Raspberry Pi Pico 2 / Pico 2 W | RP2350 port |
| ESP32 | ESP32-family port |
| ESP8266 | ESP8266 port |
| STM32 | Selected STM32 boards |
| Other microcontrollers | Depends on available ports and firmware builds |
The availability of GPIO, ADC, PWM, networking, Bluetooth, timers and other peripherals depends on the selected microcontroller, firmware build and board.
Hardware examples in this reference use the standard machine API. Pin numbers are illustrative and must be checked against the actual board.
Program Files and Execution
MicroPython commonly uses two startup files.
| File | Purpose |
|---|---|
| boot.py | Early board configuration |
| main.py | Main application executed after startup |
| Other .py files | Imported modules and application code |
On ports using this startup convention, boot.py runs before main.py.
Avoid placing long-running loops or unnecessary delays in boot.py, as these can make recovery and interactive access more difficult.
Basic programme
print("MicroPython running")counter = 0while counter < 5: print(counter) counter += 1
Interactive REPL
The Read-Evaluate-Print Loop allows Python statements to be entered directly.
>>> 2 + 35>>> import sys>>> sys.platform
The result of sys.platform identifies the running MicroPython port.
Common REPL controls include:
| Control | Typical action |
|---|---|
| Ctrl+C | Interrupt a running programme |
| Ctrl+D | Soft reset in the normal REPL |
| Ctrl+E | Enter paste mode on supported REPLs |
| Ctrl+B | Return from raw REPL mode |
Terminal software may intercept some key combinations.
Python Syntax Reference
Variables and data types
count = 10voltage = 3.3enabled = Truename = "Sensor"values = [10, 20, 30]settings = {"interval": 1000}
| Type | Example | Purpose |
|---|---|---|
| int | 42 | Integer |
| float | 3.14 | Floating-point number |
| bool | True | Boolean |
| str | “Hello” | Text |
| bytes | b”\x01\x02″ | Immutable byte sequence |
| bytearray | bytearray(8) | Mutable byte buffer |
| list | [1, 2, 3] | Mutable sequence |
| tuple | (1, 2) | Immutable sequence |
| dict | {“x”: 1} | Key-value mapping |
| set | {1, 2, 3} | Collection of unique values |
Integer representation and floating-point precision vary by MicroPython build and architecture.
Conditions
temperature = 28if temperature > 30: print("High")elif temperature < 10: print("Low")else: print("Normal")
Loops
for index in range(5): print(index)count = 0while count < 10: count += 1
Functions
def calculate_voltage(raw, reference, maximum): return raw * reference / maximumvoltage = calculate_voltage(2048, 3.3, 4095)
Exception handling
try: value = int("123")except ValueError: print("Invalid number")finally: print("Finished")
Use specific exception classes where possible rather than catching every exception.
Common Built-in Functions
| Function | Purpose |
|---|---|
| print() | Display output |
| len() | Return collection length |
| range() | Generate integer sequence |
| int() | Convert to integer |
| float() | Convert to floating-point |
| str() | Convert to string |
| bytes() | Create immutable bytes |
| bytearray() | Create mutable byte buffer |
| type() | Return object type |
| isinstance() | Check object type |
| enumerate() | Iterate with index |
| zip() | Combine iterables |
| min() / max() | Find minimum or maximum |
| sum() | Sum iterable values |
| abs() | Absolute value |
| round() | Round a number |
| hex() / bin() | Convert integer to formatted string |
| open() | Open a file on supported filesystems |
Some CPython built-ins and optional arguments may be unavailable.
Modules and Imports
import machineimport timeimport osimport sysfrom machine import Pin, ADC, PWM
Common modules include:
| Module | Function |
|---|---|
| machine | Hardware interfaces |
| time | Delays and time measurements |
| os | Filesystem operations |
| sys | Interpreter information |
| gc | Garbage collection |
| math | Mathematical functions |
| struct | Binary data packing |
| json | JSON encoding and decoding |
| socket | Network sockets, where supported |
| network | Network interface configuration |
| asyncio | Cooperative asynchronous programming |
| micropython | MicroPython-specific features |
Module availability and supported functions depend on firmware.
GPIO Input and Output
The machine.Pin class provides digital input and output control.
Digital output
from machine import Pinled = Pin(15, Pin.OUT)led.value(1)led.value(0)
Pin 15 is an illustrative GPIO number, not a universal onboard LED assignment.
Digital input
from machine import Pinbutton = Pin(14, Pin.IN, Pin.PULL_UP)if button.value() == 0: print("Pressed")
This assumes a switch connected between GPIO14 and ground.
Internal pull-up and pull-down availability varies by pin and device.
Common Pin operations
| Operation | Function |
|---|---|
| Configure output | Pin(n, Pin.OUT) |
| Configure input | Pin(n, Pin.IN) |
| Enable pull-up | Pin(n, Pin.IN, Pin.PULL_UP) |
| Read pin | pin.value() |
| Write pin | pin.value(1) |
| Set high | pin.on() |
| Set low | pin.off() |
| Toggle | pin.toggle() where supported |
Blink example
from machine import Pinfrom time import sleep_msled = Pin(15, Pin.OUT)while True: led.toggle() sleep_ms(500)
The selected GPIO must support output and have an appropriate LED circuit attached.
Timing and Delays
MicroPython provides millisecond and microsecond timing functions.
import timetime.sleep(1)time.sleep_ms(100)time.sleep_us(50)
| Function | Purpose |
|---|---|
| sleep(seconds) | Delay in seconds |
| sleep_ms(ms) | Delay in milliseconds |
| sleep_us(us) | Delay in microseconds |
| ticks_ms() | Monotonic millisecond tick counter |
| ticks_us() | Monotonic microsecond tick counter |
| ticks_diff(a, b) | Calculate wraparound-safe tick difference |
| ticks_add(ticks, delta) | Add an interval to a tick value |
Non-blocking interval measurement
import timeprevious = time.ticks_ms()while True: now = time.ticks_ms() if time.ticks_diff(now, previous) >= 1000: previous = now print("One-second interval")
The loop does not deliberately sleep, so it continuously consumes processor time.
For larger applications, insert suitable idle handling or use cooperative scheduling.
Do not subtract tick-counter values directly. Tick counters wrap, and ticks_diff() handles this correctly for intervals within its supported range.
ADC Reading
The machine.ADC class provides analogue-to-digital conversion on supported pins.
from machine import ADC, Pinadc = ADC(Pin(26))raw = adc.read_u16()print(raw)
GPIO26 is a suitable ADC input on the Raspberry Pi Pico, but this pin assignment is not portable to ESP32 or other boards.
ADC functions
| Function | Purpose |
|---|---|
| ADC(Pin(n)) | Create ADC interface on supported pin |
| read_u16() | Read scaled 16-bit ADC value |
| read_uv() | Read microvolt value where supported |
| atten() | Configure attenuation on selected ports |
| width() | Configure ADC width on selected ports |
read_u16() returns a value in the nominal range 0–65535, even when the hardware ADC has fewer than 16 bits of native resolution.
Approximate voltage calculation
raw = adc.read_u16()voltage = raw * 3.3 / 65535
This calculation assumes a 3.3 V full-scale input range.
It is only an approximation. The actual conversion relationship depends on ADC reference, attenuation, calibration, input circuitry and board design.
On ESP32 devices, ADC attenuation and non-linearity require particular attention.
PWM Output
PWM generates a periodic digital waveform with a configurable frequency and duty cycle.
from machine import Pin, PWMpwm = PWM(Pin(15), freq=1000)pwm.duty_u16(32768)
This requests approximately 1 kHz PWM with a 50% duty cycle.
PWM functions
| Function | Purpose |
|---|---|
| PWM(Pin(n), freq=f) | Create PWM output |
| pwm.freq() | Read frequency |
| pwm.freq(f) | Set frequency |
| pwm.duty_u16(value) | Set scaled duty cycle |
| pwm.duty_ns(value) | Set pulse width in nanoseconds, where supported |
| pwm.deinit() | Release PWM resource |
The duty_u16() range is 0–65535.
pwm.duty_u16(0) # 0%pwm.duty_u16(32768) # Approximately 50%pwm.duty_u16(65535) # 100%
The actual frequency and duty-cycle resolution depend on hardware timers and clock configuration.
PWM period calculation
Period (seconds) = 1 / Frequency (Hz)
For 1 kHz:
Period = 1 / 1000 Period = 1 ms
I2C Communication
The machine.I2C class provides I2C master operation on supported hardware.
Raspberry Pi Pico example
from machine import Pin, I2Ci2c = I2C( 0, scl=Pin(1), sda=Pin(0), freq=100000)print(i2c.scan())
This configuration applies to a Raspberry Pi Pico using I2C0 with GP1 as SCL and GP0 as SDA.
Common I2C methods
| Method | Purpose |
|---|---|
| i2c.scan() | Find responding 7-bit addresses |
| i2c.writeto(addr, data) | Write bytes |
| i2c.readfrom(addr, n) | Read bytes |
| i2c.readfrom_into(addr, buf) | Read into existing buffer |
| i2c.writeto_mem(addr, reg, data) | Write device register |
| i2c.readfrom_mem(addr, reg, n) | Read device register |
Register read
address = 0x48register = 0x00data = i2c.readfrom_mem(address, register, 2)print(data)
This is a transaction example; the register address and data length must match the connected device.
I2C requires suitable pull-up resistors on SDA and SCL. Internal GPIO pull-ups are generally not a substitute for correctly sized external I2C pull-ups.
SPI Communication
SPI provides synchronous serial communication using clock, data and optional chip-select signals.
Raspberry Pi Pico example
from machine import Pin, SPIspi = SPI( 0, baudrate=1000000, polarity=0, phase=0, sck=Pin(18), mosi=Pin(19), miso=Pin(16))cs = Pin(17, Pin.OUT, value=1)cs.value(0)spi.write(b"\x9F")response = spi.read(3)cs.value(1)print(response)
This demonstrates a command followed by three received bytes. The command 0x9F is commonly used to request JEDEC identification from compatible SPI flash devices.
SPI methods
| Method | Purpose |
|---|---|
| spi.write(data) | Transmit bytes |
| spi.read(n) | Receive bytes |
| spi.readinto(buffer) | Receive into buffer |
| spi.write_readinto(tx, rx) | Simultaneous transmit and receive |
| spi.init(…) | Reconfigure SPI |
| spi.deinit() | Release peripheral where supported |
Chip-select control is commonly handled using a separate GPIO.
UART Communication
UART provides asynchronous serial communication.
Raspberry Pi Pico example
from machine import UART, Pinuart = UART( 0, baudrate=115200, tx=Pin(0), rx=Pin(1))uart.write(b"Hello\r\n")if uart.any(): data = uart.read() print(data)
Common UART methods
| Method | Purpose |
|---|---|
| uart.any() | Check available receive data |
| uart.read() | Read available bytes |
| uart.read(n) | Read up to n bytes |
| uart.readline() | Read a line |
| uart.write(data) | Transmit bytes |
| uart.init(…) | Reconfigure UART |
| uart.deinit() | Release UART where supported |
UART pin assignments and available peripheral instances vary between boards.
Microcontroller UART pins normally use logic-level signalling, not RS-232 voltage levels.
Hardware Timers and Interrupts
Hardware timers can execute callbacks at configured intervals.
from machine import Timertimer = Timer()
Timer construction is port-dependent. Some platforms require a numeric timer identifier, while others support virtual timers.
A typical periodic configuration is:
timer.init( period=1000, mode=Timer.PERIODIC, callback=callback)
Here, callback must be a previously defined function.
Interrupt-safe callback pattern
from machine import Timerimport micropythonmicropython.alloc_emergency_exception_buf(100)ticks = 0def timer_callback(timer): global ticks ticks += 1
The callback performs minimal work.
Whether a timer callback executes as a hard interrupt or through scheduled execution depends on the port and timer configuration.
Interrupt restrictions
For hard interrupt callbacks:
- Avoid allocating memory.
- Avoid creating strings or lists.
- Avoid filesystem operations.
- Avoid blocking delays.
- Avoid network operations.
- Keep execution time short.
Use micropython.schedule() where supported to defer work outside hard interrupt context. Its scheduling queue is finite, so applications must handle the possibility that scheduling fails when the queue is full.
Asynchronous Programming
MicroPython supports cooperative asynchronous programming through asyncio on supported firmware builds.
Basic asynchronous tasks
import asyncioasync def blink_message(): while True: print("Running") await asyncio.sleep_ms(500)async def main(): await blink_message()asyncio.run(main())
Concurrent tasks
import asyncioasync def task_a(): while True: print("A") await asyncio.sleep_ms(1000)async def task_b(): while True: print("B") await asyncio.sleep_ms(1500)async def main(): asyncio.create_task(task_a()) await task_b()asyncio.run(main())
Tasks cooperate by yielding control through await.
A long blocking function prevents other tasks from running until it returns or yields.
Memory Management
MicroPython normally uses automatic garbage collection.
The gc module provides direct memory-management functions.
import gcgc.collect()print(gc.mem_free())print(gc.mem_alloc())
| Function | Purpose |
|---|---|
| gc.collect() | Run garbage collection |
| gc.mem_free() | Report free managed heap memory |
| gc.mem_alloc() | Report allocated managed heap memory |
| gc.enable() | Enable automatic collection |
| gc.disable() | Disable automatic collection |
The reported heap is not necessarily the microcontroller’s total available RAM.
Reduce memory allocations
Prefer reusable buffers when repeatedly processing binary data.
buffer = bytearray(64)while True: # Reuse buffer for repeated operations break
For hardware interfaces, methods such as readinto() can avoid creating a new bytes object on each transaction.
Avoid repeatedly concatenating large strings or constructing temporary lists in memory-constrained loops.
Filesystem Operations
Many MicroPython boards provide a flash-based filesystem.
with open("data.txt", "w") as file: file.write("123\n")
Read the file:
with open("data.txt", "r") as file: content = file.read()print(content)
Filesystem functions
import osprint(os.listdir())print(os.getcwd())
| Operation | Function |
|---|---|
| List files | os.listdir() |
| Current directory | os.getcwd() |
| Create directory | os.mkdir(“logs”) |
| Remove file | os.remove(“data.txt”) |
| Rename file | os.rename(“old.txt”, “new.txt”) |
| File information | os.stat(“data.txt”) |
Filesystem availability, capacity and wear characteristics depend on the port and storage medium.
Avoid frequent writes to flash for high-rate logging. Buffer data and write at controlled intervals where practical.
Wi-Fi Networking
Wi-Fi is available only on supported hardware and firmware builds.
The following pattern applies to MicroPython ports implementing the conventional network.WLAN station interface.
import networkimport timewlan = network.WLAN(network.STA_IF)wlan.active(True)wlan.connect("YOUR_SSID", "YOUR_PASSWORD")start = time.ticks_ms()while not wlan.isconnected(): if time.ticks_diff(time.ticks_ms(), start) > 10000: raise OSError("Wi-Fi connection timeout") time.sleep_ms(100)print(wlan.ifconfig())
The example provides a connection timeout but does not implement reconnection or recovery.
Common WLAN operations
| Method | Purpose |
|---|---|
| wlan.active(True) | Activate interface |
| wlan.connect(ssid, password) | Connect to access point |
| wlan.isconnected() | Check connection |
| wlan.ifconfig() | Inspect IP configuration |
| wlan.disconnect() | Disconnect |
| wlan.scan() | Scan nearby networks where supported |
| wlan.status() | Inspect connection status |
Status codes and advanced interface methods vary by port.
Do not assume every ESP32, RP2040 or RP2350 board includes wireless hardware.
JSON Data Handling
The json module supports structured data exchange.
import jsonreading = { "temperature": 24.5, "humidity": 52}encoded = json.dumps(reading)decoded = json.loads(encoded)print(decoded["temperature"])
| Function | Purpose |
|---|---|
| json.dumps(obj) | Convert object to JSON string |
| json.loads(text) | Parse JSON string |
| json.dump(obj, file) | Write JSON to stream, where supported |
| json.load(file) | Read JSON from stream, where supported |
MicroPython JSON support may differ from CPython in optional arguments and advanced features.
Debugging and System Information
Inspect the runtime
import sysimport osimport gcprint(sys.platform)print(sys.version)print(os.listdir())gc.collect()print(gc.mem_free())
Inspect hardware
import machineprint(machine.freq())print(machine.unique_id())
machine.freq() behaviour and supported arguments vary by port.
machine.unique_id() returns a device-specific identifier. Avoid publishing it unnecessarily.
Inspect available modules
help("modules")
The available module list reflects the installed firmware.
For an imported object:
from machine import Pinhelp(Pin)
This is useful when a particular firmware build differs from an example.
Common MicroPython Errors
| Error | Typical cause | Corrective action |
|---|---|---|
| ImportError | Module unavailable | Check firmware and supported modules |
| AttributeError | Unsupported method | Inspect API for selected port |
| NameError | Undefined variable | Check spelling and scope |
| TypeError | Incorrect argument type | Check function parameters |
| ValueError | Invalid value | Check pin number or parameter range |
| OSError | Hardware or I/O failure | Check peripheral configuration |
| MemoryError | Insufficient heap memory | Reduce allocations and inspect memory |
| SyntaxError | Invalid Python syntax | Check indentation and statements |
| IndentationError | Incorrect indentation | Use consistent indentation |
| KeyboardInterrupt | Execution interrupted | Inspect loop or callback behaviour |
An OSError from I2C may indicate an absent device, incorrect address, missing pull-ups or electrical problems.
An OSError from networking may indicate a disconnected interface or failed socket operation.
The exception type alone does not identify the exact hardware fault.
MicroPython and CPython Differences
| Feature | MicroPython consideration |
|---|---|
| Standard library | Reduced or modified |
| Hardware access | Provided through embedded-specific modules |
| Memory | Usually substantially more constrained |
| Threads | Port-dependent |
| Filesystem | May use internal flash |
| Floating-point precision | Build-dependent |
| Native extensions | Different from CPython |
| Package installation | Depends on firmware and available tools |
| Timing | Interpreter and garbage collection affect execution |
| Interrupt callbacks | Additional memory and execution restrictions |
MicroPython supports a substantial subset of Python 3, but desktop Python packages cannot be assumed to work without modification.
Performance and Reliability Notes
Avoid unnecessary blocking
Long sleep() calls prevent the current execution context from processing other work.
For responsive applications, use tick-based scheduling, hardware interrupts or asyncio.
Reuse memory
Repeatedly allocating objects in a fast loop increases garbage-collection activity.
Preallocate byte buffers and reuse objects where possible.
Protect shared state
Variables accessed by interrupt callbacks and normal code may require careful synchronisation.
A read-modify-write operation is not automatically atomic merely because it is written as one Python statement.
Handle peripheral failures
I2C, SPI, UART and network operations can fail.
Use appropriate exception handling and timeouts rather than assuming every transaction succeeds.
Avoid excessive flash writes
Flash storage has finite write endurance.
Store configuration only when it changes, and avoid writing sensor readings to flash at unnecessarily high rates.
Quick Command Reference
| Task | Command |
|---|---|
| Import hardware module | import machine |
| Configure output | Pin(n, Pin.OUT) |
| Configure input | Pin(n, Pin.IN) |
| Read GPIO | pin.value() |
| Set GPIO high | pin.on() |
| Set GPIO low | pin.off() |
| Delay milliseconds | time.sleep_ms(n) |
| Read elapsed ticks | time.ticks_ms() |
| Compare tick values | time.ticks_diff(a, b) |
| Read ADC | adc.read_u16() |
| Set PWM frequency | pwm.freq(hz) |
| Set PWM duty | pwm.duty_u16(value) |
| Scan I2C bus | i2c.scan() |
| Write SPI bytes | spi.write(data) |
| Read UART data | uart.read() |
| Run garbage collection | gc.collect() |
| Check free heap | gc.mem_free() |
| List files | os.listdir() |
| Check Wi-Fi connection | wlan.isconnected() |
| Inspect platform | sys.platform |
| Soft reset | machine.soft_reset() |
Deployment Checklist
- Install firmware built for the exact microcontroller or development board.
- Confirm the MicroPython port and version.
- Check that required hardware modules are available.
- Verify GPIO numbering and peripheral pin assignments.
- Confirm input and output voltage compatibility.
- Test hardware interfaces individually before combining them.
- Handle I/O errors and communication timeouts.
- Avoid memory allocation inside hard interrupt callbacks.
- Use wraparound-safe tick calculations.
- Monitor heap memory during long-running operation.
- Minimise unnecessary flash writes.
- Keep startup code recoverable.
- Test behaviour after power cycling and soft resets.
- Verify the application continues operating correctly after peripheral or network failures.




