Tuesday, June 25, 2013

Raspberry Pi Ultrasonic Sensor. Use GPIO Without Sudo

Search the web, and you’ll find quite a few pages discussing the HC-SR04 ultrasonic distance sensor. It is cheap, easy to configure and great for experimenting or non crucial measuring jobs. When used on the Raspberry Pi, however, almost all examples I have seen force you to run the sensor application as superuser (sudo). This means that the entire application has elevated privileges. In general, this is not a good idea.

Search as I might, I was not able to find an example of a non sudo application that used an interrupt where timing was crucial. So I decided to do some research to find a way around this.

Background: I am trying to add distance measurement capability to my Pi, which already does temperature measurements and tracks the sump pump level using a second Pi. All this is done without using ‘sudo’, and since the new functionality is to be integrated into the existing functionality I had to find a solution. I am using Python 2.7 as my main development language.

I am using wiringPi in the existing setup, but it does not handle interrupts. So a Google search is next.

First, I found an article in the November 2012 edition of MagPi by Richard Ryniker entitled ‘GPIO and Interrupts’, which gave a good overview of sudo, interrupts and the like. You can read a pure text version of the article here, without having to look up the magazine version.

The first thing to take away from that is that if you want to use GPIO pins without using sudo you need to download and compile gpio_control.c from Richard’s site. All the instructions on how to compile and then copy and chmod the executable are embedded in the text of the download.

Once you have done that, then you can give a command like this

$ gpio_control export 23

which simply means any user can now use pin 23 without using sudo

To revert control once you do not need this anymore:

$ gpio_control unexport 23

Next, I studied his application for the maximum LED blink rate in Python. This explains how to write a value to a pin. After that, I studied his interrupt example, showing how to detect when a designated pin goes high (or low), i.e how to read the value of a pin.

I then modified the ultrasonic sensor ‘sudo’ application that Matt Hawkins wrote over at Raspberry Pi Spy, to account for the new sudo free approach using interrupts. When running the sudo version and this version, the results are virtually identical, any differences are probably due to the CPU load at that very instant.

Here is the application:

   1: #!/usr/bin/python
   3: # Test interrupts.
   5: import time,select,sys
   7: def write_once(path, value):
   8:     f = open(path, 'w')
   9:     f.write(value)
  10:     f.close()
  11:     return
  13: pin_base23 = '/sys/class/gpio/gpio23/'   #TRIGGER
  14: write_once(pin_base23 + 'direction', 'out\n')
  15: f23 = open(pin_base23 + 'value', 'w')
  17: pin_base24 = '/sys/class/gpio/gpio24/'  #ECHO
  18: f24 = open(pin_base24 + 'value', 'r')
  19: write_once(pin_base24 + 'direction', 'in')
  20: write_once(pin_base24 + 'edge', 'both') #'rising', 'falling' are other keywords
  22: print "Ultrasonic Measurement"
  23: state_last = f24.read(1)
  24: #sys.stdout.write('Initial pin value = {}\n'.format(repr(state_last)))
  26: po = select.poll()
  27: po.register(f24, select.POLLPRI)
  28: f23.write('0')
  29: f23.flush()
  31: # Allow module to settle
  32: time.sleep(0.5)
  34: # Send 10us pulse to trigger
  36: f23.write('1')
  37: f23.flush()
  38: time.sleep(0.00001)
  40: f23.write('0')
  41: f23.flush()
  42: events = po.poll(600)
  43: t1 = time.time()
  44: f24.seek(0)
  45: state_last = f24.read(1)
  46: #print state_last
  47: events = po.poll(600)
  48: f24.seek(0)
  49: state_last = f24.read(1)
  50: #print state_last
  51: t2 = time.time()
  52: if len(events) == 0:
  53:     sys.stdout.write('  timeout  delta = {:8.4f} seconds\n'.format(t2 - t1))
  54: else:
  55:     #sys.stdout.write('value = {}  delta ={:8.4f}\n'.format(state_last, t2 - t1))
  56:     TimeElapsed = t2 - t1
  57:     nDistance = TimeElapsed * 34300/2
  58:     print ("Distance: ={:8.1f}".format(nDistance)) + " cm"


In theory, using interrupts is cleaner and leaner than the old way where the application sits in a loop, uses a lot of CPU cycles to check continuously whether or not the state of the pin has changed.

No comments: