Forum: Mikrocontroller und Digitale Elektronik HC-SR04 Ultraschallsensor + 7-Segment Anzeigen Problem mit Ausgabe wenn kein Objekt detektiert wird


von Markus E. (opc)


Angehängte Dateien:

Lesenswert?

Hallo Community,

wie man hier im Forum lesen kann haben sich schon einige mit dem HC-SR04 
Ultraschallsensor befasst. Ich habe diese Idee aufgegriffen und mit drei 
7-Segment LED Anzeigen als Anzeige des gemessenen Wertes aufgebaut.

Die Messung wird im Bereich von ca. 3-300cm erfasst und im Bereich bis 
299cm ausgegeben. Problem ist wenn kein Objekt detektiert wird und ich 
die PORTS (in dem Fall A-C) auf 0 setze, wird ein Wert (04 und 05 meist 
im Wechsel) ausgegeben.

Die PORTS werden nach Abfrage nach der Entfernung zwar noch auf 0 
gesetzt, falls der Wert außerhalb des Bereichs ist, jeodch im delay von 
(hier im Code 150ms) zeigt er die unrealistischen Werte (04 oder 06) auf 
den LED-Segmentanzeigen an.

Ich bräuchte an der Stelle einen kleinen Denkanstoß, da ich nach einigen 
Stunden und Nerven es nicht hinbekommen habe, dass diese Werte trotz 
abgefragter Bedingungen nicht mehr ausgegeben werden. Vlt. hab ich auch 
einfach einen Denkfehler...ich streite es nicht ab^^

Ich hänge den Code an, es kann jedoch sein, dass die letzte if-anweisung 
nicht mehr ganz komplett ist durch die viele Testerei.

von Markus E. (opc)


Lesenswert?

Edit: ich hatte noch #include <stdint.h> eingebunden.
Jedoch besteht der Fehler weiterhin, dass die Fehlmessung des 
Ultraschallsensors auf den Segmenten als unrealistische Werte ausgegeben 
werden. Im Prinzip sollen nur Werte zwischen 1 und 300 ausgewertet 
werden und der Rest ignoriert werden, funktioniert aber nicht.

bin um jede Idee dankbar.

von TestX (Gast)


Lesenswert?

Hast du dir mal das Datenblatt genau durchgelesen, was in dem Fall 
passiert, wenn kein Echo zurückkommt ?
Das Modul gibt dann einen "langen" Puls als "Fehler" zurück - die 
Pulsmessung per Timer-Interrupt darf dabei nicht überlaufen.

von Markus E. (opc)


Lesenswert?

In dem Datenblatt, welches ich vorliegen habe steht dazu leider nichts.

Muss ich mal nach einem anderen googlen, welches hast du?

von TestX (Gast)


Lesenswert?


von Wolfgang (Gast)


Lesenswert?

Markus E. schrieb:
> Jedoch besteht der Fehler weiterhin, dass die Fehlmessung des
> Ultraschallsensors auf den Segmenten als unrealistische Werte ausgegeben
> werden.

Bevor du an irgendwelchen Segmentanzeigen rumdoktorst, stellt sich doch 
erstmal die Frage, welche Werte die Variable counter im Fall von 
Fehlmessungen annimmt.

Und Fließkommarechnungen haben in der ISR soetwas von überhaupt nichts 
zu suchen. time kannst du unmittelbar vor der Ausgabe auf die Anzeige 
ausrechnen. Und wozu deklarierst du time als double, was real 
wahrscheinlich auf deinem Compiler als single realisiert ist, wenn du 
hinterher damit nur Integerrechnung machst?

von Rudolph R. (rudolph)


Lesenswert?

Es kommt immer ein Echo zurück.
Der Puls hat bei 38ms ein Timeout.

von Markus E. (opc)


Lesenswert?

Ich habe das delay auf 10µs geändert (lt. Datenblatt). Vorher hatte ich 
dort fälschlicherweise 10ms stehen.

wenn ich time als uint_16t deklariere wird gar kein Wert mehr ausgegeben 
auf den Segmentanzeigen.

: Bearbeitet durch User
von Markus E. (opc)


Angehängte Dateien:

Lesenswert?

@Wolfgang: ich habe die Berechnung nun aus der ISR rausgenommen und kurz
vor der Ausgabe auf die Segmente eingebaut.

Habe den jetzigen Code nochmal angehängt.

Ich probiere schon den ganzen Tag daran rum, aber bekomme den Code nun
gar nicht mehr zum laufen. Kann mir jemand sagen wo mein Fehler ist?

