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.
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.
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.
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?
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.
@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?
>>> 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.
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.
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.
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.
Stimmt du hast recht. Der Compiler hat zwar nicht gemeckert aber ich
habe die Zeile direkt angepasst:
TIMSK |= (1 << TICIE1);// Input Capture Interrupt Enable
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):
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
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 ;-)
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 ;)
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.
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.
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.
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 :)
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.
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
@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.
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.