Tutorial: GPIO/I2C/SPI-access without root-permissions

WereCatfWereCatf Posts: 201New Member
edited November 2017 in 40-PIN GP-bus
As you may have noticed, by default you do not have access to the GPIO-pins, the SPI-bus or the I2C-bus as a normal user and you have to use 'sudo' to access them or run an application that utilizes them; it is generally a good idea to limit access to such things for security, but on a dev-board like the UP and UP^2 it may be convenient to do development and testing as a regular user. The fix is reasonably simple, as we set the udev-system to change the ownership of the SPI/I2C/GPIO -devices.
Quick setup
To quickly set things up, you can just download the attachment in this post, extract the script and run that script as the user who you want to be able to use GPIO without root -- no need to run the script with sudo or as root, just run it as your regular user. After a reboot, you can just straight ahead down to the section "Using the GPIO as non-root in your code" to get started!
Manual setup
If you do not wish to use the script, you can set things up manually as follows:
SPI-bus access
For SPI-bus access create the file /etc/udev/rules.d/50-spi.rules with the following contents:
SUBSYSTEM=="spidev", GROUP="spiuser", MODE="0660"

Next, copy and paste or type these lines into the terminal as the user you want to give access to the bus:
sudo groupadd spiuser
sudo adduser "$USER" spiuser
I2C-bus access
For I2C-bus access create the file /etc/udev/rules.d/50-i2c.rules with the following contents:
SUBSYSTEM=="i2c-dev", GROUP="i2cuser", MODE="0660"

Similar to above, copy and paste or type these lines into the terminal as the user you want to give access to the bus:
sudo groupadd i2cuser
sudo adduser "$USER" i2cuser
GPIO-access
For GPIO access create the file /etc/udev/rules.d/50-gpio.rules with the following contents:
SUBSYSTEM=="gpio*", PROGRAM="/bin/sh -c '\
        chown -R root:gpiouser /sys/class/gpio && chmod -R 770 /sys/class/gpio;\
        chown -R root:gpiouser /sys/devices/virtual/gpio && chmod -R 770 /sys/devices/virtual/gpio;\
        chown -R root:gpiouser /sys$devpath && chmod -R 770 /sys$devpath\
'"

Again, copy and paste or type these lines into the terminal as the user you want to give access to the bus:
sudo groupadd gpiouser
sudo adduser "$USER" gpiouser

Now, there is nothing else to do with the SPI-bus or I2C-bus other than to reboot.

Using the GPIO as non-root in your code
With non-root GPIO-access there is one thing that you need to keep in mind: it takes a little bit of time for udev to set the permissions for all the virtual files the Linux-kernel creates every time you initialize -- or "export", to be more precise -- the pins and in your code you need to take this into account.

Here is a small application in C that simply toggles a pin HIGH and LOW:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <getopt.h>
#include <sys/ioctl.h>
#include <linux/gpio.h>
#include <time.h>
#include <mraa.h>

#define HEADERPIN 11

void sleepMillis(uint32_t millis) {
  struct timespec sleep;
  sleep.tv_sec = millis / 1000;
  sleep.tv_nsec = (millis % 1000) * 1000000L;
  while(clock_nanosleep(CLOCK_MONOTONIC, 0, &sleep, &sleep) && errno == EINTR);
}

int main(int argc, char **argv)
{
	mraa_init();
	mraa_gpio_context gpio;

	if (!(gpio = mraa_gpio_init(HEADERPIN))) {
		fprintf(stderr, "Error exporting pin %d!\n", HEADERPIN);
		mraa_deinit();
		exit(1);
	}

	/* Check if the binary has root-permissions: if not,
	sleep for 100ms to give udev time to set the GPIO-permissions
	correctly for us to use the pin we just initialized above.
	!IMPORTANT! */
	/* Try uncommenting this or changing the amount of time
	we sleep and see what happens. */
	if(geteuid()) sleepMillis(100);

	if(mraa_gpio_dir(gpio, MRAA_GPIO_OUT) != MRAA_SUCCESS){
		fprintf(stderr, "Error setting pin-direction!\n");
		mraa_gpio_close(gpio);
		mraa_deinit();
		exit(1);
	}
	printf("Blink HIGH..\n");
	mraa_gpio_write(gpio, 1);
	sleepMillis(1000); //Sleep one second
	printf("Blink LOW..\n");
	mraa_gpio_write(gpio, 0);
	sleepMillis(1000); //Sleep one second
	mraa_gpio_dir(gpio, MRAA_GPIO_IN);
	mraa_gpio_close(gpio);
	mraa_deinit();
	exit(0);
}

As you may notice, there is a call to geteuid() after mraa_gpio_init() -- mraa_gpio_init() initializes, or exports, the pin we wish to use, then geteuid() call is used to check if the app is running with root-permissions or not. If the application is running with root-permissions, there is no need for a delay as root can access all the file they want, but for non-root permissions a call to sleepMillis() is added to sleep 100ms, so udev hopefully has enough time to set the permissions correctly.

The above convention has to be followed with other languages, too, including shell-scripts:
#!/bin/bash
#Export GPIO3, or pin number 11 on the UP1-board
echo 3 > /sys/class/gpio/export
#Test if we have root-permissions and, if not, sleep for 100ms
if [ $EUID -gt 0 ]; then sleep 0.1; fi
echo out > /sys/class/gpio/gpio3/direction
echo 1 > /sys/class/gpio/gpio3/value
sleep 1
echo 0 > /sys/class/gpio/gpio3/value
sleep 1
#Set the pin back as INPUT and unexport it
echo in > /sys/class/gpio/gpio3/direction
echo 3 > /sys/class/gpio/unexport

And Python:
import RPi.GPIO as GPIO
import time
#We need this
from os import geteuid

# Pin Definitions:
ledPin = 4

# Pin Setup:
GPIO.setmode(GPIO.BCM)
GPIO.setup(ledPin, GPIO.OUT) # LED pin set as output
#If we're not root, sleep 100ms in order for udev to set
#the permissions on the exported GPIO right
if(geteuid() > 0):
	time.sleep(0.1)

print("Here we go! Press CTRL+C to exit")
try:
	while 1:
		GPIO.output(ledPin, GPIO.HIGH)
		time.sleep(0.5)
		GPIO.output(ledPin, GPIO.LOW)
		time.sleep(0.5)
except KeyboardInterrupt: # If CTRL+C is pressed, exit cleanly:
	GPIO.cleanup() # cleanup all GPIO

Comments

  • WereCatfWereCatf Posts: 201New Member
    I have now added a link to this guide to the wiki, and I attached a small script that should help people quickly set everything up. The script should be applicable to Debian, Ubilinux, Ubuntu etc. Feedback and bug-reports welcome.
  • ollittmollittm Posts: 1New Member

    There's no info UP squared wiki about GPIO/I2C userland access and no link to this thread either. Mind you the thread would have been broken with the forum downgrade anyways.

    I don't have access to edit wiki, heck, I don't even have access to change the avatar..

  • KurtKurt Posts: 141New Member ✭✭

    Thanks WereCat, I noticed the guide earlier, but did not notice this thread.

    Luckily I had most of this information from earlier threads that you gave suggestions on how to setup udev rules for GPIO...

    Thanks again

  • jleavittjleavitt Posts: 2New Member
    edited March 29

    Hi all. I cannot get this work for SPI access. It appears to grant me GPIO access, but not SPI. Anybody else have this problem? Running the app with sudo works fine. Running gdb needs sudo as well and works fine. Also, cannot get remote debugging working, probably for same reason.

  • jleavittjleavitt Posts: 2New Member

    Did some more digging. Found that after running the above mentioned script that the files created may be interfering with stock ublinux files. Specifically, /etc/udev/rules.d/50-spi.rules was almost the same as /etc/udev/rules.d/99-spi.rules the were already out there on my system. I deleted the 50-spi and the 50-i2c, added $USER to the "spi" group and the "i2c" group (which were referenced in the 99.. files)
    I then added the getuid delay to my ccode and SPI and I2C started working fine w/o need for sudo.
    Remaining question, what is the 99-leds.rules.d file and do I need the 50-GPIO.rules.d?

Sign In or Register to comment.