Tags
audio, LPC800, LPC812, LPC81x, microphone, muLaw, NXP, PDM, Raspberry Pi, RPi, SPL, SPLear, u-Law
This post describes some tweaks to SPLear™ sound level measurement module firmware to make it more useful when connected to a Raspberry Pi. This builds on observations from the earlier posts about noticing loud noises, recording some audio, and emailing it.
This is part of our series of articles on the general subject of audio signal processing from air to information.
When startled, the program described in the previous post running in the Raspberry Pi will capture about 20 seconds of audio with the trigger about 25% from the beginning, encode it to mp3, and email it. There’s room for improvement in the RPi program, but new firmware features are needed.
The firmware running in the SPLear and the 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 program used here is checked in as [957d720ccc].
Changing the “scared” threshold
The SLPear firmware has been written so that the simple threshold used to detect loud enough noises is defined in a compile-time constant near the top of the file main.c
. To mimic the threshold used in the SPL monitor, we need to change that threshold.
Line 33 had read:
#define EEK_DELTA 6
Change it to read:
#define EEK_DELTA 16 // equivalent to 12 dB SPL
Or even larger if desired. You can easily tune this value with the RPi software running in “SPL” mode. Simply multiply the threshold in delta dB by 4/3 to get the threshold in internal counts and set EEK_DELTA
accordingly. For consistency with the recent experiences with SPL mode, I’ve updated the firmware checked in to use 12 dB or 16 counts.
Setting Threshold at Run Time
We can make the threshold even more useful by allowing it to be adjusted while the SPLear is running rather than compiling it in. We’ll leave the default threshold set to the more useful 12 dB SPL delta, but add a new mode command that sets it.
The q
command will wait for a few seconds for a second character, and adjust the threshold accordingly. Once adjusted (or not, if the second character doesn’t arrive or is not valid) it will display the configured threshold.
The second character corresponds to the raw SPL delta as follows:
command delta threshold
q0 0 counts, 0 dB
q1 1 count, 0.75 dB
...
q9 9 counts, 6.75 dB
qa 10 counts, 7.5 dB
...
qz 35 counts, 26.25 dB
qA 36 counts, 27 dB
...
qZ 61 counts, 45.75 dB
As of the current firmware, the default setting is 12 dB, which is 16 counts or qg
.
This is implemented by using the configuration macro EEK_DELTA
to initialize a new global variable:
uint8_t eek_delta = EEK_DELTA;
which is used in place of the macro in the main loop when testing the change in SPL. To adjust it, we extend the function modecheck()
with the following case to handle the q
:
case 'q': n = 0; while ((c = ugetchar()) < 0) { if (++n > 100000) { c = 0; break; } } if (c>='0' && c<='9') eek_delta = c - '0'; else if (c>='a' && c<='z') eek_delta = c - 'a' + 10; else if (c>='A' && c<='Z') eek_delta = c - 'A' + 36; printf("Eek delta %d '%c'rn", eek_delta, delta2char(eek_delta)); break;
Here, the function delta2char()
simply reverses the translation to produce a character from one of three ranges of values. We limit the amount of time we wait for a character so that an accidental q
character won’t hang the firmware.
Better audio recordings with SPL and samples
The hardware flag could be ignored or augmented by additional algorithms if both the computed SPL and the raw audio were available to the RPi. Doing this requires a new operating mode, which will deliver the audio in packets that have an SPL measurement along with the raw (well, strictly speaking uLaw transformed) audio that produced it. The RPi code will then collect packets, using the SPL and audio for different purposes as required.
This will be P
mode, for “Packet”.
The first packet will begin immediately after the P
command is heard. The packet will consist of a six byte header followed by a variable number of bytes of audio.
struct { uint8_t headmagic; // 'A' uint8_t SPL; // scaled log2 SPL of previous packet uint8_t N_hi; // count of audio bytes uint8_t N_lo; // ... uint8_t flags; // TBD uint8_t crc; // CRC of five previous bytes uint8_t audio[0]; // N bytes of audio data };
To provide confirmation that the packet header is a valid header, it will contain a CRC covering just the header. I chose an 8-bit CRC because it isn’t that much more complex than a simple sum, and it does just as good a job of separating 8 bytes of noise from a valid seven byte header plus CRC, and does a better job of catching a small number of single bit errors. An 8-bit wide CRC is plenty to cover the 40 bits of the header. With a little more fussiness in the implementation and header structure, a shorter CRC would likely be sufficient. The CRC polynomial I chose is known to be strong for short messages (detects all errors up to 4 bits in messages shorter than 119 bits). If I wanted to CRC the audio, I’d switch to one that better covers longer messages.
To make packet recording and plain recording cooperate, both now deliver samples through a new function named deliver()
. It consumes a single sample, and if recording is permitted it passes it through to the UART glue module which adds it to the ring buffer that is emptied by the UART at interrupt level.
For packetized recording, I added the deliverPkt()
function, called once per SPL computation window. It is passed the freshly computed SPL, and creates and queues the six byte packet header to the UART. Its final action is to enable individual sample recording. This has the net effect that when the P
command is heard, it sets the mode but defers output to the UART until the current SPL window is complete. That way, the first character emitted after P
is heard will be the first byte of a packet header. That packet contains a valid SPL value, but the audio samples it was based on were not preserved.
<br />void deliver(uint8_t s) { if (record_ok) putBufUARTn(&s, 1); } void deliverPkt(int spl) { struct { uint8_t headmagic; // 'A' uint8_t SPL; // scaled log2 SPL of previous packet uint8_t N_hi; // count of audio bytes uint8_t N_lo; // ... uint8_t flags; // TBD uint8_t crc; // CRC of five previous bytes } hdr; hdr.headmagic = 'A'; hdr.SPL = spl <= 0 ? 0 : spl >=255 ? 255 : spl; hdr.N_hi = WINDOWSIZE >> 8; hdr.N_lo = WINDOWSIZE & 0xff; hdr.flags = 0; hdr.crc = crc8(0, &hdr, 5); putBufUARTn(&hdr.headmagic, 6); record_ok = 1; }
The client program could use this as follows:
- If desired, say
qg
as desired to adjust the threshold that triggers theP13
pulse. - Read any responses from the
q
command, throughCRLF
, or just wait for the UART go quiet. - Say
P
to begin packet recording. - Use a blocking read to capture the 6 bytes header.
- Verify its CRC for sanity checking, use its SPL to preset any detector logic or ignore it. Compute the total number of bytes per packet as
Npkt = (N_hi<<8) + N_lo + 6
. - Issue blocking reads for
Npkt
bytes at a time. The last six bytes read will be the packet header structure, with an SPL value that corresponds to the audio data in the buffer. Parse, store, write to files, etc. as appropriate to your application. - When done recording, say
n
and drain any leftover bytes from the serial channel.
Demo Packet
A complete packet, captured from a live mic in a quiet office looks like this dumped in HEX:
00000000 41 1a 03 15 00 3a 7e ff ff ff ff ff ff ff ff ff
00000010 ff ff ff ff fd fe ff fc f9 7d 71 fc ed fe 71 79
...
00000300 7a 79 fa fb fe fd 7b 77 7d fb 7d 7b fd fd ff fd
00000310 fa 7e 77 fc fd fb 7c 7d ff 78 7b 41 19 03 15 00
00000320 56 fe fd fe fc fc fa f7 77 74 fe fe ff 7e 72 f4
...
The first six bytes, 41 1a 03 15 00 3a
are the header of the first packet. And as expected, the next packet header can be found at offset 0x31B, as 41 19 03 15 00 56
.
Connecting the SPLear to RPi
Other posts have documented this more completely. Connect the SPLear to the Raspberry Pi as follows:
RPi GPIO header SPLear J1
--------------- -----------
2 (5V) J1.2 (VCC)
6 (GND) J1.1 (GND)
8 (UART0_TxD) J1.6 (RXD)
10 (UART0_RxD) J1.7 (TXD)
26 (GPIO07) J1.4 (P13)
From software, the UART is known to Linux as /dev/ttyAMA0
. The P13
signal is available through the sysfs
GPIO class, also discussed in another post.
Don't forget to do sudo apt-get update
and possibly sudo apt-get upgrade
if it has been while a since you first set up Raspbian on your RPi.
What Now?
We've created our first firmware parameter with the new q
command. Other operational details could conceivably use parameters.
The scared.lua
script needs to be extended to take advantage of the packet recording mode and threshold adjustment. With this, it could have stored configuration for the threshold, and not only compress and email event audio, but also include a graph of the SPL over a similar time span.
It might be useful to have persistent storage for parameters. The LPC81x does not have a separate memory for this purpose, but one could be added with I2C on P10 and P11, or a page of the internal FLASH memory could be used.
The present firmware operates the PDM microphone at 1 MHz, but that is in an undocumented operational region according to its data sheet. We should experiment with pushing the bit rate over 2 MHz to return to the documented region, or down to the low power region. Either change will involve tweaking of the CIC filters, and likely have some impact (positive or negative) on the quality of the recovered audio.
The next step is to continue to find interesting things to do with the measured SPL level, which will certainly be the subject of future posts.
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 strange to discuss!
+1 626 303-1602
Cheshire Engineering Corp.
120 W Olive Ave
Monrovia, CA 91016
(Written with StackEdit.)