Forum: PC-Programmierung Statemachine Anfängerfrage


von Anfänger (Gast)


Angehängte Dateien:

Lesenswert?

Guten Tag,

ich versuche gerade die Statemachine (siehe Anhang) in C umzusetzen. Es 
handelt sich dabei um eine Lichtsteuerung wobei das Licht nur angehen 
soll wenn ich nachts heimkomme. Dazu habe ich 4 Status zwischen welchen 
gewechselt wird (Tagsüber/Zuhause, Tagsüber/NichtZuhause, Nachts/Zuahuse 
und Nachts/NichtZuhause).
Die Bedingungen sind wie folgt zu interpretieren:
Tag = 1
Nacht = 2
Da = 1
NichtDa = 2

An den Übergängen ist die jeweilige Aktion beschrieben die bei dem 
Übergang in einen anderen Status ausgeführt werden soll.
Hierzu habe ich mir den unten stehenden C-Code überlegt. Allerdings 
kommt mir das nicht sehr elegant vor, aber ich komme im Moment auf keine 
bessere Idee. Daher die Frage wie man diese Statemachine eleganter in C 
Code umsetzen kann.
Die Beispiele die ich gefunden habe basieren leider alle darauf das die 
einzelnen Staus die Funktion beschreiben (in meinem Fall hätte ich dann 
zwei Status, LichtAn und LichtAus). Hierbei verstehe ich allerdings 
nicht wie ich das mit meinen Übergangsbedingungen umsetzen könnte.
PS: Ich weiß das der Code C# ist, ich wollte es nur schnell testen, das 
eigentliche Programm wird in C realisiert.

Vielen Dank im Voraus.

1
private void runstate()
2
        {
3
            switch (state)
4
            {
5
                case 1:
6
                    label1.Text = "state 1";
7
8
                    if (TagNacht == 1 && DaWeg == 1)
9
                    {
10
                        state = 1;
11
                    }
12
                    if (TagNacht == 1 && DaWeg == 2)
13
                    {
14
                        lightOFF();
15
                        state = 2;
16
                    }
17
                    if (TagNacht == 2 && DaWeg == 1)
18
                    {
19
                        lightOFF();
20
                        state = 3;
21
                    }
22
                    if (TagNacht == 2 && DaWeg == 2)
23
                    {
24
                        lightOFF();
25
                        state = 4;
26
                    }
27
                    break;
28
29
                case 2:
30
                    label1.Text = "state 2";
31
32
                    if (TagNacht == 1 && DaWeg == 1)
33
                    {
34
                        lightOFF();
35
                        state = 1;
36
                    }
37
                    if (TagNacht == 1 && DaWeg == 2)
38
                    {
39
                        state = 2;
40
                    }
41
                    if (TagNacht == 2 && DaWeg == 1)
42
                    {
43
                        lightON();
44
                        state = 3;
45
                    }
46
                    if (TagNacht == 2 && DaWeg == 2)
47
                    {
48
                        lightOFF();
49
                        state = 4;
50
                    }
51
                    break;
52
                   
53
                case 3:
54
55
                    label1.Text = "state 3";
56
57
                    if (TagNacht == 1 && DaWeg == 1)
58
                    {
59
                        lightOFF();
60
                        state = 1;
61
                    }
62
                    if (TagNacht == 1 && DaWeg == 2)
63
                    {
64
                        lightOFF();
65
                        state = 2;
66
                    }
67
                    if (TagNacht == 2 && DaWeg == 1)
68
                    {
69
                        state = 3;
70
                    }
71
                    if (TagNacht == 2 && DaWeg == 2)
72
                    {
73
                        lightOFF();
74
                        state = 4;
75
                    }
76
                    break;
77
78
79
                case 4:
80
81
                    label1.Text = "state 4";
82
83
                    if (TagNacht == 1 && DaWeg == 1)
84
                    {
85
                        lightOFF();
86
                        state = 1;
87
                    }
88
                    if (TagNacht == 1 && DaWeg == 2)
89
                    {
90
                        lightOFF();
91
                        state = 2;
92
                    }
93
                    if (TagNacht == 2 && DaWeg == 1)
94
                    {
95
                        lightON();
96
                        state = 3;
97
                    }
98
                    if (TagNacht == 2 && DaWeg == 2)
99
                    {
100
                        state = 4;
101
                    }
102
                    break;
103
104
            }
105
106
107
        }

von Matthias L. (Gast)


Lesenswert?

Ja. ist nichts gegen einzuwenden. Ich würde allerdings noch folgendes 
optimieren:

Das setzen des Textes muss nur einmal passieren, wenn du in den (neuen) 
Schritt reinläufst. Ebenso würde ich das Schalten der Lampen auch einem 
Schritt und nicht einer Transistion zuordnen und auch nur beim Einsprung 
machen.

Beitrag "Re: Programm bzw. Ablaeufe steuern"

von Falk B. (falk)


Lesenswert?

@Anfänger (Gast)

>soll wenn ich nachts heimkomme. Dazu habe ich 4 Status zwischen welchen

Zustände. Die Mehrzahl von Status ist die Status. Klingt komisch, ist 
aber so ;-)

http://www.duden.de/rechtschreibung/Status

>Tag = 1
>Nacht = 2

Ist das nicht redundanz? Wenn Tag ist, kann nicht Nacht sein.

>Da = 1
>NichtDa = 2

Das Gleiche!

>Hierzu habe ich mir den unten stehenden C-Code überlegt. Allerdings
>kommt mir das nicht sehr elegant vor,

