I needed some ultrasonic range finders for my project. But most of the commercial sensors like Parallax’s PING sensor and other similar products are quite expensive, especially if multiple units are needed. So I thought why not building it myself?
The theory behind ultrasonic ranging is quite simple. Typically a short ultrasonic burst is transmitted from the transmitter. When there is an object in the path of the ultrasonic pulse, some portion of the transmitted ultrasonic wave is reflected and the ultrasonic receiver can detect such echo. By measuring the elapsed time between the sending and the receiving of the signal along with the knowledge of the speed of sound in the medium, the distance between the receiver and the object can be calculated. The picture below (source: Wikipedia) illustrates this basic principal:
In my design, I used separate transducers for transmitter and receiver. It is possible to multiplex the transmission and receiving with a single transducer (e.g. Maxbotix range finders), but the design would be significantly more complex.
Ultrasonic Transducer
There are quite a few ultrasonic transducers to choose from, and the main criteria are the resonant frequency, radiation pattern and sensitivity. Generally speaking, these parameters affect the measurement in the following ways: a higher resonant frequency can provide finer details of the surroundings due to the shorter wavelength. A more directional radiation pattern can also enhance the resolution of the measurement. Sensitivity affects the efficiency of the transducer and also attributes to the SNR (signal to noise ratio).
I bought these 24 kHz transducers on sale (see picture below). These transducers are very inexpensive (around a dollar each, and even cheaper when on sale) but effective. With properly designed circuits these sensors can easily achieve a range of more than 20 feet. Of course, using the higher priced 40 kHz sensors should achieve even better performance.
But for these DIY ultrasonic range finders, the choice of the transducers are really not that critical and this transducer really hits the performance to price sweet spot.
[adsense] |
The Transmitter
The ultrasonic transmitter is powered from ATmega328’s counter 1 PWM output (chip pin 16 and Arduino digital pin 10). In order to achieve the maximum output power of the transducer for a given supply voltage, I used the bridged output design as shown in the following schematics:
This bridged circuit produces an output voltage roughly twice the Vcc. I used +5V for Vcc and the result is already quite good (more than 20 feet of range). For even longer range measurement, you can safely increase this driving voltage to around 12 Volts as most ultrasonic transducers can be driven with voltage as high as 20 to 30 volts. If you increase the voltage significantly above 5V however, you will have to change the transistors to allow more power dissipation. With 2N3904 and 2N3906 the transistors get warm during normal operation and would heat up drastically with voltage above 6V.
Here is the output of the ultrasonic burst measured at the output transducer’s terminals:
The small “ladders” at the half-way voltage point in the output waveform is due to the slight added delay of the inverted signal stage due to the use of an extra NPN transistor. To obtain purer rectangular wave form and reduce switching loss, a PNP transistor with similar timing parameters can be used on the side that is directly connected to the driving signal. For this application though, the waveform is more than adequate and the added switching loss is negligible.
The transmitter and receiver transducers can be mounted on a circuit board with approximately one inch of spacing (see below).
In order to reduce possible interference from the reflected ultrasonic waves, the components are mounted on the reverse side of the board (below is the H-bridge circuit that drives the ultrasonic transducer, a few decoupling capacitors are used to reduce noise and they are not shown in the schematics above):
The code to drive the transducer is similar to that I used previously, except that I changed the pre-scalar to 1 so that the output frequency can be controlled more precisely in the kHz range.
void startTransducer(float freq, float dutyCycle) { if (dutyCycle > 0.5) dutyCycle = 0.5; else if (dutyCycle < 0) dutyCycle = 0; cli(); TCCR1B = _BV(WGM13) | _BV(CS10) | _BV(ICNC1); //f0 = fclk / (2 * N * Top) long topv = (long) ((float) F_CPU /(freq * 2.0 * 1.0)); ICR1 = topv; OCR1A = (int) ((float) topv * dutyCycle); OCR1B = (int) ((float) topv * (1 - dutyCycle)); DDRB |= _BV(PORTB1) | _BV(PORTB2); TCCR1A = _BV(COM1A1) | _BV(COM1B1); sei(); } void stopTransducer() { cli(); TCCR1B = 0; sei(); digitalWrite(9,LOW); digitalWrite(10,LOW); }
[adsense] |
The Receiver
The performance of the range sensor is largely determined by the sensitivity of the receiver for a given transmitter power level. Because the received signal is usually very weak (less than 1 mV), a high gain low noise amplifier is needed to ensure optimal performance.
I used a two stage inverted band-pass amplifier design (see below). Each stage has a gain of around 67 (36.5 dB) and the circuit has a combined voltage gain of 73 dB. The operational amplifier I used is National’s LPC662. In general, any operational amplifier with a sufficient gain bandwidth product should work just as well.
Each stage has a band-pass filter that is centered around the operation frequency (24 kHz). Because the amplifier has a very high gain, we must pay special attention to the circuit layout in order to prevent parasitic oscillation. The connection between the receiver transducer and the circuit input (6.8n capacitor) needs to be shielded to reduce noise and unwanted coupling.
Because we are using a single power supply the output voltage of the opamp is centered at around Vcc/2 (2.5V). In order to make it easier to process the echo, a diode (IN4148), capacitor (0.1uF) and resistor (10k) are used to demodulate the signal and a coupling capacitor (1uF) is used to rid the demodulated signal of the DC component.
You can see the demodulated envelope waveform from the following oscilloscope screenshots (you can ignore the frequency measurement as these signals are none-periodical the frequency readings are meaningless). The higher amplitude waveforms in both images are the results of the ultrasonic burst, the lower amplitude waveforms are from the echo. In the first screenshot on the left, two echoes can be seen.
The following screenshot shows the relationship between the ultrasonic pulses (measured from ATmega328 pin 16) from the transmitter and the demodulated echo output. One key observation is that the received signal takes much longer time to fade then the original pulse duration and thus we must add in some delay after the transmission of the ultrasonic pulses. A delay of 1 to 2 millisecond is typical. With a 1 millisecond delay, the shortest measurable distance is around 30 centimeters or one foot.
And here is a picture of the finished project.
Range Calculation
Since the measured distance is a function of the time interval between the time at which the pulse is transmitted and the time at which the echo is received, we need to reliability detect the echo.
Empirically, we can measure the peak of the received echo and use the time displacement to calculate the distance. We assume that the strongest echo comes from the closest object (this may not always be true as the reflectivity of different objects are different, but generally achieves very good results in real-world situations) and thus the peak measurement corresponds to the closest object’s position.
The code snippet below assumes that we are interested in measuring objects with a range of up to about 20 feet. After the pulses are transmitted from the transmitter, we wait for a millisecond for the initial receiver signal to fade. Then we search the peak value in the next 20 milliseconds or so (the loop limit 256 is set empirically, in the code below this setting corresponds to a 20 milliseconds interval between transmitted pulses which is suitable for distance measurement up to approximately 20 feet. To measure longer distance, the upper limit for the loop needs to be increased correspondingly) and assume that the peak comes from the first echo.
byte a = 0; unsigned long t_start = 0; unsigned long t_peak = 0; unsigned long t = 0; byte v_peak = 0; const float SPEED_OF_SOUND_20C = 0.0003432; //meters per micro-second float d = 0; void loop() { startTransducer(24000.0, 0.5); delayMicroseconds(300); stopTransducer(); v_peak = 0; t_start =micros(); t_peak = t_start; delayMilliseconds(1); for (int i = 0; i < 256; i++) { a = analogRead(0); t = micros(); if (a > v_peak) { t_peak = t; v_peak = a; } } t = t_peak - t_start; d = (float) t * SPEED_OF_SOUND_20C / 2.0; Serial.println(d , 2); }
Here’s the full code listing for this project:
UltraSonicRangeFinder.tar.gz
The bill of material for this ultrasonic range finder is less than 5 dollars (excluding the MCU since it can be incorporated into your projects).
Update
I would like to thank Thomas for pointing out the mistakes in my H-Bridge schematic. The PNP transistor’s collector and emitter are swapped by mistake (I should have double checked the schematics. Anyway, the photo shows the correct orientation) and I have updated the image.
Also on the receiver side, the first OP’s output was missing connection to the 0.1uF capacitor, I have updated it as well.