Report
1 week ago.

Microphone behaviour strange or normal? Help! How to go about it?

Hi everyone! I just created an account here because I really need some help. I'm using a microphone pmodmic3 with a development board called Ublox C030-U201 which uses the mbed OS. The microphone is working for me but the output it outputs doesn't seem to be accurate. I am doing a project where the goal is to detect when a rat trap goes off. So I was about to gather some data where I manually activate a mechanical rat trap however the output I get from the serialport graph plotter shows me great variation of the signal and its amplitude when the rat trap goes off. I also tried to test with some pre recorded sounds however even those signals aren't identical but vary. Not as much as when I set off the rat trap but it's still not the same signal even though I play the same pre recorded sound over and over again.

So what I'm basically wondering is if this is normal behaviour of a microphone and if not then what could be wrong and how do I fix it so it gives me stable and accurate output?

How am I supposed to detect or find a specific sound (like in this case a rat trap going off) when the output that the microphone gives me varies more or less each time?

I used some example code however I rewrote it so I could use it with my development board. Working with development boards and coding them is also completely new to me so I appreciate any help with the code if something is wrong or missing there.

The code on their website from an example project called "Library and example code": https://reference.digilentinc.com/reference/pmod/pmodmic3/start

The code after rewriting it for my board:

Main.cpp

#include "MIC3.h"
#include "mbed.h"


// SPI object and CS pin object
MIC3 myMIC3(PE_6, PE_5, PE_2, PE_11);

// Serial object
Serial pc(USBTX, USBRX);

// Variables
int intValue;
float phyValue;


int main() {

  myMIC3.begin();

  while(1) {

           // Receive an integer value reading of the PmodMIC3
           intValue = myMIC3.GetIntegerValue();

           // Receive an physical, decimal value reading of the PmodMIC3
           phyValue = myMIC3.GetPhysicalValue();

       // Print out these readings
       pc.printf("$%i;\n", intValue); 

       // Wait a bit
       wait_ms(10);
 }

}

MIC3.cpp

/* ------------------------------------------------------------ */
/*              Include File Definitions                        */
/* ------------------------------------------------------------ */
#include "MIC3.h"
#include "mbed.h"


/* ------------------------------------------------------------ */
/*              Procedure Definitions                           */
/* ------------------------------------------------------------ */


/* ------------------------------------------------------------ */
/*        MIC3::MIC3
**
**        Synopsis:
**              
**        Parameters:
**
**
**
**        Return Values:
**                void 
**
**        Errors:
**
**
**        Description:
**          Class constructor. Performs variables initialization tasks
**
**
*/
MIC3::MIC3(PinName mosi, 
                 PinName miso, 
                 PinName sck, 
                 PinName cs) : spi_(mosi, miso, sck), nCS_(cs) {


}

/* ------------------------------------------------------------ */
/*        MIC3::GetIntegerValue
**
**        Synopsis:
**              wIntegerValue = GetIntegerValue();
**        Parameters:
**
**
**        Return Values:
**                uint16_t  - the 12 bits value read from PmodMIC3
**
**        Errors:
**          If module is not initialized (using begin), the function does nothing and returns 0
**
**        Description:
**          This function returns the 12 bits value read from the PmodMIC3, obtained by reading 16 bits through the SPI interface. 
**
**
*/
uint16_t MIC3::GetIntegerValue()
{
    uint16_t wResult = 0;
    uint8_t *pbResult = (uint8_t *)&wResult;
    //if(pdspi != NULL)
    //{
        // make SS active
        nCS_ = 0;       

        // read from SPI, two separate 8 bits values
        *(pbResult + 1) = spi_.write((uint32_t) 0); // high byte
        *pbResult = spi_.write((uint32_t) 0);   // low byte

        // make SS inactive
        nCS_ = 1;
    //}
    return wResult;
}

/* ------------------------------------------------------------ */
/*        MIC3::GetPhysicalValue
**
**        Synopsis:
**              dPhysicalValue = GetPhysicalValue();
**        Parameters:
**              - float dReference - the value corresponding to the maximum converter value. If this parameter is not provided, it has a default value of 3.3.
**                                  
**
**        Return Values:
**                float - the value corresponding to the value read from the PmodMIC3 and to the reference value
**
**        Errors:
**          If module is not initialized (using begin), the function does nothing and returns 0
**
**        Description:
**          This function returns the value corresponding to the value read from the PmodMIC3 and to the selected reference value.
**          If the function argument is missing, 3.3 value is used as reference value.
**
**
*/
#ifdef MIC3_FLOATING_POINT