Ist er auch nicht.

> aber ich komme im Moment auf keine
>bessere Idee. Daher die Frage wie man diese Statemachine eleganter in C
>Code umsetzen kann.

Siehe Statemachine

von Joe F. (easylife)


Lesenswert?

ehm, Vorschlag: das hier macht das Gleiche.
1
if (TagNacht != TagNacht_old || DaWeg != DaWeg_old) // Zustand hat sich geändert
2
{
3
  if (TagNacht == 2 && DaWeg == 1) // Nacht und da
4
    lightON();
5
  else
6
    lightOFF();
7
8
  TagNacht_old = TagNacht;
9
  DaWeg_old = DaWeg;
10
}

: Bearbeitet durch User
von Joachim B. (jar)


Lesenswert?

Anfänger schrieb:
> if (TagNacht == 1 && DaWeg == 1)

warum so "merkwürdige" Variablennamen?

wäre nicht besser:

if(Tag && Da)

oder

if(!Tag)
if(!Da)

ggfs. auch verschachtelt oder negiert oder verodert -> Wahrheitstabelle.

Tag als bool
Da als bool

wenn nicht Tag, dann muss doch Nacht sein.
wenn nicht Da, dann muss doch Weg sein.

Es steht dir frei überflüssige Variablen einzufügen wenn es dadurch 
leichter lesbar wird, aber fehlerträchtiger.

 Nacht=!Tag;
 Weg=!Da

: Bearbeitet durch User
von Anfänger (Gast)


Lesenswert?

Falk B. schrieb:
>> aber ich komme im Moment auf keine
>>bessere Idee. Daher die Frage wie man diese Statemachine eleganter in C
>>Code umsetzen kann.
>
> Siehe Statemachine

Diesen Artikel habe ich mir selbstverständlich schon angesehen:
Anfänger schrieb:
> Die Beispiele die ich gefunden habe basieren leider alle darauf das die
> einzelnen Staus die Funktion beschreiben (in meinem Fall hätte ich dann
> zwei Status, LichtAn und LichtAus). Hierbei verstehe ich allerdings
> nicht wie ich das mit meinen Übergangsbedingungen umsetzen könnte.


Zu den Variablen, die Namen sind wirklich schlecht gewählt, das werden 
ich ändern.

MfG.

von Eric B. (beric)


Lesenswert?

1
typedef enum
2
{
3
  NONE,
4
  COMING_HOME,
5
  LEAVING_HOME,
6
  DAYTIME,
7
  NIGHTTIME
8
} Event;
9
10
typedef enum
11
{
12
  LIGHT_UNDEFINED,
13
  LIGHT_OFF,
14
  LIGHT_ON
15
} LightState;
16
17
typedef enum
18
{
19
  DN_UNDEFINED,
20
  DN_DAY,
21
  DN_NIGHT
22
} DayState;
23
24
typedef enum
25
{
26
  USER_UNKNOWN,
27
  USER_HOME,
28
  USER_AWAY
29
} UserState;
30
31
static LightState lightState = LIGHT_UNDEFINED;
32
static DayState   dayState   = DN_UNDEFINED;
33
static UserState  userState  = USER_UNKNOWN;
34
35
void runstate(Event ev)
36
{
37
  switch(ev)
38
  {
39
  case COMING_HOME:
40
    userState = USER_HOME;
41
    break;
42
43
  case LEAVING_HOME:
44
    userState = USER_AWAY;
45
    break;
46
47
  case DAYTIME:
48
    dayState = DN_DAY;
49
    break;
50
51
  case NIGHTTIME:
52
    dayState = DN_NIGHT;
53
    break;
54
55
  case NONE:
56
  default:
57
    /* do nothing */
58
  }
59
  if (userState == USER_HOME && dayState == DN_NIGHT)
60
  {
61
    if(lightState != LIGHT_ON)
62
    {
63
      lightON();
64
      lightState = LIGHT_ON;
65
    }
66
  }
67
  else
68
  {
69
    if (lightState != LIGHT_OFF)
70
    {
71
      lightOFF();
72
      lightState = LIGHT_OFF;
73
    }
74
  }
75
}

EDIT: fixed typos

: Bearbeitet durch User
von Anfänger (Gast)


Lesenswert?

wow beric, das sieht sehr viel besser aus als mein Ansatz, vielen Dank 
dafür thumbs up

MfG.

von Joachim B. (jar)


Lesenswert?

Eric B. schrieb:
> typedef enum
> {
>   NONE,
>   COMING_HOME,
>   LEAVING_HOME,
>   DAYTIME,
>   NIGHTTIME
> } Event;

ich sag nur Klasse!

gut lesbar klar logisch, ich lerne auch gerne dazu, mit den enum muss 
ich mich endlich mal anfreunden, fällt mir immer noch schwer.

: Bearbeitet durch User
von Possetitjel (Gast)


Lesenswert?

Joe F. schrieb:

> ehm, Vorschlag: das hier macht das Gleiche. [...]

Nee, leider nicht.

Bei Dir geht das Licht im Zustand "Nacht/Da" immer an - also
auch beim Übergang "Tag/Da --> Nacht/Da"; laut Graph soll es
in diesem Zustand aber aus bleiben.

Deine Idee ist trotzdem gut:

IF ((AlterZustand=Weg) AND (NeuerZustand=Nacht/Da))
  THEN LichtAn
  ELSE LichtAus
;

von Weinga U. (weinga-unity)


Lesenswert?

