Report
3 months, 2 weeks 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

...

posted by Denci Denci 22 May 2019
Comment on this question

1 Answer

3 months, 2 weeks 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

Yes the ; counts, I'd missed that. If you used a binary output then that plotting program wouldn't work. It would be possible to write a MATLAB program that connected to the serial port directly and decoded the binary data. Or you would need a new program to convert the binary serial data to text numbers.

Binary data is harder to work with but it is a lot more efficient.

That new code is effectively identical to what you had before. Only for some reason it takes the value, adds it to itself 32 times and then divides the result by 32 before sending if out.

I don't know the sensor your using and right now I'm on my phone so googling data sheets is a little painful but most devices have an ID register you can read to check that communications are working.

Is the accelerometer mounted on the trap? Does it change orientation when the trap triggers? If so looking for a change in the direction of gravity may be the best solution.

posted by Andy A 21 Mar 2019

Alright seems like I understand it. Then it also means that I can only run rates of up to 1645 with 7 bytes or 1440 with 8 bytes? Hmm, yeah I figured it wouldn't work with the plotting program. I tried outputting from the serial port directly to MATLAB when I started but it didn't work particularly well. I think it sent data too slow to MATLAB because the output didn't react so well. I tried the example program they had for the accelerometer here when interfacing to MATLAB. What I also found strange though was when I wanted to use that same program for the microphone. It wouldn't work at all and I didn't understand why. I changed the code from three inputs to one input but something else seemed to be the problem and I was unable to find the issue then I found that plotting program and decided to use it instead. It was a lot easier to use as well. Yeah I could try to work with binary data but it seems pretty tricky and I'm not sure I have enough time to write completely new code and play around with it so I'll have to work with what I have at the moment and if I have some time left later then I can try working on the binary output.

Oh alright, I also don't know why they are adding it 32 times and then dividing it with the same number. Could you just explain to me what exactly is done with the data from the part when the spi.write is done? I assume they store the low byte and the high byte at recu(0) and recu(1) respectively because the number is represented by 12 bits? But then after that I'm not sure what all the bit shifting is for?

The microphone (SPA2410LR5H-B) doesn't seem to have any registers you can control yourself but you can check in case I missed. Link to data sheets: https://reference.digilentinc.com/_media/reference/pmod/pmodmic3/pmodmic3_rm.pdf https://reference.digilentinc.com/_media/pmod:pmod:mic3:spa2410lr5h-b-276175.pdf

The accelerometer is directly connected to the development board and the board is mounted inside a metal box. It changes orientation mostly in the Y and Z axis. X axis is basically unchanged but it's also a bit hard to see because the signals are on top of each other.

posted by Denci Denci 22 Mar 2019

Anyway I wanted to give you a quick update regarding the results. I tried using a different computer and I tried a baud rate of 115200 and the max available sample rate for that baud rate which is approximately 1900 Hz. The signals aren't exactly the same but they seem a lot more consistent and stable. Below is a picture when the rat trap snapped at two occasions. I then put them together in paint so don't be confused by the values in X axis. Anyway the red signal represents the microphone and the other three signals represent the X, Y and Z axis of the accelerometer. The signal usually looks like the first one but sometimes it varies like the second one. One other thing I'd like to mention is that both the signal of the microphone and the signals of the accelerometer clip when they reach a height in their amplitude of about 4000. I don't know why they have the same limitation but do you think it is fine even if they clip? Like I should still be able to do an analysis on it? Because the waveform still looks good enough.

/media/uploads/Denci/rat_trap_snap_signal.jpg

One last thing. Since I'm going to save all data on a SD card I also would like to save the time or timestamp for when the signal happened so I can use it in the analysis later or the development of the algorithm. So what is the best way to do this? Can I simply use a timer and keep it going all the time and just read from it accurately after I get the value of a sensor? So if we take the old code as an example. Can I create a timer t, start it and then do a t.read() after the line latestValue = myMIC3.GetIntegerValue(); ?

posted by Denci Denci 22 Mar 2019

<<code>> cs = 0; set the chip select for the microphone. wait_us(20); for (i=0;i<2;i=i+1) { SPI you read and write at the same some so in order to read a byte you need to write something. recu[i] = spi.write(0); read one byte } cs = 1; clear chipselect 12 bit data the first byte contains the most significant 4 bits

X = recu[1]; set the lease significant 8 bits of 8. X |= (recu[0] << 8); shift the first byte left by 8 and or with the low byte, this sets bits 15-8.

for(int i=0; i<32; i++) { somme = somme + X; Add x to somme 32 times } somme >>= 5; divide somme by 32 Display in serial plotter pc.printf("$%u;\n", (uint16_t)somme); <</code>> This could be simplified to: <<code>> initial setup

spi.format(16,3); set SPI to 16 bit mode so we only need to do 1 16 bit access rather than 2 8 bit ones. spi.frequency(1000000); 1 MHz, microphone can run at up to 20 MHz if connections are good enough quality

for each reading

cs = 0; set the chip select for the microphone. min wait time is 10 ns, that's one CPU cycle so no need to wait. uint16_t reading = spi.write(0); / cs = 1; clear chipselect pc.printf("$%u;\n",reading ); <</code>>

There shouldn't be an issue if things clip. If you do end up looking at the spectrum then that will add some high frequency noise but that's about it.

How are you planning on timing when the measurements are taken. If you are using a fixed sample rate with a Ticker to trigger each reading then just keep a count of how many times that has been triggered, that will be just as accurate are reading a timer. If you are using a loop with a wait() to time the measurements then use a Timer and read the value each reading. Use read_us() to give you integer time in microseconds, it's a lot faster and more accurate than reading it as a float of time in seconds.

posted by Andy A 22 Mar 2019

I hit the comment length limit :-)

For doing the FFT I used the mbed-dsp library https://os.mbed.com/users/mbed_official/code/mbed-dsp/docs/tip/ The code to do the FFT then came down to something like this:

  const unsigned int fftSamples = 1024; // number of samples to run the FFT on. Must be a power of 2
  const unsigned int sampleRate = 1000; // Hz
  const float samplePeriod = 1.0/sampleRate;
  const float FFTBinSize = ((float)sampleRate)/fftSamples;  // resolution of the FFT

  float sampleBuffer[fftSamples]; // buffer for storing measurements
  float outputBuffer[fftSamples]; // buffer for storing FFT data

// add code here to collect the correct number of samples at the correct rate.

  // clear the results buffer    
   memset(outputBuffer,sizeof(float)*fftSamples,0);

    // do the FFT
    arm_rfft_fast_instance_f32 S;
    arm_rfft_fast_init_f32(&S, fftSamples);
    arm_rfft_fast_f32(&S, sampleBuffer, outputBuffer,0);
    arm_cmplx_mag_f32(outputBuffer,outputBuffer,fftSamples/2);

   pc.printf("Frequency,Signal\r\n");
   for (int i=0;i<fftSamples/2; i++) {
      pc.printf("%f,%f\r\n",FFTBinSize*i,outputBuffer[i]);
   }

My full code was a little more complex, I had rotating buffers so I could run an FFT on one set of samples while collecting the next set, but I don't think I've broken anything while trimming it down to the core FFT calculation.

Rather than outputting the full results like that (which I did for development but it was too slow for final use) what I did was detect the frequency I was looking for. I was looking for a strong signal at a very specific frequency. I measured the background noise level by averaging a few frequencies either side of the one I was looking for and then triggered if the frequency of interest was a certain amount above the calculated background noise level.

posted by Andy A 22 Mar 2019

Hi Andy, I can't believe it's been 2 months since the last post. I've had a lot to do but I'm back with a couple of questions so I figured I could maybe ask it here in this thread. Regarding your last post about the timing. I did it the way you suggested with a ticker etc. I saved the data from the sensors on a SD card and all seems good. What I'm wondering though is how do I get the total time of the recorded measurements? In one file I have about 750000 data points so I multiplied it with the sample period, 1/Fs, where the sampling rate is Fs = 1900. This results in approximately 395 seconds which is about 6.6 minutes. However the actual time it took to record that amount of data was roughly 30 minutes based on what I noted so I don't understand what's wrong here. Am I missing something?

My second question is do you know what the unit is from the sensor output? I tried searching for it but I couldn't really get a clear answer. The microphone outputs data in form 12 bits but what exactly does that represent in terms of units?

posted by Denci Denci 22 May 2019

You have a ticker that you leave running the whole time and use to make a sample? Then the timing should be very good. If you're getting less samples than expected that would imply things aren't keeping up.

What's your structure like? Is it closer to:

volatile int readCount = 0;
void onTicker() {
  readCount++;

}
main() {
...
int lastCount=0;
open file; 
  while (!stopButton) {
    if (readCount != lastCount) {
      lastCount = readCount;
      read value;
      save readCount and value to file
    }
  }
  close file
}

Or more like

volatile int readCount = 0;
void onTicker() {
  readCount++;
  read value;
  open file
  save readCount and value to file
  close file
}
main() {
...
}

The first one keeps the time in the interrupt to a minimum and will make it obvious if you don't keep up, the second one could run slowly without you knowing.

If you aren't keeping up then look at what you can speed up. Opening/closing files is slow, writing binary to a file is faster than writing text, writing in blocks of 512 bytes is far faster than lots of small writes.

And run the SPI speed for the SD card and the sensors as fast as you can.

The microphone output will be voltage. 12 bits means a maximum value is 4096 so that is going to be for the highest supported voltage, probably 3.3V but you'd have to check that. To convert reading to voltage do reading*3.3/4096

posted by Andy A 22 May 2019

Thanks for the quick response. Yes, the ticker is on all the time once I start it. I'd say it looks more like the first one. You can take a look at my code. I posted it in my second post because I exceeded number of characters.

I tried to do some quick 10 minute tests. In one test I used the sample rate 1900 with a SD card and the amount of points it saved resulted in about 2 minutes. Then I tried doing the same test without storing data on the SD card and the data points shown on the serial port plotter resulted in 3.33 minutes. I also did one more test after this where I set the sampling frequency to 200 and also used the SD card. Here the time seemed pretty accurate. The data points resulted in about 9.4 minutes so I'm not sure whether I was a little off with the time or if things didn't keep up all the way. What do you think is the reason for this? Does it have a hard time keeping up with my sampling frequency of 1900? What would be the best solution to fix this and is it easy to fix?

I took a quick peek at the data sheet for the accelerometer and it says "The maximum SPI clock speed is 5 MHz with 100 pF maximum loading". Not sure what they mean with loading. Can I safely set it at 5 MHz and what if the other sensor is for example 4 MHz? Should I run them with their max speeds or with the same speed? What happens if I set a larger speed than max?

I see, so the acclerometer output is also voltage? At full resolution it outputs 13 bits and the max voltage it can supply is 3.6V. To read voltage I take the reading*3.6/8192?

Also, I had some problems when I created a new project and copied my code to it. The output was not right. For example the accelerometer output was all 0 and nothing happened. However, when I tried the code in my old folder where I initally created the project it works just fine. What's happening when I create a new project do I need to do some manual update of something?

posted by Denci Denci 22 May 2019

include the mbed library with this snippet

#include "mbed.h"
#include "FATFileSystem.h"
#include "SDBlockDevice.h"
#include <stdio.h>
#include <errno.h>
/* mbed_retarget.h is included after errno.h so symbols are mapped to
 * consistent values for all toolchains */
#include "platform/mbed_retarget.h"

#include "MIC3.h"
#include "ADXL345.h"

#define _sampleRate 1900 
#define _samplePeriod_us (1000000/_sampleRate)


SDBlockDevice sd(MBED_CONF_SD_SPI_MOSI, 
				 MBED_CONF_SD_SPI_MISO, 
				 MBED_CONF_SD_SPI_CLK, 
				 MBED_CONF_SD_SPI_CS);

FATFileSystem fs("sd", &sd);


void return_error(int ret_val) {
  if (ret_val) 
    printf("Failure. %d\n", ret_val);
  else
    printf("done.\n");
}

void errno_error(void* ret_val) {
  if (ret_val == NULL) {
    printf(" Failure. %d \n", errno);
    while (true) __WFI();
  } else
    printf(" done.\n");
}


// SPI object and CS pin object
MIC3 myMIC3(PE_6, PE_5, PE_2, PD_12); 
ADXL345 accelerometer(PE_6, PE_5, PE_2, PD_15); 

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

// Variables
Ticker sampleTick; // a timer to make the measurements at a fixed rate.
int ticksTriggered = 0; // A variable to keep track of how many times tick has been triggered
int latestValue;  // the last reading from microphone
volatile bool newValue  = false;  // flag to indicate new data.
int readings[3] = {0, 0, 0}; // Accelerometer data for X, Y and Z axis will be stored here 
 
void onSampleTick() {

    newValue = true;

}


