Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 18 of 18

Thread: Ranging using Teensy 4 + mic and speaker, timing variance issue

  1. #1

    Ranging using Teensy 4 + mic and speaker, timing variance issue

    Hi all,

    The project I am trying out is to do acoustic ranging using a Teensy 4 + speaker + I2S microphone. The issue I need help with is the large variance in the time elapsed between the sound being sent and heard.

    Code:
    /*
      SD -> pin 8 (in1)/pin 5 (in2)
      L/R -> GND/VCC (left/right channel)
      WS -> pin 20 (LRCLK1)/pin 3 (LRCLK2)
      SCK -> pin21 (BCLK1)/pin 4 (BCLK2)
    */
    
    /*
       Sample rate is 44.1khz
       Therefore max frequency is 22.05khz
       For FFT1024 we have each bin = 22.05/512 = 43.06 hz
       For FFT256 we have each bin = 22.05/128 = 172.26 hz
       for 8khz bin 45-49 for FFT256
       For 8khz 184-189 for FFT1024
    */
    
    #include <Audio.h>
    #include <Wire.h>
    #include <SPI.h>
    #include <SD.h>
    #include <SerialFlash.h>
    #include <TimeLib.h>
    #include "TeensyThreads.h"
    
    // GUItool: begin automatically generated code
    AudioSynthWaveformSine   sine1;
    AudioAmplifier           amp1;
    AudioOutputI2S2          i2s2;
    AudioInputI2S            i2s1;           //xy=500,322
    AudioAnalyzeFFT1024      fft1024_1;      //xy=701,316
    AudioAnalyzeFFT256       fft256_1;
    //AudioConnection          patchCord1(i2s1, 0, fft1024_1, 0);
    AudioConnection          patchCord1(i2s1, 0, fft256_1, 0);
    AudioConnection          patchCord2(sine1, 0, amp1, 0);
    AudioConnection          patchCord3 (amp1, 0, i2s2, 0);
    
    IntervalTimer pulse;
    
    // GUItool: end automatically generated code
    
    const float freq = 8000.0;
    const float amp = 0.8;
    const float g = 0.5;
    elapsedMillis sincebeep;
    elapsedMillis sincesilence;
    int t1;
    int t2;
    bool first_ping = false;
    elapsedMicros sincePing;
    const double air_speed = 0.0343;
    
    
    
    void setup() {
      //Serial.begin(9600);
      AudioMemory(70);
      while (!Serial) ; // wait until Arduino Serial Monitor opens
      setSyncProvider(getTeensy3Time);
      if (timeStatus() != timeSet)
        Serial.println("Unable to sync with the RTC");
      else
        Serial.println("RTC has set the system time");
    
      //while (!Serial && millis () < 3000);
    
      //  delay (6000);
      //AudioNoInterrupts();
      //AudioMemory (2);
      amp1.gain (0.8);
      sine1.amplitude(0.0);
      sine1.frequency(freq);
      //AudioInterrupts();
      Serial.println("setup done");
      //  Timer1.initialize(10000000);
      //  Timer3.initialize(11000000);
      //  Timer1.attachInterrupt(beepStart);
      //  Timer3.attachInterrupt(beepEnd);
      threads.addThread(beep);
      threads.addThread(FFT); 
    
      //  pulse.begin(beep, 500000); //call beep every 5 secs
    
    }
    
    
    
    void printDigits(int digits) {
      // utility function for digital clock display: prints preceding colon and leading 0
      Serial.print(":");
      if (digits < 10)
        Serial.print('0');
      Serial.print(digits);
    }
    
    void digitalClockDisplay() {
      // digital clock display of the time
      Serial.print(hour());
      printDigits(minute());
      printDigits(second());
      Serial.print(" ");
      Serial.print(day());
      Serial.print(" ");
      Serial.print(month());
      Serial.print(" ");
      Serial.print(year());
      Serial.println();
    }
    
    
    time_t getTeensy3Time()
    {
      return Teensy3Clock.get();
    }
    
    
    //void beepStart()
    //{
    //  sine1.amplitude(0.8);
    //  sincePing = 0;
    //}
    //
    //void beepEnd()
    //{
    //  sine1.amplitude(0.0);
    //}
    void beep()
    {
    
      while (1) {
    
        if (sincebeep >= 50)
        {
          sine1.amplitude(0.0);
          sincebeep = sincebeep - 50;
    
        }
        if (sincesilence >= 10000)
        {
          sine1.amplitude(0.8);
          sincePing = 0;
          sincesilence = sincesilence - 10000;
          first_ping = false;
    
        }
        threads.yield();
      }
    }
    
    void FFT()
    { while (1) {
        t1 = micros();
    
        if (fft256_1.available()) {
          //  Serial.println(fft256_1.read(45,49)*100);
    
          if (fft256_1.read(45, 49) * 100 > 0.4 && first_ping == false)
          {
                digitalClockDisplay();
                Serial.print("heard ping, delay of :");
            //    long long int t3 = t2-t1;
            noInterrupts();
            Serial.println(sincePing);
            interrupts();
            t2 = micros();
            //     Serial.println(t2-t1);
            double time_in_usec = sincePing;
            double dist = air_speed * (time_in_usec);
            //    Serial.print("distance :");
            //    Serial.println(dist);
            first_ping = true;
          }
        }
      }
    }
    
    //loop time check
    void loop() {
    
    }

    I am using 2 threads, one to play the 8khz sine tone periodically and another running FFT to detect the pulse. When the amplitude of the speaker is raised, I set an "elapsedMicros sincePing" timer variable to 0, and when the other microphone hears it in the other thread I display that time. In theory this time can be used to calculate the distance of the speaker to the microphone by using the speed of sound in air. These are the results I am getting for now:-
    (When a few cm away from the mic)
    Code:
    13:41:13 1 5 2020
    
    heard ping, delay of :22005
    
    13:41:23 1 5 2020
    
    heard ping, delay of :22025
    
    13:41:33 1 5 2020
    
    heard ping, delay of :22005
    
    13:41:43 1 5 2020
    
    heard ping, delay of :10603
    
    13:41:53 1 5 2020
    
    heard ping, delay of :31624
    
    13:42:03 1 5 2020
    
    heard ping, delay of :28204
    
    13:42:13 1 5 2020
    
    heard ping, delay of :26005
    
    13:42:23 1 5 2020
    
    heard ping, delay of :23805
    
    13:42:33 1 5 2020
    
    heard ping, delay of :22005
    
    13:42:43 1 5 2020
    
    heard ping, delay of :22005
    
    13:42:53 1 5 2020
    
    heard ping, delay of :22005
    (when about 30cm away from the mic)

    Code:
    13:49:46 1 5 2020
    
    heard ping, delay of :44005
    
    13:50:16 1 5 2020
    
    heard ping, delay of :31257
    
    13:50:26 1 5 2020
    
    heard ping, delay of :27837
    
    13:50:36 1 5 2020
    
    heard ping, delay of :25637
    
    13:50:45 1 5 2020
    
    heard ping, delay of :22218
    
    13:50:56 1 5 2020
    
    heard ping, delay of :22005
    
    13:51:06 1 5 2020
    
    heard ping, delay of :22006
    
    13:51:16 1 5 2020
    
    heard ping, delay of :22005
    
    13:51:26 1 5 2020
    
    heard ping, delay of :22005
    
    13:51:36 1 5 2020
    
    heard ping, delay of :32001
    
    13:51:46 1 5 2020
    
    heard ping, delay of :29801

    These numbers don't really make sense since they are inconsistent, and become more so once the speaker is moved further away.

    I think I have a few things I can check to make sure there isn't something in the background which is a problem, like for example the frequency the FFT is being computed at being constant? Or maybe there is a better way of getting the elapsed time between the signals? Any inputs would be appreciated. Thanks!

    -Akshay

  2. #2
    Senior Member
    Join Date
    Nov 2012
    Posts
    1,424
    I don't see how this can possibly work. The time between consecutive FFT256 will be 256/44100 = 5.8 milliseconds. Therefore, you can't resolve the time between sending a ping and "hearing" it any better than this, which corresponds to a distance of a bit less than two metres.
    If you can get around that problem, you still have to contend with the amount of time it takes to detect an 8kHz tone. I presume that whichever technique you use, it will need at least one cycle of 8kHz to be able to determine that the tone is present. In one cycle of 8kHz, sound can travel about 47cm. You'd be unable to resolve any distance with an accuracy better than that. This is why ultrasound is used for motion sensors. The higher frequency permits better resolution.

    Pete

  3. #3
    Hi Pete, thanks for the information! I guess I am limited by hardware in this case. Either way the maximum frequency I could get is 20Khz if I switch to perhaps a PDM microphone instead of the I2S one I have now. That might help reduce cycle time a bit.
    I don't have an electronics background, but based on some rudimentary knowledge I have, if I were to use band filters instead of the FFT to pick on a smaller frequency range (one which a speaker could produce), I might at least be able to overcome the time delay of consecutive FFTs. Do you think that is a more feasible solution?
    The reason I am not looking into the readily available ultrasonic modules is that I want to deploy this system in water, and waterproofing the microphone breakout board with silicone has been possible, but won't be so for the ultrasonic range finders since its a far more complex shape.
    -Akshay

  4. #4
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany NRW
    Posts
    6,920
    There is a project of some users in this forum. They wrote and built a bat detector (->ultrasonic). I don't know much about it - as far as I know they use almost normal microphones and a higher samplerate. Maybe a good idea to ask them?

  5. #5
    Senior Member
    Join Date
    Nov 2012
    Posts
    1,424
    If this is going to be used in water, that changes the numbers. The speed of sound in distilled water is about 1481m/s and in seawater it's about 1531 m/s. That would improve the resolution quite a bit. The FFT256 would have a resolution of about 50cm and the tone detection of 8kHz would have a resolution of around 12cm. Increasing the tone to 20kHz would improve the tone detection resolution by a factor of 2.5.

    I can't help with band filters, I'm not a hardware guy

    Pete

  6. #6
    Senior Member
    Join Date
    Oct 2015
    Location
    Vermont, USA
    Posts
    256
    I was intending to imply that the filters be done in software via the biquad class in the audio library. Definitely not hardware. It requires real skill and/or money to get a good, tight bandpass filter in hardware. In software, you can keep cascading filters to sharpen your response until you run out of cpu. Especially if you are not worrying about quantization build-up from fixed-point IIR filters.
    Last edited by chipaudette; 05-03-2020 at 06:58 PM.

  7. #7
    Senior Member
    Join Date
    Oct 2015
    Location
    Vermont, USA
    Posts
    256
    How long is your tone pulse? Presuming it is more than one cycle long, that's going to set your resolution (besides the fft length) not your operating frequency. To get your operating frequency to set your resolution, you'll need to do 1-cycle pulses or you'll need to do cross-correlation.

    Assuming that you are using tone pulses that are many cycles long, does it fit within one fft block?

    If it is close to one fft block in duration, don't forget that your pulse might arrive at a time such that it ends up straddling two fft blocks. That'll degrade your sensitivity. It's not a horrible reduction, but you should be aware.

    To mitigate, one can do overlapping fft blocks. Or, one can switch to time-domain filtering via biquads or FIR, as previously mentioned. Really, though, this is a modest problem compared to the other challenges of just getting it working at all.

  8. #8
    Senior Member
    Join Date
    Oct 2015
    Location
    Vermont, USA
    Posts
    256
    I just looked at your code. It looks like your tone pulse is 50 milliseconds long?

    If so, that's 2205 samples assuming that you're running at the standard Audio Library rate of ~44100 Hz. That's a lot longer than your ffts. So, if the rest of your system is running correctly, you'll detect your signal in several ffts in a row. You'll have to account for that in your logic.

  9. #9
    Senior Member
    Join Date
    Oct 2015
    Location
    Vermont, USA
    Posts
    256
    Increasing your frequency to 20 kHz will only improve your resolution if you were to move beyond your FFT approach. Unless you're lucky enough to have crazy-high signal to noise ratio, your resolution is going to be limited by your pulse length (50 ms) or your FFT length (256 pts? If so, that'd be 5.8 msec). Either one is way longer than the limit due to your operating frequency (1/(8kHz) = 0.125 msec). So, you have the freedom to choose your operating frequency based on other concerns.

    A really good way to pick your operating frequency is to choose the frequency where your speaker system is loudest. Or, choose the frequency where any background noise is quietest. Either one of these choices will give you a higher signal to noise ratio, which will make your life much easier.

    For example, depending on how nice your equipment is, you might find that your speaker is way louder around 1 kHz compared to 8 kHz. Like, maybe 10 dB louder. If so, feel free to shift down there...your ranging resolution will still be limited by your pulse length or fft length but you will have gained an extra 10 dB of SNR for free.

  10. #10
    Pete, Frank, Chipaudette, thanks for the valuable information and feedback!
    My current setup is to develop a proof of concept in air, I only have a couple waterproof microphones but will have to wait till the university labs open back up again due to Covid-19 to do more tests in water for ranging, so I guess for the time being I'd have to be content with poor resolution with the current system (unless I could improve upon it somehow)
    Right now I am using one of the cheaper 8ohm speakers for in air tests. Once we move to in water, we are planning on using a much more potent speaker like the one here http://www.lubell.com/UW30.html (although by this time I'd hope to have them on different teensy boards which are somehow time synced in a cheap and lightweight way). Most of these speakers have a response only till about 10Khz. Using an underwater transducer instead, which could do ultrasonic, would increase costs significantly and the project is mainly aiming to remain low cost.

    Chipaudette, yes as you noticed my pulse is long enough to be detected by multiple successive FFTs and I just note the 1st one heard as my sample for timing it.
    I will look into the biquad filters you mentioned! Although are they typically faster in processing than the FFT?

    Frank, I did look into the bat detector, but I guess I'd need to go through it more as it was fairly complicated and I set it aside since moving to ultrasonic is something I'd like to avoid due to hardware complexity and costs.

  11. #11
    Quote Originally Posted by el_supremo View Post
    If this is going to be used in water, that changes the numbers. The speed of sound in distilled water is about 1481m/s and in seawater it's about 1531 m/s. That would improve the resolution quite a bit. The FFT256 would have a resolution of about 50cm and the tone detection of 8kHz would have a resolution of around 12cm. Increasing the tone to 20kHz would improve the tone detection resolution by a factor of 2.5.

    I can't help with band filters, I'm not a hardware guy

    Pete
    Hey Pete, just a quick question, how did you come up with the FFT256 resolution of 50cm in water? I thought the time interval is only dependent on the sampling rate and FFT bin count? So with the same 5.8ms and sound would travel approx 7.3m in water right?

  12. #12
    Senior Member
    Join Date
    Nov 2012
    Posts
    1,424
    Ooooppss. You're right. And the 8kHz tone detection would be worse too.

    Pete

  13. #13
    Thanks Pete. Looks like without increasing the sample rate, FFT won't be practical for my application

  14. #14
    Senior Member
    Join Date
    Oct 2015
    Location
    Vermont, USA
    Posts
    256
    If the FFT isn't going to work for you, then you can cascade a few IIR (biquad) filters.

    Click image for larger version. 