float MIC3::GetPhysicalValue(float dReference)
{
    uint16_t wIntegerValue = GetIntegerValue();
    float dValue = (float)wIntegerValue * (dReference /((1<<MIC3_NO_BITS) - 1));
    return dValue;
}

#endif

/* ------------------------------------------------------------ */
/*        MIC3::begin
**
**        Return Values:
**                void 
**
**        Description:
**              This function initializes the specific SPI interface used, setting the SPI frequency to a default value of 1 MHz.
**
**
*/
void MIC3::begin() {

    spi_.frequency(1000000);
    spi_.format(8,3); 

    nCS_ = 1;

    wait_us(500);

}

MIC3.h

/************************************************************************/
/*  File Description:                                                   */
/*  This file declares the MIC3 library functions and the constants */
/*  involved.                                                           */
/*                                                                      */
/************************************************************************/

#ifndef MIC3_H
#define MIC3_H

#define MIC3_FLOATING_POINT

/* ------------------------------------------------------------ */
/*              Include File Definitions                        */
/* ------------------------------------------------------------ */
#include "mbed.h"

/* ------------------------------------------------------------ */
/*                  Definitions                                 */
/* ------------------------------------------------------------ */
#define MIC3_NO_BITS        12

/* ------------------------------------------------------------ */
/*                  Procedure Declarations                      */
/* ------------------------------------------------------------ */


class MIC3 {

private: 

        SPI        spi_;
        DigitalOut nCS_;

public:

/**
         * Constructor.
         *
         * @param mosi mbed pin to use for MOSI line of SPI interface.
         * @param miso mbed pin to use for MISO line of SPI interface.
         * @param sck mbed pin to use for SCK line of SPI interface.
         * @param cs mbed pin to use for not chip select line of SPI interface.
*/

        MIC3(PinName mosi, PinName miso, PinName sck, PinName cs);

    uint16_t GetIntegerValue();

#ifdef MIC3_FLOATING_POINT
    float GetPhysicalValue(float dReference = 3.3);
#endif

    MIC3();
    void begin();
};



#endif

Here are also three pictures showing the output I get on the serialport graph plotter from two different pre recorded sounds I tested with. Picture 1 and 2 shows the output of the same sound. Picture 3 is the output of a different sound and looked much better than the other one.

/media/uploads/Denci/sound_one.png /media/uploads/Denci/sound_one_again.png /media/uploads/Denci/sound_two.png

Comment on this question

1 Answer

6 days, 14 hours ago.

Firstly I'd drop the line phyValue = myMIC3.GetPhysicalValue(); it's not doing anything other than making a measurement and throwing the result away.

If you are only worried about very low frequencies and don't need accurate frequency measurement then your code should work ok. It won't however work for anything above a very low frequency or give an accurate sample rate.

You are making audio measurements every 10ms (plus a little bit), that means you have a sample rate of 100 Hz. So don't expect any audio above 50 Hz to come out as even remotely repeatable, and you're not going to get good quality reproduction of anything above about 20-30 Hz.

Also since you are using a wait your actual measurement period is 10ms plus the time to make the measurement.

Here's what I would recommend:

#include "MIC3.h"
#include "mbed.h"
 
#define _sampleRate 100 // once it's all working crank it up to 1000 or so so you can pick up higher frequencies.
 #define _samplePeriod_us (1000000/_sampleRate)

// SPI object and CS pin object
MIC3 myMIC3(PE_6, PE_5, PE_2, PE_11);
 
// Serial object
Serial pc(USBTX, USBRX);
 
Ticker sampleTick; // a timer to make the measurements at a fixed rate.
int latestValue;  // the last reading
volatile bool newValue  = false;  // flag to indicate new data.

void onSampleTick() {
  latestValue =    myMIC3.GetIntegerValue();
  newValue = true;
}
 
int main() {
 
 pc.baud(115200); // increase the baud rate, the default 9600 will max out just over 100 Hz.

 myMIC3.begin();
 sampleTick.attach_us(&onSampleTick,_samplePeriod_us); // set the ticker running, it will now make measurements at the set rate.
 
  while(1) {
      if (newValue) { if a measurement has been made
          pc.printf("$%i;\n", latestValue );  // output it.
          newValue=false;
     }
  }
}

Thank you Andy for the detailed response. I forgot to mention in my post that I tried using different sample rates. The microphone itself measures between 100Hz-15kHz so I tried for example with a sample rate of 100Hz, 8000 Hz and 15000 Hz. The pictures I posted above were taken when I used a sample rate of 15000Hz if I recall correctly.

As for the code you wrote I went ahead to try it. The compilation went flawless but nothing happened afterwards. No output at all. I also noticed the user RGB Led started blinking red. Not sure what it means but I'm guessing something isn't right, however when looking at your code I couldn't see any issues there so I have no clue what the problem is. Do you have any ideas?

posted by Denci Denci 16 Mar 2019

It doesn't matter what rate you set using the wait time. The baud rate and printf would have limited it to about 150hz.

As a sanity check that the code is running put something like pc.printf("starting"); before the while loop. You did set the pc baud rate to match the rate set in the software?

posted by Andy A 16 Mar 2019

Hmm alright that's a bit weird because when I changed the sample rate from 100Hz to 15kHz in the wait function there was a big difference in the waveform of the signal.

Yes, I tried printing something like this and I got nothing. However I kind of found the issue but I don't understand why it is a problem. The line latestValue = myMIC3.GetIntegerValue(); doesn't work when put in the onSampleTick() function. I tried putting it as the first line in the if statement inside the while loop and it worked then. Is this alright to do?

I'm still not sure what to make of the output I get. I have to look further away now because of the baud rate. The output just passes by so fast otherwise. I started with a sample rate of 100 and it didn't look particularly good so I gradually increased it up til 15kHz. I can't test it with the rat traps now because I don't have them at home so I tested on the same pre recorded sounds I used in the pictures above and it still doesn't output exactly the same thing. It might be a little bit more stable than before but it's hard to tell. It also depends on what kind of sound I test it on. For example a prolonged sound is reproduces pretty good I'd say but quick snaps or similar sounds seem to be harder to reproduce. What are your thoughts on this? Do you think it can be further improved or will I just have to accept it as it is?

posted by Denci Denci 17 Mar 2019

Ok, so something didn't like doing an spi write inside an interrupt. I've not seen that before but it's easy enough to work around as you found. No issue with what you did, the ticker is still giving you a fixed sample rate, the main loop isn't doing anything so this will add a few ns more jitter to the sample rate but not enough to matter.

15k won't work. 15k samples, 10 bits per byte on serial, 6-7 bytes per output = 6*10*15= 900k bits/second or more. You need to run your UART at 921600 baud to have any chance of getting that much data and even then your pushing it.

If the UART doesn't keep up then your sample rate will drop but not in a completely predictable way.

If you need that sample rate you'll either need to switch to a binary output format to decrease the amount for data or only run in bursts. When's the signal crosses a threshold put the data into a buffer and then when the buffer is full output it at slower than real time.

posted by Andy A 17 Mar 2019

Alright well that's good to hear.

Hmm I see. So I shouldn't even attempt setting the sample rate to more than 1920 Hz according to those calculations? Could you explain why 6-7 bytes per output?

I don't know what sample rate is required. That's why I was gradually increasing it to see if it would give me a better reproduction of the signal. I'm assuming I need a higher sample rate than 1900 Hz since it wasn't enough and I can't increase it for the current baud rate. What would be the maximum baud rate I can run the UART without pushing it? If it's better to switch to what you suggested then I'd appreciate it if you could help me out with the code for this if it isn't too much to ask? I'm still new to c++ and working with this stuff.

One more thing that crossed my mind. Will changing the spi frequency improve anything?

posted by Denci Denci 18 Mar 2019

For each measurement you are outputting $%d/n, $ and /n are a byte each and then %d is a number that could be positive or negative and up to around 2000. Most of the time that number is going to be 3 digits giving you a total of 5 bytes but when you have higher volumes it will be 6 bytes. If you are getting negative numbers too then add one more for the minus sign. So some of the time you'll have less than 6 bytes but the problem is that the times when you have more data are also the times you'll be most interested in.

On most boards the serial ports will run happily at 921600 (115200 * 8) if you can find something on the PC side that will handle it, some terminal programs max out at 115200.

Outputting in binary is actually really easy. Just replace the printf with pc.putc(latestValue&0xff);pc.putc((latestValue>>8)&0xff);. This sends low byte then high byte, feel free to swap the order depending on how you handle them at the other end.

That will cut your data down to 2 bytes per measurement allowing you to triple the sample rate.

The hard part is at the PC side, you then need to take the constant stream of bytes and split them into pairs (since you have a 12 bit number over two bytes look for a byte with the most significant 4 bits all the same, that will be the top byte) , and then recombine them into sample values.

The UART is buffered so as long as it's keeping up the UART writes don't take a meaningful amount of time. The SPI reads are reading 16 bits at 1 MHz which even if you allow for a 50% overhead is still only 32 us. So I wouldn't expect the SPI speed to be an issue until you get to around 30 kHz rates. Increasing it won't hurt assuming your cabling is up to the job but it does depend on how good the connections are, nothing in your code is checking that the data isn't getting corrupted on that SPI link.

It would really help if you knew what your end goal required you to do.. Can you record the sound you want to detect on a PC or even on a phone and then look at the waveform?

