Skip to content

Commit

Permalink
RPiGPIOMex: Remove sysmode support, rewrite wait-for-trigger.
Browse files Browse the repository at this point in the history
Waiting for trigger only worked in sysmode as non-root, whereas
almost everything else useful needed gpiomode. Lets get rid of
this mess.

The wait for trigger function was rewritten into a simple
polling loop that waits for a low->high transition, aka
raising edge, with one poll every ~200 microseconds.

Now we don't need sysmode anymore and remove all support code.
We always use GPIO mode. This needs either 'sudo octave' for
root, or as a better solution, needs the user to be part of
the gpio Unix user group, something done automatically when
a user runs PsychLinuxConfiguration() during initial setup of
their account on the RPi.

-> Can always operate all functionality as normal non-root user.
-> RaspberryPiGPIODemo.m adapted accordingly.

Note this still depends on the deprecated libwiringPi library.
  • Loading branch information
kleinerm committed Aug 18, 2023
1 parent 1d04ba1 commit 086294d
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 131 deletions.
104 changes: 60 additions & 44 deletions Psychtoolbox/PsychContributed/RPiGPIOMex.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,60 @@
#include <wiringPi.h>

static bool firstTime = 1;
static bool sysMode = 1;

void exitfunc(void)
{
// Actually nothing to do. The library does not have a shutdown function.
firstTime = 1;
}

unsigned long long nowMS(void)
{
static unsigned int thigh = 0;
static unsigned int oldT = 0;
unsigned int tlow = millis();

// 32-Bit time wraparound? Increase upper 32-Bit:
if (tlow < oldT)
thigh ++;

// Keep track of current lower 32-Bits for wraparound handling:
oldT = tlow;

// Assemble new 64-Bit time:
return ((unsigned long long) thigh) << 32 | ((unsigned long long) tlow);
}

/* waitFor 'pin' reaching target level 'waitLevel', or 'timeout' msecs elapsed. */
int waitFor(int pin, int timeout, int waitLevel)
{
// Compute deadline for timeout - -1 == Infinite wait:
unsigned long long timeoutD = (timeout >= 0) ? (nowMS() + (unsigned long long) timeout) : 0xffffffffffffffff;

// Wait for signal level not being waitLevel:
while ((nowMS() < timeoutD) && (digitalRead(pin) == waitLevel))
delayMicroseconds(200);

// Timed out?
if (digitalRead(pin) == waitLevel)
return 0;

// Wait for signal level transition to waitLevel:
while ((nowMS() < timeoutD) && (digitalRead(pin) != waitLevel))
delayMicroseconds(200);

// Timed out?
if (digitalRead(pin) != waitLevel)
return 0;

// Success:
return 1;
}

