The sysfs GPIO interface is fairly straightforward. I'll demonstrate it using perl to respond to events from three buttons connected to GPIOS 17,27, and 22 (by the Broadcom numbering). You don't need to have any experience with perl to follow the methodology. Although all of the working code is provided at the end, I only refer to a small portion of it in the conceptual explanation.
Recent versions of Raspbian give permission to anyone in the gpio
group for this but on other distros this may not be the case. Note /sys
files aren't regular files, which is why they have a size of zero. They are not stored anywhere because they do not really have content. When you write to a sysfs file, you are sending the kernel a message. When you read one, you are asking it for information.
As per the documentation linked in the question (which you should look at, since I will not regurgitate all of it, and it is assumed below you are referring to this), the first thing that needs to be done is to open or export a specific pin by writing the number to /sys/class/gpio/export
. I'm creating an object oriented API, so I do that in the constructor for the GPIO pin class, if necessary.
The sysfs interface is text based and line buffered, meaning it will output newlines at the end of each value, and "0" and "1" refer to characters, not integers. I.e., looking at this byte for byte those would be 0x30 ('0'
) or 0x31 ('1'
), 0x0A ('\n'
), 0x00 (NUL
). Of course, in most languages you might as well just use strings. You don't have to include the newline in input, but for simplicity with using and comparing constants in perl I do.
use constant {
INPUT => "in\n",
OUTPUT => "out\n",
HIGH => "1\n",
LOW => "0\n",
POSEDGE => "rising\n",
NEGEDGE => "falling\n",
BOTH_EDGES => "both\n",
NO_EDGE => "none\n"
};
The use for these will become clear. An example of creating a pin object for monitoring a button, like that used in the demo, might be:
my $pin = SysfsGPIO->new (
number => 17,
direction => SysfsGPIO::INPUT,
edge => SysfsGPIO::BOTH_EDGES,
active => SysfsGPIO::LOW,
bouncetime => 0.01
);
In the code implementing the class, the direction is set by writing to the direction
file in (for this case) /sysfs/class/gpio/gpio17
, once that directory exists (by writing to export
as mentioned above). The rest of this requires further explanation.
The event triggers make use of the select()
or poll()
system calls. These are not perl, although perl like every other language has a wrapper for them. They are most commonly used in networking and IPC; they allow a number of file descriptors (presumably, network sockets) to be polled for readiness to read or write. Basically, you submit a list of file handles, and the call blocks until one or more of them is ready. This is not any kind of busy or half-busy loop. Depending on what the handles refer to, the kernel implements this via genuine hardware interrupts.
Such calls are often used as the opening condition of a program's "main loop", and that is the model I will follow. Like a server, we are just going to wait for and respond to events when they happen. I'm using three buttons to demonstrate that you are not limited to just one at a time. This is also a single-threaded exercise.
Note again that ALL general purpose languages will have their own version of poll()
and/or select()
, which is why this is a language agnostic methodology. You should be able to find out what they are easily. If you haven't used whatever it is before, you will want to read the documentation and perhaps find some examples or tutorials. I prefer poll()
, so I'm using perl's core IO::Poll
module.
The file you actually want to hold open for the descriptor/handle is value
. However, there are a couple of special things to note:
Unlike normal use of select()
or poll()
, what's triggered isn't a read or write flag/descriptor set. For poll()
style functions you need the "error" and "priority" flags set, for select()
you would use the "exception" (not the "read") set.
use constant POLL_EVENTS => POLLPRI | POLLERR;
When a specific handle fires, you must read the value and then either close & reopen the file or rewind the handle. If you don't it will keep firing immediately. Rewinding makes more sense so the descriptor does not change for the poll/select set.
The handle is triggered when value
changes, but there this can be from 0 to 1 or from 1 to 0. The former is called the "rising" or positive edge and the latter the "falling" or negative edge (notice I defined constants for these). You can choose to respond to either or both of these events (this is the purpose of edge
).
The active_low
file allows you to invert value
, such that 1 will indicate a physically low signal and 0 a high one. I use it for style since the button is on a pull-up -- the signal will be low when the button is down. By setting the active low inversion that will be read as a 1 from value
. This is unnecessary but I use in the demo, so do not be confused by the inverted values below.
There are a few ways of reacting to both the rising and falling edge. The obvious and easy one is to write both
to the edge
file. An apparently common pitfall with buttons is "bounce", whereby the signal may oscillate when the button is depressed or released. A programmatic solution is to "debounce" by discarding multiple changes within a brief window. This doesn't require a sleep, it just requires you timestamp events.1
So, once the GPIOs are configured (as inputs, set active low with a trigger on both edges) the value
file handles are set up with a POLLERR | POLLPRI
mask and handed to poll()
. Since poll()
returns with a list of handles that actually fired, I created a hash table (%buttons
) corresponding handles to SysfsGPIO
objects. Thus the main loop begins:
while ($gpios->poll) {
foreach ($gpios->handles(POLL_EVENTS)) {
my $pin = $buttons{$_};
If this is not clear, we're now checking each of the buttons that poll()
says have been triggered.
my $prev = $pin->{lastValue};
my $state = $pin->getValue;
next if $pin->checkBounceTime;
print "$pin->{color} $state";
$pin->{output}->toggle if ($state eq SysfsGPIO::HIGH);
The color
attribute was tacked onto the object to reference to the button colors. Note that even if the button going up serves no purpose, you should still watch for it to deal with bounce (see footnote). Here, the button going up will pass checkBounceTime()
, which starts the timer again, but then be discarded by the final if ($state...)
.
Perl is pre-installed by all normal GNU/Linux distros including Raspbian and the complete code below does not require any additional libraries, so if you have some buttons and resistors you can try it out. Pressing all three buttons down then putting them up/down one at a time and finally releasing all three looks like this:
blue 1
green 1
yellow 1
yellow 0
yellow 1
green 0
green 1
blue 0
blue 1
blue 0
green 0
yellow 0
Here's a pic of the connections. There are lots of explanations of button circuits around. These use external pull-ups. In case it isn't obvious the button contacts connect numbered rows when depressed, not the top half of the board to the bottom.
Here's the code. To see this in action, place both files in the same directory, set test.pl
executable (chmod 755 test.pl
), and run ./test.pl
(also note the different extentions, .pl
and .pm
). You'll have to use ctrl-c to exit and there's no clean-up, so the directories will still exist in /sys/class/gpio
. You can restart it without closing those, however.
test.pl
#!/usr/bin/perl
use strict;
use warnings FATAL => qw(all);
use IO::Poll qw(POLLPRI POLLERR);
use SysfsGPIO;
use constant POLL_EVENTS => POLLPRI | POLLERR;
my %buttons;
my $gpios = IO::Poll->new();
foreach (
[ 17, 'blue' ],
[ 27, 'green' ],
[ 22, 'yellow' ]
) {
my $pin = SysfsGPIO->new (
number => $_->[0],
color => $_->[1],
direction => SysfsGPIO::INPUT,
edge => SysfsGPIO::BOTH_EDGES,
active => SysfsGPIO::LOW,
bouncetime => 0.01
);
if (!$pin) {
print STDERR "Pin $_->[0] ($_->[1]) failed initialization!\n";
next;
}
$buttons{$pin->{handle}} = $pin;
$pin->getValue;
$gpios->mask($pin->{handle}, POLL_EVENTS);
}
print "Begin...\n";
while ($gpios->poll) {
foreach ($gpios->handles(POLL_EVENTS)) {
my $pin = $buttons{$_};
my $prev = $pin->{lastValue};
my $state = $pin->getValue;
next if $pin->checkBounceTime;
print "$pin->{color} $state";
}
}
SysfsGPIO.pm
package SysfsGPIO;
use strict;
use warnings FATAL => qw(all);
use Fcntl qw(SEEK_SET);
use Time::HiRes;
our $SysfsPath = "/sys/class/gpio";
use constant {
INPUT => "in\n",
OUTPUT => "out\n",
HIGH => "1\n",
LOW => "0\n",
POSEDGE => "rising\n",
NEGEDGE => "falling\n",
BOTH_EDGES => "both\n",
NO_EDGE => "none\n"
};
sub openAndWrite {
open(my $fh, '>', shift) or return 0;
print $fh shift;
close $fh;
return 1;
}
sub openAndRead {
open(my $fh, '<', shift) or return undef;
my $x = "";
sysread($fh, $x, 1024);
close $fh;
return $x;
}
sub new {
my $class = shift;
my $self = {
number => -1,
@_
};
$self->{path} = "$SysfsPath/gpio".$self->{number};
return undef if (
!-e $self->{path}
&& !openAndWrite (
"$SysfsPath/export",
"$self->{number}\n"
)
);
bless $self, $class;
return undef if (
(
$self->{direction} &&
!$self->setDirection($self->{direction})
) || (
!$self->getDirection ||
!$self->openValue
)
);
return undef if (
(
$self->{edge} &&
!$self->setEdge($self->{edge})
) || !$self->getEdge
);
return undef if (
(
$self->{active} &&
!$self->setActive($self->{active})
) || !$self->getActive
);
return $self;
}
sub DESTROY {
my $self = shift;
if ($self->{handle}) {
close $self->{handle};
$self->{handle} = undef;
}
}
sub close {
my $self = shift;
return 0 if !$self->setDirection(INPUT);
if ($self->{handle}) {
close $self->{handle};
$self->{handle} = undef;
}
return 0 if !openAndWrite (
"$SysfsPath/unexport",
"$self->{number}\n"
);
return 1;
}
sub checkBounceTime {
my $self = shift;
return undef if !$self->{bouncetime};
if ($self->{lastbounce}) {
my $diff = $self->{bouncetime} -
(Time::HiRes::time() - $self->{lastbounce});
return $diff if $diff > 0;
}
$self->{lastbounce} = Time::HiRes::time();
return 0;
}
sub getActive {
my $self = shift;
my $alow = openAndRead("$self->{path}/active_low");
return undef if !$alow;
if ($alow eq LOW) {
$self->{active} = HIGH
} else {
$self->{active} = LOW
}
return $self->{active};
}
sub getEdge {
my $self = shift;
$self->{edge} = openAndRead("$self->{path}/edge");
return $self->{edge};
}
sub getDirection {
my $self = shift;
$self->{direction} = openAndRead("$self->{path}/direction");
return $self->{direction};
}
sub getValue {
my $self = shift;
return undef if (
sysread($self->{handle}, $self->{lastValue}, 3) <= 0
);
seek($self->{handle}, 0, SEEK_SET);
return $self->{lastValue};
}
sub setActive {
my $self = shift;
$self->{active} = shift;
my $alow = LOW;
$alow = HIGH if $self->{active} eq LOW;
return openAndWrite (
"$self->{path}/active_low",
$alow
);
}
sub setEdge {
my $self = shift;
$self->{edge} = shift;
return openAndWrite (
"$self->{path}/edge",
$self->{edge}
);
}
sub setDirection {
my $self = shift;
$self->{direction} = shift;
return openAndWrite (
"$self->{path}/direction",
$self->{direction}
);
}
sub openValue {
my $self = shift;
if ($self->{handle}) {
CORE::close $self->{handle};
$self->{handle} = undef;
}
if ($self->{direction} eq OUTPUT) {
return 0 if !open($self->{handle}, '>', "$self->{path}/value");
return 1;
} elsif ($self->{direction} eq INPUT) {
return 0 if !open($self->{handle}, '<', "$self->{path}/value");
return 1;
}
return 0;
}
1;
1. Bounce occurs when the button goes down and goes up, and alternate states evenly. This means even if you are just interested in the "pushed down" edge (in this case, positive/high), you still need to watch both edges so you can apply a bounce time at the end as well. Then when the button goes up, the first event will be the for opposite edge, and any subsequent bounce down again will be properly discarded.