Weekend Project: MMA8451Q Accelerometer as a Microphone?

Last weekend was the Australia Day long weekend, so aside from chasing Shortwave Radiogram broadcasts and posting radiofax galleries, I also decided to build something I thought was rather interesting.

In the past, I remember vaguely seeing articles about how computer hard drives are so sensitive to disturbances that they could be used to detect earthquakes and that smartphone accelerometers could be used as a rudimentary microphone. What might that actually sound like? I was rather intrigued so I wanted to build my own.

Rationale and Hardware

Ultimately, sound waves are vibrations in air and accelerometers are quite capable of picking up vibrations. Unfortunately, the air displaced isn’t quite enough to make an accelerometer vibrate, so instead, the accelerometer is probably best used as a contact microphone – kind of like an “inverse” bone-conduction speaker.

I decided to use the parts I had lying about which included an Arduino Leonardo for its superior USB-CDC capability and a low-cost AU$4 NXP MMA8451Q 14-bit digital accelerometer to initially prove the concept. A few simple Dupont-style jumper wires completes the build for testing. As the GY-45 module appears to have onboard voltage regulation and level shifting, there was no need to worry about 3.3V/5V compatibility issues.

Unfortunately, the MMA8451Q is capable of just 800Hz sample rate, which would result in very band-limited audio. That’s good enough for a try. A higher sample rate would be nice (e.g. the LIS3DH which has 5376Hz) but this is often traded off with greater noise (220uG/sqrt(Hz) vs 99uG/sqrt(Hz)). A nice middle-ground seems to be the ADXL355 with 20-bit resolution and 4000Hz sample rate, or the ADXL354 (analog) version, but both of these units are very pricey – about $50 for the bare IC alone.

Sketch and Testing

While the MMA8451Q does have various libraries available from third parties, I made a conscious decision to avoid using any libraries at all and instead just rely on writing “straight” Wire-library commands. My reasoning is that many of the libraries are becoming almost needlessly bloated with additional conversions (e.g. floating point calculations) which slow down operation, increase code size and cause additional complications. Many of the libraries are designed for “asynchronous” one-shot sample reading, which makes it extremely difficult for audio – how do you avoid double-reading or skipping a sample?

Instead, a quick look at the datasheet and application notes revealed that the default settings for the MMA8451Q to be the ones we desire – i.e. 800Hz ODR, no filtering, no FIFO (for simplicity). Using I2C fast mode to ensure the bus does not hinder data transfer, it is as simple as the following code:

// MMA8451 Accelerometer Microphone Experiment
// Direct access code with NO ERROR CHECKING (!!!)
// Maximal 800Hz sample rate + 14-bit/axis + 2G full-scale
// 3-channels of RAW data returned over Serial
// Does not enforce output byte alignment - import as raw
// data with 16-bit/LE/3ch/800Hz + 0/1 byte offset!
//
// January 2019 - goughlui.com

#include <Wire.h>
#define MMA_ADDR 0x1C

uint8_t axdat[6];

void setup(void) {
  Wire.begin();
  Wire.setClock(400000);      // I2C Fast Mode (400kHz)
  writeRegister8(0x2B,0x40);  // Device Soft RST 
  delay(50);                  // Wait for Reset
  writeRegister8(0x2A,0x00);  // Device Standby
  writeRegister8(0x2D,0x01);  // Enable DRDY Interrupt
  writeRegister8(0x2A,readRegister8(0x2A)|0x01); // Enable Device
  Serial.begin(921600);       // High baud rate to prevent overflow
                              // Although for CDC-based Arduinos
                              // it probably has no effect.
  while(!Serial) {}           // Leo wait for Serial Connect
}

// Operating in non-FIFO mode only works as Arduino loops faster 
// than accelerometer can generate data inclusive of bus read
// overheads which have been reduced through use of burst-read.
void loop() {
  if(readRegister8(0x0C)&0x01) { // If DRDY asserted
    readAllAccel(axdat);      // Burst read all accelerometer data
    for(int i=0;i<6;i++) {
      Serial.write(axdat[i]); // Write raw array data to Serial
    }
  }
}