/* This is the main entry point from Octave: */
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray*prhs[])
{
int cmd, pin, arg;
int cmd, pin, arg, waitRising;
int rc;
double* out;

Expand All @@ -61,9 +103,10 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray*prhs[])
mexPrintf("This file is part of Psychtoolbox-3 but should also work independently.\n\n");
mexPrintf("Pin numbers are in Broadcom numbering scheme aka BCM_GPIO numbering.\n");
mexPrintf("Mapping to physical connector pins and other restrictions and pin properties can be found at\n");
mexPrintf("http://wiringpi.com/pins \n\n");
mexPrintf("http://wiringpi.com/pins or by typing the command 'pinout' into a terminal.\n\n");
mexPrintf("For testing purposes, pins 35 and 47 on a RaspberryPi 2B map to the red power and green status LEDs.\n\n");
mexPrintf("The gpio command line utility allows to setup and export pins for use by a non-root user.\n\n");
mexPrintf("The gpio command line utility allows to setup and export pins from a shell. Octave must be run as\n");
mexPrintf("root user via 'sudo octave' or the user must be part of the 'gpio' Unix user group for non-root operation.\n\n");
mexPrintf("\n");
mexPrintf("Usage:\n\n");
mexPrintf("revision = %s;\n", me);
Expand All @@ -74,20 +117,16 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray*prhs[])
mexPrintf("- Set state of pin number 'pin' to logic level 'level': 1 = High, 0 = Low.\n\n");
mexPrintf("%s(2, pin, level);\n", me);
mexPrintf("- Set pulse-width modulation state of pin number 'pin' to level 'level': 0 - 1023.\n");
mexPrintf(" Only available on GPIO logical pin 18 (physical connector pin 12) on the RaspberryPi without extension boards.\n");
mexPrintf(" Only available if running Octave as root, ie. started via sudo octave.\n\n");
mexPrintf(" Only available on GPIO logical pin 18 (physical connector pin 12) on the RaspberryPi without extension boards.\n\n");
mexPrintf("%s(3, pin, direction);\n", me);
mexPrintf("- Set direction of pin number 'pin' to 'direction'. 1 = Output, 0 = Input.\n");
mexPrintf(" Only available if running Octave as root, ie. started via sudo octave.\n\n");
mexPrintf("- Set direction of pin number 'pin' to 'direction'. 1 = Output, 0 = Input.\n\n");
mexPrintf("%s(4, pin, pullMode);\n", me);
mexPrintf("- Set resistor mode of pin number 'pin' to 'pullMode'. -1 = Pull down, 1 = Pull up, 0 = None.\n");
mexPrintf(" Pin must be configured as input for pullup/pulldown resistors to work.\n");
mexPrintf(" Only available if running Octave as root, ie. started via sudo octave.\n\n");
mexPrintf(" Pin must be configured as input for pullup/pulldown resistors to work.\n\n");
mexPrintf("result = %s(5, pin, timeoutMsecs);\n", me);
mexPrintf("- Wait for rising/falling edge on input pin number 'pin' with a timeout of 'timeoutMsecs': -1 = Infinite wait.\n");
mexPrintf("- Wait for rising edge on input pin number 'pin' with a timeout of 'timeoutMsecs': -1 = Infinite wait.\n");
mexPrintf(" Return 'result' status code: -1 = error, 0 = timed out, 1 = trigger received.\n");
mexPrintf(" Usually only available if running Octave as non-root.\n");
mexPrintf(" Pin must be configured as input and edge trigger type must be setup via the gpio utility.\n\n");
mexPrintf(" Pin must be configured as input.\n\n");

return;
}
Expand All @@ -97,22 +136,8 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray*prhs[])
// We want to handle this gracefully instead of crashing:
setenv("WIRINGPI_CODES", "1", 0);

// Initialize wiringPi. If we are effectively root then we use
// Gpio mode for full support, otherwise Sysmode for only basic
// digital i/o.
//
// We will use native Broadcom GPIO pin numbering:
if (!geteuid()) {
// Root - Upgrade to root access mode:
rc = wiringPiSetupGpio();
sysMode = 0;
}
else {
// Standard sys mode:
rc = wiringPiSetupSys();
sysMode = 1;
}

// Always use GPIO mode - requires sudo root, or user to be in 'gpio' Unix user group:
rc = wiringPiSetupGpio();
if (rc) {
mexPrintf("wiringPi init failed: Error code %i [%s]\n", rc, strerror(rc));
mexErrMsgTxt("Failed to initialize GPIO system.");
Expand Down Expand Up @@ -155,43 +180,34 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray*prhs[])
if (arg < 0)
mexErrMsgTxt("New pwm level of output pin missing for output pin pulse-width modulation.");

if (sysMode)
mexErrMsgTxt("PWM control unsupported in sys mode! Must run as root via sudo to use this!");

pwmWrite(pin, arg);
break;

case 3: // NOT in sysmode: Set pin mode: 1 = out, 0 = in
if (arg < 0)
mexErrMsgTxt("New opmode for pin missing for pin mode configuration.");

if (sysMode)
mexErrMsgTxt("Pin input/output direction control unsupported in sys mode! Must run as root via sudo to use this!");

pinMode(pin, arg ? OUTPUT : INPUT);
break;

case 4: // NOT in sysmode: Set pullup/pulldowns: 1 = out, 0 = in
if (arg < -1)
mexErrMsgTxt("New pullup/down for pin missing for pin resistor configuration.");

if (sysMode)
mexErrMsgTxt("Pullup/Pulldown control unsupported in sys mode! Must run as root via sudo to use this!");

pullUpDnControl(pin, (arg == 0) ? PUD_OFF : ((arg > 0) ? PUD_UP : PUD_DOWN));
break;

case 5: // Wait for rising or falling edge on given input pin via interrupt.
case 5: // Wait for rising or falling edge on given input pin via polling.
if (arg < -1)
mexErrMsgTxt("Timeout value in milliseconds missing.");

rc = waitForInterrupt(pin, arg);
// Wait for rising edge:
waitRising = 1;

// Wait for target level with timeout:
rc = waitFor(pin, arg, waitRising);
plhs[0] = mxCreateDoubleMatrix(1, 1, mxREAL);
*(mxGetPr(plhs[0])) = (double) rc;

if (rc == -2)
mexErrMsgTxt("Wait for trigger unsupported in this mode! Probably only works in non-root mode.");

break;

default:
Expand Down
128 changes: 41 additions & 87 deletions Psychtoolbox/PsychDemos/RaspberryPiGPIODemo.m
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
function RaspberryPiGPIODemo
% RaspberryPiGPIODemo - Show basic use of GPIO's on RaspberryPi.
%
% Demos full access to the Pi GPIOs when running as root,
% slightly more limited access when running as a regular user,
% and also the different setup steps required.
% Demos full access to the Pi GPIOs.
%
% Accessing the GPIOs as non-root requires the user account
% to be a member of the Unix group 'gpio'. PsychLinuxConfiguration
Expand All @@ -19,10 +17,10 @@
%
% https://github.com/WiringPi/WiringPi/releases
%
% Shows digital i/o by flashing/alternating the two
% The demo shows digital i/o by flashing/alternating the two
% LEDs on the RPi 2B, Red power, and green status.
%
% Shows how to efficiently wait for a rising edge
% It shows how to efficiently wait for a rising edge
% trigger on Broadcom pin 17, a real GPIO pin on the
% connector. To avoid the need to actually connect
% a switch to the connector, uses internal programmable
Expand All @@ -42,11 +40,13 @@
%
% A nice translation table between connector pins and Broadcom GPIO
% pins can be found on the following website: https://pinout.xyz
% The command 'pinout' in a terminal will also print the pin mapping.
%

% History:
% 26-Jun-2016 mk Written.
% 22-Jul-2016 mk Clarify pin numbering more, reference https://pinout.xyz
% 18-Aug-2023 mk Update for current libwiringPi, remove now defunct demo code.

if ~IsARM || ~exist('RPiGPIOMex', 'file')
fprintf('This demo only works on the RaspberryPi. Bye.\n');
Expand All @@ -68,46 +68,17 @@

% Decouple OS from LEDs so we are alone in the driver seat:
% Not needed for real GPIO pins on the connector...
system('echo none > /sys/class/leds/led0/trigger');
system('echo none > /sys/class/leds/led1/trigger');

% Set pin 17 to input with reception of rising edge triggers.
% This will fire an interrupt if a rising edge is detected.
% The RPiGPIOMex(5, pin, timeout) command can efficiently wait
% for such triggers that are reported via hardware interrupts:
system('gpio edge 17 rising');

% Pull the input pin down to logical low via internal pulldown resistor,
% to have a good starting point for simulating a trigger by using
% the internal pullup resistor to create a low->high transition.
% All gpio commands except 'edge' and 'export' operate on wiringPi
% logical pin numbers, not on Broadcom GPIO pin numbers. As we only
% use Broadcom numbering, we need to use the -g flag for most commands
% to tell 'gpio' to use the Broadcom numbering:
system('gpio -g mode 17 down');
fprintf('Please enter your sudo password if prompted, to allow LED control on RPi.\n');
system('sudo -S /bin/bash -c ''echo none > /sys/class/leds/led0/trigger''');
system('sudo -S /bin/bash -c ''echo none > /sys/class/leds/led1/trigger''');

% List all pins which are exported for use by non-root applications,
% and their configuration:
system('gpio exports')

% Are we effectively running as root?
if geteuid ~= 0
% Nope. Need some help from our powerful command line friend:
% Call gpio command line utility to export pins of
% red led and green led for use by us without need
% to be root. Set them to output mode.
%
% Note: This does not actually work for the builtin LEDs!
% It only works for the real GPIO pins, so these lines are
% for illustration only and you need to run 'sudo octave'
% to control the LEDs.
system(sprintf('gpio export %i out', redled));
system(sprintf('gpio export %i out', greenled));
else
% Yes. Can setup stuff ourselves. Switch pins to output mode:
RPiGPIOMex(3, redled, 1);
RPiGPIOMex(3, greenled, 1);
end
% Switch pins to output mode:
RPiGPIOMex(3, redled, 1);
RPiGPIOMex(3, greenled, 1);

% Remember current LED state for restore at end:
oldRed = RPiGPIOMex(0, redled)
Expand All @@ -130,55 +101,38 @@

% Restore OS control of leds, displaying disc activity
% and state of power supply and system health:
system('echo mmc0 > /sys/class/leds/led0/trigger');
system('echo input > /sys/class/leds/led1/trigger');
system('sudo -S /bin/bash -c ''echo mmc0 > /sys/class/leds/led0/trigger''');
system('sudo -S /bin/bash -c ''echo input > /sys/class/leds/led1/trigger''');

% Test for efficiently waiting for reception of a trigger
% signal on an input pin.
if geteuid == 0
% Switch pin 17 to input:
RPiGPIOMex(3, 17, 0);

% Pull it low:
RPiGPIOMex(4, 17, -1);
statelow = RPiGPIOMex(0, 17)

% Pull it high to simulate an external rising edge trigger:
RPiGPIOMex(4, 17, +1);
statehi = RPiGPIOMex(0, 17)

% Pull it low again:
RPiGPIOMex(4, 17, -1);
statelow = RPiGPIOMex(0, 17)

% Wait for a rising edge trigger for up to 10 seconds:
% First wait will usually complete immediately due to
% some recorded low->high transition during port setup.
trigger1 = RPiGPIOMex(5, 17, 10000)
% 2nd and 3rd waits will usually actually wait for a
% real trigger, or simply time out after 10 seconds:
trigger2 = RPiGPIOMex(5, 17, 10000)
trigger3 = RPiGPIOMex(5, 17, 10000)

% Should be high after trigger:
posthi = RPiGPIOMex(0, 17)

% Tristate it:
RPiGPIOMex(4, 17, 0);
statetri = RPiGPIOMex(0, 17)
else
% Wait for a rising edge trigger for up to 10 seconds:
% First wait will usually complete immediately due to
% some recorded low->high transition during port setup.
trigger1 = RPiGPIOMex(5, 17, 10000)

% Wait for a rising edge trigger for up to 10 seconds:
% 2nd wait will usually actually wait for a real trigger,
% or simply time out after 10 seconds:
trigger2 = RPiGPIOMex(5, 17, 10000)

% Should be high after trigger:
posthi = RPiGPIOMex(0, 17)
end

% Switch pin 17 to input:
RPiGPIOMex(3, 17, 0);

% Pull it low:
RPiGPIOMex(4, 17, -1);
statelow = RPiGPIOMex(0, 17)

% Pull it high to simulate an external rising edge trigger:
RPiGPIOMex(4, 17, +1);
statehi = RPiGPIOMex(0, 17)

% Pull it low again:
RPiGPIOMex(4, 17, -1);
statelow = RPiGPIOMex(0, 17)

% Wait for a rising edge trigger for up to 10 seconds,
% three times in a row:
trigger1 = RPiGPIOMex(5, 17, 10000)
trigger2 = RPiGPIOMex(5, 17, 10000)
trigger3 = RPiGPIOMex(5, 17, 10000)

% Should be high after trigger:
posthi = RPiGPIOMex(0, 17)

% Tristate it:
RPiGPIOMex(4, 17, 0);
statetri = RPiGPIOMex(0, 17)

return;

0 comments on commit 086294d

Please sign in to comment.