Forum: Mikrocontroller und Digitale Elektronik ? zu Programmaufbau


von Thomas P. (topla)


Lesenswert?

Hallo zusammen,

für das laufende Projekt habe ich den größten Teil an Funktionen 
zusammengebaut und getestet. Gemäß den Hinweisen hier im Forum und der 
eigenen Logik nach läuft also in der main eine Schleife, in der 
fortlaufend die Hauptfunktion (mit starkt variierender Laufzeit) 
abgearbeitet wird, bei der auch keine größeren Unterbrechungen auftreten 
sollten. Am Anfang befindet sich eine Abfrage der Tastatur und dort wird 
eben auch auf die verschiedenen Tastenbetätigungen reagiert. Solange das 
dem Muster "ein Tastendruck - eine Funktionsauslösung" folgt, ist das 
auch völlig problemlos.
Jetzt bin ich aber an dem Punkt gelandet, wo nach der Tastaturabfrage in 
eine Routine verzweigt werden muss, wo diverse weitere Tastenabfragen 
erfolgen müssen, bis die Funktion soweit mit Daten versorgt ist, die sie 
zwingend für die Ausführung benötigt. Läuft also nach dem Muster "auf 
Tastendruck warten - Anzeige aktualisieren - auf Tastendruck warten - 
Anzeige aktualisieren - ggf. verzweigen - auf Tastendruck warten - .... 
- Ausführung". Da der Mensch hier mit seiner Reaktionsgeschwindigkeit 
der limitierende Faktor ist, bleibt die Hauptfunktion für diese Zeit auf 
der Strecke - was nicht passieren darf.
Man könnte das Dilemma meiner Vorstellung nach mit einer Statusmaschine 
lösen, damit ich nicht auf einen Tastendruck (respektive das Loslassen 
einer Taste) warten muss, aber in Anbetracht der zahlreich möglichen 
Verzweigungen erscheint mir das recht aufwendig und fehleranfällig. 
Notwendig wird wohl der Einbau mehrerer Verriegelungen, da ja z.B. die 
gleiche Taste in der Hauptschleife eine ganz andere Bedeutung als in der 
zweiten Stufe haben könnte.
In einer früheren Version habe ich mir damit beholfen, die Hauptfunktion 
auch beim Warten auf Tastendrücke oder beim Warten auf das Loslassen 
einer Taste aufzurufen. Hat funktioniert, scheint mir aber nicht der 
Stein des Weisen zu sein.
Wie packt man das richtig an?

Danke für konstruktive Hinweise
Thomas

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Thomas P. schrieb:
> - auf Tastendruck warten -

Das Anti-Pattern schlechthin.

Warten ist immer mies.  Für "Warten" gibt es m.E. 2 Anwendungsfälle:

-- Kurze Delays von wenigen Ticks um an lahme (externe) Hardware
   anzugleichen.

-- Einfachste (Beispiel)programme.

von Falk B. (falk)


Lesenswert?

@Thomas P. (topla)

>eben auch auf die verschiedenen Tastenbetätigungen reagiert. Solange das
>dem Muster "ein Tastendruck - eine Funktionsauslösung" folgt, ist das
>auch völlig problemlos.

OK.

>>zwingend für die Ausführung benötigt. Läuft also nach dem Muster "auf
>Tastendruck warten - Anzeige aktualisieren - auf Tastendruck warten -
>Anzeige aktualisieren - ggf. verzweigen - auf Tastendruck warten - ....

Falsch. Man wartet so nicht.

Multitasking

"    Prozesse eines kooperativen Multitaskingsystems warten nicht (lies: 
niemals) auf das Eintreten von Ereignissen, sondern bearbeiten nur 
bereits eingetretene Ereignisse.
    Größere Aufgaben werden in kleine Teilaufgaben zerlegt, welche nur 
durch mehrfaches Aufrufen der Funktion abgearbeitet werden. Das erreicht 
man meist am besten mit einer State machine.
    Prozesse eines kooperativen Multitaskings haben eine garantierte, 
maximale Durchlaufzeit, welche möglichst klein ist."

>Man könnte das Dilemma meiner Vorstellung nach mit einer Statusmaschine
>lösen, damit ich nicht auf einen Tastendruck (respektive das Loslassen
>einer Taste) warten muss, aber in Anbetracht der zahlreich möglichen
>Verzweigungen erscheint mir das recht aufwendig und fehleranfällig.

Nö.

>Wie packt man das richtig an?

Siehe oben. Statemachine + Multitasking.

von Thomas P. (topla)


Lesenswert?

Hmm, prinzipiell ist das einleuchtend und klar, aber wie ich mich (bzw. 
der µC) in dem ganzen Gewirr der verschiedensten möglichen Abfolgen von 
Tastatureingaben zurechtfinden soll, erschließt sich mir noch nicht so 
ganz. Ich krame wohl besser erstmal Papier und Bleistift hervor. Mal 
sehen, ob ich irgendwo ein Beispiel finde, irgendwie stehe ich Moment 
total auf dem Schlauch.

Thomas

von Falk B. (falk)


Lesenswert?

@ Thomas P. (topla)

>Hmm, prinzipiell ist das einleuchtend und klar, aber wie ich mich (bzw.
>der µC) in dem ganzen Gewirr der verschiedensten möglichen Abfolgen von
>Tastatureingaben zurechtfinden soll, erschließt sich mir noch nicht so
>ganz.

Eben DARUM ist eine Statemachine so toll! Da kann man sehr leicht 
Übersicht gewinnen! Dein jetziger Code ist wohl eher Sphagettistyle!

von Thomas P. (topla)


Lesenswert?

Kann man jetzt nennen wie man will, funktioniert aber, indem eben immer, 
wenn auf ein Ereignis gewartet wird, die Hauptfunktion aufgerufen wird. 
Der Aufruf steckt in den beiden Funktionen "Warten auf keine Taste 
gedrückt" und "Warten auf Tastendruck" und ist damit in der rufenden 
Funktion nicht sichtbar. Dafür ist die Abfolge in den Strängen mit 
Tastatureingabe sehr übersichtlich. So doof finde ich diese Lösung nun 
auch nicht.
Dass das so nicht die eleganteste Methode ist, weiß ich ja, sonst hätte 
ich nicht hier nach einer besseren Lösung gefragt.

Thomas

von Max (Gast)


Lesenswert?

Hallo Thomas,


Thomas P. schrieb:
> Dafür ist die Abfolge in den Strängen mit
> Tastatureingabe sehr übersichtlich. So doof finde ich diese Lösung nun
> auch nicht.
> Dass das so nicht die eleganteste Methode ist, weiß ich ja, sonst hätte
> ich nicht hier nach einer besseren Lösung gefragt.

Es stimmt sicher, dass es für die meisten Menschen einfacher ist eine 
lineare Abfolge zu programmieren, als eine FSM mit all ihren 
Verzweigungen im Kopf zu behalten.

Dafür gibt es aber einen Zwischenweg namens Protothreads: 
http://dunkels.com/adam/pt/

Damit kannst du lineare State Machines, im Stile eines Threads 
schreiben.
Was du dafür brauchst steht auf Dunkels Seite. Es sind aber eigentlich 
nur ein paar Macros, mit denen dein Praeprozessor ein großes switch case 
Statement erzeugt.

Damit kannst du immer wieder deine Tastenabfrage aus der Hauptschleife 
aufrufen und musst nicht das ganze Program blockieren.

Max

von Falk B. (falk)


Lesenswert?

@ Max (Gast)

>Es stimmt sicher, dass es für die meisten Menschen einfacher ist eine
>lineare Abfolge zu programmieren, als eine FSM mit all ihren
>Verzweigungen im Kopf zu behalten.

Nicht wirklich. Man muss bei einer Statemachine eben NICHT alles im Kopf 
haben, denn das steht IN der Statemachine! Die kann man GANZ einfach 
tippel tappel Tour Schritt für Schritt durchlaufen, das ist kein 
bisschen schwieriger als ein einfacher linearer Ablauf.

>Dafür gibt es aber einen Zwischenweg namens Protothreads:
>http://dunkels.com/adam/pt/

Och neee!!!

von Max (Gast)


Lesenswert?

Falk B. schrieb:
> das ist kein
> bisschen schwieriger als ein einfacher linearer Ablauf.

Naja, der TO scheint es aber so zu sehen. Sein linearer Ablauf 
funktioniert und er findet ihn "sehr übersichtlich". Es gibt nur das 
Problem, dass es den ganzen Controller blockiert.

>
>>Dafür gibt es aber einen Zwischenweg namens Protothreads:
>>http://dunkels.com/adam/pt/
>
> Och neee!!!

An sich ist das nur ein anderer Formalismus für lineare FSM.
Es gibt sogar ein Beispiel, welches auf Tastendrücke wartet, ganz so 
ähnlich wie die Aufgabe, bei der wir den TO unterstützen wollen:
http://dunkels.com/adam/pt/examples.html#codelock

von Falk B. (falk)


Lesenswert?

@ Max (Gast)

>> das ist kein
>> bisschen schwieriger als ein einfacher linearer Ablauf.

>Naja, der TO scheint es aber so zu sehen. Sein linearer Ablauf
>funktioniert und er findet ihn "sehr übersichtlich".

Der OP ist bockig und verschlossen. Geht nicht, kann ich nicht, will ich 
nicht.

von Thomas P. (topla)


Lesenswert?

Falk B. schrieb:

>>Naja, der TO scheint es aber so zu sehen. Sein linearer Ablauf
>>funktioniert und er findet ihn "sehr übersichtlich".
>
> Der OP ist bockig und verschlossen. Geht nicht, kann ich nicht, will ich
> nicht.

Nana, nur weil ich Deine jahrelange Erfahrung nicht habe und nicht 
sofort vollkommen durchschaue, wie ich das in diesem Fall am Besten 
anfange? Meine state machine für den Empfang serieller Daten läuft 
problemlos; allerdings ist das hier ein anderes Kaliber.
Und ja, der Ablauf funktioniert in der bisherigen Konstellation 
einwandfrei, da hängt auch nichts, da immer dann, wenn gewartet werden 
muss, die Hauptroutine läuft. Aber eben nicht durch den Aufruf in der 
main sondern durch Aufrufe von verschiedenen Stellen aus - das gefällt 
mir nicht.
Und gefragt habe ich hier, weil ich das mal richtig hinstellen möchte 
und dabei auch noch was lernen möchte.

Thomas

von Falk B. (falk)


Lesenswert?

@Thomas P. (topla)

>Und gefragt habe ich hier, weil ich das mal richtig hinstellen möchte
>und dabei auch noch was lernen möchte.

Na dann stell es auf einen Statemachine um und am Ende wirst du es nie 
mehr anders machen wollen ;-)

von Thomas P. (topla)


Lesenswert?

Jo, und da sind wir wieder beim Stand von 15:25.
Wenn ich aus der Tastaturabfrage mit einem Wert einer bestimmten Taste 
komme, kann ich problemlos verzweigen (switch) und die Funktion 
abarbeiten. Komme ich in einen Zweig, der weitere Tastaturabfragen 
benötigt, gehe ich dort den ersten Schritt, setzte die state machine und 
verlasse die Routine wieder. Für den zweiten Durchlauf muss ich jetzt 
alle anderen Abfragen deaktivieren, da ich ja zwingend in den 
"angearbeiten" Programmzweig muss, egal, ob die aktuelle Taste auch in 
der ersten Stufe eines anderen Zweiges vorkommen kann.
Und nein, ich weiß nicht, wie ich das einfach lösen kann.

Thomas

von Falk B. (falk)


Lesenswert?

@ Thomas P. (topla)

>Wenn ich aus der Tastaturabfrage mit einem Wert einer bestimmten Taste
>komme, kann ich problemlos verzweigen (switch) und die Funktion
>abarbeiten.

Das kann die Statemachine auch.

> Komme ich in einen Zweig, der weitere Tastaturabfragen
>benötigt, gehe ich dort den ersten Schritt, setzte die state machine

Du macht einen Zustandsübergang.

>und
>verlasse die Routine wieder.

Genau.

> Für den zweiten Durchlauf muss ich jetzt
>alle anderen Abfragen deaktivieren,

Nein.

> da ich ja zwingend in den
>"angearbeiten" Programmzweig muss, egal, ob die aktuelle Taste auch in
>der ersten Stufe eines anderen Zweiges vorkommen kann.

Dort kommst du so oder so hin, denn es ist eine Statemachine!

>Und nein, ich weiß nicht, wie ich das einfach lösen kann.

Weil du das Konzept der Statemachine noch nicht wirklich verstanden 
hast.

Hier ein Hinweis.
1
switch(state) {
2
  case WAIT_FOR_FIRST_KEY:
3
    if (KEY==UP) {
4
      mach was;
5
      state = WAIT_FOR_SECOND_KEY;
6
    } else if (KEY==DOWN) {
7
      mach was anderes;
8
      state = WAIT_FOR_THIRD_KEY; 
9
    }
10
11
    ansonsten bleiben wir hier
12
  break;
13
14
  case WAIT_FOR_SECOND_KEY:
15
    if (KEY==UP) {
16
      mach was;
17
      state = WAIT_FOR_WHATEVER;
18
    }
19
  break;
20
21
  case WAIT_FOR_WHATEVER:
22
    if (KEY==UP) {
23
      mach was;
24
      state = WAIT_FOR_WHATEVER;
25
    }
26
  break;
27
28
29
}

Verstehst du nun besser, wie eine FSM funktioniert?
Man hangelt sich mit mehreren Zustandsübergängen an der Kette der 
Tastendrücke entlang. Dazwischen kann man immer wieder kleine Funktionen 
aufrufen, welche aber eher ein kurze, zumindestens endliche Laufzeit 
haben (keine Warteschleifen). In JEDEM Zustand kann man immer wieder 
verschieden reagieren. Man muss und darf NICHT auf irgendwas warten, 
sondern das auswerten, was schon vorhanden ist (Tastendruck, aktueller 
Zustand).

Denk mal in Ruhe drüber nach.

von Falk B. (falk)


Lesenswert?

Noch ein Tip. Der bisherige lineare Ablauf mit Aktionen und Warten auf 
Tasten kann recht einfach in eine FSM umgewandelt werden.


Aufgabe 1

Warte auf Taste A

Aufgabe 2

Warte auf Taste B

Aufgabe 3

Warte auf Taste C

All diese Einzelaktionen kommen in einen einzelnen State. Die FSM geht 
nur dann weiter, wenn die jeweilige Taste gedrückt wurde (wird jeweils 
von der Tastenauswertung geliefert). Wird sie NICHT gedrückt, geht die 
FSM zurück und es können andere FSMs aufgerufen werden. Solange die 
jeweilige Taste nicht gedrückt wird, ist die Abfrage in der FSM sehr 
kurz und es wird keine CPU-Zeit vertrödelt.
1
switch (state) {
2
  case mache_A1:
3
    mache Aufgabe 1;
4
    state = warte_taste_A;
5
  break;
6
7
  case warte_taste_A;
8
    if (taste == A) {
9
      state = mache_A2;
10
    }
11
  break;
12
13
  case mache_A2:
14
    mache Aufgabe 2;
15
    state = warte_taste_B;
16
  break;
17
18
  case warte_taste_B;
19
    if (taste == B) {
20
      state = mache_A3;
21
    }
22
  break;
23
24
  case mache_A3:
25
    mache Aufgabe 3;
26
    state = warte_taste_C;
27
  break;
28
29
  case warte_taste_C;
30
    if (taste == C) {
31
      state = mache_A1;
32
    }
33
  break;
34
}

Ich hoffe das bringt Verständnis.

von hmmmm (Gast)


Lesenswert?

Mal doch erstmal alle Deine Zustände auf ein Blatt Papier und schreibe 
an die Pfeile von Zustand zu Zustand was den Wechsel bewirken soll.
Tastenabfrage kannst Du auch global erledigen und dann die Variable der 
Taste(n) in der FSM auswerten.
Siehe z.B. Pedas Tastenentprellung.

von Thomas P. (topla)


Lesenswert?

