Forum: Mikrocontroller und Digitale Elektronik Zustandsautomat mit avr-gcc


von Frank (Gast)


Lesenswert?

Hallo!

Wie realisiert man am besten einen Zustandsautomat mit dem GCC? Einfach 
über eine Zustandsvariablen und switch() Anweisungen oder gibts da 
bessere Wege, oder gar Compilerunterstützung?

MFG

von ... (Gast)


Lesenswert?

Variable+switch ist ok. Wenn du C++ verwendest und es bissl 
komplizierter magst, dann kannst du das auch über Objekte machen. (state 
design pattern)

von Stefan B. (stefan) Benutzerseite


Lesenswert?

Frank schrieb:

> Wie realisiert man am besten einen Zustandsautomat mit dem GCC? Einfach
> über eine Zustandsvariablen und switch() Anweisungen oder gibts da
> bessere Wege, oder gar Compilerunterstützung?

Es gibt Beschreibungssprachen für Zustandsautomaten (state machines) 
und Compiler aus diesen Sprachen nach C, C++,...
http://freshmeat.net/search?q=state+machine&submit=Search

von ... (Gast)


Lesenswert?

Vielleicht noch ein kleiner Tipp:

Bevor du jetzt zum softwaretechnischen Rundumschlag ausholst, denk bitte 
daran:

KISS - keep it simple stupid

Das soll heißen, wenn du dein Problem einfach mit einer Zustandsvariable 
und einem switch lösen kannst, dann mach es auch so. Du solltest aber 
dann nicht die gesamte Funktionalität in der switch-Anweisung 
ausprogrammieren. Also ich halte sowas hier für sinnvoll:
1
switch (state)
2
{
3
case STATE_ABC: state_abc(); break;
4
case STATE_DEF: state_def(); break;
5
...
6
}

Ich hab schon Sourcecode gesehen, da hat einer das gesamte Programm in 
einen switch Block gepackt, der dann weit über 2000 Zeilen lang war. Da 
sieht niemand mehr durch. Er hatte dann auch keine Lust gehabt, neue 
Zustände einzuführen und hat dann die Funktion rekursiv mit 
irgendwelchen Nebenbedingungen aufgerufen.

von Fralla (Gast)


Lesenswert?

>Er hatte dann auch keine Lust gehabt, neue
>Zustände einzuführen und hat dann die Funktion rekursiv mit
>irgendwelchen Nebenbedingungen aufgerufen.

In diese Versuchung kam ich auch schon, erschien mir einfacher und 
übersichtlicher eben weil kein zusätzlicher state.

Also werd ich nach dem KISS arbeiten ariable+switch

von Eddy C. (chrisi)


Lesenswert?

... schrieb:
>
1
> switch (state)
2
> {
3
> case STATE_ABC: state_abc(); break;
4
> case STATE_DEF: state_def(); break;
5
> ...
6
> }
7
>

Naja, das ist doch für Anfänger ganz praktikabel. Aber als Steigerung 
kannst Du es gleich so machen:

// Zustandvariable ist ein Funktionszeiger
void (*state)(void);

// Zustand einstellen
state = state_abc;

// Zustand ausführen:
state();

von Frank (Gast)


Lesenswert?

Zeiger auf Funktion kenn ich on Callbacks, aber welchen Vorteil hat das 
hier? Man muss ja trotzdem den aktuellen State abfragen. Ob ich 
verschiednene Funktionen aufrufe, oder der state() Funktion eine 
Addresse zuweise und ausführe ändert ja nichts. Oder doch?

von Skua (Gast)


Lesenswert?

@Frank

Abfrage erübrigt sich bei Funktionszeigern, die Funktion ist mit dem 
Status identisch.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Skua schrieb:
> @Frank
>
> Abfrage erübrigt sich bei Funktionszeigern, die Funktion ist mit dem
> Status identisch.

Kannst du das konkretisieren, zB in C?

Ich wüsste jetzt nicht, wie ich einen Funktionszeiger-Typ definieren 
könnte für Funktionen, die Funktionen dieses Typs zurückliefern.

Konkret als Skelett mal folgender C-Code:
1
// <-- header start -->
2
typedef enum
3
{
4
    STATE0,
5
    STATE1,
6
    
7
    STATE_NUM
8
}  state_t;
9
10
typedef struct
11
{
12
   int wetter;
13
   int boerse;
14
} welt_t;
15
16
typedef state_t (*statehandler_t)(welt_t*);
17
18
extern state_t handler0 (welt_t*);
19
extern state_t handler1 (welt_t*);
20
21
// <-- header end -->
22
statehandler_t handler[] = 
23
{
24
    handler0,
25
    handler1
26
};
27
28
static welt_t welt = 
29
{
30
   .wetter = 42,
31
   .boese  = -1
32
};
33
34
void main_loop (void)
35
{
36
    state_t state = STATE0;
37
    
38
    while (1)
39
    {
40
        state = handler[state] (&welt);
41
    }
42
}

Hier muss doch wieder eine Status-Variable eingeführt werden. Schaffst 
du es ohne eine solche Variable? Wie sieht dann der Typedef für 
statehandler_t aus, ohne daß man Schweinereien wie Pointer-Casts braucht 
oder über globale Variablen (bäh) kommuniziert?

Du willst also sowas stattdessen:
1
extern state_t handler0 (welt*);
2
extern state_t handler1 (welt*);
3
4
void main_loop (void)
5
{
6
    state_t state = handler0;
7
    
8
    while (1)
9
    {
10
        state = state (&welt);
11
    }
12
}

Preisfrage:
  Wie sieht der Typedef für state_t aus?

Johann

von Rolf Magnus (Gast)


Lesenswert?

> ...ohne daß man Schweinereien wie Pointer-Casts braucht
> oder über globale Variablen (bäh) kommuniziert?

Dein Code enthält doch auch eine globale Variable.
Aber wie wär's damit (vielleicht noch mit geschickterer Namensgebung):
1
typedef void (*statehandler_t)(welt_t*);
2
3
typedef struct
4
{
5
   int wetter;
6
   int boerse;
7
   statehandler_t state;
8
} welt_t;
9
10
extern void handler0 (welt_t*);
11
extern void handler1 (welt_t*);
12
13
static welt_t welt = 
14
{
15
   .wetter = 42,
16
   .boese  = -1,
17
   .state = handler0
18
};
19
20
void main_loop (void)
21
{
22
    while (1)
23
    {
24
        welt.state(&welt);
25
    }
26
}

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Rolf Magnus schrieb:
>> ...ohne daß man Schweinereien wie Pointer-Casts braucht
>> oder über globale Variablen (bäh) kommuniziert?
>
> Dein Code enthält doch auch eine globale Variable.
> Aber wie wär's damit (vielleicht noch mit geschickterer Namensgebung):
>
>
1
> typedef struct
2
> {
3
>    int wetter;
4
>    int boerse;
5
>    statehandler_t state;
6
> } welt_t;
7
> 
8
> void main_loop (void)
9
> {
10
>     while (1)
11
>     {
12
>         welt.state(&welt);
13
>     }
14
> }
15
>

Den Status hatte ich ganz bewusst aus "welt" rausgehalten, weil er da 
konzeptionell nicht hingehört. Die Statemaschine operiert auf einem 
Objekt, sie ist nicht darin verwoben.

Mit folgendem Code kann man vielleicht sowas erreichen, aber wirklich 
verständlich ist das auch nicht gerade und irgendwie grenzwertig:
1
struct welt_t;
2
3
typedef struct state_t
4
{
5
    struct state_t * (*(*state) (struct welt_t*))(struct welt_t*);
6
} state_t;
7
8
extern state_t * (*slartibartfas (struct welt_t * welt))(struct welt_t*);
9
10
extern struct welt_t * get_welt (void);
11
12
void main_loop (void)
13
{
14
    struct welt_t * welt = get_welt();
15
    state_t * (*state)(struct welt_t*) = slartibartfas (welt);
16
    
17
    while (1)
18
        state = state (welt); // warning: assignment from incomparible pointer type
19
}

Leider gibt es in der gekennzeichneten Zeile noch eine Warnung. Das 
ganze ist eher ne Spielerei, um rauszufinden ob sowas geht in C. In 
Haskell oder so ist das bestimmt easy...

Der Code ist aber immerhin korrekt trotz der Warnung:
1
main_loop:
2
  push r28   ; 
3
  push r29   ; 
4
/* prologue: function */
5
/* frame size = 0 */
6
  call get_welt   ; 
7
  movw r28,r24   ;  welt,
8
  call slartibartfas   ; 
9
.L4:
10
  movw r30,r24   ;  state,
11
  movw r24,r28   ; , welt
12
  icall

Johann

von Karl H. (kbuchegg)


Lesenswert?

Johann L. schrieb:

> typedef struct state_t
> {
>     struct state_t * (*(*state) (struct welt_t*))(struct welt_t*);
> } state_t;

Ui.
Das ist aber hart :-)

Ich habs auch noch nie geschafft. Ein typedef für einen 
Funktionspointer, der einen Funktionspointer auf denselben Typ 
zurückgibt, ist eines der grossen Mysterien in C.

PS: Ich würds gar nicht so kompliziert machen.
Einfach einen globalen Funktionspointer und jeder State hat das Recht 
einen anderen State dort einzutragen.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Beim Kopieren verloren gegangen:
1
  rjmp .L4   ;

Johann

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Karl heinz Buchegger schrieb:
> Johann L. schrieb:
>
>> typedef struct state_t
>> {
>>     struct state_t * (*(*state) (struct welt_t*))(struct welt_t*);
>> } state_t;
>
> Ui.
> Das ist aber hart :-)
>
> Ich habs auch noch nie geschafft. Ein typedef für einen
> Funktionspointer, der einen Funktionspointer auf denselben Typ
> zurückgibt, ist eines der grossen Mysterien in C.
>
> PS: Ich würds gar nicht so kompliziert machen.
> Einfach einen globalen Funktionspointer und jeder State hat das Recht
> einen anderen State dort einzutragen.

Ist wie gesagt um die Grenzen auszuloten und zu testen, ob sowas 
überhaupt geht. In meinen Programmen würd ich sowas net hinschreiben.

Was noch stört ist die Struktur, aber wie man die wegbekommen kann, da 
hab ich echt keine Idee. Und in Standard-C (also nicht in GNU-C) gibt 
das bestimmt Fehler.

Man kann natürlich eine Funktion definieren, die als Rückgabewert (oder 
auch als Partameter) Funktionszeiger hat, aber die sind dann von einem 
anderen Typ.

Auch eine Implementierung von slartibartfas, die nichts am State ändert, 
liefert eine Warning.
1
state_t * (*slartibartfas (struct welt_t * welt))(struct welt_t*)
2
{
3
    (void) welt;
4
    return slartibartfas;
5
}

Im Endeffekt passieren an den Stellen, wo gewarnt wird, Pointer-Casts, 
die man ja nicht haben will (zumindest wenn man sauber programmiert).

Johann

von geb (Gast)


Lesenswert?

ja, wirklich beeindruckend wie man einen einfachen übersichtlichen 
switch so programmieren kann, dass man gar nichts mehr versteht. Ich 
hoffe, dass das beim philosophieren bleibt, sonst erschiene es mir etwas 
masochistisch.

l.g. gebhard

von Rolf Magnus (Gast)


Lesenswert?

> Den Status hatte ich ganz bewusst aus "welt" rausgehalten, weil er
> da konzeptionell nicht hingehört.

Du wolltest ja keine separate globale Variable. Notfalls kann man auch 
eine Struktur nochmal außenrum machen, so daß welt ein Teil davon ist 
und der Status ein anderer.


> Die Statemaschine operiert auf einem Objekt, sie ist nicht darin
> verwoben

Das Objekt hat aber einen Zustand. Deshalb sehe ich kein Problem darin, 
diesen im Objekt zu speichern. Das hängt aber davon ab, ob die 
Statemachine das Objekt zur Laufzeit wechseln soll und dabei aber ihren 
Zustand beibehalten muß. In der Regel würde ich davon ausgehen, daß das 
nicht der Fall ist.
Mir gefällt es irgendwie nicht, daß deine Zustandsfunktionen jeweils 
einen neuen Zustand als Returnwert zurückgeben. Da finde ich das 
Speichern im Objekt schöner.

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.