How to use Raspberry Pi GPIO pins with Ubuntu

1. Overview

This tutorial originally appeared on William Wilson’s (jawn-smith) blog and was contributed and modified here with permission.

As of Linux kernel 5.11, the old methods of communicating with header pins on the Raspberry Pi no longer work. This means packages such as RPi.GPIO no longer function properly with newer kernels. This post by Dave Jones (waveform) explains the reasons for the changes. But fear not, there is a new package in Ubuntu 21.04 called LGPIO that allows full control over the header pins with the latest kernel version. This tutorial covers 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 how to install Ubuntu Server on your Raspberry Pi and make it 21.04.

What you’ll learn

  • How to install and get started with GPIO pins on Ubuntu
  • Basic GPIO operations
  • Basic I2C operations
  • Basic PWM operations

What you’ll need

  • A Raspberry 3 or 4 with Ubuntu 21.04 setup and installed

Optionally for the examples:

  • A simple breadboard
  • 7x 330ohm resistors
  • A single red, green or blue LED
  • 10 male jumper wires
  • 20 female to male jumper wires
  • An Arduino Uno
  • 5V PWM fan
  • A RGB LED
  • A rotary encoder
  • An MCP3008 analog to digital converter

2. Installing GPIO

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

Done. You’re ready to go.


3. Basic GPIO example

The first example is the classic “blink an LED” example. The sample script uses GPIO pin 23 on the Raspberry Pi, so we’ll wire it up with a 330-ohm resistor according to the following 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)

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


4. I2C example

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 wire the Pi and Arduino together as follows:

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

#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 this:


    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:

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


5. PWM example

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:

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

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


6. That’s all!

In Will’s original post the tutorial ended with a homework assignment where he challenges you to modify the PWM script to monitor the CPU temperature and set the speed of the fan accordingly. If you want to give it ago and see more information head over to his blog and read on.

Now you’re done and, hopefully, you’ve had a chance to play around with GPIO on Ubuntu on Raspberry Pi. If this tutorial helped you and you go on to make things using GPIO on Ubuntu on a Pi, whatever it may be, let us know. Either comment below on this tutorial or find your way over to the Raspberry Pi forum, we lurk over there too.

If you do end up building a project off the back of this tutorial and you would like to share it with the Ubuntu community as a blog post or even as a tutorial for this very site, reach out to a member of the Ubuntu Community team and we’ll get something going!