Passend zum Thema möchte ich euch auf mein XML Framework für 
Statemachines aufmerksam machen:

Seite 16 auf
http://journal.embedded-projects.net/downloads/EPJ_17_web.pdf

und Download

http://sourceforge.net/projects/bioe/files/others/XMLCodeGenerationTutorial/XMLCodeGenerationTutorial_13.05.zip/download

vielleicht hilfts oder jemand findet einen Bug.
Ich selbst habe es gemacht aber bis jetzt nicht wirklich gebraucht. Es 
war eine schöne XSLT Übung :-)

mfg Klaus

von Christian B. (casandro)


Lesenswert?

Ein typisches Verfahren so was zu machen ist eine Zustandstabelle. Das 
verhindert, dass Du so viel praktisch unlesbaren Code mit case oder 
if-Abfragen hast.

Du hättest dann an einer Stelle eine Tabelle in der der momentane 
Zustand, die Übergangsbedinung und der neue Zustand drin steht. Die 
kannst Du dann einfach durchsuchen.

Das macht Deinen Code plötzlich viel kürzer und lesbarer und somit 
leichter zu warten.

von Chris D. (myfairtux) (Moderator) Benutzerseite


Lesenswert?

Joachim B. schrieb:
>
> gut lesbar klar logisch, ich lerne auch gerne dazu, mit den enum muss
> ich mich endlich mal anfreunden, fällt mir immer noch schwer.

Mir geht es ganz genau so - ich weiss auch nicht, warum. Ist einfach so.

Auf jeden Fall ist das eine sehr schöne Implementierung :-)

Weinga U. schrieb:
> Passend zum Thema möchte ich euch auf mein XML Framework für
> Statemachines aufmerksam machen:
>
> Seite 16 auf
> http://journal.embedded-projects.net/downloads/EPJ_17_web.pdf

Vielen Dank für den Hinweis, Klaus. Irgendwie hatte ich das damals wohl 
in der Printausgabe übersehen.

von Rene H. (Gast)


Lesenswert?

Ich würde auch eher auf eine Zustandstabelle greifen. Also erst Enum und 
in der Tabellenbeschreibung enum verwenden.

Gut lesbar, kurzer Code.

von Eric B. (beric)


Lesenswert?

Danke für die Blumen :-)

Christian B. schrieb:
> Ein typisches Verfahren so was zu machen ist eine Zustandstabelle. Das
> verhindert, dass Du so viel praktisch unlesbaren Code mit case oder
> if-Abfragen hast.
>
> Du hättest dann an einer Stelle eine Tabelle in der der momentane
> Zustand, die Übergangsbedinung und der neue Zustand drin steht. Die
> kannst Du dann einfach durchsuchen.
>
> Das macht Deinen Code plötzlich viel kürzer und lesbarer und somit
> leichter zu warten.

Das kann aber, vor allem wenn die Tabelle mal länger wird, zu erheblich 
langsamere Laufzeiten führen. Was die bessere Wahl ist hängt von der 
Anzahl der Zuständen und Übergangen ab, und davon wie man die 
Übergangsbedingungen formuliert.

Rene H. schrieb:
> Ich würde auch eher auf eine Zustandstabelle greifen. Also erst
> Enum und
> in der Tabellenbeschreibung enum verwenden.
>
> Gut lesbar, kurzer Code.

Zeig her! :-)

von Chris D. (myfairtux) (Moderator) Benutzerseite


Lesenswert?

Eric B. schrieb:
>> Du hättest dann an einer Stelle eine Tabelle in der der momentane
>> Zustand, die Übergangsbedinung und der neue Zustand drin steht. Die
>> kannst Du dann einfach durchsuchen.
>>
>> Das macht Deinen Code plötzlich viel kürzer und lesbarer und somit
>> leichter zu warten.
>
> Das kann aber, vor allem wenn die Tabelle mal länger wird, zu erheblich
> langsamere Laufzeiten führen. Was die bessere Wahl ist hängt von der
> Anzahl der Zuständen und Übergangen ab, und davon wie man die
> Übergangsbedingungen formuliert.

Das Problem des langen Suchens könnte man durch Hashen lösen. Dadurch, 
dass die Zustände ja bei Programmstart bekannt sind, kann man sogar "in 
Ruhe" den optimalen Hash berechnen und hätte dann von der Anzahl der 
Zustände unabhängig konstante Zugriffszeiten (O(1)).

von Karl H. (kbuchegg)


Lesenswert?

Chris D. schrieb:

> Das Problem des langen Suchens könnte man durch Hashen lösen.

Wozu willst du da denn hashen?

Du weisst doch zu jedem Zeitpunkt, welches der aktuelle Zustand ist. Das 
ist nichts anderes als der Index in die Tabellenzeilen! Sinnvollerweise 
wird man natürlich die Nummern der Zustände aufsteigend und ohne Lücken 
vergeben. Womit wir dann wieder beim enum sind, deren numerische Werte 
automatisch vom Compiler durchnummeriert werden.

Ein sich möglicherweise ergebender neuer Zustand ist dann nicht anderes 
als der Index der ab dann gültigen Tabellenzeile. Da muss nichts gesucht 
werden.

