Python GPIO Development

python-periphery Introduction

python-periphery is a library for hardware peripheral (GPIO, SPI, I2C, etc.) development on Linux systems through Python.

Main Features

  • Based on Linux Kernel Interface: Uses standard Linux character device interfaces (such as /dev/gpiochip0)
  • Cross-platform Compatibility: Suitable for various Linux single board computers (Raspberry Pi, BeagleBone, Radxa, etc.)
  • Pure Python Implementation: No need to compile C extensions, easy to install and deploy
  • Supports Multiple Peripherals: GPIO, SPI, I2C, MMIO, Serial, PWM, etc.

Supported Peripheral Types

Peripheral Type Description
GPIO General purpose input/output
SPI Serial peripheral interface
I2C I²C bus communication
MMIO Memory-mapped I/O
Serial Serial port communication
PWM Pulse width modulation

Installing python-periphery

Install pip3

First, ensure pip3 is installed in the system:

sudo apt update
sudo apt install python3-pip -y

Install python-periphery Library

Due to PEP 668 protection mechanism in Python 3.13+, you need to use the --break-system-packages parameter:

pip3 install python-periphery --break-system-packages

Verify installation:

python3 -c "from periphery import GPIO; print('Installation successful!')"

Tip: For embedded development boards, using the --break-system-packages parameter is safe. If you prefer to use a virtual environment, you can refer to the Python Virtual Environment documentation.

GPIO Reading

Important Note: Pin3 and Pin5 are configured as I2C9 interface (SDA and SCL) by default. If you need to use them as GPIO, please ensure no other devices are occupying these pins.

Hardware Preparation

  • Main board
  • Jumper wires

Software Preparation

Test Code

The following code uses the python-periphery library to read the high and low levels of Pin3 (GPIO36).

Create file gpio_input.py:

from periphery import GPIO
import time

def read_gpio_input():
    # Configure GPIO input (modify pin number according to actual hardware)
    # Pin3 corresponds to GPIO36 (pin 36 of /dev/gpiochip4)
    try:
        # Initialize GPIO in input mode
        gpio_in = GPIO("/dev/gpiochip4", 36, "in")

        print("Starting GPIO input reading (press Ctrl+C to exit)")
        while True:
            # Read pin value
            value = gpio_in.read()
            print(f"GPIO input value: {value} (True=High, False=Low)")
            time.sleep(1)  # Read once per second

    except KeyboardInterrupt:
        print("\nProgram exited")
    except Exception as e:
        print(f"Error occurred: {e}")
    finally:
        # Ensure resources are released
        try:
            gpio_in.close()
        except:
            pass

if __name__ == "__main__":
    read_gpio_input()

Test Steps

  1. Connect Pin3 (GPIO36) to GND or 3.3V pin
  2. Save the code as gpio_input.py
  3. Run the test code with the following command:
python3 gpio_input.py

Experimental Results

The terminal will output False or True information:

  • False represents low level (pin connected to GND)
  • True represents high level (pin connected to 3.3V)

Example output:

Starting GPIO input reading (press Ctrl+C to exit)
GPIO input value: False (True=High, False=Low)
GPIO input value: False (True=High, False=Low)
GPIO input value: True (True=High, False=Low)

GPIO Output

Hardware Preparation

  • Main board
  • Jumper wires

Software Preparation

Test Code

The following code uses the python-periphery library to control Pin3 (GPIO36) output high and low levels, then reads Pin3's high and low levels through Pin5 (GPIO37).

Create file gpio_output.py:

from periphery import GPIO
import time

def gpio_output_with_feedback():
    # GPIO Configuration (modify pin numbers based on your hardware)
    # Pin3/GPIO36 (output) → maps to pin 36 of /dev/gpiochip4
    # Pin5/GPIO37 (input)  → maps to pin 37 of /dev/gpiochip4
    OUTPUT_PIN_CHIP = "/dev/gpiochip4"
    OUTPUT_PIN_NUMBER = 36  # Pin3/GPIO36 (output pin, controlled by the script)
    INPUT_PIN_NUMBER = 37   # Pin5/GPIO37 (input pin, reads output state)

    # Initialize GPIO objects as None first (for safe release later)
    gpio_out = None
    gpio_in = None

    try:
        # Initialize Pin3/GPIO36 as OUTPUT mode
        gpio_out = GPIO(OUTPUT_PIN_CHIP, OUTPUT_PIN_NUMBER, "out")
        # Initialize Pin5/GPIO37 as INPUT mode
        gpio_in = GPIO(OUTPUT_PIN_CHIP, INPUT_PIN_NUMBER, "in")

        # Print test initialization info
        print("=== GPIO Output-Input Feedback Test Started ===")
        print(f"Controlled Pin (GPIO36): {OUTPUT_PIN_CHIP} - Pin {OUTPUT_PIN_NUMBER} (OUTPUT)")
        print(f"Monitoring Pin (GPIO37): {OUTPUT_PIN_CHIP} - Pin {INPUT_PIN_NUMBER} (INPUT)")
        print("Test Behavior: GPIO36 toggles HIGH/LOW every 1s; GPIO37 verifies GPIO36's state")
        print("Press Ctrl+C to stop the test\n")

        # Main loop: Toggle GPIO36 and read GPIO37 feedback
        while True:
            # 1. Set GPIO36 to HIGH level
            gpio_out.write(True)
            time.sleep(0.1)  # Short delay for signal stabilization (avoid read lag)
            gpio37_reading = gpio_in.read()
            print(f"GPIO36 Output: HIGH (True) | GPIO37 Reading: {gpio37_reading}")

            # Keep GPIO36 HIGH for 1 second
            time.sleep(1)

            # 2. Set GPIO36 to LOW level
            gpio_out.write(False)
            time.sleep(0.1)  # Short delay for signal stabilization
            gpio37_reading = gpio_in.read()
            print(f"GPIO36 Output: LOW (False) | GPIO37 Reading: {gpio37_reading}")

            # Keep GPIO36 LOW for 1 second
            time.sleep(1)

    # Handle user-initiated exit (Ctrl+C)
    except KeyboardInterrupt:
        print("\n\nTest stopped by user (Ctrl+C)")
    # Handle other unexpected errors (e.g., GPIO access failure)
    except Exception as e:
        print(f"\nError during test: {str(e)}")
    # Ensure GPIO resources are released even if an error occurs
    finally:
        print("\nReleasing GPIO resources...")
        # Safely close GPIO36 (set to LOW first to avoid residual high level)
        if gpio_out:
            try:
                gpio_out.write(False)
                gpio_out.close()
                print(f"Successfully closed GPIO36 (Pin {OUTPUT_PIN_NUMBER})")
            except Exception as close_err:
                print(f"Failed to close GPIO36 (Pin {OUTPUT_PIN_NUMBER}): {str(close_err)}")
        # Safely close GPIO37
        if gpio_in:
            try:
                gpio_in.close()
                print(f"Successfully closed GPIO37 (Pin {INPUT_PIN_NUMBER})")
            except Exception as close_err:
                print(f"Failed to close GPIO37 (Pin {INPUT_PIN_NUMBER}): {str(close_err)}")
        print("Resource release complete.")

# Run the test when the script is executed directly
if __name__ == "__main__":
    gpio_output_with_feedback()

Test Steps

  1. Short Pin3 (GPIO36) and Pin5 (GPIO37) together
  2. Save the code as gpio_output.py
  3. Run the test code with the following command:
sudo python3 gpio_output.py

Note: GPIO output may require root permissions, so use sudo to run.

Experimental Results

The terminal will output False or True information:

  • False represents low level
  • True represents high level

Example output:

=== GPIO Output-Input Feedback Test Started ===
Controlled Pin (GPIO36): /dev/gpiochip4 - Pin 36 (OUTPUT)
Monitoring Pin (GPIO37): /dev/gpiochip4 - Pin 37 (INPUT)
Test Behavior: GPIO36 toggles HIGH/LOW every 1s; GPIO37 verifies GPIO36's state
Press Ctrl+C to stop the test

GPIO36 Output: HIGH (True) | GPIO37 Reading: True
GPIO36 Output: LOW (False) | GPIO37 Reading: False
GPIO36 Output: HIGH (True) | GPIO37 Reading: True
GPIO36 Output: LOW (False) | GPIO37 Reading: False

GPIO Pin Mapping Description

In python-periphery, GPIO pins require specifying two parameters:

  1. GPIO Chip Device: Quectel Pi H1 primarily uses /dev/gpiochip4
  2. Pin Number: GPIO number corresponding to the physical pin

40Pin Pin Mapping Example

Physical Pin ID Pin Name GPIO Number GPIO Chip Pin Number Default Function
Pin3 NFC_I2C_SDA GPIO36 /dev/gpiochip4 36 I2C09_SDA
Pin5 NFC_I2C_SCL GPIO37 /dev/gpiochip4 37 I2C09_SCL
Pin7 CAM2_RST GPIO77 /dev/gpiochip4 77 GPIO
Pin11 GPIO_16 GPIO16 /dev/gpiochip4 16 GPIO/SPI/UART/I2C
Pin13 GPIO_17 GPIO17 /dev/gpiochip4 17 GPIO/SPI/UART/I2C

View Available GPIO

Use the gpioinfo command to view all available GPIO chips and pins:

# Install gpiod tools
sudo apt install gpiod -y

# View all GPIO information
gpioinfo | grep -A 180 "gpiochip4"

# Note: In libgpiod 2.x, use grep to filter specific chips

Common Issues

Permission Issues

If you encounter permission errors, you can:

  1. Run the script with sudo
  2. Add user to gpio user group:
sudo usermod -a -G gpio $USER
  1. Log out and log back in for permissions to take effect

Pin Occupied Issue

If you encounter [Errno 16] Device or resource busy or [Errno 1] Operation not permitted errors:

Check Pin Status:

# View all GPIO pin status
gpioinfo | grep "consumer="

# View pin occupation for specific chip
gpioinfo | grep -A 180 "gpiochip4" | grep -E "(line|consumer)"

Common Causes:

  • Pin is occupied by other processes (such as I2C, SPI, etc.)
  • Pin is already being used by system functions
  • Other GPIO programs are running

Solutions:

  • Select unoccupied pins (pins shown as input with no consumer in gpioinfo)
  • If you need to use occupied pins, you need to disable the corresponding functions

Pin Number Error

If you encounter [Errno 22] Invalid argument error:

Cause:

  • Pin number does not exist or is unavailable
  • Wrong GPIO chip device is used

Solution:

  1. Use gpioinfo to view actual available pin numbers
  2. Confirm using the correct GPIO chip (Quectel Pi H1 primarily uses /dev/gpiochip4)

Debugging Tips

View Available GPIO Chips:

ls -l /dev/gpiochip*

View GPIO Chip Detailed Information:

gpioinfo | grep -A 180 "gpiochip4"

Test Specific Pin:

# Use gpioget to read pin value
sudo gpioget -c gpiochip4 36

# Use gpioset to set pin value
sudo gpioset -c gpiochip4 36=1  # Set to high level
sudo gpioset -c gpiochip4 36=0  # Set to low level

Reference Resources