von Josef D. (jogedua)


Lesenswert?

Ohne alles geprüft zu haben: Wo ist denn die zweite ISR?

von Josef D. (jogedua)


Lesenswert?

>>>void main();
ist überflüssig

>>> void main(uint16_t counter)
Woher soll der Parameter kommen? Der wird hier vermutlich immer 0 sein.

: Bearbeitet durch User
von Josef D. (jogedua)


Lesenswert?

>>>    time = counter/(F_CPU/256);  // (1)
>>>    time = time / 2.0 * 34300;    // (2)
Das kann nicht stimmen. F_CPU/256 ergibt 23437, counter ist kleiner als
65536; in (1) kann maximal 2 herauskommen.
Dann wird durch 2 (warum 2.0?) geteilt, ergibt maximal 1.
nach (2) ist time (als uint16_t) entweder 0 oder 34300,
((time>1) && (time<299)) also nie wahr.

von Rudolph R. (rudolph)


Lesenswert?

1
 TCCR1B &= ~(1 << ICES1);
2
 TCNT1 = 0;
3
}
4
else
5
{
6
 counter = ICR1; // Zaehlerstand retten

So funktioniert Input-Capture nicht, vom Auslösen der ISR bis zum ersten 
Löschen läuft der Timer doch weiter.
Wenn Du die Puls-Länge wissen möchtest musst Du die Differenz zwischen 
zwei Capture-Werten bilden.
Und den Überlauf für den die ISR fehlt kann man dabei auch 
berücksichtigen.

von Markus E. (opc)


Angehängte Dateien:

Lesenswert?

Ich habe den Code nochmal etwas gecleant wie von Josef empfohlen. Das 
mit 2.0 war natürlich quatsch. Ich habde die Parameter nochmal 
angepasst.

Die Formel habe ich ehrlich gesagt übernommen aus einem anderen Skript 
und hatte vorher als Zahlentyp für die Variable "time" double 
geschrieben, damit hat die Anzeige funktioniert nur jetzt weiß ich nicht 
wie ich die Formel bzw die ISR umschreiben muss.

: Bearbeitet durch User
von Josef D. (jogedua)


Lesenswert?

hier
>>>  TIMSK |= (1 << TICIE1) | (1 << OCIE1A);// Input Capture Interrupt 
Enable+Output Compare A Match Interrupt Enable

erlaubst du ZWEI Interrupts, hast aber immer noch nur EINE ISR.

Ich habe noch nicht drüber nachgedacht, ob beide nötig sind; aber wenn 
du ZWEI erlaubst, dann brauchst du auch ZWEI ISRs.

von Markus E. (opc)


Lesenswert?

Stimmt du hast recht. Der Compiler hat zwar nicht gemeckert aber ich 
habe die Zeile direkt angepasst:

TIMSK |= (1 << TICIE1);// Input Capture Interrupt Enable

: Bearbeitet durch User
von Josef D. (jogedua)


Lesenswert?

So etwas interessiert doch den Compiler nicht.

von Josef D. (jogedua)


Lesenswert?

Um welchen µC geht es hier eigentlich (oder habe ich 'was übersehen)?

von Josef D. (jogedua)


Lesenswert?

Und die Anzeige kann so auch nicht funktionieren.
Die solltest du zuerst korrigieren und TESTEN, wenn du nicht noch eine 
andere Möglichkeit der Anzeige hast.

Anscheinend hast du 3 Stellen zur Verfügung.
Dabei hast du wohl die Einer- und Hunderter-Stelle verwechselt (die 
Hunderter-Stelle kann nur die Werte 0..2 annehmen).

Die Bitmuster für die LEDs brauchst du nur einmal, die doch sind 
vermutlich überaqll gleich (bei den Hundertern kann ich das nicht 
erkennen).
Sie müssen auch nicht berechnet werden, sondern als konstantes Array 
angelegt werden, z.B. (nicht getestet):
1
const uint8_t segment7[10] = {0b00111111, ..., 0b01101111}; // müssen 10 Einträge sein!
2
  messwert = messwert % 1000;  // sicherheitshalber nur die letzten drei Dezimalstellen übrig lassen; (0 .. 999)
3
  PORTB = segment7[messwert/100]; //100er. LED-Segment links
4
  // time = messwert % 100;  // time hat hier nichts zu suchen!
5
  messwert = messwert % 100; // 0 .. 99
6
7
  PORTA = segment7[messwert/10]; //10er, LED-Segment mitte
8
  messwert = messwert % 10;  // 0 .. 9
9
10
  PORTC = segment7[messwert]; //1er, LED-Segment rechts

: Bearbeitet durch User
von Markus E. (opc)


Angehängte Dateien:

Lesenswert?

Hallo Josef,

die Hunderterstellen müssen nur 0-2 anzeigen das ist schon korrekt so 
und mehr PINS für die Anzeigen habe ich eh nicht zur Verfügung. Atmel 
Studio hat zumindest nicht gemeckert sagen wirs mal so ;)

Es handelt sich um einen ATmega16.

Die Anzeigen funktionieren mit den Bitmustern und der Funktion 
segment_ausgabe() genau so wie sie sollen, da der Code ja mit "double" 
Deklaration ja schonmal funktioniert hat. Siehe Code aus Beitrag #1.

Was mich daran störte war vorerst die Tatsache, dass auch die 
Fehlmessungen auf den Anzeigen ausgegeben wurden. Da ich nun deine 
Verbesserungen eingebaut bzw. geändert hatte und auch von double auf 
uint_16 Zahlentypen gewechselt habe funktioniert nun die Ausgabe auf die 
LED-Anzeigen leider gar nicht mehr.

Trotzdem bin ich dankbar für alle Tipps die ich bisjetzt von euch 
bekommen habe und bin stets gewillt mich über Dinge schlau zu machen und 
den Code zu optimieren. Sehe mich selbst noch eher als Anfänger in 
Sachen C-Programmierung.

In diesem Sinne danke für die Tipps

im Anhang habe ich nochmal den aktuellen Code angehängt, der es leider 
zu keiner Anzeige verschafft. Vlt. kann den Code nochmal jemand prüfen

Ich danke dafür

: Bearbeitet durch User
von Wolfgang (Gast)


Lesenswert?

Markus E. schrieb:
> Da ich nun deine
> Verbesserungen eingebaut bzw. geändert hatte und auch von double auf
> uint_16 Zahlentypen gewechselt habe funktioniert nun die Ausgabe auf die
> LED-Anzeigen leider gar nicht mehr.

Was heißt "funktioniert gar nicht mehr"? Bleibt alles dunkel?

Du hast immer noch eine "dicke" Rechenoperation in der ISR
1
    time = counter/(F_CPU/256);  // = Millisekunden

Und hast du mal von Hand mitgerechnet?
Welche Werte nimmt counter an?
Welcher Wert ergibt sicht aus der Rechnung counter / 23437?
Was erwartest du und was kommt raus, wenn du in main() daraus die 
Wegstecke berechnest?
1
    time = time / 2 * 34300;    // einfache Wegstrecke. Die Schallgeschwindigkeit in trockener Luft

time finde ich übrigens keinen besonders treffenden Variablennamen für 
eine Wegstrecke ;-)

von Markus E. (opc)


Lesenswert?

Hallo Wolfgang,

ja genau, die Anzeigen bleiben wohl dunkel. Ich möchte es ja nicht 
bestreiten, dass die Berechnungen mit der Formel nicht stimmen, aber 
wundere mich eben, da sie mit "volatile double time" vorher auch 
funktioniert haben.

Ich bin deiner Anmerkung gefolgt und habe die Variable "time" durch 
"distance" ersetzt. Du hast ja recht ;)

counter kann ja wegen uint_16t Zahlenwerte zwischen 0..65535 annehmen 
oder?

Muss mir dazu morgen nochmal die Berechnungen zu Gemüte führen, falls da 
der Fehler begraben liegt.

In diesem Sinne einen schönen Abend noch an alle

kleiner Edit: indem ich "uint16_t distance = 123;" festlege, kann ich 
die Segmentanzeigen testen und ausschließen, dass diese nicht 
funktionieren ;)

: Bearbeitet durch User
von Josef D. (jogedua)


Lesenswert?

aber die Anzeige KANN doch so:
1
  PORTB = led3[messwert/100]; //100er. LED-Segment links
2
    time = messwert % 100;
3
4
  PORTA = led2[messwert/10]; //10er, LED-Segment mitte
5
    time = messwert % 10;
6
7
  PORTC = led1[messwert]; //1er, LED-Segment rechts

 gar nicht funktionieren!

Angenommen messwert ist zu Beginn 123.

messwert/100 ist dann 1
messwert/10 ist dann 12

>>> PORTB = led3[1];
Das ist noch OK.