Das 'Problem' bei tabellengesteuerten Statemaschinen ist die 
Beschreibung der Übergangsbedingung bzw. die zugehörigen Aktionen. Die 
lässt sich in der Tabelle meistens eher schlecht darstellen. Bei 
einfachen Beipielen hat man für jeden Eingang eine Tabellenspalte wobei 
immer noch das Problem bleibt, wie die zu verknüpfen sind. Jeder Ausgang 
ist auch eine Tabellenspalte.
Bei komplexeren Maschinen mit vielen Ein und Ausgängen versagt das aber, 
weil man sonst riesig breite Tabellen kriegt, die dann erst recht wieder 
nicht wartbar sind. Dann kommen in die Tabelle Funktionspointer, was 
wiederrum das 'Problem' aufwirft, dass nicht mehr die komplette Logik 
ausschliesslich innerhalb der Tabelle kodiert ist.

: Bearbeitet durch User
von Joachim B. (jar)


Lesenswert?

Eric B. schrieb:
> Danke für die Blumen :-)

nicht abheben :-)
zum besseren Verständnis fehlt mir noch

wo und wie ist ev definiert und initialisiert?

Eric B. schrieb:
> switch(ev)

Eric B. schrieb:
> static LightState lightState = LIGHT_UNDEFINED;

wo und wie ist LightState definiert und initialisiert?

Eric B. schrieb:
> void runstate(Event ev)

wo und wie ist Event definiert und initialisiert?
dito LIGHT_UNDEFINED und andere?

von Eric B. (beric)


Lesenswert?

Hier mal ein Versuch mit Zustandstabellen; nichtwesentlich kürzer als 
mein obiges Beispiel :-)
1
/* State machine typedefs */
2
typedef void (TransitionFunction *)(void);
3
4
typedef enum{
5
    USER_COMES_HOME, USER_LEAVES_HOME,
6
    SUNRISE, SUNSET,
7
    REQ_LIGHT_ON, REQ_LIGHT_OFF
8
} Event;
9
10
typedef enum {
11
    USER_IS_HOME, USER_IS_AWAY,
12
    DAYTIME, NIGHTTIME,
13
    LIGHT_OFF, LIGHT_ON
14
} State;
15
16
typedef struct {
17
    State old;
18
    Event ev;
19
    State new;
20
    TransitionFunction func;
21
} StateTransition;
22
23
typedef struct {
24
    State current;
25
    uint8 nofTransitions;
26
    StateTransition *transitions;
27
} StateMachine;
28
29
/* fwd declaration transition function(s) */
30
static void flipLight();
31
32
/* User state machine */
33
static StateTransition[2] userTransitions =
34
{
35
    {USER_IS_HOME, USER_LEAVES_HOME, USER_IS_AWAY, flipLight },
36
    {USER_IS_AWAY, USER_COMES_HOME,  USER_IS_HOME, flipLight }
37
};
38
39
static StateMachine smUser =
40
{
41
    USER_IS_HOME, 2u, userTransitions
42
};
43
44
/* Day/Night state machine */
45
static StateTransition[2] dayTransitions =
46
{
47
    {NIGHTTIME, SUNRISE, DAYTIME,   flipLight },
48
    {DAYTIME,   SUNSET,  NIGHTTIME, flipLight }
49
};
50
51
static StateMachine smDayNight =
52
{
53
    DAYTIME, 2u, dayTransitions
54
};
55
56
static StateTransition[2] lightTransitions =
57
{
58
    {LIGHT_OFF, REQ_LIGHT_ON,  LIGHT_ON , lightON },
59
    {LIGHT_ON,  REQ_LIGHT_OFF, LIGHT_OFF, lightOFF }
60
};
61
62
static StateMachine smLight =
63
{
64
    DAYTIME, 2u, lightTransitions
65
};
66
67
/* transition function */
68
static void flipLight()
69
{
70
    if((smDayNight.current == NIGHTTIME) && (smUser.current == USER_IS_HOME))
71
    {
72
        handleEvent(smLight, LIGHT_ON);
73
    } else {
74
        handleEvent(smLight, LIGHT_OFF);
75
    }
76
}
77
78
/* handle events */
79
void handleEvent(StateMachine *sm, Event ev)
80
{
81
    uint8 i;
82
    
83
    /* walk through trnasitions table */
84
    for(i = 0; i < sm->nofTransitions; i++)
85
    {
86
        /* event and current state match? */
87
        if((ev == sm->transitions[i].ev) && (sm->current == sm->transitions[i].old))
88
        {
89
            /* then set new state */
90
            sm->current = sm->transitions[i].new;
91
            
92
            /* call trnasition function */
93
            sm->transitions[i].func();
94
        }
95
    }
96
}
Die Transitionstabelle kann man betimmt mit einem "Variable Length Array 
Struct" etwas besser integrieren, aber ich bin noch vom alten 
C89-Stempel :-P

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Joachim B. schrieb:
> Eric B. schrieb:
>> Danke für die Blumen :-)
>
> nicht abheben :-)
> zum besseren Verständnis fehlt mir noch

Nur zum Verständnis.

Das was Eric da weiter oben fabriziert hat ist keine Statemachine im 
klassischen Sinn.
Sieht man schon daran, dass es keinen fortlaufenden Status gibt, dir 
sich ändern könnte.

Das was er da oben gezeigt hat ist die Auswertung von Events, die auf 
Zustandsvariablen wirken und die Auswertung der 
Kombinationsmöglichkeiten dieser Zustandsvariablen.

Bei derart einfachen Beispielen ist das natürlich völlig legitim.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Eric B. schrieb:

>     /* walk through trnasitions table */
>     for(i = 0; i < sm->nofTransitions; i++)
>     {
>         /* event and current state match? */
>         if((ev == sm->transitions[i].ev) && (sm->current ==
> sm->transitions[i].old))

Hmm
Ich frage  mich gerade, ob es nicht einfacher wäre, für jeden Zustand 
eine eigene Tabelle (Array) zu benutzen. Eines davon ist der aktuelle 
Zustand (repräsentiert durch einen Pointer auf das gerade aktuell 
gültige Array). Ein Zustandsübergang ist dann nicht anderes als den 
Pointer auf ein anderes Array setzen.

Dann könnte man auch den Fall handhaben, dass es mehr als 1 Transistion 
von einem Zustand in einen anderen Zustand gibt, ohne dass man hier 
suchen muss.

Nachteil ist natürlich, dass nicht mehr die komplette Logik in einem 
einzigen Array beisammen ist.

von Martin K. (martinko)


Lesenswert?

Hi,

Eine recht übersichtliche und von der Laufzeit her schöne Methode ist 
auch die, den aktuellen State in einem Funktionspointer zu halten und 
für jeden State eine eigene Funktion zu schreiben.

static void State_Zu(enum Ereignis);
static void State_Auf(enum Ereignis)

static void (*currentState)(enum Ereignis) = State_Zu;

static void State_Zu(enum Ereignis)
{
   if (Ereignis = Auf)
   {
      machwas...
      currentState = State_Auf;
   }
}

static void State_Auf(enum Ereignis)
{
   if (Ereignis = Zu)
   {
      machwasanderes...
      currentState = State_Zu;
   }
}

void Statemachine(enum Ereignis)
{
   currentState(Ereignis);
}
...

Gruß Martin

von Joachim B. (jar)


Lesenswert?

Karl H. schrieb:
> Ich frage  mich gerade, ob es nicht einfacher wäre, für jeden Zustand
> eine eigene Tabelle (Array) zu benutzen.

so weit dachte ich auch schon im Sinne einer Wahrheitstabelle

kann aber sein das mir da noch jedes Verständnis fehlt.

Ich habe ja auch noch einige Programme zu erstellen wo mittlerweile das 
größte Problem ist alle Zustände zu erfassen bzw. zu berück
sichtigen.

egal wie ich die ifs verschachtele, einer ist immer falsch.

Mit if else komme ich da nicht weiter oder es wird ein unübersichtlicher 
Bandwurm mit doppelten Codeteilen, aber in Funktionen auslagern, dazu 
sind es zu viele Variablen, alle globals mitnehmen ist auch nicht toll, 
alle Variablen der Funktion übergeben auch nicht bzw. return, bliebe nur 
noch die Adressen aller beteiligter Variablen was es noch 
unübersichtlicher macht wer was wo ändert.

von Eric B. (beric)


Lesenswert?

Joachim B. schrieb:
> wo und wie ist LightState definiert und initialisiert?

> wo und wie ist Event definiert und initialisiert?
> dito LIGHT_UNDEFINED und andere?

In den enum typedefs ganz oben im Code.

Karl H. schrieb:
> Ich frage  mich gerade, ob es nicht einfacher wäre, für jeden Zustand
> eine eigene Tabelle (Array) zu benutzen.

In diesem Fall sind die 3 FSMs so klein (jeweils 2 Zustände und pro 
Zustand ein Event das zu einem Zustandswechsel führt), dass sich das 
nicht/kaum lohnt. Aber im Allgemeinen ist das schon ein guter Ansatz.

Martin K. schrieb:
> Eine recht übersichtliche und von der Laufzeit her schöne Methode ist
> auch die, den aktuellen State in einem Funktionspointer zu halten und
> für jeden State eine eigene Funktion zu schreiben.

Ja, und in C++ könnte man das dann in einem Functor packen.

Es gibt letztendlich viele Wege die nach Rom führen :-)

: Bearbeitet durch User
von Joachim .. (joachim_01)


Lesenswert?

Wir Lernen uns empor! (frei nach Harald Lesch :-)
Auch wenn's hier manchmal ein bissel Drunter und Drüber geht - ich liebe 
dieses Forum.

Telekolleg Mikrocontroller... Heute: Elegantes Coden für State-Machines.
Großen Dank für die kreativen Beträge, besonders von Eric und 
Karl-Heinz.

von Karl H. (kbuchegg)


Lesenswert?

Joachim B. schrieb:

> Ich habe ja auch noch einige Programme zu erstellen wo mittlerweile das
> größte Problem ist alle Zustände zu erfassen bzw. zu berück
> sichtigen.

Eine Technik die ich vor Jahren bei einem Projekt das erste mal gesehen 
habe:
Wenn es geht, erstelle für einzelne Subsysteme eigene State-maschines.

Ich kannte diese Technik damals auch noch nicht. Es hat aber nicht lang 
gedauert, bis ich begriffen habe, wie elegant damit das Gesamtsystem 
plötzlich wird. Die Maschine, die es zu steuern galt, lies sich in 
diverse einzelne Subsysteme zerlegen. Die Statemaschines haben sich über 
globale Variablen gegenseitig ihre Endzustände mitgeteilt. Die 
Statemaschines wurden ganz einfach Round Robin immer reihum 
abgearbeitet. Die Statmaschine, welches den Vorschub des Bandes regelte 
musste sich nicht darum kümmern, wie die Statemaschine, die nacheinander 
alle Positionen auf dem Bandabschnitt ansteuert das genau macht. Wenn 
alle Positionen abgefahren waren, hat die eine Statemaschine der anderen 
signalsiert, dass sie fertig wäre. Die Positionsabfahr-Maschine hingegen 
kümmerte sich nicht darum, wie genau die Position angefahren wurden. Das 
machte eine eigene, die über Motoren, deren Beschleunigungsverhalten, 
Bremsverhalten etc. Bescheid wusste. Die Positionmaschine signalisierte 
der Motormaschine, wo sie hin will und wenn die Position anliegt (bzw. 
eigentlich schon davor, denn zu diesem Zeitpunkt musste eine Kamera 
eingeschaltet werden), dann meldete sich die andere Maschine (wieder 
über globale Variablen), dass die Position anliegt. Die 
Positioniermaschine 'verharrte' dazu einfach so lange in einem 
Wartezustand, bis von der anderen Maschine das Freizeichen kam.

In gewisser Weise ist das das Pedant zu kooperativem Multitasking.

Als mir der damals mit dem Projekt betraute die Sache soweit klar 
gemacht hatte, hab ich auch gedacht: Puh, was für ein Aufwand.
Aber - ich muss gestehen ich hab das unterschätzt. Die einzelnen 
Maschinen waren simpel und einfach genug, dass es ein Klacks war, da 
Erweiterungen vorzunehmen. Unser Job war, die Zykluszeit schneller zu 
machen, indem so viele Vorgänge wie möglich parallel ablaufen sollten. 
Während das Band noch in seine nächste Halteposition vorschiebt, fährt 
bereits der Kameraarm in Position. Wenn irgendetwas dabei schief geht, 
dann warten die einzelnen Subsysteme ganz automatisch aufeinander. 
Reisst das Fürderband, dann wird auch seine Halteposition nicht 
erreicht, wodurch die Positioniermaschine ihr Freizeichen nicht kriegt, 
die wiederrum dem Arm nicht die Erlaubnis gibt, die Prüfsonden 
kameragesteuert abzusenken.
Gut, wir waren auf einem PC, der das alles mit links stemmt. Solange man 
es nicht übertreibt, würde ich heute auf einem µC mehr oder weniger so 
ziemlich dasselbe machen. Die Vorteile in der Wartung bzw. bei 
Änderungen waren einfach unübersehbar. Die Aufteilung garantierte, dass 
wir uns nicht in irgendeinem anderen Subsystem Ärger einhandeln würden. 
Solange wir an der Kommunikation untereinander nichts änderten, konnten 
wir in den Subsystemen schalten und walten, wie wir wollten.

: Bearbeitet durch User
von Matthias L. (Gast)


Lesenswert?

>Wenn es geht, erstelle für einzelne Subsysteme eigene State-maschines.

So ist das auch üblich. Es gibt eine Art Hauptschrittkette für die 
Maschine. Diese triggert nur andere Schrittketten an, irgendwas zu tun. 
Und wartet einfach als eigene Transition auf das Fertig der anderen 
Schrittkette..

von Joachim B. (jar)


Lesenswert?

Karl H. schrieb:
> Unser Job war, die Zykluszeit schneller zu
> machen, indem so viele Vorgänge wie möglich parallel ablaufen sollten.
> Gut, wir waren auf einem PC, der das alles mit links stemmt.

erinnert mich an meine Industriezeit als ich Tastwahlbock prüfen sollte,
die Wartezeit Vorwahlpause 800ms konnte ich nutzen um den 
Sperrwiderstand vom FET zu messen, mein Vorgänger hat doch echt 
sequenziell abgearbeitet und so Zeit vertrödelt, war aber auf dem CBM 
(6502 8-bit 32KB Ram 1 MHz) :)

Am µC mache ich es ja so, im IRQ wird gezählt lcd_upd-- wann 330ms um 
sind wenn 0 LCD refresh, im loop ist es ja egal wann genau der 
stattfindet, nur wenn er erfolgt ist wird lcd_upd wieder initialisiert.

PeDa hat ja auch einen Scheduler für den AVR geschrieben, ich wollte mir 
den immer schon mal ansehen.

von Karl H. (kbuchegg)


Lesenswert?

Joachim B. schrieb:
> Karl H. schrieb:
>> Unser Job war, die Zykluszeit schneller zu
>> machen, indem so viele Vorgänge wie möglich parallel ablaufen sollten.
>> Gut, wir waren auf einem PC, der das alles mit links stemmt.
>
> erinnert mich an meine Industriezeit als ich Tastwahlbock prüfen sollte,

Bei mir waren es frisch gebondete RFID-Dies.
Den Asiaten war jeder einzelne Chip pro Sekunde mehr bares Geld wert :-)

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Matthias L. schrieb:
>>Wenn es geht, erstelle für einzelne Subsysteme eigene State-maschines.
>
> So ist das auch üblich.

Ich kannte SM damals auch nur aus der 'reinen Lehre'.
Da kommt dieser Aspekt oft zu kurz. Zumindest während meiner 
Ausbildungszeit war das noch so.

von Matthias L. (Gast)


Lesenswert?

Karl H. schrieb:
> Ich kannte SM damals auch nur aus der 'reinen Lehre'.
> Da kommt dieser Aspekt oft zu kurz. Zumindest während meiner
> Ausbildungszeit war das noch so.

Da hast Du wohl recht. Wir hatten in der Technikerschule mal eine 
Schrittkette mit AWL programmiert. Weil man da von ST und CASE().. noch 
nie gehört hatte. Da hatte man viel Zeit und Tipperei verplempert um 
eine Variable mit CMP zu vergleichen und immer paar Zeilen (über den 
Code des States) zu springen.

Völlig absurd.

von Possetitjel (Gast)


Lesenswert?

Karl H. schrieb:

> Joachim B. schrieb:
>> Eric B. schrieb:
>>> Danke für die Blumen :-)
>>
>> nicht abheben :-)
>> zum besseren Verständnis fehlt mir noch
>
> Nur zum Verständnis.

Ja... Verständnis - oder vielmehr Unverständnis - ist das
richtige Stichwort: Wieso benötigt man, um DREI BIT zu
verarbeiten, Zieh-Scharp, CORBA, JavaBeans und D flat major?

Ausgangssignal ist im Beispiel eine boolsche Variable "LichtAn".
Ein Bit.
Zustandsvariablen sind "Tag" und "Da", also zwei Bit.

Über die Eingangssignale ist nix gesagt; man kann aber aufgrund
der Zustände davon ausgehen, dass Eingangssignale und Zustände
korrespondieren - mithin die Überführungsfunktion die triviale
Funktion ist.

Die Ausgangssignale sind den Kanten, d.h. den Zustandsübergängen
zugeordnet, es wird also ein MEALY-Automat gewünscht.

Jede Kante ist durch Start- und Zielzustand eindeutig beschrieben;
bei vier Zuständen gibt es also 16 Kanten (einschließlich der
Selbstschleifen); das kann im gezeichneten Graphen überprüft
werden.
Die Ausgabefunktion kann also als Tabelle mit 16 Zeilen
definiert werden; der Index wird durch die Verkettung von
Start- und Zielzustandscode gebildet und läuft von "00 00" bis
"11 11".

Von den 16 Kanten rufen allerdings nur zwei ein aktives Ausgangs-
signal hervor, und diese beiden Kanten haben überdies die
Besonderheit, dass sie in "benachbarten" Zuständen beginnen und
im selben Endzustand enden. (Benachbarte Felder im Karnaugh-Plan
bzw. ein "-" in der Ternervektorliste.)

Die Ausgabefunktion ist also, wenn ich keinen Fehler gemacht
habe, so vollständig beschrieben:

IF ((AlterZustand=Weg) AND (NeuerZustand=Nacht/Da))
  THEN LichtAn
  ELSE LichtAus
;

Wo ist das Problem? Ich versteh's nicht.

von Eric B. (beric)


Lesenswert?

Possetitjel schrieb:
> IF ((AlterZustand=Weg) AND (NeuerZustand=Nacht/Da))
>   THEN LichtAn
>   ELSE LichtAus
> ;
>
> Wo ist das Problem? Ich versteh's nicht.

Es gibt kein Problem, nur viele unterschiedliche Lösungen :-P

Edit: Und deine ist nicht richtig. Licht soll zB auch an wenn 
AlterZustand == Tag/Da und NeuerZustand == Nacht/Da

: Bearbeitet durch User
von Possetitjel (Gast)


Lesenswert?

Eric B. schrieb:

> Edit: Und deine ist nicht richtig. Licht soll zB auch an wenn
> AlterZustand == Tag/Da und NeuerZustand == Nacht/Da

Nicht im gezeichneten Graphen. Dort ist die Kante von Tag/Da
zu Nacht/Da mit "Off" beschriftet.

von Eric B. (beric)


Lesenswert?

Possetitjel schrieb:
> Eric B. schrieb:
>
>> Edit: Und deine ist nicht richtig. Licht soll zB auch an wenn
>> AlterZustand == Tag/Da und NeuerZustand == Nacht/Da
>
> Nicht im gezeichneten Graphen. Dort ist die Kante von Tag/Da
> zu Nacht/Da mit "Off" beschriftet.

Hah, haste Recht! Dann sind alle meine Statemaschinen weiter oben falsch 
:-D

von F. F. (foldi)


Lesenswert?

Falk B. schrieb:
> Siehe Statemachine

Ist auch besser beschrieben (und sogar mit höherer Komplexität) als in 
den meisten Büchern, die ich las.

von Karl H. (kbuchegg)


Lesenswert?

Possetitjel schrieb:

> Wo ist das Problem? Ich versteh's nicht.

Das Problem ist, dass man in der Lernphase oft mit Beispielen 
konfrontiert ist, bei denen man sich fragt, warum man da jetzt mit 
Kanonen auf Spatzen schiessen soll.

Sieh es als Vorstudie, mit der man Techniken erlernt, die man dann bei 
komplexeren Beispielen einsetzen kann, weil man schon weiss wie es geht, 
bzw. wie eine bestimmte Technik funktioniert.

Das Beispiel des TO ist von dieser Sorte. Kein normal denkender 
Entwickler würde sich bei der Komplexität (übrigens: schöne Analyse) 
eine komplette Statemaschine antun.

: Bearbeitet durch User
von Possetitjel (Gast)


Lesenswert?

Matthias L. schrieb:

> Wir hatten in der Technikerschule mal eine Schrittkette
> mit AWL programmiert. Weil man da von ST und CASE().. noch
> nie gehört hatte. Da hatte man viel Zeit und Tipperei
> verplempert um eine Variable mit CMP zu vergleichen und
> immer paar Zeilen (über den Code des States) zu springen.

Hmm. Was stört Dich denn an Schrittketten in AWL?
Schätzungsweise laufen Tausende Maschinen so (und das
durchaus zuverlässig).

Ich möchte AWL gar nicht übermäßig verteidigen (AWL wird in
Eleganz und Lesbarkeit wohl nur von Brainfuck übertroffen),
aber ich fand es durchaus nützlich, Automatentheorie
sozusagen auf Gatterniveau zu betreiben, ohne wirklich
Gatter verdrahten zu müssen.

