Raspberry Pi Snap Interfaces Tutorial | BlogJawn

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

Ubuntu core is a completely snap based operating system. That means that rather than apt-get commands you may be used to, Ubuntu Core uses snap install commands instead. It is designed to have a tiny memory footprint and is therefore perfect for IoT devices. Ubuntu Core is built with security at the forefront and restricts access to certain hardware by default. This tutorial will show you how to build snaps for the Raspberry Pi that have access to basic GPIO control, I²C, Serial port, and PWM. If this is your first time building a snap, please reference the following tutorial for instructions on getting started. The rest of this tutorial will assume that you have some experience with developing snaps and some familiarity with snapcraft.yaml files.

If you already have Ubuntu Core 20 or newer set up on a Raspberry Pi, you are ready for this tutorial. Otherwise, please see these Instructions for installing Ubuntu Core 20 on a Raspberry Pi. For this tutorial either armhf or arm64 Ubuntu Core images will work, but they must be Core20 or newer.

All of the scripts and their associated snapcraft.yaml files for these demos are available on Github

Terminology

First let's define some terminology:

Snap

A snap is a bundle of an app and its dependencies that works without modification across many different Linux distributions.

Interface

Interfaces are how an installed snap gets access to system resources. An interface is made up of two parts: a plug and a slot. Each supported interface in snapd has a name, such as network, system-files, wayland, etc. This tutorial demonstrates the usage of gpio, i2c, serial-port, and pwm interfaces. Snapcraft's documentation includes a full list of supported interfaces.

Slot

A slot is how a snap provides access to a system resource. They are typically provided by the core and gadget snaps in an Ubuntu Core operating system.

Plug

A plug is used by a snap to plug into a slot that has been provided by another snap, thereby gaining access to the associated system resource.

How they come together

When building a snap, the plugs are defined in snapcraft.yaml. They must take the name of the interface type that they will be plugging into. Some interface types allow the plug to connect to the slot automatically when the snap is installed, while others require the connection to be done manually with the snap connect command. This manual connection only needs to be done once, and then persists across reboots and snap revision changes.

Click Here for the official snapcraft documentation on interfaces, plugs and slots.

Examples

Basic GPIO

The first example will be the classic "blink an LED" example. The sample snap 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

To install the sample snap, log in to the Raspberry Pi and run sudo snap install gpiojawn. Installing this snap creates a binary called gpiojawn that will toggle the GPIO by writing to /sys/class/gpio/gpio23/value

If you try to run sudo gpiojawn command right after installing the snap, you will see the following: /sys/class/gpio/gpio23/direction: No such file or directory. The problem stems from the fact that gpiojawn is a strictly confined snap. This means that gpiojawn only has access to the files staged under /snap/gpiojawn/ and by default doesn't have access to files such as /sys/class/gpio/gpio23/direction. Snapd has provided an excellent workaround to this problem in the form of plugs and slots. The Raspberry Pi gadget and core20 snaps provide plugs that give access to certain hardware and files, and the snaps we build can use plugs to plug into those slots. The snapcraft.yaml file for gpiojawn defines the following plug:

1
2
3
4
5
apps:
  gpiojawn:
    command: bin/gpiojawn
    plugs:
      - gpio

After installing the gpiojawn snap, this plug can be connected by running the following command:

snap connect gpiojawn:gpio pi:bcm-gpio-23

Now if you run sudo gpiojawn again, you should see the light starting to blink!

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);
}

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, install the i2cjawn snap by running sudo snap install i2cjawn. Once again, running sudo i2cjawn will produce a "Permission denied" error because we haven't connected to the correct slot. To connect to the slot, run the command sudo snap connect i2cjawn:i2c pi:i2c-1. This connects the i2c plug defined in i2cjawn's snapcraft.yaml to the i2c-1 slot provided by the pi gadget snap. The pi gadget snap has slots for I²C channels 0-6.

Now that the plug and slot have been connected, run sudo i2cjawn again and you will see the LED on the Arduino start to blink!

Note: the plug for the i2cjawn snap is defined in snapcraft.yaml with the following lines:

1
2
3
4
5
apps:
  i2cjawn:
    command: bin/i2cjawn
    plugs:
      - i2c

Serial

This example requires two Raspberry Pis, because honestly who doesn't have tons of Pis laying around? The example snap I've created for the serial interface allows two Pis to send messages to each other over UART. Install the snap on two Raspberry Pis using the command sudo snap install serialjawn, and wire them up according to the following diagram:

Serial Wiring Diagram

Before we start sending messages, we'll need to make some changes to config.txt and cmdline.txt on the two Pis. On both, we will need to disable the serial console, so we don't get tons of spam on the line. To do so, open cmdline.txt and change the section console=ttyserial0,115200 to console=tty1. Next, because Bluetooth on the Pi uses the Serial UART pins, we will need to disable it. Edit config.txt and add the line dtoverlay=disable-bt. Now we are ready to send messages!

On either of the Pis, start listening for messages by running sudo serialjawn listen. If you've been paying attention, you'll know that we should expect a "Permission denied" error. We will need to connect the serialjawn snap on each Pi to the /dev/ttyS0 slot by running the following command: sudo snap connect serialjawn:serial-port pi:bt-serial.

Now run sudo serialjawn listen on one pi, and sudo serialjawn send "Hello World" on the other Pi. You should see the "Hello World" message appear on the listener Pi!

Note: the plug for the serialjawn snap is defined in snapcraft.yaml with the following lines:

1
2
3
4
5
apps:
  serialjawn:
    command: bin/serialjawn
    plugs:
      - serial-port

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 12 in the example snap. 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

There are plenty of software PWM solutions, but they all fall short of being able to match the speeds of hardware PWM. The Raspberry Pi has two hardware PWM channels, but a change is needed to config.txt to make them accessible. Plug your SD card into a different computer, and open <mountpoint>/ubuntu-boot/config.txt. Add the line dtoverlay=pwm-2chan, save the file, and cleanly unmount the SD card.

After connecting the fan and Raspberry Pi, install the snap by running sudo snap install pwmjawn. The snap takes two arguments in the form of sudo pwmjawn <period> <duty_cycle>. Shockingly, running this command without connecting the snap to a slot results in a "Permission denied" error! Connect pwmjawn to the slot by running sudo snap connect pwmjawn:pwm pi:pwm0. The fan speed can now be set to 50% by running sudo pwmjawn 2272720 1136360. Click here to learn more about PWM periods and duty cycles.

Note: the plug for the pwmjawn snap is defined in snapcraft.yaml with the following lines:

1
2
3
4
5
apps:
  pwmjawn:
    command: bin/pwmd
    plugs:
      - pwm

Conclusion

This is only a small subset of the slots that are available for snaps to use on the Raspberry Pi. For a full list of all interfaces supported by snapd, see the Supported Interfaces documentation on snapcraft.io. To list the available slots on the Raspberry Pi, log into your Raspberry Pi and run snap connections --all. To get the source code for all the snaps in this tutorial, visit my Github page