Skip to content

Execution Models in Embedded Systems

This reference explains the spectrum of execution models, from pure bare-metal C to full operating systems, and where MicroPython fits.


The Execution Model Spectrum

← Maximum control                                    Maximum abstraction →
← Maximum responsibility                             Minimum responsibility →

┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ BARE-METAL  │ │ MICROPYTHON │ │    RTOS     │ │  EMBEDDED   │ │  DESKTOP    │
│     C       │ │  on bare    │ │ (FreeRTOS,  │ │   LINUX     │ │     OS      │
│             │ │   metal     │ │  Zephyr)    │ │             │ │             │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
     ▲               ▲               ▲               ▲               ▲
     │               │               │               │               │
   No OS          No OS           Kernel         Full kernel      Full OS
   No runtime     Has runtime     Scheduler      Processes        Processes
   You do all     Runtime helps   Tasks help     Isolation        Full services

Pure Bare-Metal C

What it means: Your compiled code IS the firmware. Nothing else runs.

┌─────────────────────────────────┐
│  Your C Code (compiled)         │  ← You write everything
├─────────────────────────────────┤
│  Startup code (reset vector)    │  ← Minimal, often SDK-provided
├─────────────────────────────────┤
│  Hardware                       │
└─────────────────────────────────┘

What You Get

Aspect Details
Direct hardware access Write to registers directly, no abstraction layers
Deterministic timing Code executes predictably, no surprise pauses
Minimal memory footprint Only your code, no runtime overhead
Full ISR control Do anything in interrupt handlers (carefully)

What You Don't Get

Aspect Details
No OS-provided memory protection A bug can corrupt any memory
No preemptive scheduler Unless you implement one or add RTOS
No OS-provided drivers Use SDK/HAL or write your own
No safety net Null pointer = hard fault, no traceback

Drivers in Bare-Metal

"No device drivers" doesn't mean you have no drivers. It means:

  • No OS-provided drivers (no /dev/ttyUSB0, no kernel modules)
  • You use SDK drivers (Pico SDK provides gpio_put(), i2c_write_blocking())
  • Or write your own (direct register access)
// Using SDK driver (abstraction)
gpio_put(25, 1);

// Or direct register access (no abstraction)
*(volatile uint32_t*)(SIO_BASE + 0x014) = (1 << 25);

MicroPython on Bare-Metal

What it means: The MicroPython firmware runs on bare-metal, your Python runs inside it.

┌─────────────────────────────────┐
│  Your Python Script             │  ← You write this
├─────────────────────────────────┤
│  MicroPython Runtime (~256KB)   │
│  ┌───────────────────────────┐  │
│  │ Python VM / Interpreter   │  │  ← Executes bytecode
│  │ Garbage Collector         │  │  ← Automatic memory
│  │ Exception Handling        │  │  ← Tracebacks
│  │ Built-in Modules          │  │  ← machine, time, etc.
│  │ REPL                      │  │  ← Interactive debug
│  │ Filesystem (optional)     │  │  ← littlefs on flash
│  └───────────────────────────┘  │
├─────────────────────────────────┤
│  Hardware (RP2350)              │
└─────────────────────────────────┘

Is MicroPython "Bare-Metal"?

Yes, in the sense that: - No Linux/Windows/macOS underneath - No kernel, no processes, no virtual memory - The firmware runs directly on the MCU

No, in the sense that: - Your code runs inside a substantial runtime - You don't have direct register access (by default) - The interpreter adds overhead and non-determinism

What MicroPython Gives You

Aspect C Bare-Metal MicroPython
Memory management Manual (malloc/free) Automatic (GC)
Error handling Hard fault, debugging required Exception + traceback
Development cycle Edit → Compile → Flash → Run Edit → Run
Drivers Write or use SDK Pre-written (machine module)
Interactive debug JTAG/SWD, printf REPL, inspect live

What You Trade Away

Aspect C Bare-Metal MicroPython
Timing precision Deterministic (µs) GC pauses (1-10ms)
ISR flexibility Full control Limited (no allocation)
Execution speed ~10ns per op ~1µs per op (100x slower)
Memory efficiency Only your code +256KB runtime
Register access Direct, fast Possible but awkward