Und überdies habe ich den Verdacht, dass macher High-Level-
Programmierer seinen Programmierstil STARK überdenken
würde, hätte er mal eine Weile ERNSTHAFT klassische
Steuerungsprogrammierung gemacht.

von Matthias L. (Gast)


Lesenswert?

>hätte er mal eine Weile ERNSTHAFT klassische Steuerungsprogrammierung >gemacht.

Wenn das Ganze als Grundlagenlehre dient, ist das Ok. Ich habe zB selbst 
in der Ausbildung ASM gelernt und Quellcode mit acht Schaltern 
"programmiert". Für reine Grundlagen sehr lehrreich.

Ebenfalls kein Problem wenn in SPS-Technik AWL gebracht wird. Aber 
darauf nun als Stand der Technik arbeiten zu wollen, ist doch ziemlich 
veraltet.


>Was stört Dich denn an Schrittketten in AWL?

Mich hat damals gestört, das viel Zeit und Energie für den 
"Programmrumpf" aufgebraucht wurde, der sonst einfach durch das 
Hinschreiben von
1
Switch ( Status )
entsteht. Und auf die Frage nach ST zum Programmieren sah man mich an, 
als ob ich die Lottozahlen von morgen wissen wolle...

von Eric B. (beric)


Lesenswert?

Karl H. schrieb:
> normal denkender Entwickler

Wo gibt's denn den?

von Paul B. (paul_baumann)


Lesenswert?

Bei Statemaschinen kriege ich Zustände.

MfG Paul

von Possetitjel (Gast)


Lesenswert?

Karl H. schrieb:

> Possetitjel schrieb:
>
>> Wo ist das Problem? Ich versteh's nicht.
>
> [...]
> Sieh es als Vorstudie, mit der man Techniken erlernt, die
> man dann bei komplexeren Beispielen einsetzen kann, weil
> man schon weiss wie es geht, bzw. wie eine bestimmte Technik
> funktioniert.

Ich muss zurückrudern.

Eric hat - von dem Flüchtigkeitsfehler abgesehen - ziemlich
genau das gemacht, was mir vorschwebt; ich habe es beim ersten
Durchlesen nur nicht verstanden.

Was ich wirklich fürchterlich finde, das sind die Ansätze,
endliche Automaten durch einen Dschungel von Fallunterscheidungen
zu implementieren - aber ich vermute, da sind wir ohnehin
einer Meinung (?!).

Zwei Anmerkungen trotzdem.
Du hast Eric kritisiert, er hätte gar keine "richtige state
machine" implementiert. Das liegt aber nicht an Eric, sondern
an der Aufgabenstellung - in der gibt's nämlich keine
Eingangssignale; gegeben sind dort schon die Zustandsvariablen.
Die Fragestellung des TO bezog sich im Grunde auf das rein
kombinatorische Problem, aus altem und neuem Zustand das
Ausgangssignal zu bestimmen.

Zum anderen ist Dein Einwand weiter oben, große Automaten ließen
sich auf Tabellenbasis schlecht implementieren, zwar prinzipiell
richtig - er wird aber von der Tatsache überdeckt, dass sich sehr
"große" Automaten auch nicht sinnvoll SPEZIFIZIEREN lassen: Ein
Automat mit 12 binären Eingangssignalen und 12 binären Zustands-
variablen hat 16 Millionen Übergänge. Die Überführungsfunktion
braucht also 32 MByte in Tabellenform. Das wäre noch machbar -
aber wer will die 250 Aktenordner mit je 500 doppelseitig bedruckten
Blättern für die Spezifikation erstellen?
Man MUSS große Automaten also zerlegen (oder besser "komprimierbare"
Beschreibungen finden) - nicht primär, weil die Maschine sie nicht
beherrschen könnte, sondern weil der Mensch es nicht kann.

von Karl Käfer (Gast)


Angehängte Dateien:

Lesenswert?

Hallo,

weil ich mir Boost::MSM schon lange mal angeschaut haben wollte, anbei 
ein Beispiel in C++ mit ebenjenem Boost::MSM. Das ist für die hier 
gestellte Aufgabe natürlich mit Kanonen auf Spatzen geschossen, IMHO 
aber eine sehr saubere, les- und vor allem erweiterbare Veranstaltung. 
Und klein ist sie obendrein: mit "-Os -flto" kompiliert und mit "strip" 
behandelt, hat das Binary am Ende gerade mal 36K.

Liebe Grüße,
Karl

von Automatentheoretiker (Gast)


Lesenswert?

Erst mal minimiert man die Zustände, dann kloppt man das in ein Array 
und gut ists, sowas macht man automatisch mit einem entspr. 
Modellierungstool, alles andere ist fehleranfällig und vertrödelt Zeit. 
Selbst die Varianten mit dem Pseudoeventsystem lässt man sich 
generieren.

Und den Extremmurks mit XML vergessen wir mal ganz schnell wieder...

Mal ehrlich, wer das heute noch als Profi wo Zeit=Geld ist noch von Hand 
reinkloppt hat den Schuss noch nicht gehört.

von Automatentheoretiker (Gast)


Lesenswert?

Automatentheoretiker schrieb:
> Und den Extremmurks mit XML vergessen wir mal ganz schnell wieder...
Nehme die Aussage zurück, habe mal angeschaut was dort gemacht wurde, 
das ist ja nix anderes als ein Codegentool, dachte da erst an was 
anderes als ich XML las.

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.