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.
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.
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.
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.
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?
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.
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.
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.
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.
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.
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 ... ?
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.
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.
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.
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.
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.