Jo, danke, das ist nun klarer. Verstanden habe ich das Prinzip schon, 
aber ich habe am Anfang eine Stufe zu weit unten angesetzt. Wenn man den 
ersten Tastendruck in die SM mit einbezieht, ist das dann ganz einfach. 
Genau das, was die SM nach den ersten Tastendruck macht, meinte ich mit 
dem Ausdruck "Verriegeln" der anderen Tasten(codes).

Dankeschön,
thomas

von Thomas P. (topla)


Lesenswert?

So, ich buddle den ursprünglichen Thread wegen dem Kontext meiner Frage 
noch einmal aus, weil ich nach erfolgreicher Umsetzung der SM-Ratschläge 
bei einer Erweiterung auf einen Fall gestoßen bin, bei dem ich noch 
einmal Hilfe benötige.
Mit Hilfe einer SM hangle ich mich durch mehrere Tastenbetätigungen zu 
einem Punkt, an dem ein- bis dreistellige Zahlen eingegeben, korrigiert 
und das ganze mit einer Taste abgeschlossen wird.
In der alten Version, die jetzt auf nicht blockierende Arbeitsweise 
umzurüsten ist, werkelt hier eine Funktion der Art:

uint16_t get_int (uint16_t vorbel, char x, char y)
{
vorbel auf Position x;y ausgeben
do
  {
    Anzeige des eingegebenen/bearbeiteten Werts
    Tastatur abfragen
    Wert ändern
  } while (Taste "enter")
return (aktueller Wert)
}

In dem Statusnetz wird diese Funktion an unterschiedlichen Stellen 
benötigt, insgesamt 14 Mal mit unterschiedlichen Parametern. Würde diese 
Funktion nur einmal benötigt, könnte man das in das vorhandene 
Statusnetz integrieren.
Aber wie gehe ich vor, wenn das als Unterprogramm funktionieren soll? 
Man müsste sich ja dann den Aufrufpunkt (Status) bzw. den auf den Aufruf 
folgenden Status merken, also quasi einen eigeen Stack einrichten.
Hat da jemand vielleicht ein Beispiel? Irgendwie tue ich mich schwer, da 
was Vernünftiges zusammenzubringen.

Thomas

von EinmalEinsIstEins (Gast)


Lesenswert?

Dir ist vermutlich soweit schon aufgegangen, dass das Du den 
gegenwärtigen Zustand einer Zustandsmaschine in einer Variablen 
speicherst.

Nun ist also die Frage: Wenn ich statt einer nun zwei Zustandsmaschinen 
habe, wieviele Zustandsvariablen gibt es dann? Das ist wie bei den 
Autos. Wenn ich statt einem, plötzlich zwei Autos habe, wieviele 
Lagerplätze für die Winterreifen brauche ich nun?

So schwer war das nicht, oder?

Die Antwort ist: Doppelt so viele.

Gut. Das selbe Spiel noch einmal, nun aber in Bezug auf die Variablen 
die innerhalb einer Zustandsvariable manipuliert werden. In Deiner 
Frage, Dein Textbuffer.

Wenn ich erst eine Zustandsmaschine habe in der ein Textbuffer 
manipuliert wird, und dann eine zweite, die einen zweiten Text 
manipuliert damit sich die Inhalte nicht überschneiden oder stören, 
wieviele Textbuffer brauche ich dann? Lautet die Antwort: Zwei? Dann 
hast Du richtig geraten.

OK. Nächster Schritt:

Wenn ich eine Ansammlung von Variablen habe, die sinngemäß irgendwie 
zusammengehörig sind - in Deinem Fall einen Status und einen Textbuffer 
- wie kann ich die im Programm noch anders organisieren als eben zwei 
Variablen (den Zustand und den Buffer) zu haben? Das Zauberwort lautet: 
"struct".

OK. Noch ein Schritt:

Wenn ich eine Struktur habe, in der Zustand und Textbuffer vereint sind 
und ich brauch einen weiteren Zustandsautomaten, der unabhängig davon 
einen weiteren Textbuffer manipuliert, brauche ich dann eine zusätzliche 
Struktur mit der selben Bedeutung? Antwort: Ja.

Voilá: Du hast es. Einfach wie das kleine Ein-Mal-Eins. OK. Bei 14 
braucht man dann noch das große Ein-Mal-Eins.

von EinmalEinsIstEins (Gast)


Lesenswert?

Wobei meine Antwort eigentlich nicht passt, denn Deine Skizze hat ja 
keinen Zustand als Variable nötig. Ich bin irrigerweise davon 
ausgegangen, dass Du die Tastendrücke in einer Statemachine 
verarbeitest.

Also reicht es, wenn Du jeweils eine Struktur aus einem Buffer und einem 
Index anlegst und einen Zeiger darauf jeweils beim Aufruf übergibst.

So kannst Du die Buffer unterscheiden. Und der RÜckgabewert, sagt Deiner 
Statemchine ob sie nun in einen anderen Zustand wechseln soll oder 
nicht. (Weiß nicht, was in Deiner Skizze "aktueller_Wert" sein soll).

von Thomas P. (topla)


Lesenswert?

