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:
- Set up GPIO pin for use. Write
"4"
into the export file at/sys/class/gpio/export
. - Establish pin as input or output. Write
"out"
or"in"
at/sys/class/gpio/gpio4/direction
. - 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)
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!
You're welcome!
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.
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.