Wie FSM mit Events in Tabelle umsetzen (C)?

Gast #6894321
Lesenswert?
• ▲
▼
Weder im Wiki-Artikel noch in den gefundenen Threads habe ich eine 
Lösung gesehen, die eine tabellarische FSM mit mehreren Events pro 
Zustand abbildet.

Zwei Ansätze fallen mir ein:

Eine Event-Array bzw. ein Pointer dorthin (was es unübersichtlicher 
macht, da separat definiert)

    STATEx, [EVENTy->[FUNCTION,NEWSTATE], ...]
    ...

Entsprechend viele Einträge und eine Suche (größere Tabelle, langsamer, 
aber schön übersichtlich):

    STATE1, EVENT_A, FUNCTION, NEWSTATE
    STATE1, EVENT_B, FUNCTION, NEWSTATE
    ...

Ist der Ansatz brauchbar, wie geht's "besser"?
Gast
Lesenswert?
• ▲
▼
Hallo

wie gefällt dir so ein Ansatz:
1
    CREATE_FSM_TABLE(
2
        CREATE_STATE( STATE_IDLE,
3
            ADD_EVENT( EV_TWI_READ_DATA , STATE_WRITE_ADR , twi_read_wrAdr ),
4
            ADD_EVENT( EV_TWI_WRITE_DATA, STATE_WRITE_LINK, twi_write_link ),
5
        ),
6
        CREATE_STATE( STATE_WRITE_ADR,
7
            ADD_EVENT( EV_TWI_ADR_READY, STATE_READ_DATA, twi_read_data      ),
8
            ADD_EVENT( EV_TWI_I2C_ERROR, STATE_READ_WAIT, twi_startWaitTimer ),
9
            ADD_EVENT( EV_TWI_MAX_ERROR, STATE_IDLE     , twi_job_finished   ),
10
            ADD_EVENT( EV_TWI_DMA_ERROR, STATE_IDLE     , twi_job_finished   ),
11
        ),
12
        CREATE_STATE( STATE_READ_DATA,
13
            ADD_EVENT( EV_TWI_READ_READY   , STATE_READ_DATA, twi_read_data    ),
14
            ADD_EVENT( EV_TWI_READ_COMPLETE, STATE_IDLE     , twi_job_finished ),
15
        ),
16
        CREATE_STATE( STATE_READ_WAIT,
17
            ADD_EVENT( EV_TWI_TIMEOUT_OK, STATE_WRITE_ADR, twi_read_wrAdr ),
18
        ),
19
        CREATE_STATE( STATE_WRITE_LINK,
20
            ADD_EVENT( EV_TWI_WRITE_READY_DMA, STATE_WRITE_WAIT, twi_startWaitTimer ),
21
            ADD_EVENT( EV_TWI_MAX_ERROR      , STATE_IDLE      , twi_job_finished   ),
22
            ADD_EVENT( EV_TWI_I2C_ERROR      , STATE_WRITE_WAIT, twi_startWaitTimer ),
23
            ADD_EVENT( EV_TWI_DMA_ERROR      , STATE_IDLE      , twi_job_finished   ),
24
            ADD_EVENT( EV_TWI_WRITE_COMPLETE , STATE_IDLE      , twi_job_finished   ),
25
        ),
26
        CREATE_STATE( STATE_WRITE_WAIT,
27
            ADD_EVENT( EV_TWI_TIMEOUT_OK, STATE_WRITE_LINK, twi_write_link ),
28
        ),
29
    ),
Gast
Lesenswert?
• ▲
▼
Sehr sauber lässt sich so etwas mit dem State Pattern lösen. Ist OOP und 
damit besser in C++ umzusetzen, aber wo C läuft läuft fast immer auch 
C++, und es vermeidet so ein Schlachtfeld an Makros. Es ist sehr 
flexibel und bietet die Möglichkeit, pro Zustand Daten abzulegen die 
auch nur für diesen Zustand gültig sind, beliebige individuelle 
Übergangsfunktionen oder -Bedingungen und nebenher ist es auch noch 100% 
typsicher ohne Casts umsetzbar.
Gast
Lesenswert?
• ▲
▼
Das sieht gut aus (entspricht vermutlich meiner ersten Variante), aber 
für mich ist es leider nicht offensichtlich, wie die Makros (und Daten) 
dahinter aussehen.

Als Ergänzung zu meiner ersten Idee könnte man natürlich auch einfach 
eine Tabelle entsprechend der maximalen Anzahl von Events anlegen, und 
die "Platzverschwendung" in Kauf nehmen.
Gast
Lesenswert?
• ▲
▼
Flederdil schrieb:
> Das sieht gut aus (entspricht vermutlich meiner ersten Variante), aber
> für mich ist es leider nicht offensichtlich, wie die Makros (und Daten)
> dahinter aussehen.

ich habe mir 3 Strukturen dafür aufgebaut.
1
// der Container für die Events
2
struct eventTransTab_s{
3
    void (*transFunc)( event_t* const e );         /* transition function */
4
    const uint16_t eventId;
5
    const uint16_t nextState;
6
};
7

8
// der Container für die States
9
struct fsm_state_data_s
10
{
11
    /* pointer auf ein Array */
12
    const struct    eventTransTab_s ( * const eventTable)[];
13
    const uint16_t  eventTableSize;
14
    const uint16_t  state;
15
};
16

17
// und die FSM
18
struct fsm
19
{
20
   const struct fsm_state_data_s ( * const stateTable )[];
21
   const size_t stateTableSize;
22
   uint16_t currState;
23
};

nun musst du nur die Strukturen mit Daten füllen.
1
#define ADD_EVENT( evNr, nextStNr, cbFunc )     \
2
    { .eventId= (event_id_t)evNr, .nextState = (uint16_t)nextStNr, .transFunc = cbFunc }
3