When GC Pauses Matter

See this in Lab 05

Lab 05 demonstrates how blocking operations (like ultrasonic reads) destroy control loop timing - similar to GC pauses. → Ultrasonic & Timing tutorial

# GC pause can happen at ANY allocation
while True:
    data = sensor.read()     # Might trigger GC here
    result = process(data)   # Or here (creates objects)
    motor.set(result)        # Or here

For 50-100 Hz control loops: GC pause of 5ms is ~50% of a 10ms period. Usually fine, but measure!

For audio/video or fast protocols: GC pause is unacceptable. Use C or hardware (PIO).

ISR Limitations in MicroPython

# WRONG - allocates memory in ISR
def bad_isr(pin):
    message = f"Pin {pin} triggered"  # String allocation!
    data.append(time.ticks_ms())      # List growth!

# CORRECT - minimal ISR, process in main loop
flag = False
def good_isr(pin):
    global flag
    flag = True  # Just set flag, no allocation

while True:
    if flag:
        flag = False
        handle_event()  # Do real work here

RTOS (Real-Time Operating System)

What it means: A small kernel provides scheduling, but no full OS services.

┌─────────────────────────────────┐
│  Task 1    Task 2    Task 3    │  ← Your code in tasks
├─────────────────────────────────┤
│  RTOS Kernel (~5-20KB)          │
│  - Scheduler (preemptive)       │
│  - Task management              │
│  - Synchronization primitives   │
├─────────────────────────────────┤
│  Hardware                       │
└─────────────────────────────────┘

What RTOS Adds

Feature Bare-Metal RTOS
Task scheduling You manage timing Kernel schedules
Priority Manual in loop Preemption by priority
Blocking Stops everything Only stops that task
Synchronization DIY (flags, etc.) Mutex, semaphore, queue

When to Use RTOS

Use RTOS when: - 3+ tasks with different timing needs - Some tasks are time-critical (must never be delayed) - Tasks need to block without affecting others - You need proven synchronization primitives

Stay with bare-metal/super-loop when: - Simple system (1-2 main activities) - Timing can be managed with careful loop design - Memory is very constrained (<16KB) - Learning fundamentals (this course!)


Embedded Linux

What it means: Full Linux kernel, but on embedded hardware.

┌─────────────────────────────────┐
│  Your Application (process)     │
├─────────────────────────────────┤
│  Linux Kernel                   │
│  - Process isolation            │
│  - Virtual memory               │
│  - Device drivers               │
│  - Filesystem                   │
│  - Networking stack             │
├─────────────────────────────────┤
│  Hardware (needs MMU, >64MB RAM)│
└─────────────────────────────────┘

When to Use Embedded Linux

  • Complex applications (GUI, networking, databases)
  • Need standard libraries and tools
  • Hardware has MMU and sufficient RAM (>64MB)
  • Timing requirements are soft (ms, not µs)

When NOT to Use

  • Hard real-time requirements (µs precision)
  • Very resource-constrained (<1MB RAM)
  • Boot time matters (Linux takes seconds)
  • Power consumption is critical

Summary Comparison

Aspect Bare-Metal C MicroPython RTOS Embedded Linux
OS underneath None None Kernel only Full kernel
Your code is The firmware Hosted by runtime Tasks Process
Timing precision µs ms (GC pauses) µs ms
Memory overhead Minimal ~256KB ~5-20KB >8MB
Development speed Slow Fast Medium Medium
Debugging JTAG, printf REPL, exceptions JTAG, traces gdb, logs
Best for Production, safety Prototyping, learning Multi-task RT Complex apps

This Course: MicroPython on Bare-Metal

We use MicroPython because:

  1. Fast iteration - 10x faster than C compile cycle
  2. Forgiving - Exceptions instead of hard faults
  3. Focus on concepts - Not fighting syntax
  4. Still "bare-metal enough" - No OS, direct hardware access

The concepts (GPIO, PWM, control loops, timing) transfer directly to C when you need production performance.


Further Reading