Chapter 31: Python Programming for Embedded Systems: A Refresher
Chapter Objectives
By the end of this chapter, you will be able to:
- Understand the role and advantages of Python for scripting and hardware control in an embedded Linux environment.
- Implement fundamental Python concepts, including variables, data structures, and control flow, in practical scripts.
- Develop robust Python applications that interact directly with the Raspberry Pi 5’s GPIO pins to control actuators and read sensors.
- Configure the Python environment on a Raspberry Pi 5, manage dependencies, and apply best practices for writing clean, maintainable code.
- Debug common issues related to Python programming for hardware, including permission errors, incorrect pin configurations, and resource management.
- Apply file I/O operations to log sensor data, creating a foundational skill for data acquisition and monitoring applications.
Introduction
Welcome to the world of high-level programming in an embedded context. In previous chapters, we explored the low-level intricacies of the embedded Linux system, from the boot process to shell scripting. While the shell is a powerful tool for system administration and task automation, and languages like C provide unparalleled performance and control, modern embedded development often requires a faster, more intuitive approach for application-level logic. This is where Python finds its purpose.
Python has emerged as a dominant force in the embedded Linux space, particularly on powerful single-board computers like the Raspberry Pi 5. Its appeal lies in its simplicity, readability, and the vast ecosystem of libraries that abstract away hardware complexity. While C might be the language of choice for writing a real-time kernel driver, Python is often the perfect tool for writing the application that uses that driver. Imagine developing a smart home hub. The core device drivers might be written in C for maximum efficiency, but the web server, user interface logic, and rules engine that decide when to turn on a light are almost certainly better suited for Python. It allows for rapid prototyping, easier debugging, and more maintainable code, significantly reducing development time. In this chapter, we will refresh the core concepts of the Python language and immediately apply them to practical hardware interaction on the Raspberry Pi 5. You will move from theory to tangible results, making LEDs blink, responding to button presses, and logging real-world data, all through the power and elegance of Python.
Technical Background
The Philosophy of Python in an Embedded World
Before diving into syntax and code, it is crucial to understand why Python has become so integral to embedded Linux development. The language itself is guided by a set of aphorisms known as the “Zen of Python,” which can be viewed by typing import this
into a Python interpreter. Principles like “Beautiful is better than ugly,” “Simple is better than complex,” and “Readability counts” are not just abstract ideals; they have profound practical implications. In an embedded project, where you might be returning to code months later to debug a strange behavior, code that is easy to read and understand is inherently more robust.
Unlike C or C++, where the programmer is responsible for manual memory management and explicit data typing, Python employs dynamic typing and automatic memory management. This means you don’t need to declare a variable’s type before using it, nor do you need to allocate and free memory. This abstraction layer is a trade-off. The cost is a reduction in execution speed and an increase in memory footprint compared to a compiled language like C. However, the benefit is a dramatic increase in developer productivity. For a vast number of embedded applications—such as environmental monitoring, industrial automation control, robotics, and IoT gateways—the raw performance of C is not the primary bottleneck. Instead, the challenge lies in managing complexity and developing reliable application logic quickly. The Raspberry Pi 5, with its multi-core processor and gigabytes of RAM, has more than enough power to run Python scripts efficiently, making the productivity gains far outweigh the performance overhead for most application-level tasks.
The Python Environment on Raspberry Pi OS
Raspberry Pi OS comes pre-installed with Python 3. It is critical to note that Python 2 is now legacy and should not be used for new projects. You can verify your Python 3 installation by opening a terminal and running python3 --version
.
The companion to the Python interpreter is pip, the package installer for Python. You use pip to install libraries from the Python Package Index (PyPI), a vast repository of third-party software. To install a library, you would use the command pip3 install <package_name>
.
A crucial best practice in modern Python development is the use of virtual environments. A virtual environment is an isolated directory containing a specific version of the Python interpreter and its own set of installed libraries. This prevents conflicts between projects that may require different versions of the same library. For example, Project A might need version 1.2 of a sensor library, while Project B requires version 2.0. Without virtual environments, these dependencies would clash in the global system installation. To create a virtual environment, you navigate to your project directory and run python3 -m venv venv
. The second venv
is the name of the directory that will be created. To activate it, you use the command source venv/bin/activate
. Once activated, your shell prompt will change, indicating that any python
or pip
commands will now use the environment’s isolated installation.
%%{ init: { 'theme': 'base', 'themeVariables': { 'fontFamily': 'Open Sans' } } }%% graph TD subgraph System SysPython[System Python 3 Installation<br>/usr/bin/python3] style SysPython fill:#374151,stroke:#374151,stroke-width:2px,color:#ffffff end subgraph Project A direction LR A_Venv[venv for Project A] A_Lib[Sensor Library v1.2] A_App[App A<br><i>app.py</i>] style A_Venv fill:#1e3a8a,stroke:#1e3a8a,stroke-width:2px,color:#ffffff style A_Lib fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff style A_App fill:#8b5cf6,stroke:#8b5cf6,stroke-width:1px,color:#ffffff A_App -->|imports| A_Lib A_Venv -->|provides| A_Lib end subgraph Project B direction LR B_Venv[venv for Project B] B_Lib[Sensor Library v2.0] B_App[App B<br><i>app.py</i>] style B_Venv fill:#1e3a8a,stroke:#1e3a8a,stroke-width:2px,color:#ffffff style B_Lib fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff style B_App fill:#8b5cf6,stroke:#8b5cf6,stroke-width:1px,color:#ffffff B_App -->|imports| B_Lib B_Venv -->|provides| B_Lib end SysPython -.->|creates & isolates| A_Venv SysPython -.->|creates & isolates| B_Venv linkStyle 0 stroke-width:2px,fill:none,stroke:gray,stroke-dasharray: 3 3; linkStyle 1 stroke-width:2px,fill:none,stroke:gray,stroke-dasharray: 3 3;
Core Syntax, Data Types, and Structures
Python’s syntax is designed to be clean and uncluttered. It uses indentation, rather than braces {}
as in C, to define code blocks. This is not just a stylistic choice; it enforces a readable code structure.
Variables and Dynamic Typing
In Python, you create a variable simply by assigning a value to it. The interpreter infers the data type at runtime.
# In C, you would have to declare the type first:
# int sensor_id = 10;
# float temperature = 25.7;
# In Python, you just assign the value:
sensor_id = 10 # An integer
temperature = 25.7 # A floating-point number
device_name = "rpi-5-A" # A string
is_active = True # A boolean
This is dynamic typing. The type of temperature
is not fixed; you could later assign a string to it (though this is generally bad practice). This flexibility is powerful for scripting but requires careful coding to avoid type-related errors.
Fundamental Data Structures
Beyond simple types, Python provides powerful built-in data structures that are essential for handling real-world data.
- Lists are ordered, mutable (changeable) collections of items. They are the workhorse for storing sequences of data, such as a series of sensor readings over time. Lists are defined with square brackets
[]
.
# A list of temperature readings
temp_readings = [22.1, 22.3, 22.4, 22.3, 22.5]
temp_readings.append(22.6) # Add a new reading to the end
first_reading = temp_readings[0] # Access by index (starts at 0)
print(f"The first reading was: {first_reading}")
- Tuples are ordered, immutable (unchangeable) collections. Once a tuple is created, you cannot add, remove, or change its elements. This makes them perfect for data that should remain constant, such as the configuration for a hardware pin. They are defined with parentheses
()
.
# A tuple to hold pin configuration: (Pin Number, Mode, Initial State)
led_pin_config = (17, "OUTPUT", "LOW")
# led_pin_config[0] = 27 # This would cause a TypeError
- Dictionaries are unordered collections of key-value pairs. They are incredibly useful for storing and retrieving data with descriptive labels, much like a real-world dictionary. They are defined with curly braces
{}
. Dictionaries are the natural way to represent structured data, such as a JSON object received from a web API or a complete configuration for a device.
# A dictionary representing a sensor device
sensor_device = {
"id": "TEMP001",
"type": "DHT22",
"location": "Lab A",
"gpio_pin": 4,
"active": True
}
# Accessing data is intuitive
print(f"Sensor is connected to GPIO pin {sensor_device['gpio_pin']}")
Control Flow: Making Decisions and Looping
A script that only runs from top to bottom is of limited use. Control flow statements allow your program to make decisions and repeat actions.
Conditional Statements
The if, elif (else if), and else statements allow you to execute code blocks based on a condition. This is the primary way a program can respond to changing inputs, such as a sensor value crossing a threshold or a user pressing a button.
temperature = 31.5
if temperature > 30.0:
print("Warning: Temperature is high. Activating cooling fan.")
elif temperature < 10.0:
print("Warning: Temperature is low. Activating heater.")
else:
print("Temperature is within the normal range.")
Loops
Loops are used to repeat a block of code. Python has two main types of loops.
- The
for
loop iterates over a sequence (like a list, tuple, or string). It is used when you know how many times you want to loop.
# Process a list of GPIO pins to set them up
output_pins = [17, 27, 22]
for pin in output_pins:
print(f"Setting up GPIO pin {pin} as an output.")
# Code to configure the pin would go here
- The
while
loop executes as long as a condition is true. It is used when you don’t know in advance how many times you need to loop, such as waiting for a specific input.
# Wait for a button to be pressed (assuming button_pressed() returns True when pressed)
while not button_pressed():
print("Waiting for button press...")
# A small delay is crucial here to prevent the CPU from running at 100%
import time
time.sleep(0.1)
print("Button was pressed!")
Functions and Modularity
As scripts grow, it becomes essential to organize them into reusable blocks of code. Functions are the primary way to achieve this. A function is a named block of code that performs a specific task. It can accept input data (parameters) and return a result.
def read_temperature_celsius(sensor_pin):
"""
Reads the temperature from a sensor on a specific pin.
This is a placeholder function. A real implementation would have hardware code.
Returns the temperature in Celsius as a float.
"""
# In a real scenario, this would involve library calls to read the hardware.
# For this example, we'll return a simulated value.
simulated_temp = 25.0 + (sensor_pin / 10.0)
return simulated_temp
# Now we can use this function, making the main code cleaner
temp1 = read_temperature_celsius(4)
temp2 = read_temperature_celsius(5)
print(f"Sensor 1: {temp1}°C, Sensor 2: {temp2}°C")
The text within the triple quotes is a docstring, which explains what the function does. This is a critical part of writing maintainable code.
Modules and Hardware Abstraction Libraries
No one writes all their code from scratch. Python’s power comes from its vast ecosystem of modules and packages. A module is simply a Python file (.py
) containing functions and definitions. A package is a collection of modules. To use code from a module, you import
it.
For embedded systems, this is how we access hardware. Libraries like RPi.GPIO
or gpiozero
are modules that provide functions to control the Raspberry Pi’s GPIO pins. They abstract the low-level details of memory-mapped registers, allowing you to simply write GPIO.output(17, GPIO.HIGH)
instead of complex C code.
Error Handling with try...except
When interacting with the physical world, things can go wrong. A sensor might become disconnected, a file might not exist, or a network connection might drop. A robust program must anticipate and handle these errors gracefully. Python’s try...except
block is the mechanism for this.
You place the code that might fail inside the try
block. If an error (an “exception”) occurs, the code in the try
block stops immediately, and the code in the corresponding except
block is executed.
try:
# Attempt to open a configuration file for reading
with open('config.txt', 'r') as f:
config_data = f.read()
print("Configuration loaded successfully.")
except FileNotFoundError:
# This block runs ONLY if 'config.txt' does not exist
print("Error: config.txt not found. Using default settings.")
config_data = "DEFAULT"
except Exception as e:
# A general catch-all for any other unexpected errors
print(f"An unexpected error occurred: {e}")
# The program continues to run instead of crashing
print("Program continues...")
This structure is vital for creating reliable embedded systems that can recover from faults without needing a manual restart.
Practical Examples
This section translates the theoretical concepts into hands-on applications using the Raspberry Pi 5. For all examples, we will use the BCM pin numbering scheme, which refers to the “Broadcom SOC channel” numbers. This is the most common and portable method.
Warning: Always double-check your wiring before powering on the Raspberry Pi. Incorrectly connecting components can permanently damage the GPIO pins or the device itself. Always connect LEDs with a current-limiting resistor (330Ω is a good starting point).
Example 1: The “Hello, World!” of Hardware – Blinking an LED
This first example is a rite of passage. We will write a Python script to make an LED connected to a GPIO pin blink, demonstrating basic digital output.
Hardware Integration
- 1x Raspberry Pi 5
- 1x Breadboard
- 1x LED (any color)
- 1x 330Ω Resistor
- Jumper Wires
Wiring Diagram
Connect the components as follows:
- Connect a jumper wire from a GND (Ground) pin on the Raspberry Pi to one of the rails on the breadboard (this will be our ground rail).
- Connect another jumper wire from GPIO 17 (BCM pin 17) on the Raspberry Pi to a row on the breadboard.
- Place the 330Ω resistor in the same row, bridging to an adjacent row.
- Place the LED on the breadboard. Connect its anode (the longer leg) to the same row as the resistor. Connect its cathode (the shorter leg) to the ground rail on the breadboard.
Build and Configuration Steps
We will use the RPi.GPIO library, which is typically pre-installed on Raspberry Pi OS. If not, you can install it:
pip3 install RPi.GPIO
Code Snippet
Create a new file named blink.py and enter the following code:
import gpiod
import time
LED_PIN = 17
chip = gpiod.Chip('gpiochip4')
led_line = chip.get_line(LED_PIN)
led_line.request(consumer="LED", type=gpiod.LINE_REQ_DIR_OUT)
try:
while True:
led_line.set_value(1)
time.sleep(1)
led_line.set_value(0)
time.sleep(1)
finally:
led_line.release()
%%{ init: { 'theme': 'base', 'themeVariables': { 'fontFamily': 'Open Sans' } } }%% graph TD A[Start Program] --> B{Setup GPIO}; style A fill:#1e3a8a,stroke:#1e3a8a,stroke-width:2px,color:#ffffff subgraph Setup Phase B --> B1[Set BCM Pin Numbering]; B --> B2[Set LED_PIN as Output]; end style B fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff style B1 fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff style B2 fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff B2 --> C{"Enter Main Loop<br><b>(try...except block)</b>"}; style C fill:#8b5cf6,stroke:#8b5cf6,stroke-width:1px,color:#ffffff subgraph Infinite Loop C --> D["Turn LED ON<br><i>GPIO.output(LED_PIN, HIGH)</i>"]; D --> E["Wait<br><i>time.sleep(0.5)</i>"]; E --> F["Turn LED OFF<br><i>GPIO.output(LED_PIN, LOW)</i>"]; F --> G["Wait<br><i>time.sleep(0.5)</i>"]; G --> C; end style D fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff style E fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff style F fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff style G fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff C -- CTRL+C Pressed --> H{"KeyboardInterrupt<br><b>(except block)</b>"}; style H fill:#f59e0b,stroke:#f59e0b,stroke-width:1px,color:#ffffff H --> I{"Cleanup GPIO<br><b>(finally block)</b>"}; I --> J[End Program]; style I fill:#ef4444,stroke:#ef4444,stroke-width:1px,color:#ffffff style J fill:#10b981,stroke:#10b981,stroke-width:2px,color:#ffffff
Build, Flash, and Boot Procedures
- Save the code file (
blink.py
) in your home directory or a project folder. - Open a terminal on your Raspberry Pi.
- Run the script with Python 3. You may need
sudo
for GPIO access on older systems, but it’s often not required on recent Raspberry Pi OS versions if your user is in thegpio
group. Try without it first.python3 blink.py
- Expected Output: The LED connected to GPIO 17 should start blinking, turning on for half a second and off for half a second, repeatedly. The terminal will display “LED ON” and “LED OFF” messages in sync. To stop the program, press
CTRL+C
. Thefinally
block will execute, ensuring the GPIO pin is reset to its default state.
Example 2: Reading a Digital Input – The Push Button
This example demonstrates reading a digital input. We will connect a push button and have our script detect when it is pressed.
Hardware Integration
- All components from Example 1
- 1x Tactile Push Button
Wiring Diagram
- Connect one leg of the push button to a 3.3V pin on the Raspberry Pi.
- Connect the diagonally opposite leg of the push button to GPIO 27.
We will configure GPIO 27 with an internal pull-down resistor.
Code Snippet
Create a new file named button_led.py.
import gpiod
LED_PIN = 17
BUTTON_PIN = 27
chip = gpiod.Chip('gpiochip4')
led_line = chip.get_line(LED_PIN)
button_line = chip.get_line(BUTTON_PIN)
led_line.request(consumer="LED", type=gpiod.LINE_REQ_DIR_OUT)
button_line.request(consumer="Button", type=gpiod.LINE_REQ_DIR_IN)
try:
while True:
button_state = button_line.get_value()
if button_state == 1:
led_line.set_value(1)
else:
led_line.set_value(0)
finally:
led_line.release()
button_line.release()
%%{ init: { 'theme': 'base', 'themeVariables': { 'fontFamily': 'Open Sans' } } }%% graph TD A[Start] --> B{Setup GPIO}; style A fill:#1e3a8a,stroke:#1e3a8a,stroke-width:2px,color:#ffffff subgraph Setup Phase B --> B1[Set LED_PIN as Output]; B --> B2[Set BUTTON_PIN as Input<br>with Pull-Down Resistor]; end style B fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff style B1 fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff style B2 fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff B2 --> C{Enter Main Loop}; style C fill:#8b5cf6,stroke:#8b5cf6,stroke-width:1px,color:#ffffff subgraph Polling Loop C --> D["Read Button State<br><i>GPIO.input(BUTTON_PIN)</i>"]; D --> E{Button State == HIGH?}; style E fill:#f59e0b,stroke:#f59e0b,stroke-width:1px,color:#ffffff E -- Yes --> F[Turn LED ON]; style F fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff E -- No --> G[Turn LED OFF]; style G fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff F --> H[Wait 0.01s]; G --> H[Wait 0.01s]; style H fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff H --> C; end C -- CTRL+C --> I{Cleanup GPIO}; I --> J[End]; style I fill:#ef4444,stroke:#ef4444,stroke-width:1px,color:#ffffff style J fill:#10b981,stroke:#10b981,stroke-width:2px,color:#ffffff
Execution
- Save the file and run it from the terminal:
python3 button_led.py
. - Expected Output: The program will start. The LED will be off. When you press and hold the push button, the LED should turn on. When you release the button, the LED should turn off. Press
CTRL+C
to exit cleanly.
Example 3: Data Logging to a File
This final example combines hardware interaction with file I/O to create a simple data logger. We will simulate reading a sensor and write the timestamped data to a CSV (Comma-Separated Values) file.
Hardware Integration
No new hardware is needed for this simulation. We will use the button from the previous example as our “sensor” trigger.
File Structure
After running the script, you will have a new file in the same directory:
/home/pi/my_project/
├── data_logger.py
└── sensor_log.csv
Code Snippet
Create a file named data_logger.py.
import gpiod
import time
import datetime
import csv
import os
# --- Configuration ---
CHIP_NAME = 'gpiochip0'
BUTTON_LINE_OFFSET = 27 # GPIO27 (BCM)
LOG_FILE = 'sensor_log.csv'
# --- Setup GPIO ---
chip = gpiod.Chip(CHIP_NAME)
button_line = chip.get_line(BUTTON_LINE_OFFSET)
button_line.request(consumer='button-logger', type=gpiod.LINE_REQ_DIR_IN, flags=gpiod.LINE_REQ_FLAG_BIAS_PULL_DOWN)
# --- Functions ---
def write_log(event_type):
"""Appends a new event entry to the CSV log file."""
timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
with open(LOG_FILE, 'a', newline='') as f:
writer = csv.writer(f)
writer.writerow([timestamp, event_type])
print(f"Logged: {timestamp}, {event_type}")
# --- Prepare Log File ---
if not os.path.exists(LOG_FILE):
with open(LOG_FILE, 'w', newline='') as f:
writer = csv.writer(f)
writer.writerow(['Timestamp', 'Event'])
print("Created new log file with header.")
# --- Main Loop ---
print("Data logger started. Press the button to log an event.")
print(f"Log file: {LOG_FILE}. Press CTRL+C to exit.")
last_state = 0
try:
while True:
current_state = button_line.get_value()
if current_state == 1 and last_state == 0:
write_log('Button Pressed')
time.sleep(0.2) # debounce
last_state = current_state
time.sleep(0.01)
except KeyboardInterrupt:
print("\nExiting program.")
finally:
button_line.release()
print("GPIO line released.")
%%{ init: { 'theme': 'base', 'themeVariables': { 'fontFamily': 'Open Sans' } } }%% sequenceDiagram actor User participant MainLoop as Main Loop<br>(data_logger.py) participant OS as Operating System participant FS as Filesystem<br>(sensor_log.csv) MainLoop->>+FS: Check if 'sensor_log.csv' exists FS-->>-MainLoop: FileNotFoundError MainLoop->>+FS: Create file and write header<br>('Timestamp', 'Event') FS-->>-MainLoop: File created loop Detect Button Press MainLoop->>OS: Read GPIO 27 state OS-->>MainLoop: State is LOW MainLoop->>MainLoop: Wait 0.01s end User->>OS: Presses Physical Button loop Detect Button Press MainLoop->>OS: Read GPIO 27 state OS-->>MainLoop: State is HIGH end MainLoop->>MainLoop: Rising edge detected! MainLoop->>+OS: Get current timestamp OS-->>-MainLoop: '2025-07-08 22:30:15' MainLoop->>+FS: Append row to CSV<br>['2025-07-08 22:30:15', 'Button Pressed'] FS-->>-MainLoop: Write successful MainLoop->>MainLoop: Debounce wait (0.2s) User->>MainLoop: Presses CTRL+C MainLoop->>MainLoop: Cleanup GPIO MainLoop->>User: Exit Program
Execution
- Run the script:
python3 data_logger.py
. - Each time you press the button, a new line will be appended to
sensor_log.csv
and a message will be printed to the console. - After pressing the button a few times, stop the program with
CTRL+C
. - Inspect the log file using the
cat
command:cat sensor_log.csv
. - Expected Output:
Timestamp,Event
2025-07-08 22:30:15,Button Pressed
2025-07-08 22:30:17,Button Pressed
2025-07-08 22:30:18,Button Pressed
Common Mistakes & Troubleshooting
Even with a high-level language like Python, interfacing with hardware can be tricky. Here are some common pitfalls and how to resolve them.
Exercises
These exercises are designed to reinforce the concepts from this chapter. They progressively increase in complexity.
- Morse Code SOS:
- Objective: Modify the
blink.py
script to flash an LED in the pattern of an international Morse code SOS signal (… — …). - Guidance: An “S” is three short blinks (dots), and an “O” is three long blinks (dashes). Define functions for
dot()
anddash()
that turn the LED on for different durations. Usetime.sleep()
to create pauses between dots/dashes, between letters, and between words. - Verification: Observe the LED and verify the pattern is correct: three short, three long, three short, followed by a longer pause.
- Objective: Modify the
- Toggle Switch Logic:
- Objective: Modify the
button_led.py
script so that the button acts like a toggle switch. The first press turns the LED on and keeps it on. The second press turns it off. - Guidance: You will need a state variable, for example,
led_is_on = False
. When you detect a button press (a “rising edge” from LOW to HIGH), you will invert the state of this variable (led_is_on = not led_is_on
). Then, in your main loop, set the LED’s output based on the state of this variable. Remember to handle button debouncing to prevent one physical press from being registered multiple times. - Verification: Run the script. Press and release the button once; the LED should turn on and stay on. Press and release it again; the LED should turn off.
- Objective: Modify the
- Advanced Data Logger:
- Objective: Extend the
data_logger.py
script. Add a second button on a different GPIO pin (e.g., GPIO 22). The script should log which specific button was pressed. - Guidance: Set up the second button pin just like the first. In your main loop, you will need to check the state of both buttons. Modify the
write_log
function to accept a second parameter,message
, so you can call it likewrite_log('Button Event', 'Button 1 Pressed')
. - Verification: Run the script and press both buttons in various sequences. Check the
sensor_log.csv
file to ensure it correctly records which button was pressed and at what time.
- Objective: Extend the
- Traffic Light Controller:
- Objective: Create a traffic light sequence using three LEDs (Red, Yellow, Green) connected to three different GPIO pins.
- Guidance: Wire three LEDs, each with its own resistor, to three GPIO pins. Write a script that cycles through a standard traffic light sequence: Green on for 5 seconds, then Yellow on for 2 seconds, then Red on for 5 seconds. The cycle should repeat indefinitely.
- Verification: The LEDs should light up in the correct sequence and for the specified durations. The program should loop continuously until stopped with
CTRL+C
.
Summary
This chapter provided a practical refresher on the Python programming language and demonstrated its power for hardware control on the Raspberry Pi 5. We have seen that Python’s value in the embedded world is not in raw performance, but in its readability, rapid development cycle, and the rich ecosystem of libraries that simplify complex tasks.
- Core Concepts: We reviewed fundamental Python concepts including dynamic typing, variables, essential data structures (lists, tuples, dictionaries), and control flow mechanisms (
if
/else
,for
/while
loops). - Best Practices: The importance of using virtual environments for dependency management, writing modular code with functions, and handling errors gracefully with
try...except...finally
blocks was emphasized. - Hardware Interaction: We successfully used the
RPi.GPIO
library to perform digital output (blinking an LED) and digital input (reading a push button), establishing the core patterns for hardware interfacing. - Practical Application: By creating a data logging script that writes timestamped events to a CSV file, we connected software concepts directly to a common real-world embedded application.
- Troubleshooting: We identified and provided solutions for common issues, including permission errors, incorrect pin numbering, and the necessity of GPIO cleanup and loop delays.
By completing the examples and exercises in this chapter, you have built a solid foundation for using Python to create sophisticated and reliable applications on your embedded Linux system. In subsequent chapters, we will build upon these skills to interact with more complex sensors and communication protocols.
Further Reading
- The Official Python Tutorial: The most authoritative source for learning the language from its creators. (https://docs.python.org/3/tutorial/)
- Raspberry Pi Documentation – GPIO: The official guide from the Raspberry Pi Foundation on using the GPIO pins, including Python examples.
https://www.raspberrypi.com/documentation/computers/os.html#use-gpio-from-python - RPi.GPIO Module Documentation: While sparse, the official source for the
RPi.GPIO
module provides key details on its functions. (https://pypi.org/project/RPi.GPIO/) - “Automate the Boring Stuff with Python” by Al Sweigart: An excellent, practical book that covers many core Python concepts, including file I/O and error handling, in a very approachable way. (https://automatetheboringstuff.com/)
- gpiozero Documentation: An alternative, higher-level GPIO library that is excellent for beginners and for writing more readable hardware code. Exploring its documentation provides a great contrast to
RPi.GPIO
. (https://gpiozero.readthedocs.io/en/stable/) - Real Python Tutorials: A high-quality blog with in-depth tutorials on a vast range of Python topics, from beginner to advanced. (https://realpython.com/)