Python for Embedded Linux Proficient¶
When you'd use this
Run Python on Raspberry Pi and embedded Linux — GPIO, daemons and cross-compiling.
Build and script Python on embedded Linux boards — GPIO, buses, and device interfaces for IoT and robotics.
What you'll learn¶
- Where embedded Linux sits between a PC and a microcontroller
- Control GPIO from full CPython
- Run Python as a background service (daemon)
- Cross-compile and deploy to a target board
- Manage resource constraints on small Linux devices
This topic targets embedded Linux boards
Examples assume a device like a Raspberry Pi running Linux, with hardware pins and a systemd init. They use documented library and OS APIs and aren't run-verified on this desktop. To follow along you need a board (Pi, BeagleBone, or similar).
Embedded Linux vs microcontrollers¶
Full OS on a board (Raspberry Pi) vs bare-metal MCUs — different capabilities and constraints.
Unlike a microcontroller running MicroPython, an embedded Linux board (Raspberry Pi, BeagleBone, industrial gateways) runs a full Linux OS and full CPython. You get the entire standard library, pip, and PyPI — but on modest hardware and often headless (no screen).
Microcontroller Embedded Linux Desktop/Server
MicroPython full CPython full CPython
~256 KB RAM 256 MB–2 GB RAM GBs
no OS / RTOS real Linux Linux/Windows
bare-metal pins GPIO via OS drivers no GPIO
So "Python for embedded Linux" is mostly normal Python — with three twists: hardware pins, running as an unattended service, and tight-ish resources.
Controlling GPIO¶
Read sensors and drive outputs via the board's GPIO pins from Python.
On a Raspberry Pi, the modern library is gpiozero (friendly, high-level, built on lower-level drivers):
from gpiozero import LED, Button # pip install gpiozero
from time import sleep
led = LED(17) # BCM pin 17
button = Button(2)
button.when_pressed = led.on # event-driven
button.when_released = led.off
while True:
sleep(1)
Or a simple blink:
from gpiozero import LED
from time import sleep
led = LED(17)
while True:
led.on(); sleep(0.5)
led.off(); sleep(0.5)
Because it's full CPython, you can freely combine hardware control with the whole ecosystem — read a sensor and POST it to an API with requests, log to a database, or serve a web dashboard, all in one program.
Running as a service (daemon)¶
Keep your program running in the background and start on boot via systemd.
Embedded devices run headless and unattended — your program must start on boot, restart if it crashes, and log somewhere persistent. On Linux, systemd manages this. You write a unit file:
# /etc/systemd/system/sensor.service
[Unit]
Description=Sensor logging service
After=network.target
[Service]
ExecStart=/usr/bin/python3 /home/pi/sensor.py
Restart=always # restart if it crashes
User=pi
[Install]
WantedBy=multi-user.target
Then enable and start it:
sudo systemctl enable sensor.service # start on boot
sudo systemctl start sensor.service # start now
sudo systemctl status sensor.service # check it
journalctl -u sensor.service -f # follow its logs
Restart=always gives you crash recovery for free, and journalctl centralizes logs. Your Python code should use the logging module (see Automation & Scripting) so its output lands in the journal.
Design for unattended operation
A daemon has no one watching. Handle errors so a transient failure (a sensor hiccup, a dropped network) is logged and retried, not fatal. Catch SIGTERM for graceful shutdown so systemd can stop it cleanly.
Cross-compiling and deployment¶
Build on a dev machine and deploy to the target board.
Embedded boards are slow to build on. Cross-compiling means building on your fast PC for the target's architecture (often ARM), then deploying the artifact.
For pure-Python code there's nothing to compile — you just copy files. The complication is C-extension dependencies (numpy, cryptography, pillow): their compiled wheels must match the target's architecture.
pipwheels — many packages publish prebuilt ARM wheels (manylinux/aarch64), sopip installon the Pi just works.- Docker with QEMU / buildx — build an ARM image on your PC (
docker buildx build --platform linux/arm64) and run it on the board. Reproducible and increasingly the standard. - Yocto / Buildroot — build a whole custom Linux image with your Python app baked in. Heavy tooling, used for production embedded products at scale.
For deployment, common approaches are rsync/scp of the code, a git pull on the device, or shipping a Docker image.
Resource constraints¶
Work within limited CPU, memory, and storage on embedded hardware.
Embedded Linux is roomier than a microcontroller but far tighter than a server:
- Limited RAM (256 MB–1 GB typical). Avoid loading huge datasets; stream instead. Watch memory-hungry libraries.
- Slow/limited storage — often an SD card with limited write cycles. Minimize logging to disk (log to journald with rotation), avoid constant writes that wear the card.
- Modest CPU — heavy numeric work is slow; offload to C extensions or accept lower rates.
- Power — battery/solar devices need you to sleep the CPU and minimize wakeups.
- Startup time — a large Python app with many imports can be slow to boot; keep dependencies lean.
Practice exercises¶
- Write a
gpiozeroprogram that lights an LED when a sensor reading crosses a threshold, and turns it off otherwise. - Write a systemd unit for a Python script, enable it, and confirm it restarts after you
killthe process. - Add
loggingto a daemon so its output appears injournalctl, including a startup and shutdown message. - Add a
SIGTERMhandler that stops your loop cleanly, and verifysystemctl stopshuts it down gracefully. - Explain when you'd choose a Docker/buildx deployment over
rsync-ing files to the board.
💬 Discussion
Have a question about this topic? Found an error? Share your thoughts below.