e.g. if it always starts with a large volume spike then your sensor can avoid sending data until it sees one and only then start outputting. Since it doesn't need to send constant data you can buffer samples up and so sample at a higher rate than you output. Call this the Alexa method of operation, the sensor has enough intelligence to detect the start of something of interest, once it does it packages up what comes after that and sends it somewhere else to work out whether it was genuinely important or not.

One other option may be to look at the spectrum. I have managed to run an FFT in real time (well 100ms behind but close to real time) on an LPC1768, for some things looking at the frequencies is a lot more effective than looking at the waveform.

posted by Andy A 18 Mar 2019

Thanks for the explanation I understand it better now but isn't ";" also counted as a byte? The thing is this serial port plotter that I am using, I found here on this website and when I want to output something on it then it requires that I write "$" before and ";" after the specifier. Also the output I get has a reference value at about 4100 so don't I have like 7 bytes or even 8 bytes if ";" is counted as a byte?

The serial port plotter I'm using has the option to run the baud rate up to 921600 but when I tried it, it lagged so much and didn't work properly. I got some random spikes. Even at half of that baud rate it lagged and the output felt a little off even at about 260k baud rate. So I think I should maybe stick with a baud rate of 115200 at most.

Will outputting in binary even work? If I replace the printf with the two lines which output the low byte and high byte then I won't get anything on my serial port plotter because I need to include "$" and ";" and that maybe only works when outputting with printf. I'm also not sure how the "split into pairs and then recombine into sample values" works. I did use a different example code before and it might have done this but I'm not sure because I didn't quite understand it. It was also written for an arduino project so I rewrote it so I could use it for my board and mbed. It looked like this:

include the mbed library with this snippet

#include "mbed.h"
 
// SPI object
SPI spi(PE_6, PE_5, PE_2); // mosi, miso, sclk

// CS pin object
DigitalOut cs(PD_12); // cs

// Serial object
Serial pc(USBTX, USBRX); // Tx, Rx
//Serial pc(USBTX, USBRX, 115200); 

// Variables
int i;
uint16_t recu[3]; // storage of data of the module 
int X;
long somme = 0;


int main() {

    // Initialize with CS pin high because the chip (slave device) must be deselected 
    cs = 1;
 
    // Setup the spi for 8 bit data, mode 3, high steady state clock,
    // second edge capture, with a 1MHz clock rate
    spi.format(8,3); 
    spi.frequency(1000000);
 

    while(1) {

    // Select the device by setting chip select low
    cs = 0;

    wait_us(20); 

    for (i=0;i<2;i=i+1) {
        recu[i] = spi.write(0); // send 2 data to retrieve the value of the two-byte conversion
        //wait_us(20); 
    }
 
    // Deselect the device
    cs = 1;


    X = recu[1]; // X is on 12 bit format
    X |= (recu[0] << 8);
 
    for(int i=0; i<32; i++) { // Development of the sound curve
        somme = somme + X;
    }

    somme >>= 5;

    // Display in serial plotter
    pc.printf("$%u;\n", (uint16_t)somme); 
    //wait_ms(10);
    } 
}

I see, then I won't have to worry about the spi frequency at the moment because I'm not even close to those rates. Is there an easy way to check so that the data isn't corrupted or can I skip this?

posted by Denci Denci 21 Mar 2019

Had to post this in a separate comment because I exceeded the number of chars in the previous post.

The end goal is to simply detect the signal when a rat trap snaps and I need to use this microphone. So I was thinking of gathering some data and saving it on a SD card where I manually activate the mechanical rat trap. At first in a quiet environment with no noise and then outside where it is noisy. The next step after that would be to develop an algorithm that will be able to find the signal corresponding to when the rat trap has snapped and I was thinking of working on the algorithm in MATLAB. But since I noticed that the microphone didn't output that sound or even the pre recorded sounds in the same way then I wanted to try and ask around for help to fix this if it is fixable because otherwise I'm not sure how to develop this algorithm so it can detect a specific sound when the signal is varying so much. I'm also using an accelerometer (ADXL345) simultaneously with the microphone and it also gives me variations in the signal it outputs. For example, one time the Y-axis will react the most and another time Z-axis will react the most but that's just maybe how it works. The signal also varies in amplitude and maybe form but it's harder to check its accuracy because unlike the microphone with the pre recorded sounds I'm not sure what to test the accelerometer with. I'm more focused on fixing the microphone first though.

Yeah I tried googling earlier on how to run a FFT on a data file in MATLAB but when I ran some code I found I got a really weird looking plot which didn't look correct to me. How did you do it?

posted by Denci Denci 21 Mar 2019

To post an answer, please log in.