Hallo zusammen,
ich habe ein schnelles Hauptprogramm (durchlauf kleiner 5ms, passt für
meinen Zweck). Ab und an rufe ich eine Funktion auf, die mir ein paar
Daten per UART raushaut um ein Gerät fernzusteuern. Das Problem: Das
Gerät ist langsam im verstehen, wenn ich zwei oder drei Befehle sende,
muss dazwischen immer ein 500ms Delay.
Aktuell mach ich das so:
1
voidPS_on(uint8_tstatus)
2
{
3
EA_Init();
4
HAL_Delay(500);
5
EA_set_output(status);
6
}
Das ist natürlich doof für das Hauptprogramm, weil dann Eingaben liegen
bleiben. Wie macht man sowas besser?
Die Funktion mit einer static variable versehen? Also alle 5ms aufrufen
und den zweiten Teil im counter aufrufen? Was wenn ich mal 5 Befehle
schicken will und 5 Delays ersetzen möchte? Das wird doch dann schnell
unübersichtlich.
Paul B. schrieb:> Wie macht man sowas besser?
Nennt sich hochtrabend "Zustandsautomat".
In der Praxis heißt das: es gibt kein delay() im Programm. Die
Hauptschleife wird schnellstmöglich durchlaufen und Zeiten werden durch
Abfragen eines Timerticks (= ein ms-Zähler) und Berechnen von
Zeitdifferenzen ausgewertet.
Etwa so:
Fabian H. schrieb im Beitrag #7364923:
> Es gibt verschiedene Möglichkeiten, um dieses Problem zu lösen. Hier> sind einige Vorschläge:
ChatGPT was here.
Paul B. schrieb:> Das Problem: Das> Gerät ist langsam im verstehen, wenn ich zwei oder drei Befehle sende,> muss dazwischen immer ein 500ms Delay.
Sowas gehört sich nicht. Derjenige, der das verzapft hat, muß ein
blutiger Anfänger gewesen sein. Befehle werden direkt nach dem Empfang
geparst. Benötigt die Befehlsausführung längere Zeit, dann kann der
Master Statusabfragen senden, die mit "Busy" beantwortet werden.
Wie schon gesagt wurde, sendet man längere Sequenzen als
Zustandsmaschine. Jeder Befehl ist dann ein Zustand. Die Befehle werden
in den UART-FIFO gestellt, d.h. die Zustandsmaschine muß nicht bei jedem
Byte warten.
Feste lange Wartezeiten kann man über einen Sheduler ablaufen lassen
bzw. über einen durchlaufenden Timer. Sinnvoller ist allerdings, wenn
der Slave abgefragt werden kann, ob er wieder bereit ist. Feste lange
Wartezeiten sind immer nur eine Notlösung, wenn das Protokoll schon
vergurkt ist.
Peter D. schrieb:>> Das Problem: Das>> Gerät ist langsam im verstehen, wenn ich zwei oder drei Befehle sende,>> muss dazwischen immer ein 500ms Delay.>> Sowas gehört sich nicht. Derjenige, der das verzapft hat, muß ein> blutiger Anfänger gewesen sein.
Es wird dich vielleicht überraschen, aber es muss immer eine maximale
Geschwindigkeit bzw minimale Periodendauer geben.
Ob die jetzt 500ms, 500µs oder 500ns ist, ist erst einmal unerheblich
und sagt keineswegs etwas über die Erfahrung des Entwicklers aus.
MaWin O. schrieb:> aber es muss immer eine maximale Geschwindigkeit bzw minimale> Periodendauer geben.> Ob die jetzt 500ms, 500µs oder 500ns ist, ist erst einmal unerheblich> und sagt keineswegs etwas über die Erfahrung des Entwicklers aus.
Aber wie der dann die diese Aufgabe mit der langsamen Hardware löst, das
sagt durchaus was aus.
Denn wenn dieses langsame Gerät halbwegs brauchbar realisiert ist, dann
kann man es immer anpingen, aber es meldet einfach über die serielle
Schnitte zurück, dass es derzeit noch keine neuen Befehle empfangen
kann.
Da du offenbar die ST-HAL nutzt, bietet es sich an, auch gleich FreeRTOS
mit einzubinden, (geht auch via CubeMX) und deine Kommunikation in einen
eigenen Task zu verlagern.
Dort darfst du dann statt HAL_Delay() osDelay() nutzen, ohne zu
blockieren.
Das Programm schickt den "Init" Befehl in Endlosschleife und alle 500ms
den set_Output Befehl. Soweit so gut.
Aber damit habe ich doch immer noch das Problem, dass alle ich das Gerät
quasi überlaste.
Was ich suche ist ja nicht der zyklische Aufruf, dass mache ich im
Hauptprogramm so wie es in der von Falk verlinkten Seite gezeigt ist. Da
läuft ein 1ms Timer und fragt die Flags ab.
Evetuell ist das Beispiel auch schlecht gewählt, lasst uns daher etwas
einfacheres nehmen.
Angenommen ich habe eine Funktion die mir 5 LEDs mit 500ms verzögerung
anschaltet und wieder ausschaltet.
1
voidLEDs()
2
{
3
LED1an
4
Delay500
5
LED2an
6
Delay500
7
LED3an
8
....
9
}
Diese Funktion rufe ich nach einem Tasterdruck EINMAL auf, den ich in
der Hauptschleife abfrage. Es wird also nicht zyklisch ausgeführt
sondern nur bei Bedarf. Wie löse ich das Delay auf. Ich steh etwas auf
dem Schlauch.
Muss ich dann jedes mal den Status der vorherigen LED abfragen?
MaWin O. schrieb:> Es wird dich vielleicht überraschen, aber es muss immer eine maximale> Geschwindigkeit bzw minimale Periodendauer geben.
Die maximale Geschwindigkeit sollte hautsächlich nur durch das Interface
begrenzt sein. D.h. die Kommunikation und die Steuerung sollten parallel
ablaufen und sich nicht gegenseitig blockieren können. Die
Empfangspuffer sollten so groß sein, daß mindestens ein Steuerbefehl und
ein Abfragekommando Platz haben. Ich mache es auch immer so, daß ich
ADC-Werte reihum auslese, d.h. Abfragen können sofort mit dem letzten
Meßwert aus dem RAM beantwortet werden.
Und wenn eine Spannungsrampe gefahren werden soll, dann kann ich dabei
ständig den Busy-Status abfragen.
Ich würde "so etwas" lösen, in dem Ich vom Warten weg komme und hin zu
Zeitpunkten, an denen etwas zu passieren hat, gehe. Diese Zeitpunkte
(aka timer) lassen sich dann ganz gut mit anderem Input / asynchronen
Ereignissen kombinieren.
Paul B. schrieb:> Das Programm schickt den "Init" Befehl in Endlosschleife
Nein. Die HW wird natürlich nur 1x beim Powerup initialisiert. Muss man
ja nicht laufend machen.
> und alle 500ms den set_Output Befehl. Soweit so gut.> Aber damit habe ich doch immer noch das Problem, dass alle ich das Gerät> quasi überlaste.
1. Dann musst du eben 600ms oder 1000ms oder noch länger warten.
2. Was ist das für ein lahmes "Gerät"?
Paul B. schrieb:> Angenommen ich habe eine Funktion die mir 5 LEDs mit 500ms verzögerung> anschaltet und wieder ausschaltet.> ....> Diese Funktion rufe ich nach einem Tasterdruck EINMAL auf, den ich in> der Hauptschleife abfrage. Es wird also nicht zyklisch ausgeführt> sondern nur bei Bedarf. Wie löse ich das Delay auf.> Muss ich dann jedes mal den Status der vorherigen LED abfragen?
Nein, du machts einen "Zustandszähler" und wenn der auf 0 stehts, dann
fragst du den Taster ab. Wenn der Taster gedrückt ist, schaltest du den
Zähler um 1 weiter.
Wenn der Zustand 1 ist, dann schaltest du die 1. LED ein.
Wenn der Zustand 1 ist und die Zeit abgelaufen ist, dann schaltest du
den Zunstand um 1 weiter.
Wenn der Zustand 2 ist, dann schaltest du die 2. LED ein.
Usw. usf.
In SW sieht das z.B. so aus:
J. S. schrieb:> Anstelle der if-Kette würde ich das switch-case verwenden.
Mir fallen da auch noch ein paar Möglichkeiten ein, aber jetzt erst mal
einen logischen Schritt nach dem anderen. Zuerst geht es allem voran um
die Denkweise, dass man etwas nur dann macht, wenn es nötig ist und den
Rest der Rechenzeit für was anderes verwendet. Denn die mainloop wird
ist jetzt ja zigtausend mal pro Sekunde druchlaufen. Da kann man locker
noch weitere Abfragen udn Zustandsautomaten dazuhängen.
Und wie man das hinterher tatsächlich implementiert, das bleibt dem
jeweiligen Wissensstand überlassen.
Paul B. schrieb:> Das wird doch dann schnell> unübersichtlich.
Ich mache gleichzeitig folgendes:
Kartoffeln kochen,
Wäsche bügeln,
Musik geniessen,
Akkus laden,
das Wetter erleben,
und vieles mehr...
Wo soll ich da das "delay" hin schreiben?
Ich habe das niemals gebraucht, genau so wie das "goto".
"delay" ist von vorgestern und sollte verboten werden.
Danke für die Erklärung!
Lothar M. schrieb:> 2. Was ist das für ein lahmes "Gerät"?
Ein Netzteil, welches sich per UART fernsteuern lässt. Lasse ich das
Delay weg, nimmt er nur den ersten Befehl.
Lothar M. schrieb:> In SW sieht das z.B. so aus:
Ist es wirklich üblich solche Zustandsautomaten Monster in die main zu
packen? Ich dachte immer mal will so viel wie möglich in Funktionen
auslagern. Ich dachte immer man mancht nur in der main die Abfrage des
Tasters und springt dann in die Funktion.
Wenn ich den Zustandsautomaten in die Funktion packe, dann müsste ich
sie ja mehrmals aufrufen damit ich alle Zustände durchspringen kann?
Ich seh den Wald wahrscheinlich vor lauter Bäumen nicht.
Gibts irgendwo auf GitHub ein schönes Beispiel wo man sich mal ein etwas
komplexeres Projekt ansehen kann wo die Sache gut gelöst wurde.
Thomas R. schrieb:> "delay" ist von vorgestern und sollte verboten werden.
Ein Beispiel aus deinem Funduds wäre nett und würde zur Lösung/Erklärung
mehr beitragen als das Verteufeln von delay :).
Lothar M. schrieb:> In SW sieht das z.B. so aus:
Das ist ja grausam.
Mit asynchroner Programmierung schreibe ich es ganz einfach fast so hin,
wie der Threadersteller es geschrieben hat. Ganz ohne globales
Blockieren.
Paul B. schrieb:> Thomas R. schrieb:>> "delay" ist von vorgestern und sollte verboten werden.>> Ein Beispiel aus deinem Funduds wäre nett und würde zur Lösung/Erklärung> mehr beitragen als das Verteufeln von delay :).
In diesem Thread gibt es schon genug gute Beiträge und niemand verwendet
"delay", nur du verwendet diesen Mist.
Paul B. schrieb:> Wenn ich den Zustandsautomaten in die Funktion packe, dann müsste ich> sie ja mehrmals aufrufen damit ich alle Zustände durchspringen kann?> Ich seh den Wald wahrscheinlich vor lauter Bäumen nicht.MaWin O. schrieb:> Mit asynchroner Programmierung schreibe ich es ganz einfach fast so hin,> wie der Threadersteller es geschrieben hat. Ganz ohne globales> Blockieren.
Asynchrone Programmierung, also die Schachtelung von Rückrufschnipseln,
ist eine mögliche alternative Notation zu Zustandsautomaten. MaWin O.,
wäre schön dazu von dir ein Beispiel zu sehen, vielleicht ähnlich dem
Automaten von Lothar ... ?
LG, Sebastian
Thomas R. schrieb:> In diesem Thread gibt es schon genug gute Beiträge und niemand verwendet> "delay", nur du verwendet diesen Mist.
Zu denen ich noch Fragen habe, was üblich und "state of the art" ist.
Von denen du bis jetzt übrigens keine beantworten konntest.
Worin siehst du den Mehrwert deiner Beiträge? Zwei an der Zahl, zweimal
kein fachlicher Gewinn für die Diskussion. Was tribt dich an, inhaltlose
Kommenentare zu Themen abzugeben, von denen du offenbar genauso viel
weißt wie ich. Delay ist Scheiße, hab ich für mich auch festgestellt und
deswegen gibts den Thread hier.
Fabian H. schrieb im Beitrag #7364923:
> Es gibt verschiedene Möglichkeiten, um dieses Problem zu lösen. Hier> sind einige Vorschläge
Nur scheiße dass man von ChatGPT niemals Links bekommt, wo man die
Antworten vertiefen kann. Ziemlich nutzlos ist das so.
Sebastian W. schrieb:> Asynchrone Programmierung, also die Schachtelung von Rückrufschnipseln,> ist eine mögliche alternative Notation zu Zustandsautomaten. MaWin O.,> wäre schön dazu von dir ein Beispiel zu sehen, vielleicht ähnlich dem> Automaten von Lothar ... ?
Ja, gerne. Das sieht praktisch wie der Originalcode vom Threadersteller
aus. Außer, dass dem delay()-Aufruf noch ein .await() oder etwas
ähnliches angehängt wird (je nach Sprache). Und die Funktion wird meist
auch noch mit dem keyword async oder ähnlichem dekoriert.
Das ist aber eigentlich unwichtig. Das Wichtige ist: Der Programmablauf
bleibt lesbar und linear.
Hallo Leute,
ohne jetzt den Thread komplett gelesen zu haben (evtl. wurde meine
Antwort schon gegeben):
In der "Automatisierungstechnik" (ich weiß, das haben wir hier auch)
wird das so gehandhabt, dass eine Funktion erstellt wird, die in jedem
Durchlauf der
Hauptschleife aufgerufen wird. Diese hat als Argument ein
"Start"-Signal.
Ein statischer Pegel oder eine Flanke dieses Parameters triggert in der
Funktion die durchzuführende Aktion. Dabei kehrt die Funktion sehr
schnell zurück. Liegen in der Funktion dann Ergebnisse bzw. Daten vor,
signalisiert die Funktion dies über ein Bit "Auftrag erledigt". Das
übergeordnete Programm kann dann die Daten, die die Funktion erzeugt
hat, ohne Verzögerung abholen und verwenden.
ciao
Marci
Paul B. schrieb:> Ist es wirklich üblich solche Zustandsautomaten Monster in die main zu> packen? Ich dachte immer mal will so viel wie möglich in Funktionen> auslagern.
Wenn das gezeigte Beispiel den gesamten Funktionsumfang zeigen würde,
wäre ich mit der Größe der main() Funktion einverstanden. Ansonsten
haben sich enums bewährt, dann muss man den Kommentar bei der Nutzung
der magic numbers nicht immer wiederholen ;-)
Warum sollte man das nicht in Funktionen auslagern können? Bei dem ARM
Cortex, die ich hauptsächlich nutze, gibt es ein Intrinsic, dass die CPU
schlafen läßt bis irgend ein Interrupt ausgelöst hat.
Wenn jetzt jedes Modul, seine Interrupts lokal händelt und z.B. nötige
Informationen in Modul-Lokalen Variablen, Queues etvl. mit dem main
thread teilt, dann könnte z.B. jedes Modul eine Funktion zur Verfügung
stellen, dass die aktuelle Zeit nimmt, und den Zeitpunkt, zu dem das
Modul etwas zu tun hat, zurück geben lassen:
1
int main()
2
{
3
time_pnt_t next = time_now();
4
5
for ( ;; )
6
{
7
next = module1( next );
8
next = time_min( next, module2( next ) );
9
next = time_min( next, module3( next ) );
10
11
time_wait_till( next );
12
13
__WFI();
14
next = time_now();
15
}
16
}
time_pnt_t wäre ein Typ, der einen Zeitpunkt mit der nötigen Auflösung
speichern kann, der überlaufen kann und trotzdem zuverlässig,
zweifelsfrei den Abstand zwischen zwei geplanten Ereignissen
berücksichtig. `time_now()` liefert die aktuelle Zeit, `time_min()`
liefert den früheren von 2 Zeitpunkten. `time_wait_till()` setzt einen
Timer auf, der __WFI() zum gg. Zeitpunkt wieder aufweckt.
Thomas R. schrieb:> Ich habe das niemals gebraucht, genau so wie das "goto".
In Assembler kann man mit goto (JMP) oder goto mit Flag-Bedienungen viel
Gutes anstellen. In anderen Programmiersprachen wurde goto durch andere
Konstrukte ersetzt, ohne dass es offensichtlich ist. ZB der break aus
der switch oder while oder for Anweisung ist nichts anderes als ein
goto.
Sebastian W. schrieb:> wäre schön dazu von dir ein Beispiel zu sehen, vielleicht ähnlich dem> Automaten von Lothar ... ?
ich hätte da ein Beispiel für asynchrones Eventhandling: