Since porting the PDM microphone SPL measurement code base from the ATTiny85 to the ARM based LPC8xx family, it would make sense to verify that the calculations are performing as expected. This post will run through some test cases and the results in an LPC812 CPU.
This is part of a series of articles on the general subject of audio signal processing from air to information.
Previous installments include:
- Introduction to MEMS Microphones
- Using a PDM Microphone
- PDM in a Tiny CPU
- Resolution of audio recovered from a PDM data stream
- PDM in ATTiny85 Source Code
- SPL is Logarithmic
- PDM in another 8-pin CPU
- PDM to SPL Demonstrated in an LPC810
Yet another CPU?
We are playing with a variety of members of the NXP LPC8xx family. The 8 pin DIP packaged LPC810 is amusing in its nostalgic package, but the higher pin counts available for the LPC812 provide 4 times as much memory and fit in a much smaller volume at essentially no difference in price.
The firmware discussed in this post is similar to that seen in the previous post with some mostly cosmetic changes.
The most visible change is that the firmware has been ported to the LPCXpresso framework. This is practical in the LPC812 since there is a very generous 16 KB of flash available for code, compared to the 4 KB in the LPC810. The advantage of this is that the resulting firmware will be easier for others to extend and modify.
The real functional change is that given the framework we have switched the SPI code to work entirely at interrupt level. This means that the microphone clock is not interrupted when the UART prints, and the window immediately after a print is not corrupted. A framework implementation of a FIFO is used to pass finished PCM samples from the interrupt handler to the foreground loop where the SPL calculation, updates to the PWM outputs (an LED and the analog levels get separate PWM outputs), and the occasional print to the UART can happen at their own pace.
This iteration of the source code has not yet put under revision control, but that will happen “soon”. Watch the blog for an announcement.
Test signals
I hand-constructed two PDM bitstream fragments that represent conditions with levels that can be predicted.
I’ve built the test vectors as 128 bit fragments, stored 16 bits at a time. This representation matches well with the SPI
peripheral configured for 16-bit frames. If the test vectors are enabled at compile time, then they replace the value read from the LPC_SPI0->RXDAT
register each time the RXRDY
interrupt is handled. A free-running counter increments on each receive value, and is used to index the array of test vectors. A run-time parameter controls the number of bits by which the counter is shifted before indexing, this allows for easily varying the test signal’s fundamental frequency by whole octaves.
The first signal is a fully clipped 100% full scale square wave. This is easily represented by long sequences of 1 bits alternating with equal length sequences of 0 bits. At low enough frequencies, we can predict that the absolute sample value from our CIC filter will be exactly 8192. Averaged over a 781 sample window, we can predict the expected scaled log2 SPL will be 208.
The second signal is a stepped triangle wave, peaking at 100% full scale. The 8 step levels are equivalent to PCM samples of 0, 4096, 8192, 4096, 0, -4096, -8192, and -4096. It has an ideal mean absolute value of 4096, which predicts the expected scaled log2 SPL will be 192.
The implementation allows the shift to be varied by using the debugger to adjust the value of the TESTSHIFT
variable. The choice of which wave form is used is made by commenting out all the cases except the desired one from the initialization of testsamples[]
.
#ifdef PDMTEST uint16_t testcount = 0; const uint16_t testsamples[8] = { // 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, // 100%FS 0x5555, 0xf5f5, 0xffff, 0x5f5f, 0x5555, 0x5050, 0x0000, 0x0505, // Aztec }; uint8_t TESTSHIFT=3; #define TESTMASK (((sizeof testsamples) / (sizeof testsamples[0]))-1) #endif
In the implementation of SPI0_IRQHandler()
, we must read the received data from the SPI0
peripheral in order to signal that we handled the interrupt. But if the test case is compiled in, we replace the value read with the appropriate value from the test vector.
// ... if (status & SPI_STAT_RXRDY) { uint16_t pdm = LPC_SPI0->RXDAT; #ifdef PDMTEST pdm = testsamples[((++testcount)>>TESTSHIFT) & TESTMASK]; #endif // ...
Results
Running the firmware with both test vectors at a variety of frequencies produces the following results as reported on the serial port.
shift 100%FS Aztec F0, Hz
0 -150 -150 7812.5
1 -150 -150 3906.2
2 184 172 1953.1
3 202 190 976.6
4 206 192 488.3
5 208 194 244.1
6 208 194 122.1
7 208 194 61.0
8 210 194 30.5
9 210 196 15.2
Some observations:
- The first two octaves are exactly at and above the Nyquist point. The CIC filters are producing PCM samples at exactly 7812.5 Hz, so the case with
shift==1
is exactly at the Nyquist point. It isn’t surprising that the measured SPL effectively minus infinity for these rows since there isn’t any energy available below the Nyquist point to measure. - The result of -150 is consistent with the earlier point. SPL is computed from a mean absolute sample value. The code sums the absolute sample value, computes SPL as
2 * (lg2(sum_abs) - WINDOWOFFSET)
wherelg2()
is computing eight times log base 2, clipped at 0. Ifsum_abs
is sufficiently small, thenlg2()
will return 0, and the SPL will be reported as-2 * WINDOWOFFSET
, or exactly 75 in the firmware as tested. -
WINDOWOFFSET
should be exactly8 * lg2(WINDOWSIZE)
, whereWINDOWSIZE
is 781 samples, or 76. The code as tested has it set to 75. -
The 100% FS square wave measures 210, not the expected 208. This is a difference of 2, or exactly twice the error in the value of
WINDOWSIZE
used. -
The stepped triangle measures 192, 194, or 196 depending on the shift applied, but at reasonable frequencies it is 194. This is also exactly 2 counts higher than predicted, which is also exactly twice the error in the value of
WINDOWSIZE
used.
Conclusion
The test signals produce SPL results that exactly match expectations when quirks of the implementation are accounted for.
A meter on the 3.3V scale PWM output moves nicely when the bike horn is honked. The red LED also varies nicely in brightness as the SPL varies. (Blow on the mic and the ember gets brighter.)
For my next trick, I want to use the UART to transfer 8-bit PCM samples (or even better, 8-bit uLAW or aLaw) at our full sample rate to the PC. This will allow us to use the microphone to record telephone quality mono audio and verify that the resulting PCM samples sound “right”.
(Written with StackEdit.)