Tags

, , , , , , , , , , , , , ,

This post describes how to use outputs other than the UART from a SPLear™ sound level measurement module connected to a Raspberry Pi. Previous posts have relied on the serial data channel. But it would be handy to also take an interrupt when things got loud.

This is part of our series of articles on the general subject of audio signal processing from air to information.

This post will describe several approaches to using the GPIO pins on a Raspberry Pi from Lua, with a specific goal of acting on digital signals from a SPLear module without using the UART.

Much of the firmware and code running in the RPi has been discussed and published in past posts on this blog, and the source code is available from a public repository. The firmware and Lua programs used here is checked in as [123e544590].

Acting when scared

Since we are using the UART to carry the audio samples to be recorded, we can’t use it (at least with the current firmware) to carry the SPL information as well. But the SPLear has a feature we haven’t used yet: it wiggles an output pin when a condition is met. The RPi can then use one of its available GPIO pins to hear that, and take an action.

But that brings us to the next question: How do we actually listen to that pin?

The Raspberry Pi has a dedicated connector intended to be wired to external modules. This is referred to as “the GPIO Connector (P1)”, and is a 26-pin connector on the original generation of RPi (with minor variation in the detailed wiring), and a 40-pin connector in the latest generation (RPi A+, B+ and now the 2B). The 40-pin connector is a strict superset of the 26-pin, and the Pi Foundation has publicly promised not to change its pinout in the future.

The signals on this connector include the UART we use now, along with the 5V power we are using to power the SPLear. They also include two other serial buses (I2C and SPI) and a collection of general purpose pins (collectively known as GPIOs) that can be used individually, and in many cases can also trigger interrupts.

Choosing a GPIO pin

From a pure functionality standpoint, there really is not a lot of reason to prefer one GPIO over another, at least among the collection that reach the P1 connector.

For this example, I picked the signal that appears on P1-26 because it is available on all versions of the RPi, is not already in use by anthing I need to use today, and does support interrupts. I did not put much thought into that choice, figuring that availability trumped most considerations.

Revisiting that choice as I write this, I see that P1-26 has an association with the SPI bus, as SPI0_CE1_N, which might imply that if you are actually using SPI to interact with more than one peripheral, that you would prefer a different pin for the SPLear. In that case, moving to pin P1-22 is probably a decent alternative.

One source of confusion is the name that must be used to identify each GPIO pin. We’ll follow tradition and name the pins after where they surface on a connector, for instance P1-26. That pin is wired to the signal GPIO07 on the physical chip. For sysfs, what matters is the signal name, not the pin name. So pin P1-26 is signal GPIO07, and will be accessed through files in the folder /sys/class/gpio/gpio7/. I have found the tables at eLinux.org to be particularly useful for translating between physical pin locations and the internal signal names. For convenience, I reproduce their table for the RPi A+, B+, and 2B boards here:

RPi A+,B+ GPIO: J8 40-pin header
--------------------------------
        +3V3 1  2   +5V
GPIO2   SDA1 3  4   +5V
GPIO3   SCL1 5  6   GND
GPIO4   GCLK 7  8   TXD0  GPIO14
         GND 9  10  RXD0  GPIO15
GPIO17  GEN0 11 12  GEN1  GPIO18
GPIO27  GEN2 13 14  GND
GPIO22  GEN3 15 16  GEN4  GPIO23
        +3V3 17 18  GEN5  GPIO24
GPIO10  MOSI 19 20  GND
GPIO9   MISO 21 22  GEN6  GPIO25
GPIO11  SCLK 23 24  CE0_N GPIO8
         GND 25 26  CE1_N GPIO7
EEPROM ID_SD 27 28  ID_SC EEPROM
GPIO5        29 30  GND
GPIO6        31 32        GPIO12
GPIO13       33 34  GND
GPIO19       35 36        GPIO16
GPIO26       37 38        GPIO20
         GND 39 40        GPIO21
--------------------------------

