Hey Everyone!
I've been playing with the audio capabilities of the M5Stack Core 2
The microphone on this device is tucked away in the back
and there's a small outlet in the plastic case to let sound in.
The actual microphone is attached to the stack slot cover so I think if you are
stacking the Core 2 on top of other modules you may lose the microphone functionality.
I don't have any other modules to confirm this so if anyone does then please leave a comment.
this also means that any sound needs to make its way through the plastic case to the microphone
which may have an impact on how sensitive the audio pickup will be.
The Core 2 has an SPM1423 MEMS microphone which uses PDM instead of PCM.
This microphone is slightly different from the other I2S microphones we've looked at.
The microphones we've been looking at have all been PCM microphones
where the audio signal is represented as a number on the I2S interface.
With a PDM microphone, the audio signal is represented using Pulse Density Modulation.
This should not be confused with PWM which is Pulse Width Modulation.
The animation I've put together here shows a simulation of a PDM signal for a sin wave.
Now, obviously, in the real world, the PDM signal would be much higher
frequency - around 3MHz - and the modulation algorithm would be a lot more sophisticated.
Fortunately for us, this is all handled by the I2S peripheral.
So we don't need to worry too much about the microphone technology that is being used.
There are however a couple of things to be aware of in the I2S setup.
Here's the code for setting up the I2S config.
The first thing to note is that we have to specify I2S_MODE_PDM.
The second thing to note is the bits per sample.
This needs to be 16bits.
I tried using 32bits and the lower 16bits are always zero.
I believe this is a limitation on the ESP32 side of things but it's not documented anywhere.
The final thing to be aware of is that we must use I2S_NUM0.
This is the only I2S peripheral that supports PDM.
So, let's record some audio and see how it sounds.
I'm using my standard bit of test code and sending audio from the device to my machine.
This first recording will be at 16KHz sampling rate.
"Testing testing one two three"
With the audio captured on my machine, we can open it up in audacity and have a listen.
We need to use the raw data import functionality and tell audacity our sample size and sample rate.
You can see there's a bit of a wobble when the microphone first starts up.
We've seen this on other I2S microphones.
The audio is very quiet - this is a bit disappointing as I was pretty close to the mic.
But we've also seen this on other I2S microphones as well.
We can use Audacity to amplify the signal.
There seems to be quite a lot of noise. Let's have a listen
"Testing testing one two three"
It's actually not too bad.
Let's try the same thing but at a higher sampling rate of 44.1KHz.
This is a simple change in the code.
"Testing testing one two three"
You can see how much faster it needs to send data to keep up.
We'll load this into audacity as well and compare.
Once again we use the raw data import.
We've got the little wiggle at the start and we'll need to amplify the signal.
"Testing testing one two three"
It is pretty noisy.
I'm not sure where this noise is coming from but I think the signal we're getting is usable.
So, I've been having a play and I've built a small audio visualizer.
Let's have a watch and then I'll briefly run through the code
🎶 Music 🎶
"Testing testing one two three"
We'll keep this walkthrough quite short.
All the code is in GitHub so feel free to check it out and try it on your own device.
If you have any suggestions or improvements then please open a pull request - everyone is welcome to contribute.
Looking at the code in our main.cpp we define the number of samples we want to analyze.
512 samples at a sample rate of 16KHz works out at about 30 milliseconds.
We have the config for the PDM microphone and we have the pins for the Core 2.
I've structured my code into three main concerns.
We have the audio processing
This code is responsible for running an FFT against the audio samples.
We have the I2S code
This is responsible for reading samples from the I2S peripheral.
And we have the UI
This contains: the graphic equalizer, the spectrogram, and the waveform UI elements
Finally, we have our application class that coordinates everything.
When our application class starts up it creates a task to handle processing the samples
and passes that task handle to the I2S sampler.
If we look at the I2S sampler we can see that when it fills up one of its buffers
it notifies the processing task to start processing the samples
The processing task tells our audio processor to update its state with the new samples.
And it then tells the UI to update.
Looking at our processor it grabs a copy of the samples from the buffer
and applies a Hamming window before running the FFT.
For the UI's update method, we update each individual UI component and then
we trigger a drawing task to do the actual rendering of each component.
This nicely decouples drawing which can be quite time consuming
from the processing of the audio data.
I won't go into too much detail on the code as it's all in GitHub.
If you'd like me to do a more detailed walkthrough
then please leave a comment and I'll do a follow-up video.
So that's it for this video
I hope you enjoyed it and as always if you did please give it a big thumbs up
and don't forget to subscribe.
I'll see you in the next video
🎶 Music 🎶