int main() {

    pc.baud(115200);
	
    myMIC3.begin();

    pc.printf("Starting ADXL345 test...\n");
    pc.printf("Device ID is: 0x%02x\n", accelerometer.getDevId());

    //Go into standby mode to configure the device.
    accelerometer.setPowerControl(0x00);

    //Full resolution, +/-16g, 4mg/LSB.
    accelerometer.setDataFormatControl(0x0B);
    
    //3.2kHz data rate.
    accelerometer.setDataRate(ADXL345_3200HZ);

    //Measurement mode.
    accelerometer.setPowerControl(0x08);

    int error = 0;
    printf("Welcome to the filesystem example.\n");

    printf("Opening a new file, multipleSensorData.txt.");
    FILE* fd = fopen("/sd/multipleSensorData.txt", "w+");
    errno_error(fd);

    sampleTick.attach_us(&onSampleTick,_samplePeriod_us); // set the ticker running, it will now make measurements at the set rate.


    while(1) {                                 

       if(newValue) { 

          latestValue = myMIC3.GetIntegerValue();
          accelerometer.getOutput(readings);
	  ticksTriggered = ticksTriggered + 1;
          fprintf(fd, "%i %i %i %i %i\n", latestValue, (int16_t)readings[0], (int16_t)readings[1], (int16_t)readings[2], ticksTriggered);
          pc.printf("$%i, %i, %i, %i;\n", latestValue, (int16_t)readings[0], (int16_t)readings[1], (int16_t)readings[2]); //Plottar data genom serial plot plotter
          newValue = false;

      }
		
    }

//Couldn't post everything here. The rest is in the next post

posted by Denci Denci 22 May 2019

include the mbed library with this snippet

    printf("Closing file.");
    fclose(fd);
    printf(" done.\n");

    printf("Re-opening file read-only.");
    fd = fopen("/sd/multipleSensorData.txt", "r");
    errno_error(fd);

    printf("Dumping file to screen.\n");
    char buff[16] = {0};
    while(!feof(fd)) {
       int size = fread(&buff[0], 1, 15, fd);
		fwrite(&buff[0], 1, size, stdout);
    }
    printf("EOF.\n");

    printf("Closing file.");
    fclose(fd);
    printf(" done.\n");

    printf("Opening root directory.");
    DIR* dir = opendir("/sd/");
    errno_error(fd);

    struct dirent* de;
    printf("Printing all filenames:\n");
    while((de = readdir(dir)) != NULL) {
       printf("  %s\n", &(de->d_name)[0]);
    }

    printf("Closeing root directory. ");
    error = closedir(dir);
    return_error(error);
    printf("Filesystem Demo complete.\n");

    while(true) {

   }

}

posted by Denci Denci 22 May 2019

Each sample you output at least 17 characters out a serial port at 115200 baud. That means at around 650 Hz you max out that UART. Instead of setting a flag in the ticket increase a count, look for a change in the count and include the value (or the delta) in your output. That will show if you miss samples or not. Drop the printf and you should gain some speed. If that's not enough then you need to look at buffering data. The time taken for SD card writes can vary and sometimes be quite long. If you can break the link between sampling data and writing it to the card you can often get a speed up. You become limited by the cards average write rate rather than its worst case write time. The average rate can easily be 10 times faster than the worst case for small writes

posted by Andy A 22 May 2019

I tried using a sampling frequency of 200 Hz over a longer period of time and I still seem to lose samples. I recorded for about 40 minutes and the data points resulted in about 36 minutes. I'm not really sure how to do what you suggested. I tried searching for how to implement buffers and I failed to get any output. I just feel very stuck at the moment :/ Btw what exactly does it mean when it doesn't keep up? Am I losing data on specific places or is it evenly distributed? If it is evenly distributed then it would basically mean that I sample at a lower rate than I set. So do you know if it is evenly distributed? I still get all my rat trap activations on the data files. It's just that the total amount of data points don't represent the total time I actually recorded.

posted by Denci Denci 23 May 2019

The easiest way to see the pattern of missed samples is they way I said, in the ticker increase a count and then include that count in the output.

For buffering you do something like this:

#define _bufferSize_ 256
int16_t buffer1[_bufferSize_];
int16_t buffer2[_bufferSize_];
Int16_t *record;
int16_t *save;
Int recordCount;

void onTick(){
*(record + recordCount) = readMicrophone();
recordCount++;
if (recordCount==_bufferSize_) {
  If (save)
   // Overflow, data lost

  save=record;
  If (record==buffer1) 
    Record=buffer2;
  Else 
    Record=buffer1;
  RecordCount=0;
 }
}

Main(){
save=null;
Record=buffer1;
RecordCount=0;
....
While(true) {
  If (save){
    //Write save buffer to disk
    save=null;
  }
 }
}

Sorry about the poor formatting, I'm on my phone.

posted by Andy A 23 May 2019

Alright I increased the count, looked for a change and included it in the output like you suggested in the beginning. I tested with a sampling frequency of 1900 like before and now it gives me the correct data point time wise but it misses 1 to 3 data points between samples. I'm not sure if this increases with time or if it varies between 1 to 3 data points. According to the last value I should have had 1158641 data points saved but the number of points saved were 274467. Why is it that I can see this now and not before with the flag? To me it looks like the same thing.

I also did a test with a sampling frequency of 200 and it doesn't miss as many points but it still misses. According to the last value I should have had 121701 points. Instead I had 114709. I looked through the points and it seems to vary when it misses data points and how many. From what I could see at one instance the same readCount was saved twice.

I really want to fix it so I don't miss any data points between samples. I tried implementing the buffering code you gave me but I don't quite understand how it works and how to apply it to my code. Where is onTick() used? How do I also add the values from the accelerometer? How do you add the content of the buffers in the end to the SD card? Could you please go over it for me and explain what exactly is happening in onTick()? and how to apply it to my code I posted in the last couple of comments?

I really appreciate your patience with me and all the help you've given me so far.

posted by Denci Denci 26 May 2019

The difference is that by increasing a count you can tell if multiple ticks happened while you were reading the data and saving it to a file. With just a true/false there is no way of telling if you've had one or 50 ticks between samples.

The buffering works by changing the file writes into bulk actions rather than something you do every sample.

There are two buffers, one to record samples in and one to write to the file. Each tick you take a measurement and write into the next space in the record buffer. When that buffer is full you transfer the data to the save buffer and reset the record buffer. Before doing this you can check if the save buffer is empty or not, of it isn't then you know things aren't keeping up. Either you need a bigger buffer or things simple aren't fast enough. For slow sd cards when I was pushing things I've ended up with a buffer large enough for 250ms of data.

The main loop looks for data in the save buffer. When it sees something it writes the entire buffer to disk in one go. The most efficient way would be to use fwrite and copy the whole buffer as a binary block of data but it's nicer to run through each point and do a printf on it. If you have enough spare memory then use sprintf to create the text for several points in a separate buffer in memory and then fwrite that rather than using fprintf to write one line at a time. Remember the aim is to minimise the number of file writes. Once the save buffer has been written to disk the main loop flags it as empty.

posted by Andy A 26 May 2019

Now that's the basic idea but you'll notice the code does something a tiny bit different.

That's because the idea of "transfer the data [from the record buffer] to the save buffer" implies a memory copy. That's easy enough to do but bulk memory copies take enough time that you want to avoid them when possible.

Instead what we do is we create two generic buffers, buffer1 and buffer2. And then we create two pointers, save and record. Record points to the record buffer, save points to the save buffer. This way rather than copy the data we change which buffer the pointers point to, a far faster operation. So the save pointer becomes what the record pointer was and the record pointer becomes whichever buffer it wasn't pointing to before.

This makes use of the c trick that the name of an array is just a pointer to the start of the array, =buffer1 is the equivalent of =&buffer[1].

I use a null value of the save pointer to indicate that the save buffer is empty and also that any non-zero value is considered true when checking if there is anything to save.

OnTick() is the code called each sample tick. The issue here is that we we can't interrupt file writes from within the main loop, so reading the sensors has to be done outside the main loop in the interrupt.

posted by Andy A 26 May 2019

Thanks for the thorough explanation. I am still left wondering about a couple of things though. How do you tell the program at which position in the buffer next value will be stored? Is it the following line that does that?

  • (record + recordCount) = readMicrophone();

I'm kind of new to pointers and haven't seen it written in that way so I was left wondering what it actually does. Is it supposed to do this? For example:

buffer1[recordCount] = record or does record simply point to buffer1 so it is more like buffer1[recordCount] = readMicrophone()?

The second thing I'm left wondering is about sending an entire buffer to the SD card. How do I make the data binary and then send it via the fwrite? Once it is saved on the SD card how will I know what each number actually represents? How do I convert it back once saved on the SD card?

