Posts
Home | By Category | By Title | By Date | About | Contact

Accessing the Rasperry Pi GPIO Concurrently With Multiple Programs

2016/06/12

I learned something interesting about the Raspberry Pi the other day. The device's GPIO pins can't be locked for exclusive access, so they can be shared out amongst multiple tasks simultaneously. That means you could have several programs accessing different pins. Or several programs accessing the same pins, for that matter.

This was a surprise. From what I understood of the way GPIO in the Raspberry Pi was implemented, it appeared likely that a running task could gain have exclusive access to the GPIO pins. That's not the case however. You get a warning message if two programs try to access the same pins, but no error occurs.

This could be dangerous in some circumstances, but it makes the Raspberry Pi more useful since the device can easily run multiple programs simultaneously. Rather than trying to accomplish several different tasks with one program, you can create a separate program to address each task.

To illustrate this, let's assume that you've been given the task of programming a Raspberry Pi to function as a controller to flash lights on and off. These are the requirements:

  • it's not known in advance how many lights will need to be controlled.

  • it must be possible to choose which lights are flashing, or not flashing.

  • the flash rate must be settable for each individual light.

  • it must be possible to stop and start each light flashing.

  • the lights must be controllable from the command line.

You could do this with a single program but you'd soon find things getting very complicated, particularly since you have to make this work from the command line, which means you can't use a menu or a graphical user interface.

To meet these requirements I'm going to make use of the RasPi's ability to have multiple programs accessing its GPIO pins, and the Python "signal" module.

The signal module allows a Python program to receive and act upon signals sent to it from the operating system. Signals are a bit beyond the scope of what I want to get into in this document, but if you want to learn more about them, this Wikipedia article ought to get you started.

Here's the program I came up with:

#! /usr/bin/python # background_flasher.py # controls an arbitray number of LEDs as a background task # Bruce Grant # June 2016 # External module imports import RPi.GPIO as GPIO import signal import sys import time validPins = [17,27,22] flashInt = 0 flashEnable = 1 def chkGPIORange(gpioPin): return (gpioPin in validPins) def signal_term_handler(signal, frame): # Clean up and exit nicely GPIO.output(ledPin, GPIO.LOW) GPIO.cleanup() sys.exit(0) def signal_trap_handler(signal, frame): # Enable or disable flashing global flashEnable flashEnable = not(flashEnable) # Main routine ------------------------------------------- try: ledPin = int(sys.argv[1]) flashInt = float(sys.argv[2]) except: print print "Useage: mt1.py {led pin} {flashInt}" print "Valid GPIO pins:" print validPins sys.exit(1) if (not(chkGPIORange(ledPin))): print "Invalid GPIO pin. Valid pins are:" print validPins sys.exit(1) # Variables: ledState = GPIO.HIGH saveTime = time.clock() # Pin Setup: GPIO.setmode(GPIO.BCM) # Broadcom pin-numbering scheme GPIO.setup(ledPin, GPIO.OUT) # LED pin set as output GPIO.output(ledPin, GPIO.LOW) # Do this if a 'Terminate' signal is received: signal.signal(signal.SIGTERM, signal_term_handler) # Do this if a 'Trap' signal is received: signal.signal(signal.SIGTRAP, signal_trap_handler) while 1: if time.clock() > saveTime + flashInt: ledState = not(ledState) GPIO.output(ledPin, ledState & flashEnable) saveTime = time.clock()

Right, let's pick this to pieces and see how it works, then, shall we?


# External module imports import RPi.GPIO as GPIO import signal import sys import time

In addition to the usual RPi.GPIO module, which you always need if you're going to be using the GPIO, we need these as well:

  • signal allows us to respond to signals from the operating system

  • sys is needed so the program can terminate in an orderly manner, and

  • time will enable us to control the speed at which the lights are flashed


Next, we initialise some global variables that we'll need to access in several places in the program:

validPins = [17,27,22]

This is a list of the GPIO pins the program is allowed to use. We're going to give the user the ability to specify the pin to use, but we want to limit them to pins that are actually connected to lights.

The numbers in this list depend on how many pins are connected, and which pins are being used.


flashInt = 0

The flash interval in seconds. This can be a fraction. Because we initialise it as zero, the program won't have a default flash rate: that will come from the command line.


flashEnable = 1

This variable will control whether or not the light is flashing or off. Setting it to one will make the light flash. Setting it to zero will make it stay off.

You'll see how it gets set shortly.


def chkGPIORange(gpioPin): return (gpioPin in validPins)

Here's a function to determine whether the GPIO pin requested is one of the ones the program is allowed to use. Not much to this: the expression (gpioPin in validPins) will evaluate to zero if the gpioPin parameter isn't in the list of valid pins represented by validPins.


def signal_term_handler(signal, frame): # Clean up and exit nicely GPIO.output(ledPin, GPIO.LOW) GPIO.cleanup() sys.exit(0)

This function is a signal handler. It will be called whenever a particular signal is received from the operating system by the program. Which signal causes this will be determined later.

When this executes, the light is switched off. The GPIO resources are released so the pin the program was using becomes available to other tasks. And finally, the program terminates gracefully.


def signal_trap_handler(signal, frame): # Enable or disable flashing global flashEnable flashEnable = not(flashEnable)

Here's another signal handler. This one reverses the state of the flashEnable variable we met a few lines back. If flashEnable=0 then flashEnable=1, and vice-versa.


try: ledPin = int(sys.argv[1]) flashInt = float(sys.argv[2]) except: print print "Useage: background_flasher.py {led pin} {flashInt}" print "Valid GPIO pins:" print validPins sys.exit(1)

Okay, now we're into the main part of the code. This bit determines whether the program was invoked correctly from the command line. We do this by trying to extract two numbers from the command line parameters. If either of the parameters was missing or invalid (i.e., not a number), this will generate an error (or an exception). If that happens, we display a message showing the correct way to enter the command, and exit the program. We exit gracefully, but we do so with a exit status of one, to indicate the program exited with an error.

We don't care which command line parameter was missing or invalid. There's only two of them. They should be able to figure it out.

As a courtesy to the user, when we display the error message, we also tell them which pins they are allowed to use. This is very simply done by printing the validPins list variable on the screen.


if (not(chkGPIORange(ledPin))): print "Invalid GPIO pin. Valid pins are:" print validPins sys.exit(1)

If we made it this far, we know they've entered a valid number for the GPIO pin but we don't know if it's one of the pins we're allowing them to use. So we call the chkGPIORange function defined earlier to determine that. The function will return a value of zero if an invalid pin was specified, in which case we inform them of the situation, indicate which are the valid pins, and exit with an error status.


ledState = GPIO.HIGH saveTime = time.clock()

Here we initialise a couple of important variables. ledState keeps track of whether the light being controlled is supposed to be on or off. saveTime keeps track of the last time the light changed states, either on or off.


GPIO.setmode(GPIO.BCM) # Broadcom pin-numbering scheme GPIO.setup(ledPin, GPIO.OUT) # LED pin set as output GPIO.output(ledPin, GPIO.LOW)

Pretty standard setup things, here. We're telling the system which scheme we want to use to identify GPIO pins and configuring the one we'll be using, as an output. Then we set that pin "low" to ensure the main loop starts with the light off. This probably isn't necessary but I like to have things predictable.


# Do this if a 'Terminate' signal is received: signal.signal(signal.SIGTERM, signal_term_handler) # Do this if a 'Trap' signal is received: signal.signal(signal.SIGTRAP, signal_trap_handler)

These two lines specify what is to happen when signals of a particular type are received. SIGTERM is the "terminate" signal that is supposed to kill a running program. When one comes in, we're going to call the signal_term_handler function we looked at earlier, which will turn off the lights and halt the program gracefully.

The second line specifies what to do when a SIGTRAP "trap" signal is received. In this case, we'll call signal_trap_handler which, you will recall, reverses the value of the flashEnable variable.


while 1: if (time.clock() > (saveTime + flashInt)): ledState = not(ledState) GPIO.output(ledPin, ledState & flashEnable) saveTime = time.clock()

And last but not least, here's the loop where the work is done. This works as follows:

  • The flash interval from the command line is added to the last time the light the light was turned on or off.

  • The sum of these is the next time the light is supposed to be switched on or off. This compared with the current time. If the current time is less than this value, then it isn't time to switch the light yet and we do nothing.

  • If the current time is greater than this value, it means it's time to switch the light on or off. We invert the value of the ledState variable, which may be either HIGH (=1) or LOW (=0).

  • Now we output to the GPIO pin. Outputting just the ledState variable would be sufficient to switch the light on or off, but we go a little further than that. We output the logical AND (as denoted by the "&" operator) value of ledState, which may be zero or one, and flashEnable, which also may be zero or one. The value of ledState & flashEnable will only equal one if both of those variables are also equal to one.

    In other words, the light can only be switched on if the value of flashEnable is one.


The program is started like this from the Linux command line:

$ ./background_flasher.py 17 3 & [1] 4548

This is telling the program to start the light connected to GPIO pin 17 flashing on a three-second interval. The "&" character at the end of the command line instructs it to go and do its thing in the background.

See the "[1] 4548" that Linux responded with? The "4548" is the Process ID (PID) of the program just started.

If you enter this:

$ kill -SIGTRAP 4548

...a "Trap" signal will be sent to the program, which will cause it to disable the light. Executing the same command a second time will turn it back on again.

Entering this:

$ kill 4548

...will send a terminate signal to the program, which will cause it to shut down.

Earlier I alluded to multiple programs accessing the GPIO pins. Entering this series of commands:

$ ./background_flasher.py 17 .33 & $ ./background_flasher.py 27 1.25 & $ ./background_flasher.py 22 .5 &

...will start all three lights flashing at intervals of 1/3rd, 1 1/4 and 1/2 of a second. It can make for an interesting light show :-)

 

Creative Commons License
Bruce Grant's Web Page is licensed under a Creative Commons Attribution 4.0 International License.
Based on a work at http://www.wbga.ca..