DEV Community

Dave
Dave

Posted on

Raspberry Pi GPIO 4 Ways

I've been practicing recently with GPIO on my Raspberry Pi 4. My journey to controller nirvana hasn't been as easy as advertised as I chose to use Ubuntu server for Pi instead of Raspbian. I've found it very difficult to add the python GPIO module. Yet, I haven't given up. With some interwebbing, I've discovered that that little module isn't needed and one doesn't have to use python to control GPIO pins.

Automatically set up upon starting the Pi, even with Ubuntu is the sysfs system. This is similar to the procfs system that some are familiar with using. There is a /cpuinfo file in the procfs system one can read to determine machine specific data. Most of the sysfs system can be identified because they are the /sys/class/ folders in the file system.

This collection of folders and files, when properly set up, can be used to programmatically control GPIO pins on the Raspberry Pi with any programming language that supports file I/O. In this example (this is not a tutorial, folks), I am going to show the simple flow of what one does to control GPIO pins in C, C++, nim, and even python.

There are two articles I used to help figure all this out. I'll put links to each at the end of the article. My C code is the same as the Auctoris link.

First, we'll step through the things that have to be done to set up GPIO for use, set the direction of a pin, and either turn on or turn off a pin. I'll be using pin 4 in my examples, but any of the general purpose pins will work. For the SPI, one-wire, and other analog or bus type pins, you'll need to do some research on how these work through sysfs.

The steps are:

  1. Set up GPIO pin for use. Write "4" into the export file at /sys/class/gpio/export.
  2. Establish pin as input or output. Write "out" or "in" at /sys/class/gpio/gpio4/direction.
  3. Finally, turn pin on or off. Write "1" or "0" at /sys/class/gpio/gpio4/value.

Before we get to the code section, a few caveats. All of what you write to the sysfs files are strings. Don't confuse "4" with 4. If you alternate writing the "4" to both the export and unexport files, you will see the gpio4 folder appear and disappear when ls-ing the folder list. That is what is meant by setting up the pin. Without doing so, you won't have files where you can write 1's or 0's. Also, all of these files are owned by root. You must be root or use sudo when running your executable file or you will get errors.

A little side note. According to what I've read, it is correct procedure to unexport your pin set ups after you are through with them. This is probably good information; yet, I figure most real world applications will be in continuous use. Use your judgment as to whether this step is necessary or not. From my observation, when I power down the Pi, export settings are lost anyway. GPIO pin set up must be done again upon start up.

Without any more delay, let's get to our code. First up is the C code from the Auctoris post. He was very worried about buffer overwrites in the sysfs files. That is why he is using write methods that specify how many characters write to the files. While I believe that is good practice, I don't have enough facility in the other languages to mimic that operation.

File: gpio.c

#include <stdio.h> 

int main(){
FILE *sysfs_handle = NULL;           
        sysfs_handle = fopen("/sys/class/gpio/export", "w");
        if (sysfs_handle != NULL){
                fwrite("4", sizeof(char), 2, sysfs_handle);
                printf("\nGPIO 4 opened for EXPORT\n");
                fclose(sysfs_handle);       
        }
        else
        {
 printf("\nCan't open gpio/export.  Something went wrong. Are you root?\n");

        }
        sysfs_handle = fopen("/sys/class/gpio/gpio4/direction", "w" );
        if (sysfs_handle != NULL){

                fwrite("out", sizeof(char), 4, sysfs_handle);
                printf("\nGPIO 4 assigned to be an output.\n");
                fclose(sysfs_handle);
        }
        else
        {
                printf("\nCan't open gpio/direction.  Something went wrong. Are  you root?\n");
        }
sysfs_handle = fopen("/sys/class/gpio/gpio4/value", "w");
        if (sysfs_handle != NULL){
                fwrite("out", sizeof(char), 4, sysfs_handle);
                printf("\nGPIO 4 assigned to be an output.\n");
                fclose(sysfs_handle);
        }
        else
        {
 printf("\nCan't open gpio/direction.  Something went wrong. Are  you root?\n");

        }
        sysfs_handle = fopen("/sys/class/gpio/gpio4/value", "w");

        if (sysfs_handle != NULL){
                fwrite("1", sizeof(char), 2, sysfs_handle);
                printf("\nGPIO 4 given an ON command.\n");
                fclose(sysfs_handle);
        }
        else
        {

                printf("\nCan't open gpio/value. Something went wrong. Are you root?\n");

        }
return 0;
}

The above code should compile with gcc gpio.c (assuming you named your source that way, you don't have to do so). After a successful compile, you would run the a.out executable with sudo ./a.out

The following code is for C++. In all of these examples, I stuck with the file object (pointer, whatever) method to match up with Auctoris' example.

File: gpio.cpp

#include <iostream>
#include <fstream>          
using namespace std;   

int main () {

fstream sysfs_handle;                                  

sysfs_handle.open ("/sys/class/gpio/export", ios::out);        

if( sysfs_handle.is_open()){
   sysfs_handle<<"4";   
   cout<<"\nGPIO 4 opened for EXPORT.\n";
   sysfs_handle.close();                                
   }else{
       cout<<"\nCan't open gpio/export.  Are you root?\n";
   }

sysfs_handle.open ("/sys/class/gpio4/direction", ios::out);     

if( sysfs_handle.is_open()){
   sysfs_handle<<"out";         
   cout<<"\nGPIO 4 opened as output pin.\n";
   sysfs_handle.close();                           
   }else{
       cout<<"\nCan't open gpio4/direction.  Are you root?\n";
   }

sysfs_handle.open ("/sys/class/gpio/export", ios::out);    

if( sysfs_handle.is_open()){
   sysfs_handle<<"1"; 
   cout<<"\nGPIO 4 turned ON.\n";
   sysfs_handle.close();
   }else{
       cout<<"\nCan't open gpio/export.  Are you root?\n";
   }

  return 0;
}

Your C++ code should compile with g++ gpio.cpp and you should be able to run the resultant a.out file the same as you did with C.

Next, let's get to the nim version. I've been studying nim for a little while. I've only made some headway in that regard. I had to do a lot of searching to work the file I/O operations. My observation of nim is that there are several methods to accomplish file I/O. I've chosen what is closest to what I've already written above in C and C++. nim is available in the Ubuntu Raspberry Pi repository. One word of warning. Do not use tabs in nim. It doesn't like that and will give you some error about tabulators not allowed. Took quite a bit to figure out that meaning.

File: gpio.nim

var sysfs_handle: File

if open( sysfs_handle, "/sys/class/gpio/export", fmWrite):

     sysfs_handle.writeLine("4")
     echo "GPIO export set."

else:
     echo "Problem opening File, are you root?"

sysfs_handle.close()

if open( sysfs_handle, "/sys/class/gpio/gpio4/direction", fmWrite):

     sysfs_handle.writeLine("out")
     echo "GPIO pin 4 assigned to be an output."

else:

     echo "Problem opening file. Are you root?"

sysfs_handle.close()


if open( sysfs_handle, "/sys/class/gpio/gpio4/value", fmWrite):

     sysfs_handle.writeLine("1")
     echo "GPIO pin 4 turned on."

else:

     echo "problem opening file, are you root?"

sysfs_handle.close()

You could compile the above code with nim compile gpio.nim and if you needed debugging, that is the method to choose. One of the weaknesses I see in nim is the size of the executable. nim outputs C code and then compiles that, so the resulting executables are much larger than expected. I'd recommend nim compile -d:release gpio.nim in order to remove the debugging info from your executable. It reduces the size significantly.

Just a side note before moving on: this is not a nim tutorial. If you wish for me to write one (because the ones I've found can be confusing, save one), let me know in the comments below and I'll try to do one as I go along learning nim.

Finally, yes, you can still program in python and use the sysfs system. The following code is just that, for grins and giggles. It is in python3, which is standard on Ubuntu Raspberry. If you wish to use python 2.xx, you'll have to install it from the repository. It is there.

File: gpio.py

#!/usr/bin/python3

sysfs_handle = open("/sys/class/gpio/export", "w", 2)

if (sysfs_handle):
     sysfs_handle.write("4")
     print("GPIO 4 opened for EXPORT")

else:

     print("Can't open gpio/export.  Something went wrong. Are you root?")

sysfs_handle = open("/sys/class/gpio/gpio4/direction", "w")

if(sysfs_handle):
     sysfs_handle.write("out")
     print("GPIO pin 4 set as output")

else:

     print("Can't open gpio/direction. Are you root?")

sysfs_handle = open("/sys/class/gpio/gpio4/value", "w")

if(sysfs_handle):
     sysfs_handle.write("1")
     print("GPIO pin 4 is ON")

else:

    print("Can't open gpio4/value. Are you root?")

All that is required to make gpio.py executable is chmod +x gpio.py. Don't forget to run as root.

To wrap things up, it is not necessary to use Raspbian on your Raspberry Pi if you would rather use one of the other OS's. You are not tied to the python language and it's GPIO module if you would prefer to use another language. The sysfs file system in there for your use and will do an adequate job. These are example source files on the process of using sysfs to set up and command or read GPIO pins on the Pi. Your next steps would be to write a library of functions that make setting up pins and commanding them easier to do as a programmer. Yes, it is more work, but we like programming right?

Link to Auctoris article, one of two.
Link to sysfs use on Big Mess'O'Wires.

Top comments (4)

Collapse
 
pouncer29 profile image
Bennett Lewis

This is the actual greatest. Super easy implementation. I made an account just so I could tell you how excellent this solution was for me. I wrote a super simple cooling script that I guess depends more on the Raspbian OS than I had thought, switched to Ubuntu server and nothing worked. I anticipate things will be working again shortly!
Thanks!

Collapse
 
tardisgallifrey profile image
Dave

You're welcome!

Collapse
 
mongezi_khumalo_efb52cfce profile image
Mongezi Khumalo

Hi Dave, thanks for the comprehensive tutorial. I have one issue, I can't open the "...gpio4/direction" i get the following output when I run the a.out. Please see image. I am running the executable with sudo.

Image description

Collapse
 
tardisgallifrey profile image
Dave • Edited

Sorry. I don't come here often anymore. I see that you ran sudo to run the file, so steps 1 and 2 worked.

Somehow, when you opened export, it failed to write. I'm not sure of how it must be fixed. File I/O is extremely picky. Check your code for even the slightest of differences.

Some comments may only be visible to logged-in visitors. Sign in to view all comments.