Also, how can I include the acclerometer data with the microphone data? I'd like each position to contain values from both sensors so it looks like: micValue X-axis value Y-axis value Z-axis value

posted by Denci Denci 26 May 2019

*(a pointer) means the thing pointed to by the pointer. And a pointer plus an integer means the address that many locations past the pointer. So the line is saying at the memory location recordCount past the record pointer = readMicrophone (). So your second guess is correct, assuming record points to buffer1 it's buffer1[recordCount]=readMicrophone ();

When you add 1 to a pointer the pointer increases by the size of the data type that it points to rather than 1 byte.

Using pointers like this is a little weird at first but can be very powerful one you get used to it.

fwrite takes a pointer to the location the data is (the save pointer) and a size in bytes which will be sizeof(int16_t)*_bufferSize_. You will end up with the data written in order as raw bytes in the file. Make sure you open the file with "wb" for write binary rather than just "w" as the access method.

To save multiple values per sample define a structure e.g. {{{ Typedef struct { Int16_t microphone; Int16_t xAccel; Int16_t yAccel; } DataPoint_t;

You then make your buffers of type datapoint_t rather than int16_t and each sample will have each data type.

posted by Andy A 26 May 2019

Alright so my code looks like this now:

include the mbed library with this snippet

#include "mbed.h"
#include "FATFileSystem.h"
#include "SDBlockDevice.h"
#include <stdio.h>
#include <errno.h>
/* mbed_retarget.h is included after errno.h so symbols are mapped to
 * consistent values for all toolchains */
#include "platform/mbed_retarget.h"

#include "MIC3.h"
#include "ADXL345.h"

#define _sampleRate 1900 // once it's all working crank it up to 1000 or so so you can pick up higher frequencies.
#define _samplePeriod_us (1000000/_sampleRate)
#define _bufferSize_ 256


SDBlockDevice sd(MBED_CONF_SD_SPI_MOSI, 
				 MBED_CONF_SD_SPI_MISO, 
				 MBED_CONF_SD_SPI_CLK, 
				 MBED_CONF_SD_SPI_CS);

FATFileSystem fs("sd", &sd);


void return_error(int ret_val) {
  if (ret_val) 
    printf("Failure. %d\n", ret_val);
  else
    printf("done.\n");
}

void errno_error(void* ret_val) {
  if (ret_val == NULL) {
    printf(" Failure. %d \n", errno);
    while (true) __WFI();
  } else
    printf(" done.\n");
}


// SPI object and CS pin object
MIC3 myMIC3(PE_6, PE_5, PE_2, PD_12); //PE_11 is the CS pin on the board but idk if the SD card uses that pin so use PD_12 pin as CS
ADXL345 accelerometer(PE_6, PE_5, PE_2, PD_15); //PD_15 is used as the CS pin for the accelerometer

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

// Variables
Ticker sampleTick; // a timer to make the measurements at a fixed rate.
int latestValue;  // the last reading from microphone
int readings[3] = {0, 0, 0}; // Accelerometer data for X, Y and Z axis will be stored here 

// Define a structure to hold the data from each sample
typedef struct dataPoint_s {
    int16_t am; 
    int16_t ax;
    int16_t ay;
    int16_t az;

} 

dataPoint_t;

dataPoint_t buffer1[_bufferSize_];
dataPoint_t buffer2[_bufferSize_];
dataPoint_t *record;
dataPoint_t *save;
int recordCount; 


void onTick() {
 
    latestValue = myMIC3.GetIntegerValue();
    accelerometer.getOutput(readings);
    
    *(record + recordCount)->am = latestValue;
    *(record + recordCount)->ax = (int16_t)readings[0]; 
    *(record + recordCount)->ay = (int16_t)readings[1];
    *(record + recordCount)->az = (int16_t)readings[2];

    recordCount++;
    if (recordCount==_bufferSize_) {
      if (save)
       // Overflow, data lost
 
      save=record;
      if (record==buffer1) 
        record=buffer2;
      else 
        record=buffer1;
        recordCount=0;
     }
   }

posted by Denci Denci 26 May 2019

include the mbed library with this snippet

int main() {

    save=null;
    record=buffer1;
    recordCount=0;

    pc.baud(115200); // increase the baud rate, the default 9600 will max out just over 100 Hz.
	
    myMIC3.begin();

    pc.printf("Starting ADXL345 test...\n");
    pc.printf("Device ID is: 0x%02x\n", accelerometer.getDevId());

    //Go into standby mode to configure the device.
    accelerometer.setPowerControl(0x00);

    //Full resolution, +/-16g, 4mg/LSB.
    accelerometer.setDataFormatControl(0x0B);
    
    //3.2kHz data rate.
    accelerometer.setDataRate(ADXL345_3200HZ);

    //Measurement mode.
    accelerometer.setPowerControl(0x08);

    int error = 0;
    printf("Welcome to the filesystem example.\n");

    printf("Opening a new file, multipleSensorData.txt.");
    FILE* fd = fopen("/sd/multipleSensorData.txt", "wb+");
    errno_error(fd);

    sampleTick.attach_us(&onTick,_samplePeriod_us); // set the ticker running, it will now make measurements at the set rate.

    while(1) {                                    // ticksTriggered och det funkar bra med en while loop då. Kör tills tick triggas 1000 ggr.


       if (save) {
          //Write save buffer to disk
            fwrite(save, sizeof(int16_t)*_bufferSize_, sizeof(save), fd);
          //Can I do a pc.print here to see the output on the serial port plotter?
            save=null;
       }

		
    }

    printf("Writing decimal numbers to a file (1000/1000) done.\n");

    printf("Closing file.");
    fclose(fd);
    printf(" done.\n");

    printf("Re-opening file read-only.");
    fd = fopen("/sd/multipleSensorData.txt", "r");
    errno_error(fd);

    printf("Dumping file to screen.\n");
    char buff[16] = {0};
    while(!feof(fd)) {
       int size = fread(&buff[0], 1, 15, fd);
		fwrite(&buff[0], 1, size, stdout);
    }
    printf("EOF.\n");

    printf("Closing file.");
    fclose(fd);
    printf(" done.\n");

    printf("Opening root directory.");
    DIR* dir = opendir("/sd/");
    errno_error(fd);

    struct dirent* de;
    printf("Printing all filenames:\n");
    while((de = readdir(dir)) != NULL) {
       printf("  %s\n", &(de->d_name)[0]);
    }

    printf("Closeing root directory. ");
    error = closedir(dir);
    return_error(error);
    printf("Filesystem Demo complete.\n");

    while(true) {

   }

}
posted by Denci Denci 26 May 2019

However when I compile it I get some errors which I'm not sure how to fix. Here is what I get:

https://i.imgur.com/KRjDHdW.jpg

How do I fix these errors?

Btw, I also added a comment in my code just after I want to write to a file. Was wondering if I could use a pc.print there to get the output on the serial port plotter as well?

posted by Denci Denci 26 May 2019

Either *(record + recordCount).am = or (record + recordCount)->am =

I'll get back to you on the other questions when I have more time...

posted by Andy A 26 May 2019

Looking at your code I can see three other issues:

In onTick(), either remove the if(save) or add a line where the comment about overflow is. Should have made that clearer in my code.

And the fwrite syntax is wrong, again partly my bad, i forgot the syntax. If should be (buffer,size of each item, number of items, file) so it's fwrite(save,sizeof(datapoint_t),_bufferSize_,fd);

Finally you never exit the while loop so you're never going to close the file cleanly. You probably either want to write a certain number of points or exit when a button is pressed.

In terms of outputting to the serial port, yes you can do that there but keep in mind that serial ports are slow so make sure you don't try sending more data than your baud rate can cope with. And if you are outputting more than 16 bytes to the port then your code will lock up until the data is almost all sent unless you use a buffered serial library like MODSERIAL. Generally I wouldn't recommend outputting more than one data value per buffer, and keep that to under 16 bytes. Normally in this sort of situation i'll stick to just a single character output each loop to indicate that things are still running.

posted by Andy A 26 May 2019

I managed to remove the compiling errors but when I uploaded the code to my board it didn't save anything on the SD card. I added a pc.printf to see if anything gets out but it didn't seem to start running at all. A led is also blinking red.

The changes I made were:

1. Using (record + recordCount)->am =

2. Removing the if(save) in onTick()

3. Fixed the fwrite statement

4. Changed null to NULL since it gave me the error that "null was not declared in this scope"

What could be wrong for it not to work?

posted by Denci Denci 27 May 2019

To post an answer, please log in.