Die nächste Zeile:
>>> time = messwert % 100;
ist überflüssig, weil time in der Funktion segment_ausgabe gar nicht 
benutzt (ausgewertet) wird.

>>> PORTA = led2[12];
led2[12] gibt es aber gar nicht; hier wird über das Array-Ende hinaus 
gelesen; da kann irgend etwas stehen.

>>> PORTC = led1[123];
Gibt es auch nicht.

Das kann also nicht der Code sein, der funktioniert hat.


Und sorry, was ich oben über das VERTAUSCHEN der Einer- und 
Hunderter-Stelle geschrieben habe, war falsch; der Rest gilt aber 
dennoch.

Vorschlag:
1. Teste zunächst die Function segment_ausgabe(), indem du in einer 
Schleife mit z.B. 500ms Verzögerung die Zahlen 0 bis 299 anzeigst.
2. Wenn das geht:
   a) überlege, welchen Wert counter in der ISR maximal annehmen kann.
      Führe keine Berechnungen (besonders keine Division) in der ISR 
durch.
      Entferne die Deklaration von counter in der ISR, und ersetzte
         volatile uint16_t time = 0;
      durch
         volatile uint16_t counter;
      Alle Berechnungen kommen erst später; deshalb ist der Name counter
      hier OK.
   b) ersetze
         time = time / 2 * 34300;
      durch
         distance = counter / ???, vorher als uint16_t in main 
deklarieren
      Überlege, durch was du ??? ersetzen mußt, damit für distance 
maximal
      der größte darstellbare Wert (=299) herauskommen kann. Das ist 
jetzt
      zwar noch nicht wirklich der Abstand, aber du kannst schon 'mal
      sehen, ob der Wert größer und kleiner wird, wenn man den Abstand
      verändert.
   c) Wenn das geht, machst du dir Gedanken, wie die richtige Formel an
      dieser Stelle lauten muss.

von Josef D. (jogedua)


Lesenswert?

Markus E. schrieb:
> counter kann ja wegen uint_16t Zahlenwerte zwischen 0..65535 annehmen
> oder?
>
Im Prinzip: ja.

In deinem Programm wird der Zähler ja immer wieder bei 0 gestartet; kann 
man erst mal so lassen, es gibt aber eine bessere Lösung.

Du musst durch einen entsprechenden Vorteiler dafür sorgen, dass er 
nicht überläuft (habe jetzt nicht überprüft, ob das alles richtig 
gemacht ist).
Dann hängt der Maximalwert von der Taktfrequenz ab und natürlich vom 
Mess-Signal.

von Wolfgang (Gast)


Lesenswert?

Josef D. schrieb:
> Du musst durch einen entsprechenden Vorteiler dafür sorgen, dass er
> nicht überläuft (habe jetzt nicht überprüft, ob das alles richtig
> gemacht ist).

Das hättest du mal überprüfen sollen. Wenn du im Programm guckst, siehst 
du, dass der Vorteiler dort beim Start jeder Messung auf 256 gestellt 
wird. Das ist doch ok. Es müßte nicht vor jeder Messung passieren, 
kann man erstmal aber so lassen, obwohl das natürlich zu Anfang in die 
Initialisierung gehört.

Bei 6MHz CPU-Takt ergibt sich damit eine Taktfrequenz von 23437.5 Hz

Falls sich der Sensor bei ausbleibendem Echo so verhält, wie von Rudolph 
geschrieben, bekommt der Timer damit maximal genau 890 Takte.

Rudolph R. schrieb:
> Der Puls hat bei 38ms ein Timeout.

Bei einem 16-Bit Timer gäbe es also überhaupt keinen Grund für einen 
Überlauf. Das sieht IMHO alles soweit gut aus.

Überdenken muss man die Integerrechnung und die Reihenfolge der 
Operationen, wie sie in C statt findet.

von Markus E. (opc)


Angehängte Dateien:

Lesenswert?

Ich habe nun die Funktion segment_ausgabe() angepasst (die Variable ist 
nun überall distance. Hatte vorher übersehen time nach distance 
umzuschreiben, so ergab die Restwertberechnung natürlich keinen Sinn da 
hast du recht Josef).
Habe der Funktion testweise wie auf dem angehängten Bild den Wert 123 
übergeben und dieser wird auch korrekt (wie auf dem anderen angehängten 
Bild zu sehen) angezeigt. Das funktioniert also schonmal.

@Josef:

zu 1) deines Vorschlages funktioniert damit nun

zu 2) habe die Division in der ISR in die main verlagert

