Control GPIO pins on the PINE64 with C#

Similar to the Raspberry Pi, GPIO pins on the PINE64 can be controlled through sysfs. You can refer to my previous post which goes into the concept in detail, and the C# code remains the same for the PINE64. However, with the longsleep Ubuntu image, root access is required to control the GPIO pins. You will need to grant the necessary permissions in order to be able to control GPIO pins as a normal user.

Granting user permissions
We’ll assume that we want to be able to control the pins as the default ubuntu user. Follow these steps to grant the necessary permissions.

  1. Create a user group called gpio.
    groupadd gpio
  2. Add the ubuntu user to the gpio group.
    useradd -G gpio ubuntu
  3. Add a udev rule to run chown on the sysfs files. The chown command will set group ownership to the gpio group. Adding the udev rule will run the chown command automatically whenever you export a pin. Create a file called 99-com.rules in /etc/dev/rules.d and paste the following contents.

    Physical pin to GPIO number mapping
    I took some time to test the physical pins on the PINEĀ in order to determine the sysfs gpio numbers and came up with this table. As an example, physical pin 22 on the Pi 2 pinout corresponds to /sys/class/gpio/gpio79.

    Pin #GPIO #
    Pi 2 pinout
    3227
    5226
    832
    1033
    1171
    1272
    13233
    1576
    1677
    1878
    1964
    2165
    2279
    2366
    2467
    26231
    27361
    28360
    29229
    31230
    3268
    3369
    3573
    3670
    3780
    3874
    4075
    Euler pinout
    7363
    10232
    1135
    1236
    1337
    1538
    1639
    18100
    1998
    2199
    22101
    2397
    2496
    26102
    2734
    28103
    2940
    3041
    Exp pinout
    2359
    740
    841

How to control GPIO pins on the Raspberry Pi 3 using C#

GPIO pins on the Raspberry Pi can be controlled using the sysfs interface, which is a virtual filesystem that the Linux kernel provides. In this guide, we will write a basic C# class to control available pins on the Pi through sysfs.

Understanding the sysfs interface
sysfs provides access to the GPIO pins at the path /sys/class/gpio. You can cd into this path and ls to list files in the directory. There are two special files here which are export and unexport. You write to the export file to activate a particular pin, while writing to unexport deactivates the pin. The following example activates GPIO pin 18.

You can verify that the pin is activated by listing the files in the /sys/class/gpio directory. You should see a gpio18 folder in the directory listing. After the pin has been activated, you should specify whether the pin should be an input or output pin before you can read or write values. You do this for input like so:

Or for output:

If the pin is specified as an output pin, you can write a value of either 0 (low) or 1 (high) for the pin. If a LED is connected to the pin for this example, a value of 0 will turn the LED off, while a value of 1 will turn the LED on. To specify the pin value, you can do this:

Once you are done with the pin, you can deactivate it using:

Writing the C# class
Now that we have an idea of how sysfs works, we can create a class to implement the necessary steps. The sysfs approach basically requires writing values to the file, so we can use simple file I/O operations to achieve the desired result. The full listing for the GPIO class can be found at https://gitlab.com/akinwale/NaniteIo/blob/master/Nanite/IO/GPIO.cs.

The first thing we’ll do is add the using statements for the namespaces. System.IO is required for FileStream, StreamReader and StreamWriter which are used for file I/O. System.Threading is required for the Thread class, while Nanite.Exceptions contains the custom exceptions defined for our project. We’ll also define enumerations for the GPIO direction and value, and a few constants for strings like the GPIO path and other special files. The class will be defined as static, because we do not need to create an instance of the class.

Pretty straightforward so far. The first method we’re going to define is the PinMode method, which will take the pin number and direction as parameters. This method will activate the pin and then set the direction to either in or out depending on the specified parameter value.

We build the pinPath string making use of Path.Combine(GPIOPath, string.Format("gpio{0}", pin));. If the value specified for the pin parameter is 18, pinPath will contain the string, "/sys/class/gpio/gpio18". The ClosePin method call is optional, but the idea behind this is that the pin should be deactivated first before activating. We also check if the gpio pin directory exists using if (!Directory.Exists(pinPath)) before activating to make sure we are not activating a pin that has already been activated.

After the request for pin activation, there may be a small delay which is why we have a while loop which waits until the corresponding gpio pin directory has been created before we set the pin direction. Thread.Sleep(500) makes the program wait 500 milliseconds before proceeding to the next statement. Note that this while loop is completely optional, but it acts as a safeguard against setting the pin direction before the gpio pin directory has been created by the system. One thing to take note of is if the gpio pin directory never gets created (for instance, if the pin is invalid), the loop may end up running forever. To fix this, we can set a maximum number of times the loop should run before ending the loop.

The next method is the ClosePin method which takes the pin number as a parameter. This method checks if the pin directory exists before it writes the pin number to the /sys/class/gpio/unexport file.

We create the Write method to write a value to a pin. It takes two parameters, the pin number and the value which is of the Value enumerator type with possible values Value.Low or Value.High. In this method, we make use Path.Combine to create the full path to the value file in the gpio pin directory. For pin 18, this will be "/sys/class/gpio/gpio18/value". If value for the value parameter is Value.Low, we write 0 to the file, otherwise if it’s Value.High, we write 1 to the file.

Finally, we have our Read method to read a value from a pin. It will return either Value.Low or Value.High depending on what the pin has been set to. The question mark at the end of the method return type indicates that we can return null for the method if the value retrieved is invalid.

To determine if the retrieved value is valid, we add a couple of checks in the method. The first is the int.TryParse method, which returns false if the retrieved value is not a valid integer. Then verify that the value is either 0 or 1 using if (pinValue != 0 && pinValue != 1). If it’s neither 0 nor 1, null is returned. Otherwise, the corresponding enumeration value is returned by casting the integer to GPIO.Value.

Finally, we can put this all together in a sample program. If a LED is connected to pin 18, the LED will light up when the value is set to High and turn off when the value is set to Low.

Source Code
The full code listing for the GPIO class can be obtained from https://gitlab.com/akinwale/NaniteIo/blob/master/Nanite/IO/GPIO.cs.