Measuring distance... with sound?

Status
Not open for further replies.

madaeon

Member
Hi, some advice would be greatly appreciated to understand what is wrong here...
I would like to measure distance between two points using sound.
Ultrasonic transducers can be used for this goal, but they must face the point to measure.
I would like to use "normal" speaker and microphone, so the sound is not so directional, and to some point, I can measure the distance between microphone and speaker even if they do not face each other.
I am using Teensy 3.6,
microphone is this https://www.amazon.it/gp/product/B07PXP8BQ7/ref=ppx_yo_dt_b_asin_title_o01_s00?ie=UTF8&psc=1,
speaker is connected to pin 8 through 100ohm resistor.

Photo of the setup, where speaker and microphone is about 10cm. 11.jpg

Code
Code:
// Speed of sound 0.34029 millimeters / microsecond 
// 10cm = 100mm = approx 300us ??


#include <ADC.h>

ADC *adc = new ADC();; // adc object
volatile int elapsed; 
int readPin = A21; // ADC0
int value = 0;

void setup() {

    pinMode(readPin, INPUT);
    Serial.begin(1000000);
    adc->setAveraging(0); // set number of averages
    adc->setResolution(12); // set bits of resolution
    adc->setConversionSpeed(ADC_CONVERSION_SPEED::HIGH_SPEED); // ,  change the conversion speed
    adc->setSamplingSpeed(ADC_SAMPLING_SPEED::HIGH_SPEED); // change the sampling speed
    adc->startContinuous(readPin);
 

tone(8, 10000);
}

void loop() {

  
unsigned long startTime = micros ();
tone(8, 1000);
delay(50);


int uu=0;// (uint16_t)adc->analogReadContinuous(ADC_0);//=analogRead(A21);

//do { uu=(uint16_t)adc->analogReadContinuous(ADC_0); } while (uu<2000);
 while (uu<2150)
 { if (adc->isComplete(ADC_0))
   { uu=(uint16_t)adc->analogReadContinuous(ADC_0); } 
 }


  unsigned long endTime = micros ();

  unsigned long elapsedTime = endTime - startTime;
  Serial.println (elapsedTime);
  noTone(8); 
  delay(50);

  
}

For each loop, I start tone, wait 50 microseconds, then start reading the ADC, until i get signal above a threasold that I have measured is slightly under the peak measured during the tone.

The problem is that I get approx 50045us as a reading (50000us are the 50ms that I wait for the tone to "stabiliyze"), so just 45us? It should be approx 300us for 100mm.
I tried with different frequencies for the tone from 200 to 14000, the better result I get is that if I move mic and speaker closer (about 1cm) the reading is about 15us.
But something is wrong, I think.
Thanks!
 
The major problem with your code is that you are waiting 50milliseconds after your tone starts and then starting to read the ADC values, but you don't really know the phase relationship between your tone and the actual collection at the ADC.

You should try sending a brief burst of sound, then reading the ADC until you get a return. The problem with that is that the length of the burst will determine your minimum distance. The simplest output that might work with your simple amplitude detection would be a very short 'click'

Code:
digitalWriteFast(speakerpin, HIGH);
delayMicroSeconds(100);
digitalWriteFast(spekerpin, LOW);
starttime = micros();
int uu=0;// (uint16_t)adc->analogReadContinuous(ADC_0);//=analogRead(A21);


 while (uu<2150){
     if (adc->isComplete(ADC_0)){
           uu=(uint16_t)adc->analogReadContinuous(ADC_0); 
     } 
 }
  etc. etc.


The problem is that such a short click really doesn't put much energy into the speaker unless you have an amplifier. Your tone output is putting 3.3V across (100 ohms + speaker impedance) or < 1/10 Watt (Pwr = V*V/R) . Most small speakers will have pretty high impedance at 10KHz, further reducing the power. Small speakers also don't respond instantly.

In an earlier project I found that the piezo speaker in ultrasonic transducers take a while to get started and you have to pulse very near their resonant frequency--which makes wide-bandwidth chirps impossible. That was what prompted me to use a powered speaker and audio frequencies.


I spent many hours last week working on a similar, but more complex, project.

Instead of sending a tone, I sent a 4-millisecond chirp that went from 3000 to 6000Hz. I also applied a sine window to the chirp amplitude and sent the output to the DAC output to drive a powered speaker. The return was captured by a Sparkfun electret microphone connected to an ADC input.

The return echo was detected using autocorrelation with the chirp data to reduce noise sensitivity and add processing gain.

I was able to measure distances from about 4 feet to 18 feet with reasonable reliability and a resolution of about 0.1 foot.
The generation of the chirps, the collection and processing of the data is encapsulated in a C++ object, so it's not something I would want to toss at someone unfamiliar with C++ and correlation detection. However, if you're interested I can post the code.
 
You don't mention what Speaker you use, but from the picture I am guessing is a piezo transducer.
These guys have a resonant point that is easy to excite with a pulse. To make it more efficient and get more amplitude (assuming can handle it) what you can do is to connect it between two I/O pins and toggle them inverted each other as simultaneously as possible (use fast digitalWrite). After half the period of the resonant frequency toggle reverse and after other half equalize voltage in both pins and wait the longest reasonable fly time and repeat (if you are not measuring farther than 2m, that would be about 6mS). Essentially what will happen is like when you hit a pot with a solid object, the piezo will resonate with a sine wave and a number of bounces creating a sine burst decaying (in the oscilloscope looks like an sine arrow. What your mic has to catch is the beginning of that burst. I use to design devices like this for small robotics with hardware and software with resolution down to mm. The trick is to create a good fast swim and back. In the mic you need to look the output with a scope to make sure you see what you expect and adjust for the DC level if any. Also very recommended is to do this with a timer interrupt, where you reset the counter as you swim the piezo, and then trigger capture count with the microphone (just adapt the mic level to the logic threshold). To make your life easier, they sell in Amazon electret audio detectors that use an adjustable comparator for this purpose (rather than a pre amp). Here: https://www.amazon.com/DAOKI-Sensit...words=electret+detector&qid=1589011339&sr=8-2 .
This way you can avoid ADC conversion that can be slow for this application.
Hope this helps.
 
Thank you very much mborgerson and RHormigo for the answers.
I will try to apply them. The distance I have to measure is between 0 and 20 cm, 1 mm resolution would be very good, is this feasible? Thanks again.
 
I think you're out of luck trying to measure with 1mm resolution using sound at frequencies below 100KHz. Given that the speed of sound is about 343mm/millisecond,
one mm resolution means a time resolution in measuring the propagation delay of about 3microseconds.

At 20KHz, the sound waves have a wavelength of 17mm. I don't think you can determine the propagation time with a peak detector to 1/17th of a wavelength. Over a long time period, you might get that kind of resolution with a phase-locked loop and continuous waves, but with a peak detector and ambient noise I don't think it is feasible.

Your problem looks like it might be better solved with one of the optical time-of-flight detectors. Here is an example:
https://www.sparkfun.com/products/12785
 
For that type of resolution you will need definitively to track your micros() with interrupts to mark start (when you toggle the piezo pin) and stop (when you heard back). Even a bit better if you use a dedicated hard counter that you can reset (this would help to avoid the roll over tracking). Also use fast digital write definitively to avoid the latency and likely lack of determinism. Keep in mind that audio takes about 3uS to fly 1mm in air, so your measurements needs to be more deterministic than 3uS. The sonars I developed in the past that could achieve this were using a much slower pic CPU, so in this front should be easier. However I used ultrasound transducers with much faster rising/falling times (rT, fT), here you want to use quite low frequencies with a piezo that even for a 5KHz transducer your rT/fT will be more like 100uS, what should not be a problem in ideal acoustic conditions, but unfortunately is not such thing. Angular position, mulipath scattering, surrounding resonances, even moisture... can displace randomly the threshold points that within these 100uS fT/rT can introduce a big error. You may want also to average over multiple readings to correct as possible.
As you don't want to go further than 200 mm. In theory you can keep kicking your piezo very fast up to each 300 uS?, if the CPU and software can keep on it:confused:. What I would try is to do several measurements fast, average the value, and store that value in a low level fast loop, then work with and report the stored value.

I hope that help,
Rick.
 
I just caught on to the fact that you want to measure the distance between the microphone and the speaker. That's somewhat easier than the problem I was working on: measuring the distance from the mic/speaker and a reflective object. In your case, you have a much stronger received signal---particularly with a 20cm maximum range.

One technique to consider takes advantage of the change in sound wavelength with frequency. A 1700Hz tone has a wavelength of 20.33 cm. A 16KHz tone has a wavelength of 2.16cm. To take advantage of this linear change, you can sweep your output frequency over that range and look for the maximum Pk-Pk signal when you sample at times 1/2 wavelength apart. At that point your microphone is 1 wavelength away from the speaker. And, let's face it, a minimum range of 0 cm is not going to happen with real-world transducers. Better to accept a minimum range of 2cm to keep your frequencies in a range that your speaker and microphone can handle. You can add an RC filter to reduce harmonics at the lower frequencies. That will reduce your output amplitude at higher frequencies---but at higher frequencies, you expect the mic and speaker to be closer together, so the signal will be stronger due to the smaller distance.

Given the fast floating-point calculations and the DAC on the T3.6, you should be able to generate clean sine waves over the 1700Hz to 16Khz range with an interval timer ranging from 17,000 samples/second to 170,000 samples/second. That gives you 10 samples per output cycle, which should reduce output harmonics when compared to a simple square wave driving the speaker.

Warning: much hand-waving, buzz words like "Auto-correlation", "Speaker harmonics", "harmonic distortion" and lots of experimentation lie between this simple proposal and a working system.
 
Schema1.png

I have thought about ToF sensors, I have used them before.
The problem, as is maybe easier to understand with the above scheme, is that I would like to measure the distance from point A to point B. They do not always face each other, so a Tof sensor is not suitable, and ultrasonic have the same problem.
I had some luck with a solenoid and a hall sensor, good resolution, but range is limited to a few cm.
This is why I have thought that an audio source and a mic could have been a good solution for the problem. But I am open to any possible other solution.
I even build a small wire rope sensor based on a potentiometer, but ideally the sensor/recevier at point A and B should be small and less invasive as possible.
Thank you all again.
 
Unfortunately nothing can be applied except on point A and point B. The 2 joints are actually much more complex, the drawing is a very simplified view.
 
Hi,
What about having a single tone produced at A, a second tone at twice that frequency produced at joint 2 (or some other fixed distance from B), with both synchronised, and a microphone at B? Use filters to separate out the two signals, and then measure the time difference between zero crossings. You could average over multiple cycles for improved accuracy. Frequencies of around 800 and 1600Hz could work.
 
The problem with all these proposals are some things I hadn't yet considered:
1. Many speakers that have adequate output at 500 to 2500Hz are an inch or two in diameter. The resulting audio output may have some interference effects (near edge vs far edge, etc.) in near-field applications like this. Remember that a 1715Hz tone has a wavelength of 20cm
2. Amplitude and zero-crossing detection is going to get messed up by echoes from nearby objects--and for a 10milliSecond ping, objects nearer than about 5 feet will have echoes that return within the duration of the outgoing ping. That's one of the reasons to use an outgoing chirp with significant frequency modulation and correlation detection.

I bring up these issues, because I spent some time with a sketch using chirps and correlation detection yesterday. I got mixed results at distances from 5 to 20cm. Even when I sampled and correlated at 160KHz, I wasn't able to get distance resolution better than 2mm. i also found that changes in chirp characteristics made cm-level changes in the distance measured. But hey---if it was easy, everyone would do it! (Tom Hanks as Jimmy Dugan in "A league of their own". "There's no crying in baseball!" is even better, but off topic here.)
 
I agree - there are lots of potential problems. The speed of sound in air is highly temperature dependent, so you'll have difficulty getting accuracy of +/-1mm over a 20cm range for that reason alone without some method of compensation.
 
If you orient your sensors so that their axes are neutral to each other then you can neglect any change in response of the transducers with angle, so point them in the z-axis (upwards) based on your 2D diagram.

A technique I used many years ago for determining the effects of barriers on acoustic loss was to use Maximum Length Sequence as the source noise generation and signal processing. This allows the impulse response of the system to be determined in low signal to noise ratio (SNR) environments.
28 years ! ago this was done using an ISA card in a PC costing €10k, nowadays you may be able to do this on a teensy.

See this link for what MLS are https://en.wikipedia.org/wiki/Maximum_length_sequence

There would be some delay to get the impulse response measurement/calculation but it should be accurate.
 
I like to through in a complete different idea.
measure the phase shift between transmitted and received signal.
OK, you may first to calibrate the electronic phase shift and you need a frequency that is low enough to result in unique results
 
If you orient your sensors so that their axes are neutral to each other then you can neglect any change in response of the transducers with angle, so point them in the z-axis (upwards) based on your 2D diagram.

A technique I used many years ago for determining the effects of barriers on acoustic loss was to use Maximum Length Sequence as the source noise generation and signal processing. This allows the impulse response of the system to be determined in low signal to noise ratio (SNR) environments.
28 years ! ago this was done using an ISA card in a PC costing €10k, nowadays you may be able to do this on a teensy.

See this link for what MLS are https://en.wikipedia.org/wiki/Maximum_length_sequence

There would be some delay to get the impulse response measurement/calculation but it should be accurate.

Link didnt appear

Try

https://en.wikipedia.org/wiki/Maximum_length_sequence

and

http://www.commsp.ee.ic.ac.uk/~mrt102/projects/mls/MLS Theory.pdf

Should be a case of generate the Maximum Length sequence -> send to speaker -> measure with microphone -> convolute with original MLS signal -> impulse response -> first peak is the delay time
 
I just caught on to the fact that you want to measure the distance between the microphone and the speaker. That's somewhat easier than the problem I was working on: measuring the distance from the mic/speaker and a reflective object. In your case, you have a much stronger received signal---particularly with a 20cm maximum range.

One technique to consider takes advantage of the change in sound wavelength with frequency. A 1700Hz tone has a wavelength of 20.33 cm. A 16KHz tone has a wavelength of 2.16cm. To take advantage of this linear change, you can sweep your output frequency over that range and look for the maximum Pk-Pk signal when you sample at times 1/2 wavelength apart. At that point your microphone is 1 wavelength away from the speaker. And, let's face it, a minimum range of 0 cm is not going to happen with real-world transducers. Better to accept a minimum range of 2cm to keep your frequencies in a range that your speaker and microphone can handle. You can add an RC filter to reduce harmonics at the lower frequencies. That will reduce your output amplitude at higher frequencies---but at higher frequencies, you expect the mic and speaker to be closer together, so the signal will be stronger due to the smaller distance.

Given the fast floating-point calculations and the DAC on the T3.6, you should be able to generate clean sine waves over the 1700Hz to 16Khz range with an interval timer ranging from 17,000 samples/second to 170,000 samples/second. That gives you 10 samples per output cycle, which should reduce output harmonics when compared to a simple square wave driving the speaker.

Warning: much hand-waving, buzz words like "Auto-correlation", "Speaker harmonics", "harmonic distortion" and lots of experimentation lie between this simple proposal and a working system.

Can you supply example code of a chirp ranging from 1Hz to 60Khz with maximum sample rate, at a rate of 6 times per second minimum?, I am using a Teensy 4.1, this would help me out a lot,
Kind Regards
 
Since the T4.1 doesn't have an internal DAC, you would need to add an external DAC to get a sine-wave output.

Here is the code I used to make a chirp table:
Code:
// chirp data float from -1.9 to +1.0 it is scaled and offset when sent to dac
void MakeChirp(float f1, float f2, float msec){
  float k, theta, t;
  uint16_t i;
  freq1=f1;
  freq2=f2;
  k = (f2-f1)/(msec/1000);
  chirpmsec = msec;
  if(verboseflag){
    Serial.printf("f1: %6.0f   f2: %6.2f  msec: %4.0f", f1,f2,msec);
    Serial.printf("  samplerate: %6u\n",samplerate);
  }
  chirplen = uint16_t(msec * (float)samplerate/1000.0 + 0.5);
  chirpdata[0] = 1.5;  // set o'scope trigger peak
  for(i=1; i<=chirplen; i++){
    t = (float)i/(float)samplerate;
    theta = 2 * PI *(f1*t + (k/2)*t*t);
    chirpdata[i] = sin(theta);   
  } 
  if(verboseflag){
    
    for(i=0; i<=chirplen; i++){ 
      if((i%10) == 0) Serial.println();
      Serial.printf("%6.3f ", chirpdata[i]);
    }
  }
  Serial.printf("\nChirp data generated: %u words\n", chirplen);
}

The code uses an external SampleRate global, which was 40KHz the last time I used the code. the chirpdata array is also an external global array of floats.
 
I just noted that you asked for a chirp ranging from 1Hz to 60KHz to play 6 times per second. You need to rethink that frequency range, since you can't play a 1Hz signal 6 times per second!
 
Looking for suggestions

I just noted that you asked for a chirp ranging from 1Hz to 60KHz to play 6 times per second. You need to rethink that frequency range, since you can't play a 1Hz signal 6 times per second!

Hi, What would you suggest as the lowest frequency?, also, do you use an external clock such as the Si5351?

Kind regards
 
Since the T4.1 doesn't have an internal DAC, you would need to add an external DAC to get a sine-wave output.

Here is the code I used to make a chirp table:
Code:
// chirp data float from -1.9 to +1.0 it is scaled and offset when sent to dac
void MakeChirp(float f1, float f2, float msec){
  float k, theta, t;
  uint16_t i;
  freq1=f1;
  freq2=f2;
  k = (f2-f1)/(msec/1000);
  chirpmsec = msec;
  if(verboseflag){
    Serial.printf("f1: %6.0f   f2: %6.2f  msec: %4.0f", f1,f2,msec);
    Serial.printf("  samplerate: %6u\n",samplerate);
  }
  chirplen = uint16_t(msec * (float)samplerate/1000.0 + 0.5);
  chirpdata[0] = 1.5;  // set o'scope trigger peak
  for(i=1; i<=chirplen; i++){
    t = (float)i/(float)samplerate;
    theta = 2 * PI *(f1*t + (k/2)*t*t);
    chirpdata[i] = sin(theta);   
  } 
  if(verboseflag){
    
    for(i=0; i<=chirplen; i++){ 
      if((i%10) == 0) Serial.println();
      Serial.printf("%6.3f ", chirpdata[i]);
    }
  }
  Serial.printf("\nChirp data generated: %u words\n", chirplen);
}

The code uses an external SampleRate global, which was 40KHz the last time I used the code. the chirpdata array is also an external global array of floats.

Hello, can you give me the code missing from the beginning?, can you explain "the chirpdata array is also an external global array of floats?", how is this done exterally?
Kind regards
 
Status
Not open for further replies.
Back
Top