Forum: Mikrocontroller und Digitale Elektronik Ultraschall Modul HCSR04, falsche Auswertung


von Mario E. (Gast)


Lesenswert?

Hallo,

nach Monaten mitlesen, muss ich jetzt doch tatsächlich mal selbst eine 
Frage stellen, weil ich nicht weiter komme.

Wie im Betreff schon erwähnt, geht es um das HC SR04.

Das Problem ist, dass ich bei 10cm Abstand auch 10cm angezeigt bekomme.
Ich habe mich an dem Datenblatt

http://kt-elektronic.de/wawi11/artikeldaten/sen-hr_sr4-e1/ultraschallmodul_beschreibung_3.pdf

entlang gehangelt,

Aber bei 20cm Abstand zeigt mein Display 25 cm an.
bei 30 sind es 45
bei 40 sind es 63
bei 50 sind es 83
vom Tisch zur Decke sind es 140 cm angezeigt werden 260 cm

Echo ist an PA0 als Interrupt Eingang
Trigger ist an PC0 angeschlossen.
Ich nutze einen Atmega 1284P und das AtmelStudio 6

Ich bin mir etwas unsicher mit den Umrechnungen aber ich denke, dass da
das Problem liegt allerdings erkenne ich es nicht.

Der Code schaut folgendermassen aus:
1
#define  F_CPU 16000000
2
#include <avr/io.h>
3
#include <util/delay.h>
4
#include <stdlib.h>
5
#include <avr/interrupt.h>
6
#include "lcd-routines.h"
7
8
char lcd[4];
9
uint8_t con = 0;
10
11
int a1;
12
int a2;
13
int a3;
14
15
int main (void)
16
{
17
  DDRC  = 0b00011101;  
18
  PORTC = 0b10000011;
19
  
20
  PCMSK0 |= (1<<PCINT0);     //Interrupt control
21
  PCICR  |= (1<<PCIE0);      //Interrupt control
22
  sei();
23
  
24
   TCNT1   = 0;
25
   
26
  lcd_init();
27
  
28
  while (1)
29
  {    
30
      _delay_ms(100);                        
31
      PORTC &= ~(1<<PC0);    // 
32
      _delay_us(15);         //  -> Löst die Messung des HCSR04 aus.
33
      PORTC |= (1<<PC0);     // 
34
35
    
36
//Jetzt löst der Interrupt aus, weil das Modul den Echo auf high setzt.
37
38
39
    if (con > 1)    // Da der Interrupt 2 mal ausgelöst wird bis
40
                    // die Messung beendet ist, nutze ich den Zähler um
41
                    // nur jedes 2te Mal umzurechnen und auszugeben. 
42
    
43
    {  
44
      a1 = (TCNT1/0.0625);  //Umrechnung auf  µs 
45
      a2 = ((0.0341*a1)/2);  // Umrechnung auf cm
46
            
47
      itoa(a2,lcd,10);
48
      
49
      lcd_setcursor(0,0);
50
      lcd_string("    ");
51
      lcd_setcursor(0,0);
52
      lcd_string(lcd);
53
      lcd_string(" cm");
54
      
55
      con   = 0;
56
      TCNT1 = 0;
57
    }  
58
  }  
59
}
60
61
ISR(PCINT0_vect)
62
{
63
  TCCR1B ^= (1<<CS12); // Teiler 256
64
  con++;  
65
}


Ich würde mich freuen, wenn mit jemand helfen könnte.

mfg mario

: Bearbeitet durch Moderator
von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

1
int a1;
2
int a2;
3
  :
4
  :
5
      a1 = (TCNT1/0.0625);  //Umrechnung auf  µs 
6
      a2 = ((0.0341*a1)/2);  // Umrechnung auf cm
Du traust dich was...

Mario E. schrieb:
> TCCR1B ^= (1<<CS12); // Teiler 256
Was soll denn das?
Welchn Sinn hat es, mitten im Zählablauf den Vorteiler umzuschalten?

