Guten Abend,
ich habe bei einer Steuerung für eine Maschine das Problem, dass
zeitkritisch
Ausgänge geschaltet werden müssen. Zeitkritisch heißt in diesem Fall
maximal 4-5ms Ungenauigkeit. Ich schreibe nebenbei Werte auf ein I2C
Display, und hier liegt das Problem: Die Steuerung empfängt Interrupts
von einem Inkrementalgeber und fängt bei einem bestimmten Ereignis an
die Impulse zu zählen. In der ISR inkrementiere ich entsprechend eine
Variable. Wenn diese Variable >= einem definierten Wert ist, soll der
Ausgang geschaltet werden. Das alles funktioniert genau genug, bis das
Display ins Spiel komm. Durch das Schreiben wird die loop Schleife so
langsam, dass ich meine zeitliche Genauigkeit nicht mehr einhalten kann,
da die Bedingung zum schalten des Ausganges im worst case erst nach
einem kompletten loop-Zyklus erkannt wird.
Wie kann man das besser lösen? Gibt es die Möglichkeit einen Interrupt
auszulösen, wenn 2 Variablen gleich sind?
Viele Grüße
rbcn
rbcn schrieb:> Gibt es die Möglichkeit einen Interrupt> auszulösen, wenn 2 Variablen gleich sind?
Nein, normalerweise nicht. Aber es weiß ja auch keiner, was für einen
Controller du verwendest.
rbcn schrieb:> Zeitkritisch heißt in diesem Fall> maximal 4-5ms Ungenauigkeit.
Jetzt mach hier mal nicht so eine Hektik. Das ist ein Zeitbereich, in
dem der lahmste Controller der Welt noch nebenbei Primzahlen und
Mondphasen berechnen kann.
Zeig dein Programm, erklär genau, was du vorhast, dann sehen wir schon
weiter.
Ich erwähnte den Controller nicht, weil ich der Meinung war ich habe
hauptsächlich ein strategisches Problem. Es ist ein Atmega 328P.
Den kompletten Code werde ich nicht posten, weil es total
unübersichtlich wäre. Die relevanten Dinge sind wie oben geschrieben,
dass das Schreiben aufs LCD mittels einer Library zu lange dauert und
deshalb ein Vergleich nicht immer rechtzeitig ausgewertet wird. Und nein
ich schreibe nicht in jedem Schleifendurchlauf aufs LCD, sondern nur
wenn ein zweiter externer Interrupt dieses auslöst.
> In der ISR inkrementiere ich entsprechend eine Variable.> Wenn diese Variable >= einem definierten Wert ist, soll der> Ausgang geschaltet werden.
Wo ist das Problem???
Warum kannst du den Ausgang nicht gleich in der ISR schalten???
Eine Display-Bearbeitung, die den Programmablauf für >= 4 ms
aufhält, kann aber auch nur suboptimal (vulgo: beschissen)
programmiert sein: Da kriegt das träge menschlische Auge ja schon
den Bildaufbau mit...
Jacko schrieb:> Warum kannst du den Ausgang nicht gleich in der ISR schalten???
Weil das gleiche auch noch mit drei weiteren Vergleichen und unter
Einbezug von anderen Korrekturwerten passiert. Da hatte ich die Sorge,
dass die Interrupts bei höheren Drehzahlen zu schnell kommen würden.
Aber danke, guter Ansatz, das werd ich ausprobieren! Manchmal sieht man
den Wald vor lauter Bäumen nicht
rbcn schrieb:> Zeitkritisch heißt in diesem Fall> maximal 4-5ms Ungenauigkeit. Ich schreibe nebenbei Werte auf ein I2C> Display, und hier liegt das Problem: Die Steuerung empfängt Interrupts> von einem Inkrementalgeber und fängt bei einem bestimmten Ereignis an> die Impulse zu zählen.
da muss was grundlegend falsch sein in deinem Programm,
was für ein Display? mehr als 4x pro Sekunde schreiben lohnt kaum, kann
kein Mensch erfassen oder ist es Video, dann ist I2C eh ungünstig.
4x20 Zeichen LCD sind ja auch in wenigen µs erledigt -> i2c 100kHz ca
10k Zeichen/s (PI x Daumen) meist geht auch 400 kHz.
Ich frage im ms Raster den Inkrementalgeber ab, zähle bis 250 und
aktualisiere das LCD
4 ms für ein Display sind OK. Selbst 100ms wären es, wenn man es richtig
macht. Man muss die Display-ausgabe dann halt Niederprior machen, z.B:
- Rtos nehmen,
- Aufteilung in kleine Pakete
- verschiedene Interrupt Level und Loop im Interrupt
- SW-Interrupt triggern.
Sieh es mal so:
Wenn die Reaktionszeit < 4...5 ms sein soll
und die ISR alles Wichtige (mit Nebenbedingungen, ...)
in < 1...2 ms erledigt hat, (schaffst du das?)
ist das doch besser,
als wenn die Hauptschleife im Programm nur alle 4...5 ms
die Bearbeitung drängender Probleme zulässt!
Du sollst ja auch nicht den kompletten Code Posten, sondern nur den
kritischen Teil. Reduziere das Programm auf eine minimale ausführbare
Demo, die das Problem zeigt. So ist es uns allen am liebsten.
Also besteht deine erste Aufgabe darin, diesen Teil zu bestimmen. Wenn
du das nicht kannst, helfen wir gerne, aber dann brauchen wir doch den
kompletten Code.
5 ms wären bei 8Mhz Taktfrequenz ca. 40.000 Maschinen-Befehle. Wenn wir
pi mal Daumen 10 Maschinenbefehle pro C Codezeile annehmen, komme ich
auf 4000 Zeilen Code, die in dieser Zeit ausgeführt werden.
Damit kann man eine ganze Menge anstellen.
rbcn schrieb:> Weil das gleiche auch noch mit drei weiteren Vergleichen und unter> Einbezug von anderen Korrekturwerten passiert. Da hatte ich die Sorge, ...
Sorgen sind da ein schlechter Ratgeber. Gucke dir die Laufzeit an und
sorge ggf. durch Interrupt-Freigabe dafür, dass der nächste Interrupt
vom Inkrementalgeber heil durchkommt.
Noch eine ketzerische Frage: Was machst du eigentlich mit deinem Timing,
wenn dein Inkrementalgeber prellt?
Kannst du deinen Inkrementalgeber nicht per Timerinterrupt abfragen?
Wolfgang schrieb:> Noch eine ketzerische Frage: Was machst du eigentlich mit deinem Timing,> wenn dein Inkrementalgeber prellt?>> Kannst du deinen Inkrementalgeber nicht per Timerinterrupt abfragen?
Prellen sehe ich am Oszi keines, es gibt kein Problem damit. Ich stelle
es mir auch schwierig vor, wie ein Hallgeber oder ein optischer Geber
(eines von beiden ist es) prellen soll.
Aber Danke schonmal an alle, gemach gemach, es braucht etwas Zeit das
auszuprobieren.
Ich muss mich der allgemeinen Meinung: 4-5 ms sind eine "Ewigkeit"
anschließen.
Also auch wenn ich es für Unsinn halte, ist die Lösung recht einfach.
Da der Programmablauf innerhalb einer Interruptroutine etwas anders ist,
wie im normalen Programmablauf, sind ein paar Sicherheitsmaßnahmen
nötig.
1. Alles, was in der Interruptroutine passieren soll muss in eine
Funktion ausgelagert werden. Also eine "leere" Interruptroutine,
die nur den Funktionsaufruf enthält.
Dadurch wird der Programmablauf unabhängig davon, ob Du die Routine
aus der Unterbrechung heraus aufrufst oder aus dem normalen
Programmablauf.
2. Du musst dafür Sorge tragen, dass die Routine nicht während sie
gerade läuft, sich selber aufruft.
Ein Flag als erstes setzen und als letzten Befehl zurück setzen und
selbiges innerhalb der Unterbrechung abfragen. In der Routine und
in der Unterbrechung.
Muss aber nicht sein.
@ rbcn (Gast)
>Die Steuerung empfängt Interrupts>von einem Inkrementalgeber und fäng
Was schon mal Unsinn ist. Ein Inkrementalgeber wird durch periodisches
Abfragen in einem Timer-Interrupt ausgewertet, siehe Drehgeber. Und
schwups hast du auch schon einen Timer-Interrupt, der all deine Probleme
löst.
>Display ins Spiel komm. Durch das Schreiben wird die loop Schleife so
Arduino?
>langsam, dass ich meine zeitliche Genauigkeit nicht mehr einhalten kann,>da die Bedingung zum schalten des Ausganges im worst case erst nach>einem kompletten loop-Zyklus erkannt wird.>Wie kann man das besser lösen?
Indem man die zeitkritischen Dinge in einen Interrupt packt, der hat
Priorität und wird immer ausgeführt und die eher langsamen,
zeitunkritischen Dinge in der Hauptschleife, aka Arduino-Loop audführt.
Siehe Interrupt. Oder hier.
Beitrag "Re: Arduino Nano, SD Card, PCM"> Gibt es die Möglichkeit einen Interrupt>auszulösen, wenn 2 Variablen gleich sind?
Nö.
rbcn schrieb:> Ich schreibe nebenbei Werte auf ein I2C> Display, und hier liegt das Problem
Du hast ein organisatorisches Problem.
Wenn du versuchst, langwierige Dinge in Interrupt-Routinen zu erledigen,
dann wirst du in jedem System damit scheitern.
Da helfen auch keine Pseudo-Tips wie "Was schon mal Unsinn ist. Ein
Inkrementalgeber wird durch periodisches Abfragen in einem
Timer-Interrupt ausgewertet.."
Auch der Ratschlag, aus einer Interruptroutine irgendwelche Funktionen
aufzurufen, die du ebenfalls von woanders her aufrufst, ist ein sehr
schlechter Ratschlag.
Mein Rat:
1. Halte Interrupt-Funktionen stringent und kurz im Durchlauf. Also
keine zeitaufwendigen Dinge darin tun, auch nicht warten.
2. Nur solche Reaktionen, die wirklich zeitkritisch sind, machst du in
der Interruptroutine selbst.
3. Reaktionen, die sich mehr Zeit lassen können, machst du aus der
Grundschleife in main heraus. Dazu richtest du dir einen Ereignis-Puffer
ein (fifo), wo du in der Interrupt-Routine lediglich einen Kenner
hineinschreibst, der dann später von der Grundschleife wieder
herausgelesen und abgearbeitet wird. Wenn's einfacher gehen soll, dann
kannst du dir auch einige globale Booleans einrichten, die im Interrupt
gesetzt und in der Grundschleife abgearbeitet werden. Zum Beispiel
bool SetzeDisplayNeu;
oder so ähnlich.
W.S.
rbcn schrieb:> Ich schreibe nebenbei Werte auf ein I2C Display, und hier liegt das> Problem: Die Steuerung empfängt Interrupts von einem Inkrementalgeber> und fängt bei einem bestimmten Ereignis an die Impulse zu zählen. In der> ISR inkrementiere ich entsprechend eine Variable. Wenn diese Variable >=> einem definierten Wert ist, soll der Ausgang geschaltet werden. Das> alles funktioniert genau genug, bis das Display ins Spiel komm.
Das Display ist an dieser Stelle unnötig und insgesamt sowieso das
letzte Glied der Nahrungskette, das bedient werden muss. Denn ob ein
Wert angezeigt wird oder nicht ist eigentlich völlig uninteressant.
Zumindest für die Funktion einer Maschine.
Also gehört solch nebenrangiges Zeug wie das Display weg von der
eigentlichen Maschninensteuerung.
Und das ist jetzt dein Problem: du hast alles miteinander verknotet und
vermischt. Das Display wird immer sofort dann angesteuert, wenn
eigentlich die Maschine was machen sollte.
Du solltest die Hauptarbeit in einer Hauptschleife machen und dafür
sorgen, dass diese Hauptschleife durchschnittlich nicht länger als
5..10ms braucht. Dann kannst du nämlich die allermeisten Sachen dort
erledigen und die Interruptroutinen kurz und knackig halten.
Ich mache es z.B. so, dass ich einen lokalen Display-Puffer habe, in den
ich auch aus einem Interrupt sofort reinschreiben kann. Und dann wird
pro Hauptschleifendurchlauf 1 einziges Zeichen an das Display
ausgegeben. Bei einem 20x2 Textdisplay und einer durchschnittlichen
Hauptschleifen-Zykluszeit von 5ms wird das Display also 5x pro Sekunde
komplett neu beschrieben.
W.S. schrieb:> 2. Nur solche Reaktionen, die wirklich zeitkritisch sind, machst du in> der Interruptroutine selbst.
Zeitkritisch bedeutet hier "zeitkritisch für die Maschinenfunktion".
Ein Display ist z.B. nicht zeitkritisch (wer kann schon mehr als 3 Werte
pro Sekunde ablesen). Eine serielle Schnittstelle auch nicht (dafür gibt
es Puffer), die Reaktion auf einen menschlichen Tastendruck auch nicht
(die hätte auch 20ms später gedrückt werden können).
Man kann das LCD auch per Interrupt im Hintergrund updaten:
Beitrag "Formatierte Zahlenausgabe in C"
Man kann auch zeitkritische Sachen in einen Interrupt verschieben, ohne
den auslösenden Interrupt zu lang werden zu lassen. Der
SPM-READY-Interrupt bietet sich dafür an.
...und ich frage mich überhaupt, warum der Vergleich von Variablen eine
Interrupt-Routine auslösen soll. Je nach Ergebnis des Vergleichs wird
einfach ein Unterprogramm ausgeführt...und wie schon mehrfach
geschrieben, du mußt selbst Prioritäten für die Abarbeitungen setzen!
Das macht weder eine "einfache"- noch eine Interrupt-Routine für dich.
Gruß Rainer
Joachim B. schrieb:> Ich frage im ms Raster den Inkrementalgeber ab, zähle bis 250 und> aktualisiere das LCD
genau das meinte ich, im IRQ läuft der ms Zähler, wenn bei mir 250
erreicht ist setze ich das flag LCD_up und in der main loop wird es dann
irgendwann passieren, schietegal ob +-10ms
Lothar M. schrieb:> Zeitkritisch bedeutet hier "zeitkritisch für die Maschinenfunktion".> Ein Display ist z.B. nicht zeitkritisch
jj janz jenau
Lothar M. schrieb:> Eine serielle Schnittstelle auch nicht (dafür gibt> es Puffer), die Reaktion auf einen menschlichen Tastendruck auch nicht> (die hätte auch 20ms später gedrückt werden können).
desgleichen, Tasterabfrage mit Entprellung im IRQ nach Dannegger
in der main loop wird es dann irgendwann passieren, schietegal ob +-10ms