Tags
audio, email, GPIO, LPC800, LPC812, Lua, microphone, NXP, PDM, Raspberry Pi, RPi, SMTP, SPL, SPLear, sysfs
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.)
Reblogged this on Dinesh Ram Kali..
LikeLike