Aber auch der Ablauf ist eher kurios. Ich würde es etwa so machen:
1
volatile int response;
2
:
3
:
4
 while (1) {
5
   :
6
   :
7
   if (respone) {  
8
      // Berechnung überarbeiten!
9
      a1 = (TCNT1/0.0625);   // Umrechnung auf  µs 
10
      a2 = ((0.0341*a1)/2);  // Umrechnung auf cm
11
      :
12
      response = 0;
13
   }  
14
   :
15
   :  
16
ISR(PCINT0_vect)
17
{
18
  if PORTC&1 // steigende Flanke --> Start
19
     TCNT1 = 0;
20
  else       // fallende Flanke  --> Ende
21
     response = TCNT1;  
22
}

Mario E. schrieb:
> uint8_t con = 0;
con sollte volatile sein, denn sonst darfst du keine Optimierung 
einschalten und deine Delay-Funktionen passen nicht...

Mario E. schrieb:
> bei 10cm Abstand auch 10cm
> Aber bei 20cm Abstand zeigt mein Display 25 cm an.
> bei 30 sind es 45
> bei 40 sind es 63
> bei 50 sind es 83
> vom Tisch zur Decke sind es 140 cm angezeigt werden 260 cm
Dir ist schon klar, dass der Schall den Weg 2x zurücklegen muss?
Und schwuppdiwupp hast du nur noch ein Problem ganz am Anfang:
10 --> 10 --> 5
20 --> 25 --> 12
30 --> 45 --> 22       (der kleine Rundungstrick hier und ...
40 --> 63 --> 32        hier sei erlaubt, dann passt es besser ;-)
50 --> 83 --> 42
140 -> 260 --> 130
Addiere jetzt einfach mal überall rechts 8cm drauf und was passiert?
10 --> 13
20 --> 20
30 --> 30
40 --> 40
50 --> 50
140 -> 138
Na?
Die 8cm kommen übrigens von dem seltsamen Starten des Timers in deiner 
SW...

: Bearbeitet durch Moderator
von Karol B. (johnpatcher)


Lesenswert?

Mario E. schrieb:
> a2 = ((0.0341*a1)/2);  // Umrechnung auf cm

Im Datenblatt ist übrigens eine Näherungsformel angegeben, die deinen 
Mikrocontroller wohl deutlich weniger fordert, weil sie sich auf 
Ganzzahlen beschränkt.

> You can calculate the range through the time interval between sending
> trigger signal and receiving echo signal. Formula: uS / 58 = centimeters
> or uS / 148 =inch;

Mit freundlichen Grüßen,
Karol Babioch

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Lothar Miller schrieb:
> Ich würde es etwa so machen:  ...
In diesem Code läuft der Timer immer weiter und wird einfach bei der 
steigenden Flanke des Sensorsignals auf 0 gesetzt. Bei der fallenden 
Flanke wird der Timerwert in die Variable response übernommen.

response ist normalerweise 0, und enthält nur im Fall einer Antwort 
einen Wert. Das ist wichtig für die Abfrage mit
if (response) {
Diese Abfrage ist funktionell gleich mit
if (response!=0) {
Denn eine if-Abfrage in C ist immer eine Abfrage auf "ungleich Null". 
Ergo steht da: wenn ein Wert ungleich 0 in response steht, dann werte 
diese Variable aus und setze sie anschließend wieder auf 0 zurück.

Und jetzt noch ein wort zur Berechnung:
      a1 = (TCNT1/0.0625);  //Umrechnung auf  µs
"geteilt durch 0,0625" ist das selbe wie "mal 16"
Und "mal integer" ist eine ungleich viel einfachere Rechenoperation für 
einen uc als "geteilt durch float".

Aber es geht noch einfacher:
wenn du die "mal 16" und die "geteilt durch 58" zusammenfasst, dann 
kommt nur noch ein
a2 = (response*16)/58;
heraus.
Bleibt die Frage: woher kommt eigentlich die 58?
Ganz einfach: du rechnest a2 = ((0.0341*a1)/2) und 0.0341/2 sind 58,651. 
Da ist also ein spürbarer Rundungsfehler (fast 1%). Aber genauer wird 
das wegen der variablen Schallgeschwindigkeit (Temperatur) eh' nicht...

Wobei mir auffällt: in der Formel ist die Halbierung der Strecke schon 
drin. Ist evtl. noch dein Timer falsch eingestellt?

: Bearbeitet durch Moderator
von Mario E. (marioe)


Lesenswert?

Hallo,

schönen Dank für die Hilfe, es funktioniert jetzt.
Auch wenn ich den ganzen Vormittag kämpfen musste, weil einige Dinge 
immernoch nicht funktioniert hatten.

Ich habe Ihre ISR an meine gewohnte Struktur angepasst, da es 
funktioniert schätze ich, dass es nur eine andere Schreibweise ist.

Ich hatte im ersten Versuch am Anfang des Programms ein Delay eingefügt, 
damit das Display nicht so schnell aktualisiert. Dieses Delay hat aber 
die Messung verfälscht.
Nun habe ich das Delay für das auslösen des Messvorgangs verlängert und 
nun funktioniert es.

Warscheinlich gibts da auch elegantere Möglichkeiten aber beim Versuch 
eine Variable per ISR hochzuzählen und mit IF abzufragen bekomme ich 
wieder Messfehler.

Wenn ich das jetzt richtig Verstanden habe, rechnet con*16 die Takte die 
der Timer zählt in ms um und /58 rechnet das Ergebnis in cm um.
Somit muss ich nur die 16 ändern, wenn ich einen anderen Timer oder uc 
mit anderem Takt benutzten möchte.

Super, ich freu mich jedenfalls das es klappt. Danke nochmal.


Hier nochmal mein Code:
1
#define  F_CPU 16000000
2
#include <avr/io.h>
3
#include <util/delay.h>
4
#include <stdlib.h>
5
#include <avr/interrupt.h>
6
#include "lcd-routines.h"
7
8
char         lcd[4];
9
volatile int con;
10
int          a1;
11
12
13
int main (void)
14
{
15
  DDRC  = 0b00011101;  
16
  PORTC = 0b10000011;
17
18
  PCMSK0 |= (1<<PCINT0);      //Interrupt control
19
  PCICR  |= (1<<PCIE0);      //Interrupt control
20
  sei();
21
22
  TCCR1B |= (1<<CS12);      // Teiler 256
23
   
24
  lcd_init();
25
  
26
  while (1)
27
  {                                
28
      PORTC &= ~(1<<PC0);    // 
29
      _delay_ms(100);        //  -> Löst die Messung des HCSR04 aus.
30
      PORTC |= (1<<PC0);     //   
31
        
32
//Jetzt löst der Interrupt aus, weil das Modul den Echo auf high setzt.
33
    
34
    if (con)
35
    {      
36
      a1 = ((con*16)/58);
37
        
38
      itoa(a1,lcd,10);
39
        
40
      lcd_setcursor(0,0);
41
      lcd_string("       ");
42
      lcd_setcursor(0,0);
43
      lcd_string(lcd);
44
      lcd_string(" cm");
45
        
46
      con = 0;
47
    }    
48
  }  
49
}
50
51
ISR(PCINT0_vect)
52
{  
53
  if (bit_is_set(PINA, 0))
54
  {  
55
    TCNT1  = 0;
56
  }
57
   else
58
  {
59
    con = TCNT1;
60
  }
61
 }

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Mario Eckel schrieb:
> Wenn ich das jetzt richtig Verstanden habe, rechnet con*16 die Takte die
> der Timer zählt in ms um
Abgesehen vom Typo (ms statt us) stimmt das, wenn du eine Zählfrequenz 
von 1/16us also 62,5kHz hast. Sowas bekonnst du z.B. wenn du einen 16MHz 
hättest und den durch 256 teilst:
> #define  F_CPU 16000000
> TCCR1B |= (1<<CS12);      // Teiler 256

Mario Eckel schrieb:
> Somit muss ich nur die 16 ändern, wenn ich einen anderen Timer oder uc
> mit anderem Takt benutzten möchte.
Du musst den Wert 16 UND den Vorteiler vom Timer so anpassen, dass du 
auf 1us kommst.
Bei 16MHz wäre ein toller Vorteiler z.B. 16. Denn dann müsstest du gar 
nichts umrechnen.

Oder noch besser wäre bei 16MHz ein Vorteiler von 940, denn dann 
müsstest du gar nichts mehr rechnen, sondern hättest im Zähler gleich 
den Abstand in cm.

von Mario E. (marioe)


Lesenswert?

Hallo und nochmals danke für die Hilfe.

Inzwischen bin ich dabei, eine Wassersteuerung aufzubauen, die mehrere 
Regenwassertonnenstände ausmisst und Ventile und Pumpen steuert, sodass 
für die Toiletten im Haus kein Frischwasser mehr gebraucht wird.

Ich möchte allerdings noch eine Erfahrung teilen, die ich quasi eben 
gerade gemacht habe und geschlagene 3 Tage brauchte um das Problem zu 
lösen.

Ich habe dieses mal einen Atmega 1284P benutzt weil ich ein Grafisches 
LCD ansteuern wollte.
Ich habe das Programm weitgehend wie oben benutzt, nur das ich anstatt 
die INT eingänge die PCINT´s benutzt habe.

Das Problem war, dass ab ca. 60 cm nur noch misst gemessen wurde, es 
sprang dann irgendwo von 50 - 70 hin und her.
Ich habe alles ausprobiert, mit den Timern und den Ext. Interrupts und 
ich währ beinahe wahnsinnig geworden.

Und was war die Lösung des Problems? Genau, Kondensator direkt an den 
HCSR04 und schwubbs war ruhe. Je größer der Kondensator, desto weiter 
konnte ich messen.

Das Problem hatte ich mit meinem Atmega8 den ich anfangs benutzt hatte 
nicht. Aber mit dem 168-20PU den ich zum testen raus gekramt habe auch.

Naja jetzt funktionierts wieder und es kann weiter gehen.
Ich habe irgendwo gelesen, das jemand auch das Problem hatte, dass er 
nur begrenzt gemessen hat. Ich hoffe das hier hilft den einen oder 
anderen noch.

mfg mario

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.