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.

Python
# 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 [].
Python
# 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 ().
Python
# 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.
Python
# 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']}")

Characteristic List Tuple Dictionary
Syntax [item1, item2] (item1, item2) {'key1': value1}
Ordering Ordered Ordered Unordered (Ordered in Python 3.7+)
Mutability Mutable (Changeable) Immutable (Unchangeable) Mutable (Changeable)
Indexing By integer position (e.g., my_list[0]) By integer position (e.g., my_tuple[0]) By key (e.g., my_dict['key'])
Common Use Case Storing a sequence of items that may change, like sensor readings over time. Protecting data that should not change, like hardware pin configurations. Storing structured data with named fields, like a device’s complete configuration.

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.

Python
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.
Python
# 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.
Python
# 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.

Python
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.

Python
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:

  1. 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).
  2. Connect another jumper wire from GPIO 17 (BCM pin 17) on the Raspberry Pi to a row on the breadboard.
  3. Place the 330Ω resistor in the same row, bridging to an adjacent row.
  4. 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:

Bash
pip3 install RPi.GPIO

Code Snippet

Create a new file named blink.py and enter the following code:

Python
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

  1. Save the code file (blink.py) in your home directory or a project folder.
  2. Open a terminal on your Raspberry Pi.
  3. 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 the gpio group. Try without it first.
    python3 blink.py
  4. 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. The finally 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

  1. Connect one leg of the push button to a 3.3V pin on the Raspberry Pi.
  2. 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.

Python
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

  1. Save the file and run it from the terminal:
    python3 button_led.py.
  2. 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:

Plaintext
/home/pi/my_project/
├── data_logger.py
└── sensor_log.csv

Code Snippet

Create a file named data_logger.py.

Python
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

  1. Run the script: python3 data_logger.py.
  2. 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.
  3. After pressing the button a few times, stop the program with CTRL+C.
  4. Inspect the log file using the cat command: cat sensor_log.csv.
  5. 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.

Mistake / Issue Symptom(s) Troubleshooting / Solution
Permission Denied Script fails immediately with an error like RuntimeError: No access to /dev/gpiomem. Add user to the ‘gpio’ group.
Run sudo usermod -a -G gpio $USER, then log out and log back in. Avoid using sudo python3 as a first resort.
Incorrect Pin Numbering Code runs without errors, but the wrong LED lights up, or nothing happens at all. Explicitly set the mode.
Ensure GPIO.setmode(GPIO.BCM) is at the start of your script and your wiring matches the BCM pin numbers, not the physical board numbers.
Forgetting GPIO.cleanup() The script works once, but on the second run, you see a RuntimeWarning: This channel is already in use. Use a ‘try…finally’ block.
Always place your main loop in a try block and GPIO.cleanup() in the finally block to guarantee it runs, even if the script crashes.
Floating Input Pin Reading a button gives random HIGH/LOW values. The LED flickers uncontrollably when the button isn’t pressed. Use internal pull-up/pull-down resistors.
When setting up an input pin, specify a default state: GPIO.setup(pin, GPIO.IN, pull_up_down=GPIO.PUD_DOWN).
CPU Overload The script runs, but the system becomes sluggish. Running top or htop shows a Python process at 100% CPU. Add a delay in loops.
Inside any while True: loop that polls hardware, add a small delay like time.sleep(0.01) to yield CPU time back to the OS.

Exercises

These exercises are designed to reinforce the concepts from this chapter. They progressively increase in complexity.

  1. 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() and dash() that turn the LED on for different durations. Use time.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.
  2. 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.
  3. 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 like write_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.
  4. 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

  1. The Official Python Tutorial: The most authoritative source for learning the language from its creators. (https://docs.python.org/3/tutorial/)
  2. 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
  3. 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/)
  4. “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/)
  5. 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/)
  6. 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/)

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top