void writeRegister8(uint8_t reg, uint8_t value) {
  Wire.beginTransmission(MMA_ADDR);
  Wire.write((uint8_t)reg);
  Wire.write((uint8_t)(value));
  Wire.endTransmission();
}

uint8_t readRegister8(uint8_t reg) {
  Wire.beginTransmission(MMA_ADDR);
  Wire.write(reg);
  Wire.endTransmission(false);
  Wire.requestFrom(MMA_ADDR, 1);
  return (Wire.read());
}

// Extra function that reads all of the current accelerometer 
// values in burst-read to save bus transaction time.
void readAllAccel(uint8_t *axdat) {
  Wire.beginTransmission(MMA_ADDR);
  Wire.write(0x01);
  Wire.endTransmission(false);
  Wire.requestFrom(MMA_ADDR, 6);
  for(int i=0;i<6;i++) {
    *(axdat+i)=Wire.read();
  }
}

The code makes no error checks – it assumes an accelerometer is there and it assumes it is working. It resets everything to default and enables the data ready interrupt to be alerted whenever there is a new sample. In the main code, it checks if the interrupt register has a flag set indicating new data – if so, grab the six bytes corresponding to the data and output them out on the USB-CDC serial port.

Note that the code itself is very crude – but that’s part of the reason why it works. Just blindly polling the interrupt flag and copying out data wouldn’t work if the sensor was converting data faster than it was being polled! That’s where FIFO buffering and burst-read operations to minimise bus overhead come into play. I’ve already made advantage of the burst read and auto-increment feature when reading out the X, Y and Z two-byte values – hence Wire.requestFrom(MMA_ADDR,6) rather than reading one byte from each address which takes much longer.

But of course, you shouldn’t just take my word for it – it would be good to know that we are safely polling faster than the sensor is generating data. As a result, I hooked up the Rohde & Schwarz RTM3004 (that won me RoadTest Review of the Year at element14) to do some bus decoding and give me a chance to test the new V1.400 firmware at the same time.

First step is just to capture a series of transactions for analysis and enable/configure the bus decoding for the situation – SCL on C1 and SDA on C2 with a 1.5V threshold.

Examining the bus table makes it clear what is happening – we are polling faster than the data is being generated – it doesn’t fit on the screen here but we poll about seven times before we get a sample.

Notice the series of reads returning 0x00 (i.e. no interrupt), before we get one that returns 0x01 (i.e. DRDY interrupt), which triggers our attempt to read 6-bytes from 0x01 returning the accelerometer data for X, Y and Z (two bytes each).

Given the fact we are polling so much more frequently than the accelerometer generates data, we aren’t at any risk of losing data. But we can see that there is a “pause” right after the data is returned, as the microcontroller needs to push the data out via USB-CDC emulated serial – that code does consume some processing time. If the bus rate were to be slower and an inefficient library is used, there is a potential that data could be lost resulting in a “variable” sample rate.

In Use

As I wasn’t really happy with the length of the jumper cables – they made it hard to attach it to anything (e.g. my throat, my table, etc), I built a second unit around an Arduino Mini Pro, another MMA8451Q, a scrap of old USB lead (four conductor is great for I2C) and some heatshrink (as someone suggested in the past).

With some “green” sticky tack, I’m able to stick this onto things to record the vibrations (e.g. windows). The power and data go over the microUSB-B connection exposed on the end, still as USB-CDC serial data.

The easiest way to use it (I found) was to first open the serial port at the correct baud rate (e.g. via the Arduino serial monitor) and then close it. I then used Cygwin under Windows to dump raw to file using cat /dev/ttyS? > output.dat, substituting the correct number for the port (e.g. COM4 is /dev/ttyS3 as Linux convention starts from 0).

Then, the raw data could be imported as a three-channel set of data into the free audio editor Audacity. For it to be imported correctly, the correct format has to be specified – Signed 16-bit PCM is appropriate (as it is padded 14-bit values) in little-endian mode, three channels at 800Hz to match the sketch.

As the sketch code doesn’t enforce which channel and which byte (MSB/LSB) comes out first upon opening the serial port, you may get “noise”. In which case, set the offset to 1 byte to correct the MSB/LSB alignment. Unfortunately, that still doesn’t clearly identify the X, Y and Z channels, but a modification to the code (i.e. continuously check if Serial.available() and emit starting from the X channel) would be able to do this. Whether this would cost too much time from the microcontroller is not known – I’m running on the 3.3V 8Mhz version, so being efficient is even more important.

Once imported correctly, a recognisable waveform should be the result. Notice how, unlike most audio sources, there is a DC-offset to the signals representing Earth’s gravity. To best extract the audio, I would remove the less sensitive channels (keeping the one with the strongest signal), delete any erroneous spikes, correct the DC offset (to avoid damage to speakers/headphones) and normalise the volume to the full scale.

Results and Discussion

What do the recordings from an accelerometer sound like? The following spectrograms from Spek and .wav files based on the recorded data. Note that the files are single-channel only and are 800Hz rate, so some applications may not be happy to play them!

We start off examining the quiet no-signal state. (Alternative 48kHz Upsamped MP4)

While the signal looks somewhat white, it seems like there are some resonances resulting in spectral components at around 240Hz and also a little below. Playing it back with its band limited nature, it reminds me of being in a swimming pool and listening to the water.

The next test has me reading some test sentences out – can you understand me? (Alternative 48kHz Upsamped MP4)

The answer is, probably only one or two words here and there. Losing practically all of the fricatives makes intelligibility difficult – I wonder if a computerised neural network can still identify words based on patterns. I suspect it might be …

For the curious, here is what I said:

Hello.
Hello.
This is a test.
This is a test.
My name is Gough.
This is a test of an accelerometer as a microphone.
Specifically, the MMA8451 from NXP sampling at 800Hz.
The accelerometer is connected to an Arduino Mini Pro via I2C in Fast Mode.
Samples are sent raw over the USB to serial interface.
This has been Gough of goughlui.com demoing an accelerometer as a microphone.

Finally, I decided to place it on my 2.1 subwoofer and play some music. What is this song? (Alternative 48kHz Upsamped MP4)

Well, chances are, you probably don’t know it but it is (G)-IDLE – LATATA. The accelerometer does a good job recording the bass-line and some resonances in the table the subwoofer is sitting on. However, playing it back, Shazam certainly doesn’t recognize it!

Conclusion

Through this little weekend project, I’ve discovered that the MMA8451Q can work as a band-limited contact microphone of sorts. Perhaps this can have applications where a “sealed” band-limited monitoring of vibration spectrum needs to be done as accelerometers are fairly low power and are happy to be sealed up. For audio, the limited sample rate equates to a limited spectral response that isn’t enough for intelligible speech. The noise characteristics were surprisingly like listening to something underwater, with the audio like listening to a lecture from outside the lecture hall.

But more than that, I found that for some applications, ditching the libraries and working from the datasheets to be more painless than anticipated, resulting in a much more efficient and compact code and the ability to better exploit the peripheral’s abilities. While libraries can be convenient, there are some things you lose to the convenience (among them, code efficiency/size, reliance on other people’s code which might be buggy/unmaintained and perhaps library dependency nightmares due to different versions and poor documentation on changes to APIs).

Encouraged by this, I do have an ST LIS3DH on the way – it’s a noisier unit and can only do 8-bit resolution at 5376Hz sample rate, but I wonder whether it’s any good at being a microphone. But compared to the (normally) 50-200Hz that most mobile phones offer which results in an even more band-limited signal, I don’t think we need to be too worried about eavesdropping of voice from the accelerometer, after all, due to the mass of the smartphone, it won’t be vibrating much due to just voice in the air.

About lui_gough

I'm a bit of a nut for electronics, computing, photography, radio, satellite and other technical hobbies. Click for more about me!
This entry was posted in Audio, Computing, Electronics and tagged , , , , , . Bookmark the permalink.

Error: Comment is Missing!