Skip to content

Introduction to MicroPython

What is MicroPython?

MicroPython is a lightweight and efficient implementation of Python designed specifically for microcontrollers and embedded systems. It brings the ease and readability of Python to hardware programming, making it an excellent choice for beginners and experienced developers alike. Unlike standard Python, MicroPython includes libraries and modules tailored for controlling hardware components such as GPIO pins, I2C, and SPI interfaces.

Why use MicroPython on the Raspberry Pi Pico2?

The Raspberry Pi Pico2 is a powerful yet affordable microcontroller based on the RP2350 chip. MicroPython is an excellent choice for programming the Pico because:

  • Ease of Use: MicroPython simplifies embedded programming by eliminating complex build systems and compilation steps.
  • Interactivity: The REPL (Read-Eval-Print Loop) allows for immediate testing and debugging of code.
  • Rich Standard Library: Many built-in functions make development faster.
  • Cross-Platform Development: Code written in MicroPython is easier to port between different microcontrollers.
  • Community Support: A strong community and extensive documentation make it easier to find solutions to common problems.

Raspberry Pi Pico vs. Pico W vs. Pico 2

The Raspberry Pi Pico family consists of multiple versions, primarily the Pico, Pico W, and Pico 2:

Feature Raspberry Pi Pico Raspberry Pi Pico W Raspberry Pi Pico 2
Chip RP2040 (Dual-core Cortex-M0+) RP2040 (Dual-core Cortex-M0+) RP2350 (Dual-core Cortex-M33)
WiFi Support No Yes (built-in Infineon CYW43439 WiFi chip) No
Bluetooth No No (though the chip supports Bluetooth, it's currently not enabled) No
GPIO Pins 26 26 30
Power Consumption Lower Slightly higher due to WiFi module Optimized for efficiency
Performance Standard Standard with WiFi overhead Higher (Cortex-M33 improvements)

The Pico W is ideal for IoT applications requiring wireless connectivity, while the Pico is better suited for projects that do not need WiFi and require lower power consumption. The Pico 2, based on the RP2350, provides improved performance and additional GPIOs, making it a strong candidate for more demanding embedded applications.

Differences between C and MicroPython in Embedded Programming

MicroPython and C are two popular choices for embedded development, but they differ in various aspects:

Feature MicroPython C
Ease of Learning High (simplified syntax, no need for pointers) Moderate to Hard (requires memory management, pointers)
Execution Speed Slower (interpreted) Faster (compiled)
Development Speed Fast (no compilation, interactive testing) Slower (compilation required)
Code Size Larger due to interpreter overhead Smaller, optimized for hardware
Hardware Access Simplified through built-in modules Full control over hardware
Portability High (works across different microcontrollers) Requires porting effort

Here’s a simple LED blinking example in both C and MicroPython:

C Code (Using SDK):

#include "pico/stdlib.h"

int main() {
    gpio_init(25);
    gpio_set_dir(25, GPIO_OUT);
    while (1) {
        gpio_put(25, 1);
        sleep_ms(500);
        gpio_put(25, 0);
        sleep_ms(500);
    }
}

MicroPython Code:

from machine import Pin
from time import sleep

led = Pin("LED", Pin.OUT)  # Onboard LED on Pico/Pico 2

while True:
    led.toggle()
    sleep(0.5)

Key Takeaways:

  • Simplicity: MicroPython code is shorter and easier to read.
  • Interactivity: You can test MicroPython code in real-time from REPL.
  • Performance: C is faster and more memory-efficient, making it ideal for time-critical applications.

How Slow is Python, Really?

Let's measure it! Here's what a simple pin toggle takes on a Pico 2:

Operation Time per call Speed
Python led.on()/led.off() ~535 µs ~1,900 toggles/sec
Python empty loop iteration ~2.6 µs ~385,000 iter/sec
Direct register write (Python) ~6 µs ~167,000 writes/sec
C compiled code ~0.008 µs (8 ns) ~125,000,000 ops/sec
PIO hardware 0.1 µs (100 ns) 10,000,000 ops/sec

Python is ~67,000x slower than C for pin operations!

Why Does This Matter?

Some hardware protocols require nanosecond precision:

WS2812B (NeoPixel) LED protocol:
├── '0' bit: 350ns HIGH, 900ns LOW
├── '1' bit: 800ns HIGH, 450ns LOW
└── Tolerance: ±150ns

Python speed:     535,000 ns per toggle
Required speed:       350 ns per toggle
                  ─────────────────────
Python is 1,500x TOO SLOW!

So Why Use MicroPython at All?

Because most embedded tasks don't need nanosecond timing:

Task Timing Needed Python OK?
Read button state ~10 ms ✓ Yes
Read temperature sensor ~100 ms ✓ Yes
Control motor speed ~1 ms ✓ Yes
Update display ~50 ms ✓ Yes
Decision logic ~1 ms ✓ Yes
WS2812B LED protocol ~350 ns ✗ No
Ultrasonic echo timing ~1 µs ✗ Marginal

For timing-critical tasks, the Pico has hardware peripherals that run independently:

┌─────────────────────────────────────────────────────────┐
│                    Pico Architecture                    │
│                                                         │
│   ┌─────────────┐     Your code runs here              │
│   │  MicroPython │     (slow but easy)                 │
│   │  Interpreter │                                      │
│   └──────┬──────┘                                      │
│          │ "Hey PIO, send this LED data"               │
│          ▼                                              │
│   ┌─────────────┐     Hardware runs here               │
│   │  PIO / PWM  │     (fast and precise)               │
│   │  Hardware   │     Runs INDEPENDENTLY!              │
│   └──────┬──────┘                                      │
│          │                                              │
│          ▼                                              │
│      GPIO Pins ────► LEDs, Motors, Sensors             │
└─────────────────────────────────────────────────────────┘

The Best of Both Worlds

MicroPython lets you:

  1. Write simple code for logic, decisions, and user interaction
  2. Use hardware peripherals (PIO, PWM, DMA) for timing-critical operations
  3. Focus on learning concepts, not fighting compiler errors
The Embedded Developer's Rule

"Use software for thinking, use hardware for timing."

  • Python handles: logic, state machines, sensor processing, communication
  • Hardware handles: precise PWM, LED protocols, motor control signals, accurate measurements

This is why professional embedded systems often use a high-level language for application logic and hardware peripherals or C for timing-critical drivers - you'll see this pattern everywhere from Arduino to industrial PLCs.


MicroPython in Production: Not Just for Prototyping

A common misconception: "MicroPython is for learning, C is for real products."

Reality: Many production embedded systems use Python (or similar high-level languages) deliberately. The key is architectural separation:

┌──────────────────────────────────────────────────────────────┐
│                     Application Layer                        │
│  ┌────────────────────────────────────────────────────────┐ │
│  │              MicroPython / High-Level Code             │ │
│  │  • State machines       • Communication protocols      │ │
│  │  • Business logic       • Configuration & settings     │ │
│  │  • User interaction     • OTA updates                  │ │
│  └────────────────────────────────────────────────────────┘ │
│                           ▲                                  │
│                           │ API calls                        │
│                           ▼                                  │
│  ┌────────────────────────────────────────────────────────┐ │
│  │              C / SDK / Hardware Drivers                │ │
│  │  • PIO programs         • DMA transfers                │ │
│  │  • Interrupt handlers   • Timing-critical code         │ │
│  │  • Low-level protocols  • Hardware abstraction         │ │
│  └────────────────────────────────────────────────────────┘ │
│                           ▲                                  │
│                           │                                  │
│                           ▼                                  │
│  ┌────────────────────────────────────────────────────────┐ │
│  │                      Hardware                          │ │
│  │         GPIO / PIO / PWM / DMA / UART / I2C            │ │
│  └────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘

Why Professionals Choose MicroPython

Reason Explanation
Development Speed No compile-flash cycles. Hardware teams often finish late—software must adapt fast.
Field Updates Change behavior by uploading a .py file. No reflashing, no bricking risk.
Team Separation Firmware engineers write C drivers; application engineers write Python logic.
Safety Python crash → restart interpreter. C bug → potential hardware lockup or brick.
Readability Non-embedded specialists can understand and modify behavior code.

When MicroPython is NOT Appropriate

Use Case Why Not
Hard real-time control loops Jitter from garbage collection
Sub-microsecond timing Interpreter overhead
Bit-banged protocols Too slow for precise timing
Maximum power efficiency Interpreter uses more resources
Safety-critical systems Certification requirements

These belong in C / PIO / DMA.

The Teaching Value

This course uses MicroPython because it lets you focus on:

  • Concepts (state machines, control loops, sensor fusion)
  • Architecture (how to structure embedded software)
  • Problem-solving (debugging, testing, iteration)

Without drowning in:

  • Linker scripts and memory maps
  • Toolchain setup and CMake
  • Pointer arithmetic and memory management
  • Hours of compile-flash-debug cycles

Then, when you understand why timing matters (because you've seen Python fail at WS2812B!), the motivation for learning C/SDK becomes clear.

The Course Philosophy

"Understand the problem before optimizing the solution."

We deliberately use a "slow" framework so you can: 1. See timing failures happen (NeoPixels glitching) 2. Understand why they happen (Python interpreter overhead) 3. Appreciate how hardware peripherals solve them (PIO) 4. Know when to reach for C/SDK in your career


Going Further: The Pico C/C++ SDK

Advanced Topic

This section is for students who want to understand what "bare-metal" programming looks like. You don't need this for the course, but it helps understand what MicroPython is abstracting away.

When you need deterministic timing or maximum performance, the Pico C/C++ SDK provides direct hardware access.

What "Using the SDK" Means

MicroPython Pico SDK
Write .py files Write .c files
Run directly via REPL Build with CMake, flash .uf2
Interpreter handles hardware You control everything
~535 µs per GPIO toggle ~8 ns per GPIO toggle

Minimal SDK Example

Project structure:

my_project/
├── CMakeLists.txt
├── pico_sdk_import.cmake  # copied from SDK
└── main.c

main.c - LED blink in C:

#include "pico/stdlib.h"

int main() {
    const uint LED_PIN = 25;

    gpio_init(LED_PIN);
    gpio_set_dir(LED_PIN, GPIO_OUT);

    while (true) {
        gpio_put(LED_PIN, 1);
        sleep_ms(500);
        gpio_put(LED_PIN, 0);
        sleep_ms(500);
    }
}

Build and flash:

mkdir build && cd build
cmake ..
make
# Hold BOOTSEL, plug in Pico, copy .uf2 file
cp my_project.uf2 /media/$USER/RPI-RP2/

Concept Mapping: MicroPython → SDK

Concept MicroPython Pico SDK
GPIO control Pin() gpio_init(), gpio_put()
Delays time.sleep() sleep_ms()
PWM PWM() pwm_init(), pwm_set_wrap()
PIO @rp2.asm_pio .pio files (compiled)
Serial output print() printf() (via stdio_init_all())
Build process None (interpreted) CMake → Make → .uf2

When to Consider SDK

Start here: MicroPython
Does it work? ─── Yes ──► Ship it! ✓
     No (timing issues)
Can PIO/PWM/DMA fix it? ─── Yes ──► Use hardware peripheral ✓
     No (need full control)
Rewrite critical part in C/SDK
Keep Python for application logic
Resources

The Hybrid Pattern: C Library + Python Application

In production systems, you often don't choose between MicroPython OR C — you use both:

┌─────────────────────────────────────────────────────────────┐
│  Your Application (MicroPython)                             │
│  ┌───────────────────────────────────────────────────────┐  │
│  │  from led_driver import set_pixel, show               │  │
│  │                                                       │  │
│  │  # High-level logic in Python                         │  │
│  │  if sensor.detected():                                │  │
│  │      set_pixel(0, RED)                                │  │
│  │  else:                                                │  │
│  │      set_pixel(0, GREEN)                              │  │
│  │  show()                                               │  │
│  └───────────────────────────────────────────────────────┘  │
│                          │                                   │
│                          ▼ Python calls C                    │
│  ┌───────────────────────────────────────────────────────┐  │
│  │  Native C Module (compiled into firmware)             │  │
│  │                                                       │  │
│  │  // Timing-critical code in C                         │  │
│  │  void set_pixel(int idx, uint32_t color) {            │  │
│  │      led_buffer[idx] = color;                         │  │
│  │  }                                                    │  │
│  │  void show() {                                        │  │
│  │      pio_sm_put_blocking(pio0, 0, led_buffer);        │  │
│  │  }                                                    │  │
│  └───────────────────────────────────────────────────────┘  │
│                          │                                   │
│                          ▼ C controls hardware               │
│  ┌───────────────────────────────────────────────────────┐  │
│  │  Hardware (PIO state machine)                         │  │
│  │  Generates precise 800kHz WS2812 timing               │  │
│  └───────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘

This is exactly how MicroPython's built-in modules work!

When you write from neopixel import NeoPixel, you're using a C module that: - Exposes a simple Python API (leds[0] = (255, 0, 0)) - Internally uses PIO hardware for precise timing - Was compiled into the MicroPython firmware

You're Already Using This Pattern

Every machine.Pin(), machine.PWM(), and neopixel.NeoPixel() call is Python talking to C code that controls hardware. The architecture you've been learning about is what you've been using all along!

Example: What a C Driver Looks Like

Here's a simplified WS2812 driver in C (what runs "behind" MicroPython's NeoPixel):

// ws2812_driver.c - Compiled into MicroPython firmware

#include "hardware/pio.h"
#include "ws2812.pio.h"  // PIO program (generated from .pio file)

static uint32_t led_buffer[8];
static PIO pio = pio0;
static uint sm = 0;

// Called once at startup
void ws2812_init(uint pin, uint num_leds) {
    uint offset = pio_add_program(pio, &ws2812_program);
    ws2812_program_init(pio, sm, offset, pin, 800000);
}

// Called from Python: set_pixel(index, color)
void ws2812_set_pixel(uint index, uint32_t grb) {
    led_buffer[index] = grb;  // Just store in buffer (fast!)
}

// Called from Python: show()
void ws2812_show(void) {
    for (int i = 0; i < 8; i++) {
        pio_sm_put_blocking(pio, sm, led_buffer[i] << 8);
    }
}

Key insight: The C code is simple! Most of the work is done by the PIO hardware. C just: 1. Initializes the PIO state machine (once) 2. Stores colors in a buffer (fast) 3. Sends buffer to PIO (hardware does the timing)

The PIO Program (Assembly)

The actual timing-critical code runs on PIO hardware, written in PIO assembly:

; ws2812.pio - Runs on PIO hardware, NOT on CPU!
.program ws2812
.side_set 1

.wrap_target
bitloop:
    out x, 1       side 0 [T3 - 1]  ; Shift 1 bit, drive low
    jmp !x do_zero side 1 [T1 - 1]  ; If 0: short high pulse
do_one:
    jmp bitloop    side 1 [T2 - 1]  ; If 1: long high pulse
do_zero:
    nop            side 0 [T2 - 1]  ; Pad low time
.wrap

This runs at 10MHz, independent of Python or even C!

The CPU (running Python or C) just feeds data to PIO. PIO generates the precise nanosecond pulses.

For Curious Students: Building Custom C Modules

If you want to add your own C code to MicroPython:

  1. User C Modules — Add C code that compiles with MicroPython firmware
  2. Rebuild firmware — Compile MicroPython with your module included
  3. Flash and useimport your_module in Python

Resources: - MicroPython: Extending with C - Pico SDK + MicroPython Integration

This is advanced — but now you understand WHY it exists.


Summary: The Complete Picture

┌────────────────────────────────────────────────────────────────┐
│                    EMBEDDED SYSTEM ARCHITECTURE                 │
├────────────────────────────────────────────────────────────────┤
│                                                                 │
│   WHAT              │  LANGUAGE   │  SPEED      │  USED FOR    │
│  ──────────────────────────────────────────────────────────    │
│   Application       │  Python     │  ~1 ms      │  Logic,      │
│   Logic             │             │             │  decisions,  │
│                     │             │             │  state       │
│  ──────────────────────────────────────────────────────────    │
│   Hardware          │  C / SDK    │  ~1 µs      │  Drivers,    │
│   Drivers           │             │             │  protocols,  │
│                     │             │             │  buffering   │
│  ──────────────────────────────────────────────────────────    │
│   Hardware          │  PIO / PWM  │  ~100 ns    │  Precise     │
│   Peripherals       │  / DMA      │             │  timing,     │
│                     │             │             │  waveforms   │
│                                                                 │
└────────────────────────────────────────────────────────────────┘

     "Software for thinking, hardware for timing"

In this course, you work at the top layer (Python) and use pre-built hardware peripherals (PIO via NeoPixel library, PWM via machine.PWM).

In your career, you may also work at the middle layer (C drivers) or even the bottom layer (PIO programs) — and now you understand how they all fit together.


In summary, MicroPython is a great language that provides good tools for prototyping and production. The key is knowing when to let hardware handle timing, and when to reach for C/SDK for full control.

Info

You can find a more detailed article about the basics of the MicroPyhton and compered to C here.


➡ Next Steps