Most likely, all of you will know about how bats avoid obstacles and hunt for insects even in complete darkness. The mystery of the bats using ultrasonic sonar was discovered by the works of Donald R. Griffin (1938) and Sven Dijkgraaf (1946). The history of ultrasound is not very old; not surprisingly, early advances were made in World War I.
The standard audio devices only work up to 20, 000 Hertz and therefore are not usable for ultrasonic purpose. But transducers optimized for 40, 000 Hertz are available at cheap prices.
Actually, you better go for a module called “HC-SR04” which contains both transmitter and receiver. Take care when dismantling the parts.
Part I: Check how it works even with lower frequenciesPreparing the hardwareTo start with the experiment, we need to generate an electrical signal of that frequency. I opted to use the ATMEL microprocessor ATmega8 that can be operated without any external components (crystals etc.). You might take a smaller microcontroller but you would not save much money. Of course, you also can use an ARDUINO UNO but then you have to find the proper pins.
It is well-known that computer can only send square-wave signals. Their spectrum contains a third, a fifth, a seventh and more harmonics. For this reason, the software implemented in the transmitter was designed to produce a series of frequencies such as 40000/3, 40000/5, 40000/7 and so on. Although the resonance of the transducers is 40000 Hz, they also process signals at other frequencies but with much lower performance. The index of the frequency actually produced is indicated by the LED module.
To show the received signal there are many possibilities. I think the easiest way is to count the pulses on the receiver. The good news is there is a CD4060 counter chip. It also contains an oscillator circuit but we are not using in this experiment. The chip counts modulo 2^14 and can drive a few LEDs. When the received signal is too low the state of the counter will not change. Only when the level exceeds the threshold the LEDs start flashing quickly. Only the leftmost LEDs can be observed properly.
The software was written for the ATmega8. If you use the ATmega328 some registers have to be changed. The pulses are produced in the interrupt service routine (ISR). To enable even higher frequencies this routine was rewritten in assembler.
What experiments can be done?The software produces four audible tones and one ultrasonic tone. Although the transducers are not designed for low frequencies you can still hear them.
Now you can place sender and receiver at various distances and check if the receicer still counts pulses. You can change their directions to observe the angle of the beam. You also can use a "mirror" (any cardboard will do) to prove that sound is reflected in a manner similar to light.
The codeTo upload the code with the ARDUINO IDE to an ATmega8 without crystal you first have to install the appropriate lines to the "boards.txt" file:
##############################################################
atmega8noxtal.name=
ATmega8-noxtal @8MHz
atmega8noxtal.upload.protocol=stk500
atmega8noxtal.upload.maximum_size=7168
atmega8noxtal.upload.speed=38400
atmega8noxtal.upload.using=usbasp
atmega8noxtal.upload.tool=usbasp
atmega8noxtal.bootloader.tool=
avrdude1
atmega8noxtal.bootloader.low_fuses=0xe4
atmega8noxtal.bootloader.high_fuses=0xc4
atmega8noxtal.bootloader.path=atmega8_noxtal
atmega8noxtal.bootloader.file=atmega8_noxtal/ATmegaBOOT.hex
atmega8noxtal.bootloader.unlock_bits=0x3F
atmega8noxtal.bootloader.lock_bits=0x0F
atmega8noxtal.build.variant=arduino:standard
atmega8noxtal.build.mcu=atmega8
atmega8noxtal.build.f_cpu=8000000L
atmega8noxtal.build.core=arduino
Having done this you can perform the upload via a second Arduino selecting "Upload using programmer" from the IDE menu as shown in many instructables and tutorials.
/*
generates push-pull signals at Pin-8 and Pin-9
Frequencies between 4.4 kHz and 40 kHz
*/
const byte pin1 = 8;
const byte pin2 = 9;
int freqNr = 1; // harmonics
void setup() {
Serial.begin(9600);
Serial.println("\n" __FILE__);
Serial.println(F_CPU);
pinMode(pin1, OUTPUT);
pinMode(pin2, OUTPUT);
// set bit-0 to 1 and bit-1 to 0:
PORTB = B00000001;
DDRC = 255; // PORTC = output
}
void loop() {
setupTimer1(freqNr);
// select next frequncy:
freqNr++;
if (freqNr > 5) freqNr = 1;
delay(5000);
setupTimer1(0); // stop sound
delay(200);
}
ISR(TIMER1_COMPA_vect, ISR_NAKED) {
asm("push r0 \n\t"
"in r0, __SREG__ \n\t"
"push r0 \n\t"
"in r0,%0 \n\t"
// invert bit-0 and bit-1
// and all the other bits which are unused
"com r0 \n\t"
// unfortunately, "com PORTB" is not possible
"out %0,r0 \n\t"
"pop r0 \n\t"
"out __SREG__,r0 \n\t"
"pop r0 \n\t"
"reti"
: :"I" (_SFR_IO_ADDR(PORTB))
);
}
/*
ISR(TIMER1_COMPA_vect) {
static boolean v;
if (v)
PORTB = 1; else PORTB = 2;
v = !v;
}
00000524 <__vector_6>:
524: 1f 92 push r1
526: 0f 92 push r0
528: 0f b6 in r0, 0x3f ; 63
52a: 0f 92 push r0
52c: 11 24 eor r1, r1
52e: 8f 93 push r24
530: 9f 93 push r25
532: 80 91 cc 00 lds r24, 0x00CC
536: 88 23 and r24, r24
538: 79 f0 breq .+30 ; 0x558 <__vector_6+0x34>
53a: 81 e0 ldi r24, 0x01 ; 1
53c: 88 bb out 0x18, r24 ; 24
53e: 80 91 cc 00 lds r24, 0x00CC
542: 91 e0 ldi r25, 0x01 ; 1
544: 89 27 eor r24, r25
546: 80 93 cc 00 sts 0x00CC, r24
54a: 9f 91 pop r25
54c: 8f 91 pop r24
54e: 0f 90 pop r0
550: 0f be out 0x3f, r0 ; 63
552: 0f 90 pop r0
554: 1f 90 pop r1
556: 18 95 reti
558: 82 e0 ldi r24, 0x02 ; 2
55a: f0 cf rjmp .-32 ; 0x53c <__vector_6+0x18>
*/
void setupTimer1(int nr) {
if (nr == 0) {
TIMSK = 1; // TOIE0, stop ISR
PORTC = 0; // no frequency produced
return;
}
// indicate selected frequency:
PORTC = 1 << (nr - 1);
// convert to harmonics:
int nr1 = 11 - 2 * nr;
long freq = 40000 / nr1;
cli(); // stop interrupts
TCCR1A = 0; // set entire TCCR1A register to 0
TCCR1B = 0; // same for TCCR1B
TCNT1 = 0; // initialize counter value to 0
// set compare match register for
// 6001.500375093773 Hz increments
// = 16000000 / (1 * 6001.500375093773) - 1
// (must be <65536)
// correction factor:
// (the factor might be dependend on your chip)
freq = freq * 0.978;
word ocr1a = F_CPU / (freq * 2) - 1;
Serial.print(nr);
Serial.print("\t");
Serial.print(freq);
Serial.print(" Hz\t");
Serial.println(ocr1a);
OCR1A = ocr1a;
// turn on CTC mode
TCCR1B |= (1 << WGM12);
// Set CS12, CS11 and CS10 bits for 1 prescaler
TCCR1B |= (0 << CS12) | (0 << CS11) | (1 << CS10);
// enable timer compare interrupt
TIMSK |= (1 << OCIE1A); // modified for ATmega8
sei(); // allow interrupts
}
Part II: Analysing the overall transfer function. How about transmitter and receiver? Do their resonance frequencies match?To check this, this time we use an ARDUINO NANO, the transmitter and the receiver. To increase the resolution we alternatively use an AD9850 module to produce fine-tuned frequencies. The output is selected by a multiplexer (just a SN7400 will do) and sent to the transmitter, the voltage obtained at the receiver is sent to pin A0.
While the frequency is increased, the output voltage is printed either to the Serial plotter or to the Serial Monitor to insert it into an EXCEL sheet. See the results below the code.
/* check the transfer function
of ultrasonic transducers
generate the signal for the transmitter
either using TIMER1, CTC mode or AD9850
reading the receiver with analogRead
but setting the prescaler to /16
Received values either sent to plotter
or export to Excel.
All done in setup, loop remains empty */
//#define USE_TIMER1
// else use AD9850
//#define USE_PLOTTER
// else export to Excel
const byte select = A1;
#ifdef USE_TIMER1
const byte psc = 1;
const byte p1 = 9; // OC1A
#define SEL LOW
#else
#include <SPI.h>
// Arduino board FQ, frequency Update
const byte FQ = 10;
#define SEL HIGH
#endif
const byte receive = A0;
word from = 38000;
word to = 43000;
void setup() {
Serial.begin(115200);
Serial.println(__FILE__);
pinMode(select, OUTPUT);
digitalWrite(select, SEL);
// receive: prescaler = 16
ADCSRA = B10000000 + 4;
initGenerator();
for (word f = from; f < to; f = f + 20) {
#ifndef USE_PLOTTER
Serial.print(f);
Serial.print("\t");
#endif
setFrequency(f);
delay(50);
Serial.println(getAmp());
}
}
void loop() {}
int getAmp() {
const int N = 500;
int x[N];
delay(50);
for (int i = 0; i < N; i++)
x[i] = analogRead(A0);
int mi = 1000;
int ma = 0;
for (int i = 0; i < N; i++) {
ma = max(ma, x[i]);
mi = min(mi, x[i]);
}
return ma - mi;
}
void initGenerator() {
#ifdef USE_TIMER1
pinMode(p1, OUTPUT); // OC1A
// toggle OC1A on Compare Match:
TCCR1A = B01000000;
// CTC, prescaler of 1
// ICNC1 ICES1 – WGM13 +WGM12 CS12 CS11 CS10
TCCR1B = 8 + psc;
#else
SPI.begin();
SPI.setBitOrder(LSBFIRST);
pinMode(FQ, OUTPUT);
upDownFq();
#endif
}
void setFrequency(float f) {
#ifdef USE_TIMER1
word ocr1a = F_CPU / (2 * f * psc) - 1;
OCR1A = ocr1a;
/*
Serial.print(ocr1a);
Serial.print("\t");
/* */
#else
// transfer frequency and start DDS
f = f / 1000000 * 4294967295 / 125;
// for a 125 MHz crystal
union {
long y;
byte b[4];
} freq;
freq.y = f; // float to long
SPI.transfer(freq.b, 4);
SPI.transfer(0);
upDownFq();
#endif
}
void upDownFq() {
// set new frequency value
asm("sbi 5, 2");
asm("cbi 5, 2");
}
Now we use some ordinary hardware to produce the ultrasonic and use a standard ARDUINO to analyze what we receive. First, let's have a look at the generator:
The CMOS chip is a very old one but still available. It supports the crystal oscillator and delivers many frequencies, one of them is about 39 kHz at pin Q7. The other chip is a tricky one.
Let me try to make a 60 year story short: In 1962, long before anyone thought of personal computers, the RS232 standard was defined, specifying voltages up to +/- 15 volts, and when computers were built eventually, a voltage of +/- 12 volts was supported. But now most of the parts of the computer run on 5 volts, especially the USB ports. Hence, some additional expense had to be made to support this old standard. Special chips like MC1488 / MC1489 had to be added and the power supply expanded. In 1987, Charlie Allen of Maxim Integrated designed a chip called the MAX232, which replaced the MC1488 / 89 and made the additional power supply superfluous (yes, the same Charlie Allen, who later became famous for proposing Charlieplexing). Years later, some clever engineers repurposed this chip in order to gain a higher voltage to drive ultrasonic transducers, and used it to develop the distance sensor SR-HC04. A nice guy, whose name is Emil, did some reverse engineering and published it at http://uglyduck.vajn.icu/HC-SR04E. The two transmitter channels are controlled in push-pull mode, so you need one output for one channel and another one being inverted for the other. Now the good news: Mr. Allen has also built in two receiver stages that invert the signal. When using one of these receivers, you only need one output to control the transmitters in push-pull mode.
By using this trick we get a very powerful ultrasonic signal. Without that chip the amplitude was only from 0 to 5 volts. Using the MAX232 the amplitude is form -15 volts to 15 volts.
At the receiver side we do not even need to amplify it, we can attach the receiver directly to an ARDUINO analog input (no picture to be shown). At zero distance the signal is so powerful that we get the full range from 0 to 1023. Even at a distance of ten or more inches you can still detect the signal, of course at a much lower level.
[code]
/*
analyzes ultrasonic input A0
determine count of maxima
can also performed with ATmega8
be aware that start of ATmega8 will take a few seconds
*/
void setup() {
Serial.begin(115200);
ADCSRA = 128 + 1;
}
#if defined(__AVR_ATmega8__)
const int N = 360;
#else
const int N = 500;
#endif
int a[N];
void loop() {
long t1 = micros();
for (int i = 0; i < N; i++)
a[i] = analogRead(A0);
long t2 = micros();
int cnt = 0;
int b0 = a[0];
int b1 = a[1];
for (int i = 2; i < N; i++) {
int b2 = a[i];
if ( (b0 < b1) && (b2 <= b1) ) cnt++;
b0 = b1;
b1 = b2;
}
for (int i = 0; i < N; i++) {
Serial.print("0 1000 ");
Serial.println(a[i]);
}
Serial.println("------------------");
Serial.println(t2 - t1);
Serial.println(cnt);
Serial.println("------------------");
delay(1000);
}
[/code]
You can use this code to watch the received signal on the Serial plotter or check the data on the Serial monitor.
Depending on the prescaler value inserted in the ADCSRA register you get different diagrams. Be aware that you need to sample at least with twice the frequency of the signal to analyze, so only prescalers of 1, 2, and 3 are valid (Shannon/Nyquist theorem).
With prescaler 1, you should get a count of 72 maxima in a time interval of 1860 microseconds equal to 0.001860 seconds, giving you a frequency of 72/0.001860 = 38709 Hz which is not far from what we expected.
Unfortunaly, I need to make a sad comment: connecting the ultrasonic receiver to A0 and GND worked on most of my ARDUINOs, but not on all of them. Just a few were working fine in normal applications but would not work with the ultrasonic receivers. The same results with some old ATmega8. So, if you build the project, make sure you have some spare ARDUINOs.
Comments
Please log in or sign up to comment.