Hmm, erstmal Danke für die Antwort.
Ist aber an der Frage vorbei, da habe ich mich wahrscheinlich nicht 
eindeutig genug ausgedrückt.
Ich habe 14 verschiedene Aufrufstsellen für diese Funktion, die aber 
immer sequentiell aktiv sind. Ich benötige also das Umfeld dieser 
Funktion (Buffer) nur einmal.
Mal als Beispiel mit zwei Aufrufpunkten:
Benötigt wird der Aufruf der Funktion aus den Zuständen 45 und 88. Jetzt 
kann man die Zustände der Funktion (angenommen 3 mit 100, 101, und 102) 
hinter 45 und 88 anordnen: 45-100-101-102-46 und 88-103-104-105-89. 
Dabei hat man die Funktion quasi als Inline (und eben doppelt bzw. 
14-fach. Das soll aber ein Unterprogramm (Funktion) bleiben: 
45-100-101-102-46 und 88-100-101-102-89. Wie rettet man also nun die 46 
und die 89 möglichst geschickt über den Funktionsaufruf? Denkbar wäre ja 
auch eine mehrstufige Schachtelung.

Thomas

von Thomas P. (topla)


Lesenswert?

EinmalEinsIstEins schrieb:
> (Weiß nicht, was in Deiner Skizze "aktueller_Wert" sein soll).

Die Variable steht für das Ergebnis der Eingabe bzw. der Bearbeitung in 
der Funktion.

Thomas

von EinmalEinsIstEins (Gast)


Lesenswert?

Dann habe ich Dich wohl nicht verstanden und ich versuche es nochmal.

Du hast die Funktion als Zustandsmaschine implementiert. Du willst die 
Zustände und deren Abfolge in die erste Zustandsmaschine "integrieren".
Du willst aber die immer gleiche Folge von Zuständen nicht 14 mal 
hinschreiben.
Richtig?

Da wäre die Frage, wozu Du die Funktion als ihre Zustände in die andere 
Zustandsmaschine integrieren willst. Das ist doch gar nicht nötig und 
erforderte andernfalls eben solche Tricks mit Stacks von Zuständen.
Mit einem ganz normalen Funktionsaufruf hast Du doch schon einen Stack 
(nämlich den der Rückkehradresse).

von EinmalEinsIstEins (Gast)


Lesenswert?

Mal so als Skizze:

[c]
void Statemachine ()

static int Status = START;

switch Status:
   case START:
      Resultat = Funktion (x,y,z...
      if (Resultat == ...)
         Status = WEITER;
      break;
   .
   .
   .
   case HASE_GESEHEN:
      Resultat = Funktion (x,y,z...
      if (Resultat == ...)
         Status = GRILL_ANWERFEN;
      break;
   .
   .
   .
}

int Funktion (x,y,z...
int Status:
   switch (Status)

   wie gehabt
}

von EinmalEinsIstEins (Gast)


Lesenswert?

Wobei der Status innerhalb der "Funktion", wenn ich Dein Problem recht 
verstehe, und sie innerhalb eines Zustands der "großen Zustandsmaschine" 
mehrmals aufgerufen wird, ehe die ihren Zustand ändert, natürlich auch 
static sein muss.

Das sind einfach nur zwei verschiedene Zustandsmaschinen. Die haben 
nicht nötigerweise was miteinander zu tun; heisst sie haben keine 
Variablen gemeinsam oder bewegen sich im selben "Zustandsraum".

von Thomas P. (topla)


Lesenswert?

EinmalEinsIstEins schrieb:
> Dann habe ich Dich wohl nicht verstanden und ich versuche es nochmal.
>
> Du hast die Funktion als Zustandsmaschine implementiert. Du willst die
> Zustände und deren Abfolge in die erste Zustandsmaschine "integrieren".
> Du willst aber die immer gleiche Folge von Zuständen nicht 14 mal
> hinschreiben.
> Richtig?

Ja, genau das ist das Problem.

> Da wäre die Frage, wozu Du die Funktion als ihre Zustände in die andere
> Zustandsmaschine integrieren willst. Das ist doch gar nicht nötig und
> erforderte andernfalls eben solche Tricks mit Stacks von Zuständen.
> Mit einem ganz normalen Funktionsaufruf hast Du doch schon einen Stack
> (nämlich den der Rückkehradresse).

Weil die Funktion mehrfach die Tastatur abfragt, um eine bis zu 
dreistellige Zahl einzugeben bzw. einen Vorbelegungswert zu editieren 
bis die Funktion mit der Taste "enter" beendet wird. In der Zwischenzeit 
steht die Main, in der aber das Hauptprogramm zyklisch abgearbeitet 
werden muss. Auflösung über den Vorschlag von Falk mit einer SM, die 
eben nicht blockierend funktioniert.

Thomas

von EinmalEinsIstEins (Gast)


Lesenswert?

Thomas P. schrieb:
> EinmalEinsIstEins schrieb:
>> Dann habe ich Dich wohl nicht verstanden und ich versuche es nochmal.
>>
>> Du hast die Funktion als Zustandsmaschine implementiert. Du willst die
>> Zustände und deren Abfolge in die erste Zustandsmaschine "integrieren".
>> Du willst aber die immer gleiche Folge von Zuständen nicht 14 mal
>> hinschreiben.
>> Richtig?
>
> Ja, genau das ist das Problem.
>
>> Da wäre die Frage, wozu Du die Funktion als ihre Zustände in die andere
>> Zustandsmaschine integrieren willst. Das ist doch gar nicht nötig und
>> erforderte andernfalls eben solche Tricks mit Stacks von Zuständen.
>> Mit einem ganz normalen Funktionsaufruf hast Du doch schon einen Stack
>> (nämlich den der Rückkehradresse).
>
> Weil die Funktion mehrfach die Tastatur abfragt, um eine bis zu
> dreistellige Zahl einzugeben bzw. einen Vorbelegungswert zu editieren
> bis die Funktion mit der Taste "enter" beendet wird. In der Zwischenzeit
> steht die Main, in der aber das Hauptprogramm zyklisch abgearbeitet
> werden muss. Auflösung über den Vorschlag von Falk mit einer SM, die
> eben nicht blockierend funktioniert.
>
> Thomas

Dann habe ich ja Deine Frage beantwortet. Siehe mein Beispiel.
Oder gibt es da noch ein Detail, das Du erklärt haben möchtest?

Wenn das mit dem Satzanfang mal versuche zu deuten, dann liegt das 
Missverständnis vielleicht darin, das Eingaben "innerhalb" der 
Zustandsmaschine geholt werden. Wenn man das aber macht, dann ist die 
Maschine tatsächlich "blockieren" bis der nächste Input kommt.

Das ist aber nicht die übliche Vorgehensweise. Vielmehr ruft man die 
Zustandsmaschine auf und sie beendet sich wieder wenn sie den vorherigen 
Input verarbeitet hat. Dann gibt es neuen Input und wieder einen Aufruf 
der Funktion mit der Zustandsmaschine.
Man wartet nicht in dem Code eines Zustandes auf Input und macht das 
verlassen der Funktion davon abhängig. (Nur falls man eben die 
Nebenläufigkeit nicht braucht).

von Thomas P. (topla)


Lesenswert?

Beim Aufruf der "Funktion" habe ich ja auch kein Problem, da ich weiß, 
dass ich diese Funktion benötige und nach dem Zustand 45 einfach 100 
setzen. Der Rest in dieser Funktion läuft dann immer gleich ab: 101-102. 
Woher weiß ich jetzt aber die Rückkehradresse nach 102? In diesem 
Beispiel 46, im zweiten Beispiel 89. Da brauche ich doch so eine Art 
Stack, um das zu verwalten, könnte ja auch mehrstufig werden.
Ich kann doch nicht der Erste sein, der auf dieses Problem läuft. Oder 
habe ich mir das aus Dämlichkeit selber organisiert?

Thomas.

von EinmalEinsIstEins (Gast)


Lesenswert?

Ich erweitere mal meine obige Skizze, damit man das sehen kann:
1
main () {
2
   while(1) {
3
      if (InputAvailable) {
4
         Statemachine (Input);
5
      }
6
      // Tue was anderes
7
   }
8
}
9
10
void Statemachine (char Input)
11
12
static int Status = START;
13
14
switch Status:
15
   case START:
16
      Resultat = Funktion (Input);
17
      if (Resultat == ...)
18
         Status = WEITER;
19
      break;
20
   .
21
   .
22
   .
23
24
25
   case HASE_GESEHEN:
26
      Resultat = Funktion (Input);
27
      if (Resultat == ...)
28
         Status = GRILL_ANWERFEN;
29
      break;
30
   .
31
   .
32
   .
33
}
34
35
int Funktion (char Input);
36
int Status:
37
int ret_val = RUF_MICH_NOCHMAL_AUF;
38
39
   switch (Status)
40
     case START:
41
        if (Input == ...
42
           Status = NOCH_KEIN_ENDE;
43
        break;
44
     case ...:
45
   // wie gehabt
46
  return (ret_VAL);
47
}

von EinMalEinsIstEins (Gast)


Lesenswert?

Thomas P. schrieb:
> [...]
> Oder
> habe ich mir das aus Dämlichkeit selber organisiert?

Hm. Ich fürchte, so könnte man das formulieren.

Guck mal meine letzte Skizze an.

von EinMalEinsIstEins (Gast)


Lesenswert?

Thomas P. schrieb:
> [...]
> Woher weiß ich jetzt aber die Rückkehradresse nach 102? In diesem
> Beispiel 46, im zweiten Beispiel 89.

Der Compiler erzeugt Code, so daß nach einem Funktionsende genau mit dem 
Befehl nach dem Funktionsaufruf fortgefahren wird.
1
a();    // nach DIESEM Aufruf ...
2
z+=1;   // geht es genau HIER weiter
3
.
4
.
5
.
6
a();    // und nach DIESEM Aufruf ...
7
k = k + 7;   // geht es genau hier weiter
8
9
a () {
10
  // mach irgendwas
11
}

Das ist aber jetzt schon very basic, wenn ich das anmerken darf.
Und mit Zustandsmaschinen ist das ganz genauso. Das ist einfach ganz 
normaler C-Code.

Oder was ist das Problem?

von Thomas P. (topla)


Lesenswert?

Danke, ich schaue mir das Morgen an, jetzt mache ich mich erstmal auf 
die Matratze, der Tag war lang.

Thomas

von Thomas P. (topla)


Lesenswert?

Das Problem ist, dass ich nach meinem Verständnis nach dem Umbau einer 
Funktion auf SM eben keinen klassischen Funktionsaufruf mit Parametern 
mehr habe.
Im Beispiel ist der erste Schritt der Funktion die Ausgabe des 
übergebenen Wertes auf das LCD.
Der zweite Schritt fragt die Tastatur ab. Ein Tastendruck wird zwingend 
benötigt, um die Funktion zu verlassen. Um ein Blockieren der 
Hauptfunktion zu vermeiden, kann (soll) ich nicht auf den Tastendruck 
warten und muss die Funktion verlassen, um die weitere Abarbeitung in 
der Main zuzulassen. Wie komme ich jetzt nach dem Durchlauf der Main 
wieder an die Stelle der Funktion?

Thomas

von EinMalEinsIstEins (Gast)


Lesenswert?

Thomas P. schrieb:
> Das Problem ist, dass ich nach meinem Verständnis nach dem Umbau einer
> Funktion auf SM eben keinen klassischen Funktionsaufruf mit Parametern
> mehr habe.
> Im Beispiel ist der erste Schritt der Funktion die Ausgabe des
> übergebenen Wertes auf das LCD.
> Der zweite Schritt fragt die Tastatur ab. Ein Tastendruck wird zwingend
> benötigt, um die Funktion zu verlassen. Um ein Blockieren der
> Hauptfunktion zu vermeiden, kann (soll) ich nicht auf den Tastendruck
> warten und muss die Funktion verlassen, um die weitere Abarbeitung in
> der Main zuzulassen. Wie komme ich jetzt nach dem Durchlauf der Main
> wieder an die Stelle der Funktion?
>
> Thomas

Du hast doch den Zustand in einer static-Variable gespeichert. Das ist 
jedenfalls der übliche Weg, wenn man Nebenläufigkeit erreichen will. Der 
Switch fällt also genau in den case der darin steht. Solange Du den 
Zustand nicht änderst, trifft immer genau dieser case zu. Und das tut es 
immer wieder solange bis Du den Zustand änderst.

von Tommi (Gast)


Lesenswert?

Thomas P. schrieb:
> Aber wie gehe ich vor, wenn das als Unterprogramm funktionieren soll?
> Man müsste sich ja dann den Aufrufpunkt(Status) bzw. den auf den Aufruf
> folgenden Status merken, also quasi einen eigenen Stack einrichten.

Denkbar waere, der Tastenabfrage eigene Zustaende im gleichen 
Zustandsautomaten zu spendieren und vor Aufruf den gewuenschten Zustand 
nach Tastenabfrage zu speichern. Die Zustaende der Tastenabfrage tanzen 
damit aus der Reihe, ist aber klar, da sie von verschiedenen Stellen 
gerufen werden koennen.

Also

Zustand1{
...
Zustand= Zustand2
}
Zustand2{
...
FolgeZustand= Zustand3
Zustand= Tastenabfrage
}
Zustand3{
...
Zustand= Zustand1
}
Tastenabfrage{
...
Zustand= FolgeZustand
}

von EinMalEinsIstEins (Gast)


Lesenswert?

@ Thomas

Ich möchte Dir raten den Beitrag von Tommi zu ignorieren.
"Viele Köche verderben den Brei". Falls Du weiter mit mir reden oder 
jedenfalls der von mir eingeschlagenen Linie folgen willst helfe ich 
gerne, ansonsten bin ich raus.

Tommis Vorschlag würde so gehen ist aber eigentlich Deine Idee (nur ohne 
das Wort Stack zu verwenden und indem ein ein-stufiger Stack verwendet 
wird) und komplizierter als überhaupt notwendig.
Als wenn man ein neues Auto kauft, weil man den Tankdeckel nicht 
aufkriegt.

Meine Linie hingegen macht irgendwelche Stacks, Tricks usw. völlig 
unnötig einfach nur indem Du Deine Sichtweise des Problems veränderst 
, dass was Du über das Problem denkst .

Du hast, soweit ich das bis jetzt verstehe, ein Problem damit, zwei 
Zustandsmaschinen in ihrer Bedeutung zu trennen und daraus die 
Implementierung abzuleiten.

Insbesondere ist unnötig, dass eine Sub-Zustandsmaschine irgendwas davon 
weiß, was abhängig von ihrem eigenen Verhalten oder Zustand, der 
Folgezustand in der übergeordneten Zustandsmaschine ist. Anders 
ausgedrückt muss die übergeordnete Maschine nicht von der Sub-Maschine 
mitgeteilt bekommen, was ihr eigener Folgezustand ist. Das kann sie 
allein entscheiden. Es muss eine Verabredung (eine Festlegung durch 
Dich) existieren, mit welchen Codes (Zahlen, Texten uswusf.) die 
Sub-Maschine der übergeordneten Maschine etwas mitteilt.
Was nötig oder unnötig ist, ist aber ein elementares Thema in der 
Programmierung mit häufig bedeutenden Konsequenzen.

Das zweite Verständnisproblem scheint verursacht zu haben, dass Du die 
Eingabe in die Zustände hineingeschrieben hast und nicht ausserhalb der 
Maschine abhandelst. Das hat aber die Folge das so die Zustandsmaschine 
blockierend ist und die Funktion in der sie implementiert ist, nicht 
mehr nebenläufig zu anderen Funktionen sein kann.

von Possetitjel (Gast)


Lesenswert?

Thomas P. schrieb:

> Das Problem ist, dass ich nach meinem Verständnis nach
> dem Umbau einer Funktion auf SM eben keinen klassischen
> Funktionsaufruf mit Parametern mehr habe.

Ein klares und entschiedenes Jein.

Rein formal wird man einen endlichen Automaten ("finite state
machine") sicherlich als Funktion implementieren - ob mit oder
ohne Parameter sei mal dahingestellt.

Inhaltlich ist diese "C-Funktion" ein Objekt -- denn natürlich
braucht unser endlicher Automat eine Zustandsvariable, die
den aktuellen Zustand des Automaten von Funktionsaufruf zu
Funktionsaufruf rettet. Gekapselte Variable plus Methode
gleich Objekt.

> Im Beispiel ist der erste Schritt der Funktion die Ausgabe
> des übergebenen Wertes auf das LCD.
> Der zweite Schritt fragt die Tastatur ab. Ein Tastendruck
> wird zwingend benötigt, um die Funktion zu verlassen. Um
> ein Blockieren der Hauptfunktion zu vermeiden, kann (soll)
> ich nicht auf den Tastendruck warten und muss die Funktion
> verlassen, um die weitere Abarbeitung in der Main zuzulassen.

Richtig. Soweit klar.

> Wie komme ich jetzt nach dem Durchlauf der Main wieder an die
> Stelle der Funktion?

Durch die Zustandsvariable.

Die static-Variable innerhalb der Funktion behält ja ihren Wert
auch außerhalb der Laufzeit der Funktion. Beim nächsten Aufruf
steht dieser Wert also ohne weitere Maßnahmen zur Verfügung.

Die einzelnen Zustände werden - wie von EinmalEinsIstEins oben
vorgeschlagen - als große switch-case-Konstrukion ausprogrammiert.

Die static-Zustandsvariable verwendest Du als Steuervariable im
switch/case. Bei Aufruf der Funktion findest Du Dich sofort ohne
weitere Maßnahme im "case"-Teil des aktuellen Zustandes wieder.

Was ist daran unklar?

von Possetitjel (Gast)


Lesenswert?

EinMalEinsIstEins schrieb:

> Meine Linie hingegen macht irgendwelche Stacks, Tricks
> usw. völlig unnötig einfach nur indem Du Deine Sichtweise
> des Problems veränderst, dass was Du über das Problem denkst .

Naja. Der Fairness halber möchte ich anmerken, dass endliche
Automaten, die von Steuerungstechnikern im Halbschlaf programmiert
werden können, für "normale" Programmierer i.d.R. recht ungewohnt
sind. Das ist schon eine spezielle Denkweise.

Gekoppelte Automaten (=Automatennetze) sind dann noch eine
Stufe härter.

> Insbesondere ist unnötig, dass eine Sub-Zustandsmaschine
> irgendwas davon weiß, was abhängig von ihrem eigenen
> Verhalten oder Zustand, der Folgezustand in der übergeordneten
> Zustandsmaschine ist. Anders ausgedrückt muss die übergeordnete
> Maschine nicht von der Sub-Maschine mitgeteilt bekommen, was ihr
> eigener Folgezustand ist. Das kann sie allein entscheiden.

Richtig.
Der untergeordnete Automat muss vom übergeordneten Rechenzeit
erhalten, d.h. die Funktion muss aufgerufen werden, und es muss
"irgendwas" vom untergeordneten Automaten zurückgemeldet werden.
Im einfachsten Falle ist das ein boolscher Funktionswert als
Freigabe.

> Es muss eine Verabredung (eine Festlegung durch Dich) existieren,
> mit welchen Codes (Zahlen, Texten uswusf.) die Sub-Maschine der
> übergeordneten Maschine etwas mitteilt.

Genau.

> Das zweite Verständnisproblem scheint verursacht zu haben, dass
> Du die Eingabe in die Zustände hineingeschrieben hast und nicht
> ausserhalb der Maschine abhandelst. Das hat aber die Folge das
> so die Zustandsmaschine blockierend ist

Das sehe ich noch nicht ganz.
Wenn man erstmal prüft, ob überhaupt ein neues Zeichen vorhanden
ist, dann passiert doch nix Schlimmes. Wenn keins da ist, wird
abgebrochen, und wenn eins da ist, wird gearbeitet.

von Thomas P. (topla)


Lesenswert?

Zunächst mal ein Dankeschön für die schnelle Hilfe.
Ich hoffe, ich habe das richtig gelesen und verstanden:
Die Teile der Funktion sind nicht in die große SM zu inegrieren.
Die Funktion für die Dateneingabe/-darstellung ist so zu modifizieren, 
dass
- die eigentlichen funktionsinternen Variablen global definiert
  werden
- eine SM in die Funktion gebaut wird, die bei mehrmaligem
  Aufruf das Ansprechen der verschiedenen Schritte steuert
- Abschluss des letzten Funktionsschrittes durch Rückgabewert 
signalisieren

Da werde ich mich mal ans Werk machen.

Danke,
Thomas

von Nosnibor (Gast)


Lesenswert?

Ich habe solche "unter-Zustandsmaschinen" gelegentlich als eine Art 
Filter implementiert, also als Funktionen, die zuerst aufgerufen werden 
und dann zurückmelden, ob sie die Eingabe "verbraucht" haben oder nicht:
1
void keypressed(char key) {
2
  if(zahleneingabe(key)) return;
3
  switch(state) {
4
    ...
5
  }
6
}
zahleneingabe() hat dann natürlich einen eigenen Zustand (der 
normalerweise auf IDLE steht, also nichts tun und false zurückliefern), 
plus weitere globale Variablen für die Anzahl einzugebender Stellen, 
aktuelle Stelle, aktueller Wert, Darstellungsformat (z.B. xx:xx oder 
xxx.x), ...
Wenn die Haupt-Zustandsmaschine eine Zahleneingabe braucht, setzt sie 
sämtliche globalen Variablen der Zahleneingabe (incl. den Zustand) 
passend (dafür gibt es natürlich eine Funktion), und setzt dann ihren 
eigenen Zustand entsprechend. Die Haupt-Zustandsmaschine wird dann erst 
wieder erreicht, wenn die Zahleneingabe fertig ist (also z.B. wenn "OK" 
oder "Abbrechen" gedrückt wurde).

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.