The 26 pin connector from the earlier revision 1 and 2 RPi models A and B is just the first 26 pins of this table, with a few signal name differences in revision 1 boards. See the linked documentation for the gory details.

For convenience, here is the list of GPIO pins in signal name order:

signal  P1 pin
------  ------
GPIO02    3 
GPIO03    5 
GPIO04    7 
GPIO05   29
GPIO06   31
GPIO07   26
GPIO08   24
GPIO09   21
GPIO10   19
GPIO11   23
GPIO12   32
GPIO13   33
GPIO14    8 
GPIO15   10
GPIO16   36
GPIO17   11
GPIO18   12
GPIO19   35
GPIO20   38
GPIO21   40
GPIO22   15
GPIO23   16
GPIO24   18
GPIO25   22
GPIO26   37
GPIO27   13

Accessing GPIOs in Linux

As is typical for Linux in general, and the RPi platform specifically, there is more than one way to do it.

The natural approach to any hardware access problem in Linux is to write a device driver. The device driver is the abstraction that translates actions (made through system calls) by user-mode programs into changes in concrete hardware. Device drivers are technically complex, and because they are loaded into the operating system itself, that complexity leads to errors that lead to unstable systems. So this is also a risky approach, and would seem to be a lot of work to support a single-bit peripheral.

Modern Linux kernels include Loadable Kernel Modules, which make driver development and testing easier (modules can usually be loaded and unloaded without needing to reboot the whole system) but don’t do much to protect the whole system from bugs in the driver code.

So the trend has been to reserve kernel modules for those functions which cannot be done outside of the kernel (in general known as “userland”), and implement the majority of the logic in userland. GPIOs have such a standard shim, which presents a directory full of files in the sysfs file system (typically mounted on /sys), in the directory /sys/class/gpio.

An alternative approach is to use the kernel’s virtual memory management capabilities to make a view into the GPIO device registers available in the userland process. For a complex device such as a USB bus or a disk drive, this would be nearly impossible to do well. But for a simple device that just needs to read and write the levels and configuration of a few pins, it can actually work quite well. So well, that the Python users on the RPi have a rich and powerful module (imaginatvely) named RPi.GPIO that is implemented this way, using the mmap(2) system call.

Both of these approaches work on the RPi, and bear some discussion.

Using SYSFS from Lua

Many Linux distros targeting hardware platforms include a sysfs module that provides an abstract layer over the native hardware GPIO pins, in the form of files that can be read and written. Raspbian is no exception.

Because its API is accessed through ordinary file input and output, it can be used from any programming language on the RPi, including shell scripting. Since Lua has no problem with opening any file that can be named by user mode code, it works from Lua too.

Most of the articles I found online make the claim that the directory /sys/class/gpio is only accessible to the user root. That may have been the case for early releases of Raspbian, but as of this writing this has been relaxed. Now, while the files and folders of the GPIO class are indeed still owned by root, they are in the group gpio, and generally allow full control to any user in that group. As a complete coincidence, the default user account named pi is also a member of group gpio.

This means that a normal user of the RPi can have full access to GPIO pins merely by configuring their user account to be in the group gpio.

For the simplest access where you merely want to test an input pin’s current value or set an output pin to a specific state, you simply do something like the following:

$ echo 7 > /sys/class/gpio/export
$ echo in > /sys/class/gpio/gpio7/direction
$ echo 8 > /sys/class/gpio/export
$ echo out > /sys/class/gpio/gpio8/direction
$ cat /sys/class/gpio/gpio7/value
0
$ echo 1 > /sys/class/gpio/gpio8/value
$ echo 0 > /sys/class/gpio/gpio8/value
$

It is also possible to configure some (all?) GPIO pins on the RPi to trigger interrupts. The sysfs module will catch the interrupt, and reflect it to userland by setting an event on an open file descriptor. To use this from Lua, we need a module that provides access to some system calls that are normally not provided by the Lua runtime. The best choice is the luaposix module, which is easiest to install with the luarocks tool. If luarocks is already installed on your RPi, you don’t need to install it again, you just need to make sure luaposix is up to date:

$ sudo apt-get install luarocks
$ sudo luarocks install luaposix

Installing luaposix will take a few minutes as it is a fairly large and complicated module.

With luaposix installed, we have access to low level file descriptors and the ability to make system calls, especially to call poll(2) which will be needed to catch the event that sysfs can set on interrupt. The poll(2) system call is provided by posix.poll.

A simple script that manages pin P1-26 and prints a high resolution time stamp any time the SPLear alerts looks like this:

local P = require "posix"

-- write str to the named file
function putfile(name,str)
  local f = assert(io.open(name,"w"))
  f:write(str)
  f:close()
end

-- transform a list into a set for quick lookup
function SET(t)
  local s = {}
  for _,v in ipairs(t) do
    s[v] = true
  end
  return s
end

gpio={
  pins2_0 = SET{ -- A+, B+, 2A, 2B
             2,  3,  4,  5,  6,  7,
     8,  9, 10, 11, 12, 13, 14, 15,
    16, 17, 18, 19, 20, 21, 22, 23,
    24, 25, 26, 27},
}
gpio.pins = gpio.pins2_0

-- export a pin from sysfs to userland
function gpio.export(pin)
  assert(gpio.pins[pin], "Can't use pin")
  putfile("/sys/class/gpio/export",tostring(pin))
end

-- return a sysfs pin to the kernel
function gpio.unexport(pin)
  assert(gpio.pins[pin], "Can't use pin")
  putfile("/sys/class/gpio/unexport",tostring(pin))
end

-- userland path to a specific pin's atribute file
function gpio.pinpath(pin,attr)
  return ("/sys/class/gpio/gpio%d/%s"):format(pin,attr)
end

-- set pin direction
function gpio.direction(pin,dir)
  local path = gpio.pinpath(pin, "direction")
  putfile(path, dir)
end

-- set pin edge detector
function gpio.edge(pin,edge)
  local path = gpio.pinpath(pin, "edge")
  putfile(path, edge)
end

-- blocking wait with timeout for an edge on a pin
function gpio.wait(pin)
  assert(gpio.pins[pin], "Can't use pin")
  local path = gpio.pinpath(pin, "value")
  if not gpio.pinfd then
    gpio.pinfd = P.open(path, P.O_RDONLY)
  end
  local p = gpio.pinfd
  if not gpio.fds then gpio.fds = {} end
  if not gpio.fds[p] then 
    gpio.fds[p] = {events={}, revents={}} 
  end
  gpio.fds[p].events.PRI=true
  local ret = P.poll(gpio.fds, 10000)
  if ret == 0 then return 0 end
  P.lseek(p,0,P.SEEK_SET)
  ret = tonumber(P.read(p,8))
  --P.close(p)
  --gpio.pinfd = nil
  return ret
end


-- Use pin P1-28 aka GPIO7 for SPLear alerts
gpio.export(7)
gpio.direction(7,"in")
gpio.edge(7,"rising")

while(true) do
  local r = gpio.wait(7)
  if r == 0 then
    -- timeout
  elseif r==1 then
    local tim = P.gettimeofday()
    print("eek "..tim.sec..'.'..tim.usec)
  else
    print("???", r)
  end
end

if gpio.pinfd then P.close(gpio.pinfd) end

When run, this commands sysfs to export pin GPIO7 to userland, then sets it up as an input and watches it for rising edges. On each rising edge, poll() will detect a PRI event and read the pin’s value and return it.

The main loop waits for edges, and prints a message containing a time stamp on each one. Here’s a run where I scared the SPLear a couple of times:

pi@treepi ~/pdmstunts/Scared $ lua pinpoll.lua
eek 1433550800.131744
eek 1433550802.654124
eek 1433550804.497714
eek 1433550804.982800
eek 1433550805.467922
^Clua: pinpoll.lua:95: interrupted!
stack traceback:
        [C]: in function 'poll'
        pinpoll.lua:95: in function 'wait'
        pinpoll.lua:111: in main chunk
        [C]: ?
pi@treepi ~/pdmstunts/Scared $

Obviously, the big shortcoming here is the need for Ctrl+C to exit the program. Watching stdin for a key could be done with poll, but I didn’t bother. Consider that an exercise…

Using RPi.GPIO from Lua

In the Python language environment that the Pi foundation encourages users to consider to be the default language of the RPi, there is a library that allows access to GPIO pins from Python without using the filesystem. But I don’t want to use Python for a task that I’ve already got working neatly in Lua, so what I’d love to have is a Lua port of the Python GPIO library.

And as luck would have it, I’m not alone, and someone has already done the work. Following his instructions, I installed the LuaRocks package manager, and used it to install rpi-gpio, copas, darksidesync, and bit32. I plan to use the first package, the others could come in handy for more advanced uses:

$ sudo apt-get install luarocks
$ sudo luarocks install rpi-gpio
$ sudo luarocks install copas
$ sudo luarocks install darksidesync
$ sudo luarocks install bit32

The rpi-gpio lua module is compiled against the guts of the Python module, so it uses the same low level interface to the hardware. In this case, this is done by mapping the device registers into the program’s address space, then manipulating them to configure and read pins. This is a somewhat dangerous thing to do, I am not sure what other device registers might have come along in this mapping.

A side effect of its using of mmap(2) internally is that any program using this module must be run as root, and hence bugs in that program could have far-reaching consequences to your system. I didn’t make any such bugs in this demo (nope, never, seriously, use this with care!) but you have been warned!

Another defect is that the current release of the module has not been updated for the RPi B+ (or 2B, for that matter). So it won’t allow access to any pins beyond the first 26 pins of P1.

On the other hand, this module does allow you to use the pin numbers on the connector to identify the signal. That makes it slightly easier when trying to figure out what to wire up.

local GPIO=require"GPIO"
-- Use the board's pin numbers from P1
GPIO.setmode(GPIO.BOARD)

-- Make pin P1-26 be an input
GPIO.setup(26,GPIO.IN)

-- Poll it for changes and print the new value
local b = false
while(true) do
  --GPIO.wait_for_edge(26, GPIO.BOTH)
  local b0 = GPIO.input(26)
  if b0 ~= b then
    print(b0)
  end
  b = b0
end

There is a lot of room to refine this, and its apparent simplicity is quite attractive.

There are also a number of issues.

First, it runs as root. That is not good, since that would expose the health of the entire system to the ravages of a bug. I always believe that bugs should scream loudly, but it is only wise to limit the amount of damage they can cause.

Second, a number of the documented functions behave oddly, or not at all. Most obviously, I attempted to use GPIO.gpio_function() to find out what pins it thought were mapped initially, and that flat out fails on my RPi B+. I suspect this is an issue caused by the code not knowing about anything newer than the model B rev 2, but I haven’t attempted to dig into the source code to find out.

Third, since it accesses the registers directly, nothing else in the kernel or sysfs is aware of what the module is doing. That shouldn’t impact my intended use, but it is still not the right thing to do, and could cause problems if I accidentally changed the function of a pin that the RPi platform needs for some important purpose. As a case in point, when I was first playing with this module, I accidentally set the UART TxD and RxD to “input”, and until I rebooted the UART no longer worked to talk to the SPLear in either direction.

So unless someone gets motivated to address some of these issues, I think I will stick to the sysfs mechanism going forwards.

What Now?

Our next exploit is to take a useful action when triggered by the SPLear’s alert.

The next post will demonstrate a program that can capture either audio or SPL surrounding the trigger, using the alert pin as the trigger.

Beyond that, we plan to continue to find interesting things to do with the measured SPL level, which will certainly be the subject of future posts. Watch this space!


Please let us know what you think about the SPLear!

If you have a project involving embedded systems, micro-controllers, electronics design, audio, video, or more we can help. Check out our main site and call or email us with your needs. No project is too big or too small!

+1 626 303-1602
Cheshire Engineering Corp.
120 W Olive Ave
Monrovia, CA 91016


(Written with StackEdit.)