Name:	Cascade filters.png 
Views:	9 
Size:	172.2 KB 
ID:	19990

    You'll need to configure each biquad filter by issuing commands in your setup() function. You'll want to use "biquadXX.setBandpass(stage, frequency, Q);" If you're really doing separate biquads as I've shown in the figure, set stage to zero, set frequency to your tone's frequency (8000 Hz?) and set Q at least to 1.0 and you could try up to 3.0. The lower value will give a filter that is less focused on 8 kHz, but will avoid any numerical stability problems that might pop up when cascading 3 high-Q filters in series. I'd start with 1.0 and then, once you get the whole rest of the system working, you can try higher Q to get tighter filters.

    After filtering, you see that your filtered waveforms will end up in those queue objects. You'll need to write code that you put in your loop() function that will: (1) look to see if data is available in each queue, (2) scan through the processed audio to find see if the echo has arrived. To see how to work with the queue objects, look at the example code in the Audio Library. They'll show you how to see if data is available and they'll show you how to access the data when it is available.

    For step (2), where you want to detect the presence of your echo, the simplest (though not most sensitive) method is simply to loop through the audio block looking for the sample index with the max(abs(value)). You'll need to pick some threshold value where, if you detect a sample that is larger than the threshold, you know that an echo is present. That's your detector! Be sure to note what sample was the peak. That's your best time estimate of when the echo arrived.

    The time accuracy of your detector will depend upon how long your pulse is. As already discussed, you're using a fairly long pulse. Your "max(abs(value))" could end up being found nearly anywhere in that long pulse. So, your long pulse will result in less-than-optimal resolution. That's why shorter pulses can be better.

    Alternatively, if you have a high SNR, you can set a lower detection threshold and just look for the start of the echo. If you've got the SNR, this will give you much higher resolution.

    If you don't have high SNR, using max(abs(value)) as your detector might not be sensitive enough. In that case, you'll want to use a smarter way of detecting your pulse when scanning through the queue data. Usually "smarter" means averaging, which is easy to do and effective, but there are decisions about how long an average to use. That's refinement that can be done later.

  15. #15
    This was very informative. Thank you! I'll have to read up more on this. From the snapshot of the audio library GUI, I see that two channels are being used from the i2s1 and then being fed into i2s2. I am currently using only a single mic so I guess I need to use just one channel? Also why do we need to feed this as input into i2s2? Won't processing the queue be the endpoint, since i2s2 will be used for the speaker? (Unless this was just an example for representation purposes )

  16. #16
    Senior Member
    Join Date
    Oct 2015
    Location
    Vermont, USA
    Posts
    256
    Regarding the I2S output, I find that the system never operates quite right if I don't have any audio output specified. So, even if I'm not listening to the audio output, I still connect it.

    You are using the audio output for your tone pulse, so you'll already have it connected, so that'll probably make it all work out just fine.

  17. #17
    Senior Member
    Join Date
    Oct 2015
    Location
    Vermont, USA
    Posts
    256
    As for the two channels, this is going back to my recommendation that you measure the time of the transmitted pulse as well as the time of the received pulse. You had expressed some confusion over why your transmitted pulse seemed to have variable timing. If you actually measure the time of each transmitted pulse, you don't have to worry about any variability in its timing. You'll simply be taking the difference of the *measured* time of the transmission and the *measured* time of the echo. That time difference should be the best representation of the travel time, which should give you the best estimate of the distance that you're looking to measure.

    To look for both the outgoing and return pulses, you can do that with one microphone. You'll just need to place your one microphone in a good location to catch both the outgoing and the incoming. That's probably pretty easy to do. Or, you could choose to do it with two microphones, looking for the outgoing pulse in one mic and the incoming echo in the other mic. If you use two mics, you need two identical signal processing chains. Having two identical signal processing chains is what I showed.

  18. #18
    Got it. Thanks!

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •