Ultraschall Entfernungsmessung

Ein Artikel von Alexander Starke

Ziel dieses Projektes ist die Realisierung einer Entfernungsmessung mit Ultraschall. Auf das Erläutern eventuell auftauchender Probleme sowie allgemeine Erklärungen wird hier verzichtet, diese Betrachtungen haben bereits hier statt gefunden.

Als Baugruppen werden Standardbauteile verwendet. Als Sender bzw. Empfänger dienen die Module UST-40T bzw. UST-40R, welche man über gängige Elektronik-Versender erhalten kann. Die Frequenzerzeugung für den Sender erfolgt mittels PWM durch einen Mikrocontroller. Die Auswertung des Empfangssignals erfolgt beispielsweise über den AD-Wandler des selben Mikrocontrollers.

VARIANTE 1

Sender

Somit ist der Senderteil an sich einfach, da nur per PWM ein 40kHz-Signal erzeugt werden muss, welches dann auf den Sender gegeben werden kann. Die Signalerzeugung sieht bei mir codetechnisch folgendermaßen aus:

Code:
void _40khz_init (void) {
    TCCR1A = _BV(COM1A1) | _BV(COM1A0) | _BV(WGM11);
    TCCR1B = _BV(WGM12) | _BV(WGM13) | _BV(CS10);
    OCR1A = 100;
    ICR1 = 200;
}
Die Funktion generiert durch einen Timer-das notwendige Signal. Durch "toggeln" (also An- bzw. Ausschalten) des Datenrichtungsregister-Bits für PB1 können so kurze Ultraschall-Impulse gesendet werden.

Es gilt jedoch zu experimentieren, wie man die Sendereichweite auf ein ordentliches Maß bringen könnte. Hierzu ergeben sich theoretisch verschiedene Möglichkeiten.

  • Man gibt das PWM-Signal direkt auf den Sender, dessen zweiter Pin liegt auf Masse. Er wird nun ständig zwischen 0 und 5V hin und her geschalten. Dies ist die von der Senderstärke her schwächste Variante.
  • Man legt das PWM-Signal an die Basis eines Transistors, welcher kollektorseitig an 12V (Batterie- oder Netzteilspannung) angeschlossen ist. An den Transistor ist mit dem Emitter in Reihe der Sender angeschlossen. Das Prinzip bleibt das selbe wie bei der obigen Variante, nur die Leistungsstärke hat zugenommen.
  • Man versucht ein Wechselsignal zu erzeugen. Hierzu muss auf irgendeine Art und Weise das PWM-Signal des MC invertiert werden. Nun wird an den einen Pin des Senders das normale PWM-Signal gegeben, an den anderen das invertierte Signal. Man erhält so eine Wechselspannung am Sender mit einer Spitze-Spitze-Spannung von etwa 10V. Diese Variante sollte die obigen beiden leistungsmäßig übersteigen.
  • Dasselbe wie im vorigen Beispiel, nur das zusätzlich noch zwei Treibertransistoren eingefügt werden. Es ergibt sich eine Spitze-Spitze-Spannung von 24V. Ob der Sender diese Spannungen unbeschädigt übersteht gilt es auszutesten


Die Erzeugung des invertierten Signals kann entweder in Software oder über ein Logik-Gatter erfolgen. Geeignet wären beispielsweise NAND oder NOR. Da ich zufällig noch den Schaltkreis MOS4001 herumliegen habe, werde ich diesen verwenden. Hierbei handelt es sich um einen IC, welcher 4 NOR-Gatter mit je zwei Eingängen beinhaltet. Legt man bei einem NOR an beide Eingänge eine '1', so ergibt sich am Ausgang eine '0' und umgekehrt. Schematisch sähe der Anschluss des Senders dann so aus:



Empfänger

Nun zum nicht ganz so einfachen Empfängerteil. Wird ein Signal detektiert, so erhält man an den Klemmen des Empfängers ein relativ schwaches Wechselsignal. Dieses direkt auszuwerten ist nahezu unmöglich, da der AD-Wandler nicht für Wechselwerte geeignet ist sowie das Signal reichlich schwach ist. Es muss also gleichgerichtet werden. Bevor man dies machen kann, muss es verstärkt werden, da über der Gleichrichterdiode sofort wieder etwa 0,7V Flussspannung verloren gehen. Hierfür eignet sich der Schaltkreis TL084. Dieser stellt intern 4 OPVs zur Verfügung.

Nun immer der Reihe nach:

Signalverstärkung und Herausfiltern von Störungen

Die Signalverstärkung wird über die beiden angeschlossenen Widerstände vorgenommen und ergibt sich zu V = R2/R1. Der Kondensator C2 über R2 bildet einen Tiefpass, der Kondensator vor R1 einen Hochpass. Somit wurde insgesamt ein Bandpass realisiert, welcher mehr oder weniger selektiv das Nutzsignal (mit 40kHz) verstärkt.


An den nicht invertierenden Eingang wird Vcc/2 gelegt, da ein Wechselsignal verstärkt werden soll und keine +/- 5V zur Verfügung stehen. Das Signal ist nun etwa um den Faktor 14 verstärkt.

Erneute Signalverstärkung und Filterung

Selbes Funktionsprinzip wie oben. Verstärkung des Signals um den Faktor 21.



Gleichrichtung und Glättung

Das nun ausreichend verstärkte Wechselsignal muss zur Auswertung noch gleichgerichtet und geglättet werden. Die Gleichrichtung wird durch eine Diode vorgenommen, die Glättung durch einen Tiefpass.


Last but not least ...

Zuletzt wird das Signal noch auf einen OPV gegeben, welcher als Spannungsfolger geschalten ist. Diese Maßnahme dient lediglich dem Abkoppeln des Signal-Out-Pins von der restlichen Schaltung. Das Signal wird um den Faktor 1 verstärkt.


Eine experimenteller Versuchsaufbau hat nun folgende Daten zutage gefördert. Mit ausgeschaltetem Sender liegt am Ausgang der Empfängerschaltung immer eine Spannung von 1,850V an. Diese ist als Offset zu betrachten. Bewegt man nun den Sender über dem Empfänger, so erhält man einen Minimalwert von 1,35V. Dieser ist aufgrund der Richtungswirkung des Senders (Ultraschallkeule) stark positionsabhängig. Typische Werte am Empfängerausgang sind etwa 1,5-1,7V. Alle gemessenen Werte beziehen auf den ersten Punkt zum Thema Senderaufbau, welcher oben bereits dokumentiert wurde. Eine Zuführung des invertierten Signals brachte leider keine wesentliche Verbesserung.

Nachdem der Aufbau soweit geklärt ist, kann sich jetzt an die Programmierung gemacht werden.

Programmierung

Nachdem Sender und Empfänger nebeneinander auf einer oder mehrerer Platinen angeordnet wurden, kann man sich nun an die Ultraschalldistanzmessung begeben. Zu beachten ist lediglich, dass sich die beiden Gehäuse nicht berühren. Man kann zusätzlich noch etwas Plaste, Metall, o.ä. zwischen ihnen befestigen, um ein direktes Übersprechen möglichst zu verhindern.

Wie die Erzeugung des 40kHz-Signals erfolgt wurde oben bereits angegeben. Nun muss ein kurzer Schallimpuls erzeugt werden. Dieser sollte in etwa 2-3ms lang sein und kann mittels Timer oder delay-Schleife gesteuert werden. Das Prinzip ist trivial, da man einfach den entsprechenden Pin als Ausgang deklariert, kurz wartet und ihn dann wieder als Eingang definiert.
Da alle Operationen von einem MC ausgeführt werden sollen und der Empfänger die Laufzeit des Signals soundso exakt bestimmen muss, wird ein Timerinterrupt verwendet. Es wird Timer0 verwendet.
Die Schallgeschwindigkeit beträgt bei Raumtemperatur etwa 344 m/s. Bei einer Pulslänge von 2ms hat die Spitze der Schallwelle bei Beendigung des Impulses bereits 0,68m zurück gelegt. Somit ist der Sensor nur für Distanzen größer 35-40cm geeignet. Sollen geringere Distanzen ausgewertet werden, muss der Schallimpuls entsprechend kürzer gestaltet werden. Je kürzer dieser allerdings gemacht wird, desto größer sollte die Sendeleistung sein, wenn man noch etwas zum Auswerten haben möchte.

Ich habe das ganze mal testweise implementiert und es stellte sich die gewünschte Funktionalität ein. Allerdings sind die Messwerte mit einem gewissen Rauschen unterlegt, so dass sich eine Mehrfachmessung und/oder Mittelwertbildung empfiehlt. Hier nun der Sourcecode.


Code:
#include <avr/io.h>
#include <avr/signal.h>
#include <avr/interrupt.h>
#include <stdlib.h>

#define F_CPU 8000000
#define USART_BAUD_RATE 9600
#define USART_BAUD_SELECT (F_CPU/(USART_BAUD_RATE*16l)-1)

volatile unsigned char pulse = 0;
volatile unsigned char rec_buffer [400];
volatile unsigned char send_in_progress = 0;
volatile unsigned short i = 0;
volatile unsigned char receive_complete = 0;

void delay(unsigned short us) {
    while ( us ) us--;
}

void USART_transmit (unsigned char c) {
	while (!(UCSRA & (1<<UDRE))) {}
	UDR = c;
}

void USART_transmit_string (unsigned char *string) {
    while (!(UCSRA & (1<<UDRE))) {}
	while ( *string)
		USART_transmit (*string++);
}


SIGNAL (SIG_OVERFLOW0) {		//2ms Pulslänge erzeugen
	if (pulse) {
		DDRB |= _BV(PB1);
		pulse = 0;
		return;
	}
	else {
		DDRB &= ~_BV(PB1);
		TCNT2 = 0;
		TIMSK |= _BV(TOIE2);
		TIMSK &= ~_BV(TOIE0);
	}
}

SIGNAL (SIG_OVERFLOW2) {
	ADCSRA |= _BV(ADSC);
	while (ADCSRA & _BV(ADSC));
	rec_buffer [i] = ADC;
	ADC = 0;
	i++;
	if (i == 400) {
		i = 0;
		receive_complete = 1;
		TIMSK &= ~_BV(TOIE2);
	}
}

void US_init (void) {

	//40kHz Signal
	TCCR1A = _BV(COM1A1) | _BV(COM1A0) | _BV(WGM11);
	TCCR1B = _BV(WGM12) | _BV(WGM13) | _BV(CS10);
	OCR1A = 100;
	ICR1 = 200;
	//Zeitbasis für Senderroutine
	TCCR0 = _BV(CS00) | _BV(CS01);    					//ca. 500Hz = 2ms            
    //TIMSK |= _BV(TOIE0);
	//Zeitbasis für Senderroutine
	TCCR2 |= _BV(CS20);
    //TIMSK |= _BV(TOIE2);
	//AD-Initialisierung
	ADCSRA =  _BV(ADEN) |  _BV(ADPS2);					//Prescaler 16 --> 500kHz --> 2us
	ADMUX =  _BV(REFS0) | _BV(MUX2) | _BV(MUX0);		//PC5 - 8bit Auflösung
	//USART_init
	UCSRB |= _BV(TXEN);
    UBRRL = (unsigned char) USART_BAUD_SELECT;
	sei ();
}

void send_pulse (void) {
	if (!send_in_progress) {			//entspricht einer Tasterentprellung
		send_in_progress = 1;
		pulse = 1;
		TCNT0 = 0;
		TIMSK |= _BV(TOIE0);
	}
}

int main (void) {
	unsigned short j = 0;
	unsigned char buffer [4];
	US_init ();
	USART_transmit_string ("Ultraschall:");
	USART_transmit ('\n');
	USART_transmit ('\r');
	for (;;) {
		if (!(PINB & _BV(PB0))) {
			send_pulse ();
		}
		if (receive_complete) {
			for (j=0; j<400; j++) {
				itoa(rec_buffer [j], buffer, 10);	
				USART_transmit_string (buffer);
				delay (100);
				USART_transmit('\n');
				delay (100);
				USART_transmit('\r');
				delay (100);
			}
			send_in_progress = 0;
			receive_complete = 0;
		}
	}
	return 0;
}

Das Programm macht im wesentlichen folgendes:

  • Bei Tasterdruck wird Schallpuls ausgesendet. (mittels Timer0)
  • Mit Ende des Schallimpulses wird Timer0-Interrupt deaktiviert und Timer2-Interrupt aktiviert.
  • Nun wird mit einer Frequenz von 8MHz/256=31,25kHz abgetastet, also alle 32μs ein neuer AD-Wert aufgenommen.
  • Alle Werte werden in ein Array geschrieben.
  • Nach 400 Werten, also 12,8ms (entspricht einer Distanz von etwa 2,20m) wird Timer2-Interrupt wieder deaktiviert.
  • Nun werden die Daten über den USART ausgegeben und können beispielsweise im Excel visualisiert werden.


Bei auf dem Schreibtisch liegendem Sensor ergab sich für einen Schallimpuls Richtung Decke das folgende Bild:


Nimmt man Datenpunkt 250 als Mitte des Echos an, so ergibt sich eine Signallaufzeit von 250*(1/(8MHz/256))= 8ms. Dies entspricht einer Distanz von s = 0,5*v*t = 0,5*340*0,008 = 1,36m. Addiert man nun noch die 2ms des Impulses dazu (es wird ja erst nach dessen Aussenden mit der Abtastung begonnen), so ergibt sich eine Distanz von 1,7m, welches im Prinzip der exakte Wert ist.

Für einen Impuls von 1ms Länge ergibt sich das folgende Bild:



Man sieht, dass das Echo nun nur noch 1ms lang ist, eine Auswertung kann aber noch vorgenommen werden.

Als Mögliche Verbesserungen fallen mir diverse Punkte ein. Beispielsweise könnte man eine feinere Abtastung des empfangenen Signals vornehmen. Dann muss angefangen werden, zwischen Echos und dem real reflektierten Signal zu unterscheiden. Auch kann man sicherlich an der Schaltung (Verstärkungen) und dem allgemeinen Layout noch Optimierungen vornehmen.
Sobald ich entsprechende Fortschritte gemacht habe, werden diese natürlich hier veröffentlicht.

