ja, mit Multithreading.
Es gibt dazu z.B. die Scheduler Lib von M. Patel, mit der man mehrere loops() parallel, unabhängig voneinander laufen lassen kann, und die sogar auch auf AVRs funktioniert.
Da sie nicht pre-emptives, sondern kooperatives MT bieten und die loops dauerhaft parallel laufen, können z.B. Semaphoren zur Thread-Steuerung benutzt werden: Diese werden vom Haupt-loop() aus gesetzt und beginnen zu messen, sobald ein Semaphor einen bestimmten Wert besitzt, ansonsten können sie auch "leer" laufen. Erfordert nt ein wenig Übung und ERfahrung, zugegebenermaßen.
Mit pulsin habe ich es noch nicht getestet, wäre aber einen Versuch wert:
https://github.com/mikaelpatel/Arduino-Scheduler
Was du tun kannst ist, ein timeout für pulseIn festzulegen.
Dazu rechnest du anhand der Schallgeschwindigkeit die nötige Laufzeit für deine maximal nötige Entfernung aus (bei den billigen hc-sr04 sind das maximal 4m, aber nimm das doppelt, da das Echo auch zurückmuss, wenn du nur kürzere Entfernungen zu nutzen gedenkst, umso besser), und hast dein Timeout. Länger wartet der Prozessor dann nicht!
Lies dazu mal die Doku zu Pulsein, da steht dann auch, ob man es in Millisekunden (ich glaub schon, habs aber nicht im Kopf) angeben muss, und auch, wie man das macht- müsste der dritte, optionale Parameter bei pulseIn() sein...
In der Regel kommst du dann auf Wartezeiten, mit denen man gut leben kann (8m dauern bei Schallgeschwindigkeit ja nicht so wirklich lange).
Oder du nimmst eben was schnelleres als Ultraschall-prinzipbedingt ist das ein bissel lahmarschig.
Alternativ guckst du dir mal die NewPing- (oder ähnlich...) Bibliothek an, die arbeitet angeblich nicht-blockierend.
So ganz theoretisch _könnte es sogar gehen, als Eingang einen der Hardware-Interrupt-Pins zu benutzen.
Dann ist da definitiv nix blockierend, wenn man es ungefähr so macht:
-Ping senden (wie das beim HC nötig ist, über einen normalen Digitalpin)- den Eingang (Echo) aber auf einen der Interrupt-Pins legen. Der müsste dann so konfiguriert werden, dass er bei High den Interrupt auslöst.
Beim Senden die aktuellen Millisekunden (oder auch die Microsekunden, geht auch) merken, und in der ISR wiederum die aktuellen merken.
Wenn dann "das andere"- was nebenbei laufen soll, abgearbeitet ist kann man MillisSenden von MillisEmpfangen abziehen und hat- die Laufzeit (aus der man wiederum die Entfernung errechnen kann). Da dank dem Interrupt sofort reagiert wird, können in der Zwischenzeit durchaus andere Dinge erledigt werden....
Kann man dann noch verfeinern, indem man z.B. den Interrupt nur aktiviert, wenn auch gemessen werden soll usw.
Müsste eigentlich funktionieren-probiert hab ich es aber noch nicht!
Wenn du es probieren willst (nicht besonders schwierig, aber aus unverständlichen Gründen haben viele Angst vor Interrupts), halt mich mal auf dem Laufenden, die Idee kam mir grade beim schreiben erst.
*notiz an mich selber: das ^^ mal im Hinterkopf behalten, hat was...
Grüssle, Sly
..dem Inschenör ist nix zu schwör..
Hallo,
ich hab's mal mit dem Interrupt probiert. Klappt aber nur mit einem merkwürdigen Haken.
Prinzip:
stoße alle 500 ms den US-Sensor an, speichere "mikrosanf = micros()" und erwarte mit "attachInterrupt(1, Interruptaktion, FALLING)" das Echo des US's, speichere "mikrosend = micros()".
So weit so gut.
Der Haken ist, dass die von Hand gemessene Entfernung, die sich auch mit der Variante "pulseIn" ergibt, nur dann mit dem Ergebnis in "Entf_int" übereinstimmt, wenn ich von den ermittelten Mikrosekunden "mikrosec = mikrosend - mikrosanf" 540 µs abziehe. Zwischen Start US-Sensor und Interrupt kommen also irgendwo 540 µs dazu. Nur wo???
Das sind die Porgrammteile:
Code:int Ausg_US_Trigger = 13; volatile int Eing_US_Signal = 3; int US_Zykluszeit = 500; //Messintervall für die Ultraschallmessung unsigned long millis_alt; unsigned long mikrosend; int Entf_int; void setup() { // SETUP SETUP SETUP SETUP SETUP Serial.begin(250000); while (!Serial); pinMode (Ausg_US_Trigger, OUTPUT); // Ausgang für das US-Triggersignal pinMode (Eing_US_Signal, INPUT); // Rückmeldungesignal vom US-Sensor } // ENDE SETUP void loop() { // LOOP LOOP LOOP LOOP LOOP attachInterrupt(1, Interruptaktion, FALLING); // 1 ist hier das zum Eingang 3 gehörige Interruptsignal if ((millis() - millis_alt) > US_Zykluszeit) { // in dieser if wird das UP Entfernung() alle 0,5s aufgerufen millis_alt = millis(); Entf_int = Entfernung(); // Ergebnis aus dem UP Entfernung() wird nach Entf_int übertragen Serial.print ( " Entf_int = "); Serial.println (Entf_int); } // >>>>> ENDE if (millis() - millis_alt > US_Zykluszeit) } // >>>> ENDE LOOP
Code:int Entfernung () { static unsigned long mikrosec; static unsigned long mikrosanf; mikrosec = mikrosend - mikrosanf; digitalWrite (Ausg_US_Trigger, LOW); //negative Flanke für Triggersignal mikrosanf = micros(); // micros() beim ersten Durchlauf dieses UP's; der nächste folgt erst nach 500 ms mikrosec = mikrosec - 540; // 540 = Korrektur für eine unerklärbare Zeitverzögerung = Messverfälschung return mikrosec / 58; // Entfernung in cm; sieh Buch Seite 230 } // >>>>> ENDE int Entfernung()
GrußCode:void Interruptaktion() { mikrosend = micros(); digitalWrite (Ausg_US_Trigger, HIGH); }
fredyxx
Hier mal mein Code für zwei hc-sr04 an Arduino Nano mit Interrupts.
Eventuell kann das bei dem Problem mit der Interruptvariante helfen.
Code://Zielplatform Arduino Nano // zwei HC-SR04, ein Fundurino 3 Kanal line Tracking Modul //Nutzt beide Interrupteingänge const int us1_echo = 2; //Interrupteingang für Echo const int us2_echo = 3; //Interrupteingang für Echo const int us1_trig = 4; //Digitalausgang für Trigger const int us2_trig = 5; //Digitalausgang für Trigger //Linienfolger mit IR Reflexlichtschranken const int lf_le = 8; const int lf_ce = 9; const int lf_ri = 10; int lf_le_state = LOW; int lf_ce_state = LOW; int lf_ri_state = LOW; unsigned long us1_echo_st; unsigned long us2_echo_st; unsigned long us1_echo_et; unsigned long us2_echo_et; volatile unsigned long us1_srt; volatile unsigned long us2_srt; unsigned long us1_dist; unsigned long us2_dist; unsigned long prev1micros = 0; const long toggleinterval = 1000; int togglestate = LOW; volatile int us1_flag = 0; volatile int us2_flag = 0; char* string_[]={"Linefollow:", "US-Echo1:", "US-Echo2:", "Cycletime:"}; unsigned long prev2micros = 0; void setup() { Serial.begin(9600); pinMode(us1_echo, INPUT); pinMode(us2_echo, INPUT); pinMode(us1_trig, OUTPUT); pinMode(us2_trig, OUTPUT); pinMode(lf_le, INPUT); pinMode(lf_ce, INPUT); pinMode(lf_ri, INPUT); attachInterrupt(0, US1_ISR, CHANGE); attachInterrupt(1, US2_ISR, CHANGE); } void loop() { lf_le_state = digitalRead(lf_le); lf_ce_state = digitalRead(lf_ce); lf_ri_state = digitalRead(lf_ri); unsigned long cur1micros = millis(); if (cur1micros - prev1micros >= toggleinterval) { //alle 10ms umschalten prev1micros = cur1micros; if (togglestate == LOW){ togglestate = HIGH; digitalWrite(us1_trig, HIGH); digitalWrite(us2_trig, LOW); us1_echo_st = 0; us1_flag = 0; }else{ togglestate = LOW; digitalWrite(us1_trig, LOW); digitalWrite(us2_trig, HIGH); us2_echo_st = 0; us2_flag = 0; } } us1_dist = (us1_srt / 58); // Umrechnung des Sensorwerts, ungefähr in cm (für 343m/s bei trockner Luft und 20° wäre 58,3 der genaue Wert) us2_dist = (us2_srt / 58); //=== Beginn Ausgabe === Serial.print(string_[1]); Serial.println(us1_dist); Serial.print(string_[2]); Serial.println(us2_dist); Serial.print(string_[0]); Serial.print(lf_le_state); Serial.print(lf_ce_state); Serial.println(lf_ri_state); unsigned long cur2micros = micros(); //Zykluszeit Messung Serial.print(string_[3]); Serial.println(cur2micros - prev2micros); prev2micros = cur2micros; //=== Ende Ausgabe === } // Inerrupt Serviceroutine Für US Zeitmessung void US1_ISR(){ if (us1_echo_st == 0) { us1_echo_st = micros(); } else { us1_echo_et = micros(); ++us1_flag; } if (us1_flag == 1) { us1_srt = (us1_echo_et - us1_echo_st); } } // Inerrupt Serviceroutine Für US Zeitmessung void US2_ISR(){ if (us2_echo_st == 0) { us2_echo_st = micros(); } else { us2_echo_et = micros(); ++us2_flag; } if (us2_flag == 1) { us2_srt = (us2_echo_et - us2_echo_st); } }
Ach siehste- den US-Sensor wollt ich ja im aktuellen Gebastel auch noch....hm, mal gucken ob der Pro Mini nachher noch nen passenden Pin frei hat...
Zum Problem: so ganz verstehe ich dein Programm nicht: wieso triggerst du den US-Brüllausgang da in der ISR?
Das _könnte_ Probleme machen, denn eine ISR sollte so schnell wie nur möglich abgelaufen sein.
Beispielsweise wär es ja denkbar, dass in der Zwischenzeit ein weiterer Interrupt ausgelöst wird oder so.
Gestartet wird eine Messung (also das aussenden) mit High(10ms);LOW
Das Timing scheint da halbwegs wichtig zu sein, also würd ich das (wenn denn die Zeit mal wieder reif ist) auch da direkt so machen, wie empfohlen:
-Trigger=HIGH;
delay(10);//kann man natürlich auch smarter lösen
Trigger=LOW;
// nun lauschen
Ahja- da haben wir es vielleicht schon:
Die eigentliche Messung wird über den Anschluss Trigger (Pin 2) gestartet. Der Messvorgang wird durch eine fallende Flanke am Trigger-Eingang ausgelöst. Das vorhergehende High-Signal muss dabei eine Mindestzeit von 10 uS anliegen.
Der Ultraschallsensor hc-sr04 sendet daraufhin nach ca. 250 µs ein 40 kHz Burst-Signal für die Dauer von 200 µs zur eigentlichen Sensorkapsel (Transducer). Danach geht der Ausgang Echo (Pin 3) sofort auf H-Pegel und der Ultraschallsensor wartet auf den Empfang des akustischen Echos. Sobald das Echo registriert wird, fällt der Ausgang auf Low-Pegel. Nach 20 ms kann die nächste Messung erfolgen.
Quelle
Das würde die Verzögerung erklären, zumindest Ansatzweise.Eventuell muss da erst irgendein Kondensator aufgeladen werden oder sowas (irgendwoher muss der Bursche ja bissel Leistung kriegen, irgendwo las ich mal, dass die mit 20V an der Kapsel laufen). Das wären also schonmal 250µs.
Der Rest- hm- ist FALLING wirklich richtig?
Ich hatte mal massiv Probleme mit irgendeinem Programm, weil ich da auch FALLING benutzen wollte (weil es das von der Logik her war), und habs dann damit beheben können, indem ich stattdessen RISING genommen hatte (oder umgekehrt)- jedenfalls war damit das Problem vom Tisch->verstanden hab ich das aber nie. Manches ist eben doch bissel Magie.
Insgesamt scheint aber in deinem Programm einiges nicht ganz korrekt zu sein, denn:
bei jedem Durchlauf bindest du den Interrupt wieder ein-warum?
Man _kann_ das durchaus machen, wenn man z.B. in der ISR weitere Interrupts unterbinden möchte, indem man ihn dort zuerst lahm legt, mit detachInterrupt().
Generell übrigens ne gute Idee, aber ich glaube, nicht ganz so gut, wie sie klingt. Wenn ich mich recht entsinne, wird damit zwar ein weiterer Aufruf der ISR (der dann schlimmstenfalls innerhalb der bereits laufenden ISR käme) verhindert, jedoch wird der Interrupt quasi "gespeichert" und dann beim neuen aktivieren sofort ausgelöst. Bin da aber nicht ganz sicher...
Dann muss man ihn anschliessend natürlich wieder aktivieren.
Sonst aber ergibt das keinen Sinn, und du könntest das _einmal_ machen, im setup()-Teil nämlich.
Weiters hast du zwar volatile int Eing_US_Signal = 3; deklariert (wozu-hast du dich da einfach nur vertan?), aber _nicht_ mikrosend...._die_ aber wird in der ISR geändert (und sollte somit volatile sein).
Mal gucken, was die Tage wird bei mir, die Geschichte wär ein bisschen herumprobieren allemal wert- weil man damit eben die (eigentlich lahmarschigen) US-Messungen doch etwas beschleunigen könnte.
Irgendwo hab ich noch paar solche Dinger herumliegen, und nen UNO könnt ich auch mal frei machen, zum testen.
Grüssle, Sly
..dem Inschenör ist nix zu schwör..
Das ergibt doch schon mal 450 µs und erst danach wartet der Sensor auf den Empfang. Wenn ich statt mit 540 µs mit 450 µs korrigiere, ändert sich eine beispielhaft gemessene Entfernung von 43 auf 44 cm. Wenn ich das noch mal nachmesse, ist das zienlich genau die Entfernung bis zur Mitte der silbernen Kapseln. Damit kann ich leben.Der Ultraschallsensor hc-sr04 sendet daraufhin nach ca. 250 µs ein 40 kHz Burst-Signal für die Dauer von 200 µs zur eigentlichen Sensorkapsel (Transducer). Danach geht der Ausgang Echo (Pin 3) sofort auf H-Pegel und der Ultraschallsensor wartet auf den Empfang des akustischen Echos. Sobald das Echo registriert wird, fällt der Ausgang auf Low-Pegel. Nach 20 ms kann die nächste Messung erfolgen.
Ja, RISING klappt nicht.Der Rest- hm- ist FALLING wirklich richtig?
Weil ich es nicht besser wusste. Habe ich ins SETUP verschoben.Insgesamt scheint aber in deinem Programm einiges nicht ganz korrekt zu sein, denn:
bei jedem Durchlauf bindest du den Interrupt wieder ein-warum?
einfach nur vertan, habe ich geändert.Weiters hast du zwar volatile int Eing_US_Signal = 3; deklariert (wozu-hast du dich da einfach nur vertan?), aber _nicht_ mikrosend...._die_ aber wird in der ISR geändert (und sollte somit volatile sein).
Danke für die umfangreiche Hilfe. Einen schönen Tag noch.
Gruß
fredyxx
Klasse.
Besonders, dass es funktioniert.
Natürlich hast du recht: wenn das "aufladen" (oderwasauchimmer) schon 250 dauert, und dann das Signal auch 200, ehe gelauscht wird, sind wir bei 450.
Das hatte ich irgendwie übersehn.
Grüssle, Sly
..dem Inschenör ist nix zu schwör..
Aus einem Forum
Kurz zusammengefasst, PulseIn ist eigentlich unbrauchbar.PulseIn is a function available with the Arduino that implements an approach know as 'poling'. It essentially sits around waiting for something to happen, until something happens the rest of your code is blocked. This is okay for a simple lab exercise to read and print values from a receiver but it is a hopeless approach for a real world application. Fortunately there are better approaches that do not require a major learning curve.
Um Pulselängen zu messen gibt es bei den meisten µC die "input capture" Hardware. Wie sich das aber in die Arduino Welt einordnet, kann ich mangels Erfahrung nicht sagen. Aber eine Google-Suche nach "arduino input capture" oder "avr input capture" fördert einiges zu Tage, die arduino-Prozessoren scheinen diese Funktion zu besitzen.
MfG Klebwax
Strom fließt auch durch krumme Drähte !
bei Robotern hat man immer wieder das Problem, dass irgendwelche notwendigen Pausen für Events bei anderen Events stören, wenn Ereignisse quasi-simultan registriert und bearbeitet werden müssen. Das gilt ntl auch für andere Datenverarbeitungsprobleme. Daher ist mE Multithreading per time-slice-scheduling sowieso unumgänglich, was ja auch der Grund dafür ist, dass MT von den allermeisten Programmiersprchen (besonders auch für Roboter-Plattformen) unterstützt wird (gcc C POSIX pthread, C++ std::thread, Java, C#, sogar Lego NXT-G und EV3-G (alle pre-emptiv) und Fischertechnik RoboPro (kooperativ). Tatsächlich nutzt MT bei Arduino nur bestimmte Interrupts sehr geschickt, um Daten, Datenregister und Speicherbereiche zu sichern und wiederherzustellen, was man auch bare-metal tun kann, aber MT libs machen das eben sehr elegant und user-freundlich per high-level-API-Funktionen, die auch zusätzlich sehr "failsafe" programmiert und getestet sind.
Von daher würde ich mich eher um Multithreading kümmern, wie ich es oben verlinkt habe, dann klappt es auch mit diesen und anderen blockierenden Funktionen.
Auch wenn die Scheduler Lib bereits für Nano und Uno geeignet sind, würde ich dennoch eher einen Mega empfehlen als MCU, besser noch einen SAMD oder SAM (Arduino Zero/M0, Due, Teensy).
Robotik ist nun mal etwas anspruchsvoller als simple Katzenklappensteuerungen, und z.B. auch Lego- oder FT "bricks" besitzen ja keine AVRs sondern ARM Prozessoren, aus gutem Grund.
Aber wie erwähnt, der Scheduler funktioniert dennoch auch bei kleinen AVRs.
Lesezeichen