Raspberry Pi GPIO Tutorial | BlogJawn

Posted by William 'jawn-smith' Wilson on Fri 16 April 2021

As of Linux kernel 5.11, the old methods of communicating with the header pins on the Raspberry Pi will no longer work. This means that packages such as RPi.GPIO will no longer function properly with newer kernels. This post by Dave Jones explains the reasons for the changes. Fear not, there is a new package in Ubuntu 21.04 called LGPIO that allows us full control over the header pins with the latest kernel version. This tutorial will cover some basic functionality of LGPIO, including examples using basic GPIO control, I²C, PWM, and SPI. If you already have Ubuntu 21.04 or newer set up on a Raspberry Pi, you are ready for this tutorial. Otherwise, please see my Instructions for installing Ubuntu 21.04 on a Raspberry Pi

Installing LGPIO

Installing LGPIO is easy! Ensure that your software repositories are up to date with sudo apt update and install the Python LGPIO library with sudo apt install python3-lgpio

Basic GPIO

The first example will be the classic "blink an LED" example. The sample script uses GPIO pin 23 on the Raspberry Pi, so we will wire it up with a 330 ohm resistor according to the following diagram:

LED Wiring Diagram

If wired correctly, the following script will turn the LED on and off for one second each until it receives a keyboard interrupt signal (Ctrl+C). It can be run by saving this script as a file named gpio_led.py and then using the command python3 gpio_led.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#  Blink an LED with the LGPIO library
#  Uses lgpio library, compatible with kernel 5.11
#  Author: William 'jawn-smith' Wilson

import time
import lgpio

LED = 23

# open the gpio chip and set the LED pin as output
h = lgpio.gpiochip_open(0)
lgpio.gpio_claim_output(h, LED)

try:
    while True:
        # Turn the GPIO pin on
        lgpio.gpio_write(h, LED, 1)
        time.sleep(1)

        # Turn the GPIO pin off
        lgpio.gpio_write(h, LED, 0)
        time.sleep(1)
except KeyboardInterrupt:
    lgpio.gpio_write(h, LED, 0)
    lgpio.gpiochip_close(h)

The line h = lgpio.gpiochip_open(0) opens /dev/gpiochip0. Then lgpio.gpio_claim_output(h, <pin num>) sets the pin as an output. The lgpio.gpio_write() function drives the GPIO pin to HIGH or LOW to turn the LED on or off.

I²C

The I²C example I have created makes use of the Raspberry Pi as the leader and an Arduino Uno as the follower. It uses I²C to have the Arduino Uno blink its onboard LED. The I²C pins on the Raspberry Pi are GPIO 2 and 3. We will wire the Pi and Arduino together as follows:

Arduino I²C Wiring Diagram

In order to have the Arduino act as an I²C follower, run the following sketch on it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <Wire.h>

// use the built in LED
const int ledPin = 13;

void setup() {
    // Join I2C bus as follower
    Wire.begin(0x8);

    Wire.onReceive(receiveEvent);

    // Setup initial state for pin 13
    pinMode(ledPin, OUTPUT);
    digitalWrite(ledPin, LOW);
}

void receiveEvent(int howMany) {
    while (Wire.available()) {
        char c = Wire.read();
        digitalWrite(ledPin, c);
    }
}

void loop() {
    delay(10);
}

This sketch joins the I²C bus at address 8. To test that it is working properly, run i2cdetect -y 1 on your Raspberry Pi. The output should look something like:

     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         08 -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

If you see the 08 in the first row as above, the Arduino is awaiting instructions over I²C. If you are new to Arduino boards and are unfamiliar with how to compile and upload the sketch, click here for more information.

To have the Raspberry Pi send instructions to the Arduino over I²C, run the following Python script:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#  Raspberry Pi Leader for Arduino Follower
#  Uses lgpio library, compatible with kernel 5.11
#  Connects to Arduino via I2C and periodically blinks an LED
#  Author: William 'jawn-smith' Wilson

import lgpio
import time

addr = 0x8 # bus address

h = lgpio.i2c_open(1, addr)
while True:
    try:
        lgpio.i2c_write_byte(h, 0x0) # switch it off
        time.sleep(1)
        lgpio.i2c_write_byte(h, 0x1) # switch it on
        time.sleep(1)
    except KeyboardInterrupt:
        lgpio.i2c_write_byte(h, 0x0) # switch it off
        lgpio.i2c_close(h)
        break

If run successfully, the built-in LED on the Arduino Uno will blink on and off for one second each. As you can see, the LGPIO code is as simple as lgpio.i2c_open(1, addr) to open the I²C device and lgpio.i2c_write_byte(<destination>, <byte>) to send the data.

PWM

For a PWM example I have a Noctua 5V PWM fan that can be controlled via the Raspberry Pi header pins. The fan can be directly powered by the Raspberry Pi 5V and GND pins. No resistor is needed for the PWM pin, which is GPIO 18 in the example script. The fan model I am using has an open-collector circuit design (as do most fans), so a 1kΩ pull-up resistor is needed. NOTE: The pull-up resistor must be connected to one of the 3.3V pins on the Raspberry Pi. If it is connected to a 5V pin, the Pi could be severely damaged. See the wiring diagram below as an example:

PWM Wiring Diagram

After connecting the fan and Raspberry Pi, run the following Python script to control the speed of the fan with PWM.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#  Control a 5V PWM fan speed with the lgpio library
#  Uses lgpio library, compatible with kernel 5.11
#  Author: William 'jawn-smith' Wilson

import lgpio
import time

# Configuration
FAN = 18 # pin used to drive PWM fan
FREQ = 10000

h = lgpio.gpiochip_open(0)

try:
    while True:
        # Turn the fan off
        lgpio.tx_pwm(h, FAN, FREQ, 0)
        time.sleep(10)

        # Turn the fan to medium speed
        lgpio.tx_pwm(h, FAN, FREQ, 50)
        time.sleep(10)

        # Turn the fan to max speed
        lgpio.tx_pwm(h, FAN, FREQ, 100)
        time.sleep(10)

except KeyboardInterrupt:
    # Turn the fan to medium speed
    lgpio.tx_pwm(h, FAN, FREQ, 50)
    lgpio.gpiochip_close(h)

As you can see, the GPIO chip is opened the same way as in the basic GPIO example. Rather than just setting the GPIO to LOW/HIGH, we use the LGPIO library to set PWM transactions that control the speed of the fan. This example sets the speed to 0%, 50%, and 100% for 10 seconds each.

Homework Assignment: Modify the PWM script to monitor the CPU temperature and set the speed of the fan accordingly

SPI

The following SPI example was created by Dave Jones and is used with permission. His blog is an excellent source for all things Raspberry Pi. It uses a rotary encoder along with an MCP3008 analog to digital converter to control the color of an RGB LED. The components should be wired as follows

SPI Wiring Diagram

The following code reads from the rotary encoder and sets the color of the LED

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import colorsys
import lgpio
from time import sleep

chip = lgpio.gpiochip_open(0)
try:
    channel = 0
    mcp3008 = lgpio.spi_open(0, 0, 100000, 0)
    lgpio.gpio_claim_output(chip, 22)
    lgpio.gpio_claim_output(chip, 27)
    lgpio.gpio_claim_output(chip, 17)
    try:
        while True:
            count, data = lgpio.spi_xfer(mcp3008, [1, channel << 4, 0])
            reading = (int(data[1]) << 8 | int(data[2])) / 2**10
            r, g, b = colorsys.hsv_to_rgb(reading, 1, 1)
            print(f'hue={reading:.2f}, rgb=({r:.2f}, {g:.2f}, {b:.2f})')
            lgpio.tx_pwm(chip, 22, 1000, int(r * 100))
            lgpio.tx_pwm(chip, 27, 1000, int(g * 100))
            lgpio.tx_pwm(chip, 17, 1000, int(b * 100))
            sleep(0.1)
    finally:
        lgpio.gpio_write(chip, 17, 0)
        lgpio.gpio_write(chip, 27, 0)
        lgpio.gpio_write(chip, 22, 0)
        lgpio.gpio_free(chip, 17)
        lgpio.gpio_free(chip, 27)
        lgpio.gpio_free(chip, 22)
        lgpio.spi_close(mcp3008)
finally:
    lgpio.gpiochip_close(chip)

The chip is opened the usual way, and the SPI is opened separately by a call to lgpio.spi_open(<device>, <channel>, <baud>, <spi_flags>). The reading is converted into a hue and applied to the RGB LED.

Conclusion

Kernel changes that break existing functionality can be scary, but LGPIO is simple to use and integrate into your projects. It gives access to a wide variety of communication protocols with easy-to-use functions and clear documentation. Full documentation for LGPIO can be found at http://abyz.me.uk/lg/