VARIANTE 2

Alternativ habe ich noch eine zweite Variante zur Distanzmessung anzubieten. Diese basiert nicht auf einer dauernden AD-Wandlung des empfangenen Signals sondern ähnelt mehr einem Schwellwert-Schalter.

Das Signal wird zunächst mit Hilfe von zwei Verstärkerstufen (OPVs) verstärkt. Danach folgt ein dritter OPV, dessen Widerstand am Eingang als Poti realisiert ist(50K). Der Widerstand im Rückkopplungszweig hat einen Wert von 50k. Es lässt sich somit eine Verstärkung zwischen 1 und 50 einstellen. Der Ausgang des 3. OPV geht direkt auf die Basis eines npn-Transistors. Zusätzlich wird noch mittels eines weiteren Potentiometers eine Offsetspannung auf die Basis gegeben, um das Umschalten zu unterstützen. Kommen als zu der Offsetspannung noch die Signalspitzen, so schaltet dieser durch. Diesen Vorgang kann man mittels eines angeschlossenen Mikrocontrollers detektieren.
Wiederum ergibt sich die Distanz aus der Zeit zwischen dem Senden des Impulses und dem Schalten des Transistors.



Die Senderoutine kann im Prinzip komplett von Variante 1 übernommen werden.

Mit dem Start der Senderoutine wird gleichzeitig ein Timer gestartet, in welchem eine Zählvariable inkrementiert wird. Trifft der Impuls ein (Erkennung bspw. durch externen Interrupt), so kann der Timer gestoppt werden und die Zählvariable ausgewertet werden. Ihre Größe ist ein direktes Maß für die Distanz. Überschreitet sie einen bestimmten Wert, so kann die Messung als gescheitert betrachtet werden und der Zähler muss zurück gesetzt werden.
Natürlich können mehrere Interrupts auftreten, für deren Auswertung es wieder einen geeigneten Algorithmus zu finden gilt.

Eine simple Implementierung ohne weitere Auswertealgorithmen sieht so aus:

Code:
#include <avr/io.h>
#include <avr/signal.h>
#include <avr/interrupt.h>
#include <stdlib.h>
#include <lcd.c>

volatile unsigned char count = 0;
volatile unsigned short timecount = 0;
volatile unsigned char send_in_progress = 0;
volatile unsigned char rec_complete = 0;

void US_init (void);			//Erzeugung eines 40kHz-Signales
void send_pulse (void);		//sendet Impuls
void auswertung (void);

SIGNAL (SIG_OVERFLOW0) {			//alle 0,032ms
    if (count == 64)				//2ms langer Impuls
		DDRB |= _BV(PB1);
	if (count != 0)
		count--;
	if (count == 0)				//2ms um, Ende des Impulses
		DDRB &= ~_BV(PB1);
	if (send_in_progress)
		timecount++;				//Auszählen der Laufzeit
	if (timecount > 500) {
		timecount = 0;
		send_in_progress = 0;
		rec_complete = 1;
	}
}

SIGNAL (SIG_INTERRUPT0) {
	send_in_progress = 0;
	rec_complete = 1;
}

void US_init (void) {

	//40kHz Signal
	TCCR1A = _BV(COM1A1) | _BV(COM1A0) | _BV(WGM11);
	TCCR1B = _BV(WGM12) | _BV(WGM13) | _BV(CS10);
	OCR1A = 100;
	ICR1 = 200;
	
	//Zeitbasis für Sender- und Empfängerroutine
	TCCR0 |= _BV(CS00);                
    TIMSK |= _BV(TOIE0);
	
	//ext. Interrupt
	MCUCR |= _BV(ISC01);
	GICR |= _BV(INT0);
	
	
}

void send_pulse (void) {
    count = 64;
    send_in_progress = 1;
    TCNT0 = 0;
    sei ();
}

void auswertung (void) {
	unsigned char buffer [5] = {0,0,0,0,0};
	lcd_clrscr ();
	lcd_gotoxy (0,0);
	lcd_puts ("timecount: ");
	itoa (timecount, buffer, 10);			//ausgegebene Zahl * 0,032ms = Laufzeit
	lcd_puts (buffer);						//Strecke = 0.5 * 340 * Laufzeit
}

int main (void) {
	US_init ();
	lcd_init (LCD_DISP_ON);
	for (;;) {
		if (!(PINC & _BV(PC4)))			//wenn Taster an PC4 gedrückt, dann
			send_pulse ();					//mache Distanzmessung
		if (rec_complete) {
			cli ();
			auswertung ();
			rec_complete = 0;
		}
	}
	return 0;
}

Der Artikel wurde bereitgestellt von Alexander Starke
Homepage des Autors http://www.mc-project.de/