4
#define CREATE_STATE( stateNr, ...)                                    \
5
{ .state= stateNr, .eventTable= &(struct eventTransTab_s []){ ##__VA_ARGS__ },  \
6
  .eventTableSize = sizeof((struct eventTransTab_s []){ ##__VA_ARGS__ }) / sizeof( struct eventTransTab_s )}
7

8
#define CREATE_FSM_TABLE( ... )      .stateTableSize= sizeof((struct fsm_state_data_s []){ ##__VA_ARGS__ }) / sizeof( struct fsm_state_data_s ),\
9
                                .stateTable    = &(struct fsm_state_data_s []) { ##__VA_ARGS__ }
Gast
Lesenswert?
• ▲
▼
und hier der Event-Handler:
1
// Return: False = Error
2
//         True  = alles Okay
3
bool_t fsm_state_handler( struct fsm* const fsm, event_t* const e)
4
{
5
    const event_id_t id = e->Id;
6
    const uint16_t   currState = fsm->currState;
7

8
    /*  Iterate through the state transition matrix, checking if there is both a match with the current state
9
        and the event
10
    */
11
    for( size_t i = (size_t)0; i < fsm->stateTableSize; i++ )
12
    {
13
        const struct fsm_state_data_s* tabState= &(*fsm->stateTable)[ i ];
14

15
        if ( tabState->state == currState )
16
        {
17
            /* hier die abgehenden Transitions prüfen */
18
            for( size_t x = (size_t)0; x < tabState->eventTableSize; x++ )
19
            {
20
                const struct eventTransTab_s* eventTransition = &(*tabState->eventTable)[ x ];
21
                if ( eventTransition->eventId == id )
22
                {
23
                    // Transition to the next state
24
                    fsm->currState = eventTransition->nextState;
25

26
                    // Call the function associated with transition
27
                    if ( eventTransition->transFunc != NULL_PTR )
28
                    {
29
                        eventTransition->transFunc( e );
30
                    }
31

32
                    return True;
33
                }
34
            }
35

36
            /* wir haben zwar den State gefunden aber kein passendes Event! */
37
            break;
38
        }
39
    }
40

41
    return False;
42
}

so das war es. Ich hoffe ich habe nichts Kaput gemacht oder vergessen zu 
kopieren. :-)

Es ist ein Ansatz, ob er dir gefällt und du diese Art der Programmierung 
mit den Macros mags, musst du entscheiden.

PS: Du kannst auch diese FSM-Macros umschreiben und mit Hilfe von 
Plantuml ein Diagramm zeichnen lassen. Es sieht nicht schön aus, bei 
mir, aber es hilft beim Programmieren.
Gast
Lesenswert?
• ▲
▼
Stephan schrieb:
> Ich hoffe ich habe nichts Kaput gemacht oder vergessen zu
> kopieren. :-)

Es fehlen noch ein paar Typen.. Habe versucht, zu raten, aber das 
funktioniert noch nicht:
1
#include <stdint.h>
2

3
typedef enum {STATE_ZERO, STATE_ONE} state;
4
typedef enum {EV_ZERO, EV_ONE, EV_TWO} event_id_t;
5
typedef void* event_t;
6
typedef uint8_t size_t;
7

8
// der Container für die Events
9
struct eventTransTab_s{
10
    void (*transFunc)( event_t* const e );         /* transition function */
11
    const uint16_t eventId;
12
    const uint16_t nextState;
13
};
14

15
// der Container für die States
16
struct fsm_state_data_s
17
{
18
    /* pointer auf ein Array */
19
    const struct    eventTransTab_s ( * const eventTable)[];
20
    const uint16_t  eventTableSize;
21
    const uint16_t  state;
22
};
23

24
// und die FSM
25
struct fsm
26
{
27
   const struct fsm_state_data_s ( * const stateTable )[];
28
   const size_t stateTableSize;
29
   uint16_t currState;
30
};
31

32

33

34
#define ADD_EVENT( evNr, nextStNr, cbFunc )     \
35
    { .eventId= (event_id_t)evNr, .nextState = (uint16_t)nextStNr, .transFunc = cbFunc }
36

37
#define CREATE_STATE( stateNr, ...)                                    \
38
{ .state= stateNr, .eventTable= &(struct eventTransTab_s []){ ##__VA_ARGS__ },  \
39
  .eventTableSize = sizeof((struct eventTransTab_s []){ ##__VA_ARGS__ }) / sizeof( struct eventTransTab_s )}
40

41
#define CREATE_FSM_TABLE( ... )      .stateTableSize= sizeof((struct fsm_state_data_s []){ ##__VA_ARGS__ }) / sizeof( struct fsm_state_data_s ),\
42
                                .stateTable    = &(struct fsm_state_data_s []) { ##__VA_ARGS__ }
43

44
struct fsm test { 
45
CREATE_FSM_TABLE(
46
    CREATE_STATE( STATE_ZERO,
47
        ADD_EVENT( EV_ZERO , STATE_ONE, NULL ),
48
        ADD_EVENT( EV_ONE, STATE_ONE, NULL ),
49
    ),
50

51
    CREATE_STATE( STATE_ONE,
52
        ADD_EVENT( EV_TWO, STATE_ZERO, NULL),
53
    ),
54
),
55
.currState = STATE_ZERO;
56
};

Sollte ".eventId" nicht eigentlich auch event_id_t sein?
Gast
Lesenswert?
• ▲
▼
Flederdil schrieb:
> Stephan schrieb:
>> Ich hoffe ich habe nichts Kaput gemacht oder vergessen zu
>> kopieren. :-)
>
> Es fehlen noch ein paar Typen.. Habe versucht, zu raten, aber das
> funktioniert noch nicht:
>
1
#include <stdint.h>
2
 
3
typedef enum {STATE_ZERO, STATE_ONE} state;
4
typedef enum {EV_ZERO, EV_ONE, EV_TWO} event_id_t;
5
typedef void* event_t;
6
typedef uint8_t size_t;
> Sollte ".eventId" nicht eigentlich auch event_id_t sein?
Ja ist richtig. Das war mal ein Enum.
Gast
Lesenswert?
• ▲
▼
Programmierer schrieb:
> Stephan schrieb:
>> size_t i = (size_t)0
>
> Der Cast nach size_t beim Initialisieren ist unnötig.
Ich bekomme hier eine Warnung "Implizierter CAST". daher der cast von 
meiner Seite. Ich glaube, ich hätte auch "0UL" schreiben können.

Programmierer schrieb:
> Stephan schrieb:
>> if ( eventTransition->transFunc != NULL_PTR
>
> Und warum nicht einfach NULL? Es gibt keine sinnvolle Situation in C in
> welcher man den Null-Pointer anders definieren würde als NULL, solche
> Konstrukte machen es nur schlechter lesbar & wartbar.
Bei meinem Compiler ist NULL wirklich eine dezimale 0!
Daher würde das eine Warnung vom Compiler generieren!
1
#define NULL_PTR                ((void*)0)
Dies wird akzeptiert!

EAF schrieb:
> Programmierer schrieb:
>> Und warum nicht einfach NULL?
> Warum überhaupt irgendeine Null zum Vergleich?
>
> if ( eventTransition->transFunc )
> sollte doch reichen.
Die ist kein Boolesche Ausdruck und es gibt eine Warnung!

Mein Compiler hat ein paar MISRA Regeln mit eingebaut und prüft diese 
auch.
Die "##" musste ich bei meinen Compiler auch erstmal freigeben, sind 
auch nicht erlaubt!
Gast
Lesenswert?
• ▲
▼
Stephan schrieb:
> Ich bekomme hier eine Warnung "Implizierter CAST".

Meines Wissens ist das eine Initialisierung und kein Cast, daher völlig 
legal. Das geht mit allen Integer-Typen.

Stephan schrieb:
> Bei meinem Compiler ist NULL wirklich eine dezimale 0!

Das ist auch legitim.

> Daher würde das eine Warnung vom Compiler generieren!

Nö, 0 ist implizit in jeden Pointer-Typ konvertierbar, daher kann man 
alle Pointer-Typen mit 0 vergleichen oder zuweisen. In aktuellen 
C++-Versionen ist es dank nullptr eleganter gelöst, aber in C geht es 
halt so. NULL ist explizit genau dafür gedacht, auf Pointer zugewiesen 
oder verglichen zu werden, daher macht es keinen Sinn etwas eigenes 
dafür zu definieren. Wenn der Compiler davor warnt, etwas genau dafür zu 
benutzen wofür es vorgesehen ist, stimmt mit dem was nicht.

Stephan schrieb:
> Die ist kein Boolesche Ausdruck und es gibt eine Warnung!

Es ist nach bool konvertierbar, und daher ist es absolut korrekt und 
wohldefiniert. Wer hier warnt, ist extrem paranoid, denn hier gibt es 
kein Fehlerpotenzial.

Stephan schrieb:
> Mein Compiler hat ein paar MISRA Regeln mit eingebaut und prüft diese
> auch.

Ah, MISRA. Dann haben wir hier ein paar tolle Beispiele, wie MISRA den 
Code schlechter macht - schlechte Lesbarkeit (NULL_PTR ???) kann eben 
auch Sicherheitsprobleme bewirken, weil man nicht mehr sieht, was 
passiert!

Stephan schrieb:
> Die "##" musste ich bei meinen Compiler auch erstmal freigeben, sind
> auch nicht erlaubt!

Ist auch absolut Standard und erlaubt. Sich so etwas erst zu verbieten 
und dann wieder zu aktivieren...
Gast
Lesenswert?
• ▲
▼
Mit meinem obigen Versuch funktioniert es nicht, d.h. ich erhalte eine 
Reihe an solchen Fehlern:
    error: pasting "{" and "CREATE_STATE" does not give a valid 
preprocessing token

Flederdil schrieb:
> struct fsm test {

Hier fehlt wohl auch noch ein = ?

Und: gab es nicht einen Schalter für den Compiler (GCC), damit er den 
output nach dem Präprozessor nicht löscht? Ich würde mir gerne das 
Resultat ansehen...
Gast
Lesenswert?
• ▲
▼
Hi  Flederdil,
1
// hab den include hinzugefügt
2
#include <stdlib.h>
3
// '=' Zeichen fehlte
4
struct fsm test= {
5
// Da gehört ein *KOMMA* hin! :-)
6
.currState = STATE_ZERO,

Hier mal der Code von dir durch den Präprozessor gejagt:
1
struct fsm test= {
2
CREATE_FSM_TABLE(
3
    CREATE_STATE( STATE_ZERO,
4
        ADD_EVENT( EV_ZERO , STATE_ONE, NULL ),
5
        ADD_EVENT( EV_ONE, STATE_ONE, NULL ),
6
    ),
7
    CREATE_STATE( STATE_ONE,
8
        ADD_EVENT( EV_TWO, STATE_ZERO, NULL),
9
    ),
10
),
11
.currState = STATE_ZERO,
12
};

ergibt:
1
struct fsm test= {
2
    . stateTableSize= sizeof((struct fsm_state_data_s []){{ . state= STATE_ZERO, . eventTable= &(struct eventTransTab_s []){{ . eventId= (event_id_t)EV_ZERO, . nextState = (uint16_t)STATE_ONE, . transFunc = 0 }, { . eventId= (event_id_t)EV_ONE, . nextState = (uint16_t)STATE_ONE, . transFunc = 0 }, }, . eventTableSize = sizeof((struct eventTransTab_s []){{ . eventId= (event_id_t)EV_ZERO, . nextState = (uint16_t)STATE_ONE, . transFunc = 0 }, { . eventId= (event_id_t)EV_ONE, . nextState = (uint16_t)STATE_ONE, . transFunc = 0 }, }) / sizeof( struct eventTransTab_s )}, { . state= STATE_ONE, . eventTable= &(struct eventTransTab_s []){{ . eventId= (event_id_t)EV_TWO, . nextState = (uint16_t)STATE_ZERO, . transFunc = 0 }, }, . eventTableSize = sizeof((struct eventTransTab_s []){{ . eventId= (event_id_t)EV_TWO, . nextState = (uint16_t)STATE_ZERO, . transFunc = 0 }, }) / sizeof( struct eventTransTab_s )}, }) / sizeof( struct fsm_state_data_s ),
3
    . stateTable = &(struct fsm_state_data_s []) 
4
    {
5
        {   . state= STATE_ZERO, 
6
            . eventTable= &(struct eventTransTab_s [])
7
            {
8
                { . eventId= (event_id_t)EV_ZERO, . nextState = (uint16_t)STATE_ONE, . transFunc = 0 }, 
9
                { . eventId= (event_id_t)EV_ONE, . nextState = (uint16_t)STATE_ONE, . transFunc = 0 }, 
10
            }, 
11
            . eventTableSize = sizeof((struct eventTransTab_s []){{ . eventId= (event_id_t)EV_ZERO, . nextState = (uint16_t)STATE_ONE, . transFunc = 0 }, { . eventId= (event_id_t)EV_ONE, . nextState = (uint16_t)STATE_ONE, . transFunc = 0 }, }) / sizeof( struct eventTransTab_s )
12
        },         
13
        {   . state= STATE_ONE, 
14
            . eventTable= &(struct eventTransTab_s [])
15
            {
16
                { . eventId= (event_id_t)EV_TWO, . nextState = (uint16_t)STATE_ZERO, . transFunc = 0 }, 
17
            }, 
18
            . eventTableSize = sizeof((struct eventTransTab_s []){{ . eventId= (event_id_t)EV_TWO, . nextState = (uint16_t)STATE_ZERO, . transFunc = 0 }, }) / sizeof( struct eventTransTab_s )
19
        }, 
20
    },
21
    .currState = STATE_ZERO,
22
};

im Notepad++ sah der Text okay aus. wird hier nicht richtig angezeigt.
Kopiere den Text einfach nochmal.
Gast
Lesenswert?
• ▲
▼
Danke dir, aber mein GCC mag es (Anhang) leider immer noch nicht. Die 
Fehler

    error: pasting "{" and "ADD_EVENT" does not give a valid 
preprocessing token

bleiben.

Vielleicht macht der Präprozessor nicht genügend Durchläufe?
1
gcc -v
2
Using built-in specs.
3
COLLECT_GCC=gcc
4
COLLECT_LTO_WRAPPER=c:/mingw/bin/../libexec/gcc/mingw32/8.2.0/lto-wrapper.exe
5
Target: mingw32
6
Configured with: ../src/gcc-8.2.0/configure --build=x86_64-pc-linux-gnu --host=mingw32 --target=mingw32 --prefix=/mingw --disable-win32-registry --with-arch=i586 --with-tune=generic --enable-languages=c,c++,objc,obj-c++,fortran,ada --with-pkgversion='MinGW.org GCC-8.2.0-5' --with-gmp=/mingw --with-mpfr=/mingw --with-mpc=/mingw --enable-static --enable-shared --enable-threads --with-dwarf2 --disable-sjlj-exceptions --enable-version-specific-runtime-libs --with-libiconv-prefix=/mingw --with-libintl-prefix=/mingw --enable-libstdcxx-debug --with-isl=/mingw --enable-libgomp --disable-libvtv --enable-nls --disable-build-format-warnings
7
Thread model: win32
8
gcc version 8.2.0 (MinGW.org GCC-8.2.0-5)
Angehängte Dateien:

Antwort schreiben

Eine eigene Frage? Dieser Thread ist über 4 Jahre alt. Stell sie in einem eigenen Thread – dort lesen mehr Leute mit, und sie wird öfter und schneller beantwortet.

Neuen Thread starten

Eine Lösung, Ergänzung oder Rückmeldung zu diesem Thread? Dann antworte hier.

Formatierung
  • [c]C-Code[/c]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] – Link zu einem Artikel
  • Verweis auf einen anderen Beitrag: Rechtsklick auf dessen Beitrags-ID (#8078628), „Adresse kopieren“, in den Text einfügen

Mehr zur Formatierung

Längeren Sourcecode als Anhang, nicht in den Text. JPG nur für Fotos und Scans; Zeichnungen und Screenshots als PNG oder GIF. Siehe Bildformate.

Schon registriert? Anmelden

Wird nicht angezeigt. Wir schicken dir einen Link; sobald du damit bestätigst, erscheint dein Beitrag.

Mit dem Abschicken legst du einen kostenlosen Account an und erkennst die Nutzungsbedingungen und die Datenschutzerklärung an.