2

I work with embedded stuff, and I have some software module that manages hardware. This module has state, and state transitions are complicated: depending on events, the module could go from state A to state B or probably to C. But, when it exits some state, it should perform some actions with the hardware, in order to keep it in correct state too.

For rather simple modules, I just have a couple of functions like this:

enum state_e {
    MY_STATE__A,
    MY_STATE__B,
};

static enum state_e _cur_state;

void state_on_off(enum state_e state, bool on)
{
    switch (state){
        case MY_STATE__A:
            if (on){
                //-- entering the state A
                prepare_hardware_1(for_state_a);
                prepare_hardware_2(for_state_a);
            } else {
                //-- exiting the state A
                finalize_hardware_2(for_state_a);
                finalize_hardware_1(for_state_a);
            }
            break;
        case MY_STATE__B:
            if (on){
                //-- entering the state B
                prepare_hardware_1(for_state_b);
                prepare_hardware_2(for_state_b);
            } else {
                //-- exiting the state B
                finalize_hardware_2(for_state_b);
                finalize_hardware_1(for_state_b);
            }
            break;
    }
}

void state_set(enum state_e new_state)
{
    state_on_off(_cur_state, false);
    _cur_state = new_state;
    state_on_off(_cur_state, true);
}

Obviously, we need to keep all necessary actions for all states in the _state_on_off() function, and when we need to move to another state, we just call _state_set(new_state) and state transition goes smoothly independently of the direction: all needed actions are performed.

But it works for simple situations only. What if we have something in common between states MY_STATE__B and MY_STATE__C, so that when state is changed from MY_STATE__B to MY_STATE__C and back we should perform only shortened desctruction / construction? But when we go to some other state (say, to MY_STATE__A), we should perform full destruction.

What comes to mind is substates. So we have one state MY_STATE__BC, and substates like MY_BC_SUBSTATE__B and MY_BC_SUBSTATE__C; and of course we have its own function like _state_bc_on_off(). Even this is already a pain, but imagine something more complicated: it goes terrible.

So, what are the best practices for things like that?

Dmitry Frank
  • 10,417
  • 10
  • 64
  • 114
  • What you are describing is called a state machine. State machine is defined by a matrix: rows are states, columns are inputs, cell values are tuples of action to perform and a new state to transition into. Lay this all out on a piece of paper, Excel spreadsheet, etc. This is not a programming question; this is a question of how your device is controlled. Once that is done, implementation is straightforward and pretty much boils down to a switch statement, which you already have. – void_ptr Feb 06 '15 at 00:39
  • 1
    The approach I would take is to draw out a directed graph showing the state transitions along with the conditions of moving from one node to another in the graph. This article may help https://engineering.purdue.edu/ece362/Refs/Pld/pal_state.pdf as well as this article http://www.cse.chalmers.se/~coquand/AUTOMATA/book.pdf as well as this one http://www.drdobbs.com/cpp/state-machine-design-in-c/184401236 – Richard Chambers Feb 06 '15 at 00:39
  • 1
    This codeproject article with source may provide assistance as well. http://www.codeproject.com/Articles/19082/Construction-of-UML-State-Machines-Using-C-C-Macro – Richard Chambers Feb 06 '15 at 00:46
  • generally, when I'm implementing a state machine, each condition is a separate state. so the code currently has states a,b,c listed, but the reality is there are some intermediate states that should also be listed as part of the state machine. So, for example, state a,ab,b,ac,c when a -> ab or a ->ac or ab->b or ac->c or c->a or c->b. are the state transitions. each state makes the decision on what to do and which state to be in next (which may be the same state). Writing a state transition diagram will clarify all the conditions, etc – user3629249 Feb 06 '15 at 01:13
  • 1
    @RichardChambers, thanks for the excellent references. – user3629249 Feb 06 '15 at 01:14

1 Answers1

2

A slightly more general state machine has

  • primitives -- subroutines that performs a specific action on a specific piece of hardware
  • sequences -- one or more primitives called in a specific order
  • transitions -- one or more sequences executed in a specific order

The transitions are encoded in an array of structs. The sequences are selected by switch statement, and each sequences calls one or more primitives.

#define stA    0x00000001  // bit mask for state A
#define stB    0x00000002  // bit mask for state B
#define stC    0x00000004  // bit mask for state C
#define stAny  0xffffffff  // matches any state

enum { seqXtoY, seqError, seqEnterA, seqExitA, seqEnterB, seqExitB, seqEnableC, seqDisableC, seqEnd };

typedef struct
{
    int oldState;     // bit mask that represents one or more states that we're transitioning from
    int newState;     // bit mask that represents one or more states that we're transitioning to
    int seqList[10];  // an array of sequences that need to be executed
}
stTransition;

static stTransition transition[] =
{
    // transitions from state A to B or C
    { stA, stB, { seqExitA, seqXtoY, seqEnterB, seqEnd } },
    { stA, stC, { seqExitA, seqXtoY, seqEnableC, seqEnterB, seqEnd } },

    // transitions from state B to A or C
    { stB, stA, { seqExitB, seqXtoY, seqEnterA, seqEnd } },
    { stB, stC, { seqXtoY, seqEnableC, seqEnd } },

    // transitions from states C to A or B
    { stC, stA, { seqDisableC, seqExitB, seqXtoY, seqEnterA, seqEnd } },
    { stC, stB, { seqDisableC, seqXtoY, seqEnd } },

    // any other transition (should never get here)
    { stAny, stAny, { seqError, seqEnd } }
};

static int currentState = stA;

void executeSequence( int sequence )
{
    switch ( sequence )
    {
        case seqEnterA:
            prepare_hardware_1(for_state_a);
            prepare_hardware_2(for_state_a);
            break;

        case seqExitA:
            finalize_hardware_2(for_state_a);
            finalize_hardware_1(for_state_a);
            break;

        case seqEnterB:
            prepare_hardware_1(for_state_b);
            prepare_hardware_2(for_state_b);
            break;

        case seqExitB:
            finalize_hardware_2(for_state_b);
            finalize_hardware_1(for_state_b);
            break;

        case seqEnableC:
            enable_hardware_3();
            break;

        case seqDisableC:
            disable_hardware_3();
            break;
    }
}

void executeTransition( int newState )
{
    if ( newState == currentState )
        return;

    // search the transition table to find the entry that matches the old and new state
    stTransition *tptr;
    for ( tptr = transition; tptr->seqList[0] != seqError; tptr++ )
        if ( (tptr->oldState & currentState) && (tptr->newState & newState) )
            break;

    // execute the sequence list
    int *seqptr;
    for ( seqptr = tptr->seqList; *seqptr != seqEnd; seqptr++ )
    {
        if ( *seqptr == seqXtoY )
            currentState = newState;
        else if ( *seqptr == seqError )
            printf( "The state table is missing the transition from %d to %d\n", currentState, newState );
        else
            executeSequence( *seqptr );
    }

    // if the seqList doesn't have an explicit update, then we update at the end
    currentState = newState;
}
user3386109
  • 34,287
  • 7
  • 49
  • 68