wegen dem Vorteiler: den hatte ich auf 256 gesetzt wobei ein Timertakt 
von 23437.5 herauskommt -wie Wolfgang ja bereits schrieb-.


Ich hatte testweise NUR folgende Berechnung stehen lassen:
"distance = counter / 2" also ohne "distance = counter/(F_CPU/256)..."
Die Werte die auf den Segmentanzeigen angezeigt werden stimmen 
allerdings noch nicht ganz (6cm auf der Anzeige = 10cm lt. Maßstab).

Ich stehe nun ehrlich gesagt noch etwas auf dem Schlauch wie ich auf die 
korrekten Werte für die Berechnung komme, denn muss nicht auch die 
Schallgeschwindigkeitsberechnung eingebaut werden?


Ich habe den aktuellen Code nochmal angehängt und danke euch vielmals 
für euere Hilfe. Ich versuche jedem Tipp nachzugehen und habe bereits 
schon vieles durch eure Tipps dazugelernt und aufgenommen :)

von P. M. (mikro23)


Lesenswert?

Markus E. schrieb:
> allerdings noch nicht ganz (6cm auf der Anzeige = 10cm lt. Maßstab).

Wenn der Controller mit 8 MHz läuft stimmt es fast.
Bist Du sicher daß er mit 6 MHz läuft?

Edit: Andersrum. Wenn der Controller mit 6 MHz läuft, kommen 6-7cm raus. 
Bei 8 MHz würden 9-10cm rauskommen.

: Bearbeitet durch User
von Wolfgang (Gast)


Lesenswert?

Markus E. schrieb:
> Ich hatte testweise NUR folgende Berechnung stehen lassen:
> "distance = counter / 2" also ohne "distance = counter/(F_CPU/256)..."
> Die Werte die auf den Segmentanzeigen angezeigt werden stimmen
> allerdings noch nicht ganz (6cm auf der Anzeige = 10cm lt. Maßstab).

Wie sollen sie auch. Wenn du die Schallgeschwindigkeit ignorierst, muss 
da Unfug herauskommen - fast schon erstaunlich, wie nahe das zufällig an 
der Wahrheit ist.

Natürlich brauchst du den richtigen Umrechnungsfaktor, um von Counter 
auf die Zentimeteranzeige zu kommen.

Wenn ich jetzt richtig gerechnet habe, ist
1
distance_cm = counter * 2997UL / 4096

von Markus E. (opc)


Lesenswert?

@Peter M: ja der Controller läuft mit 6Mhz ;)

@Wolfgang: könntest du mir den Rechenweg deiner Rechnung noch erklären, 
denn ich denke es funktioniert damit und würde das noch gerne 
nachvollziehen können. Zumindest die Entfernungen <1m sind ziemlich 
genau :) Werde die Schaltung morgen an seinem Aufstellort montieren und 
mal schauen ob die Entfernungen alle richtig angezeigt werden und dann 
müsste ich mir ggf. noch Gedanken machen darüber wenn Fehlmessungen 
ausgegeben werden.

von Wolfgang (Gast)


Lesenswert?

Markus E. schrieb:
> könntest du mir den Rechenweg deiner Rechnung noch erklären

Der Faktor 2997 stammt aus der Originalumrechnung, allerdings alle 
konstanten Werte zusammengefasst und mit 4096 skaliert.

Ursprünglich hattest du
1
time = counter/(F_CPU/256);                     (1)
2
distance_cm = time /2 * 34300

Zusammengefasst ergibt das
1
distance_cm = counter/(F_CPU/256) /2 * 34300    (2)
also
1
                         256 * 34300
2
distance_cm = counter * -------------           (3)
3
                          F_CPU * 2
Nach Zusammenfassung der Konstanten
1
distance_cm = counter * 0.7317                  (4)
oder
1
                          2997
2
distance_cm  = counter * ------                 (5)
3
                          4096

Die Erweiterung mit 4096 in (5) sorgt dafür, dass trotz der 
Ganzzahlrechnung genau genug gerechnet wird. Wichtig ist das "UL" damit 
der Kompiler eine 32-Bit Rechnung macht. Eine 16-Bit Variable würde 
überlaufen. Die abschließende Division durch 4096, die der µC durch eine 
einfache Schiebeoperation umsetzen kann, rückt alles zurecht.

Das Berechnen der Skalierungskonstante 2997 wird man besser dem Compiler 
überlassen, damit man später noch mal eine Chance hat, den Quellcode zu 
verstehen.

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.