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-packagesparameter 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
- Connect Pin3 (GPIO36) to GND or 3.3V pin
- Save the code as
gpio_input.py - Run the test code with the following command:
python3 gpio_input.py
Experimental Results
The terminal will output False or True information:
Falserepresents low level (pin connected to GND)Truerepresents 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
- Short Pin3 (GPIO36) and Pin5 (GPIO37) together
- Save the code as
gpio_output.py - Run the test code with the following command:
sudo python3 gpio_output.py
Note: GPIO output may require root permissions, so use
sudoto run.
Experimental Results
The terminal will output False or True information:
Falserepresents low levelTruerepresents 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:
- GPIO Chip Device: Quectel Pi H1 primarily uses
/dev/gpiochip4 - 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:
- Run the script with
sudo - Add user to
gpiouser group:
sudo usermod -a -G gpio $USER
- 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
inputwith noconsumerin 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:
- Use
gpioinfoto view actual available pin numbers - 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