Mastering Swift's Development Blog

Follow Us On Twitter
  • Jon Hoffman

Controlling GPIO pins with Swift and libgpiod

Updated: Oct 15

In last week’s post we showed how to get the new Beaglebone AI-64 setup with Swift installed. The instructions from that post, for setting up Swift, can be used by other Single-Board-Computers like the Raspberry Pi. In this week’s post we are going to walk through how to control the GPIO ports using the libgpiod library with Swift. For this, we will write an application in Swift that will turn on and then off an led every two second.

NOTE: The steps in this post, in general, should work with any SBC that Swift is installed on and has libgpiod like the Beaglebone AI-64 and Beaglebone Black. According to the documentation, libgpiod can be installed on the Raspberry PI OS as well. The gpiochip, line numbers and ground pins will be different.

In last week’s post we created a swift-code directory under our home directory to keep our Swift code in. We will want to start by going to that directory and making a new project called gpiodtest. The following commands will do this:

cd ~/swift-code
mkdir gpiodtest
swift package init --type executable

Now that we have our project setup we will need to start off by importing the libgpiod library into our project. There are a couple of ways that we can import system libraries into our Swift project. For this example, we will be importing the libraries directly into our project. Let’s see how to do this.

The first thing we will want to do is to create a module under our Sources directory named gpiod and create two files within the module named gpiod.h and module.modulemap. The following commands will do that.

cd Sources
mkdir gpiod
cd gpiod
touch gpiod.h module.modulemap

In the module.modulemap file we will need to tell Swift how to import and link the library. To do this, we will want to add the following lines to that file.

module gpiod {
    umbrella header "gpiod.h"
    link "gpiod"

These lines start off by declaring a module named gpiod. This means that Swift will see the module by that name and when we want to import the library withing our Swift code we will use the code import gpiod. The next line declares an umbrella header name gpiod.h which specifies the path to the C header file to include. In our example, we will be using the gpiod.h file that we just created. The last line specifies the linker flag that will be used to link the system library.

Now in the gpiod.h file we will want to add the following line:

#include <gpiod.h>

This file is just a normal C header that tells clang to look for the gpiod.h header file installed in the usual locations.

We now need to tell the package manager about the system library module. To do this, we will want to go to the main project directory and edit the Package.swift file. The following code shows the new Package.swift file with the lines that changed from the original highlighted.

// swift-tools-version: 5.7
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "gpiodtest",
    dependencies: [
        // Dependencies declare other packages that this package depends on.
        // .package(url: /* package url */, from: "1.0.0"),
    targets: [
        // Targets are the basic building blocks of a package. A target can define a module or a test suite.
        // Targets can depend on other targets in this package, and on products in packages this package depends on.
        .systemLibrary(name: "gpiod", pkgConfig: "gpiod"),
            name: "gpiodtest",
            dependencies: ["gpiod"]),
            name: "gpiodtestTests",
            dependencies: ["gpiodtest"]),

Now we are all setup to use libgpiod within our Swift project. Now we need to figure out how to use the libgpiod library. This page is where I got a lot of my information from:

Let’s start by seeing what gpio chips the library sees on the Beaglebone AI-64. The term chip is used by the library to identify groups of GPIO hardware and these groups may or may not correspond to actual hardware chips. To see the gpio chips run the following command.


This command should have the following output if you are using the Beaglebone AI-64. If you are using another SBC, your results will be different.

gpiochip0 [42110000.gpio] (84 lines)
gpiochip1 [600000.gpio] (128 lines)
gpiochip2 [601000.gpio] (36 lines)

From this output we see that gpiod reports that we have three gpio chips. In order to interact with a gpio pins we will need to know what chip is associated with the pin and also the line number for the pin. The Beaglebone AI-64 contains two expansion headers P8 and P9. Each pin on the headers is numbered from 1 to 46. In the following image, you can see where the headers are labeled and where the numbering starts

We will be using the P8 expansion header for our blinking light project. The Beaglebone page, indicates that the expansion headers on the Beaglebone AI-64 are compatible with the many original Beaglebone capes therefore it is a very good assumption that the power and ground pins are the same between the two. With this assumption, pins 1 and 2 on the P8 expansion header should be digital ground, which can be used to ground the LED. To make our wiring easy we will use pin 3 to control our Led. In the previous image we see a wire coming out of pin one, which goes to the cathode (short leg from the LED), and a wire coming out of pin three, which goes to the anode (long leg on the led).

Now we need to run the gpioinfo command to get the chip and line numbers associated with pin 3 on the P8 expansion header. If we run the gpioinfo command we will see a lot of lines like this:

gpiochip0 - 84 lines:
    line   0: "MB_CLK/BOOT_BTN" unused input active-high 
    line   1:    "MB_MISO"       unused   input  active-high 
    line   2:    "MB_MOSI"       unused   input  active-high 
    line   3:      "MB_CS"       unused   input  active-high 
    line   4:   "SOC_WAKE"      "POWER"   input   active-low [used]
    line   5:  "EEPROM_WP"       unused   input  active-high 

gpiochip1 - 128 lines:
    line   0:      unnamed       unused   input  active-high 
    line   1:      "P9_11"       unused   input  active-high 
    line   2:      "P9_13"       unused   input  active-high 
    line   3:      "P8_17"       unused   input  active-high 
    line   4:      "P8_18"       unused   input  active-high 
    line   5:      "P8_22"       unused   input  active-high 
    line   6:      "P8_24"       unused   input  active-high 

gpiochip2 - 36 lines:
    line   0:      "P9_41"       unused   input  active-high 
    line   1:     "P9_19A"       unused   input  active-high 
    line   2:     "P9_20A"       unused   input  active-high 
    line   3:  "TYPEC_DIR"  "typec-dir"   input   active-low [used]
    line   4:  "TYPEC_INT"       unused   input  active-high

As we mentioned, we will be using pin 3 of the P8 expansion header to control our led, therefore will be looking for the line that has “P8_03” in it. It would be easy to grep for that however, we also need to know what gpiochip it is under. To save you from looking through all of the results, it is line 20 under the gpiochip1.

Now we have all the information we need to create the application to blink an LED. Let’s edit the gpiodtest.swift file under the Sources/gpiotest/ directory of our project to include the following code:

import gpiod

public struct gpiodtest {

    public static func main() async {
        var chipToUse = "gpiochip1"
        if let chip = gpiod_chip_open_by_name(chipToUse) {
            var led = gpiod_chip_get_line(chip, 20)
            var retVal = gpiod_line_request_output(led, "example1",0)
            var setVal: Int32 = 0
            for i in 1...11 {
                gpiod_line_set_value(led, setVal)
                setVal = setVal == 0 ? 1 : 0
                try? await Task.sleep(until: .now +  .seconds(2), clock: .continuous)

This code starts off by importing the gpiod module that we created within our project earlier in this post. The next thing to notice is we made the main function async so we could put a sleep task in to give us a slow blinking led. Now we can get into the fun part of the code.

Within the main function we start off by defining what gpio chip to use. In our case, we determined earlier that for pin 3 of the P8 expansion header we needed to use gpiochip1. We then use the gpiod_chip_open_by_name() function defined within the libgpiod library to open the gpio chip.

NOTE: Any function that starts off with “gpiod” is from the libgpiod library.

Next we use the gpiod_chip_get_line() function to get access to Line 20 of the gpiodchip1 chip, which we know as pin 3 of the P8 expansion header and then use the gpiod_line_request_output() function to set the direction of the pin to output which enables us to write to it.

Within the for loop, we use the gpiod_line_set_value() function to write a value to the pin. We use the ternary operator to reverse the value that we are setting to the pin each time through the loop. Finally we use the Task.sleep() function to pause for two seconds before looping.

After we are finished we use the gpiod_line_release() and the gpiod_chip_close() functions to clean up the pins and release everything prior to exiting. If we use the swift run command, and have everything wired correctly, we will indeed see the led blink on and off.

Here is a video of it working:

NOTE: This code should work on other SBC like the Beaglebone Black and the Raspberry Pi. You will just need to change the gpiochip and the line number to match your board

This was just a quick, proof-of-concept application to prove that we could use the libgpiod library to control the GPIO pins with Swift. Our next step is to begin to incorporate this into an open-source library to make it easy to use. One of the things to keep in mind is this library only enables us to work with the GPIO. In order to really unleash the power of the boards like the Beaglebone AI-64, Raspberry PI, Beaglebone Black… we will also need to work with the PWM, Analog and other pins but getting the GPIO pins working is the first step. Follow us on twitter for the latest updates on the open source library as well as other projects.


Mastering Swift 5.3

The sixth edition of this bestselling book, updated to cover through version 5.3 of the Swift programming language




Protocol Oriented Programming

Embrace the Protocol-Oriented design paradigm, for better code maintainability and increased performance, with Swift.