Electronic – Non blocking delay for state machines

cdelayembedded

What is the best way to implement a non blocking delay for a state machine for each state?
So far, the best I found is something like that:

static uint8_t state = STATE_ONE;
if (state == STATE_ONE)
{
   static uint64_t time_value_ms = (uint64_t)0;
   if (time_value_ms == (uint64_t)0)
   {
      time_value_ms = system_get_ms() + delay_ms;
   }
   else
   {
      if (system_get_ms() > time_value_ms)
      {
         time_value_ms = (uint64_t)0;
         state = STATE_TWO;
      }
   }
}
else if (state == STATE_TWO)
{
       static uint64_t time_value_ms = (uint64_t)0;
       if (time_value_ms == (uint64_t)0)
       {
          time_value_ms = system_get_ms() + delay_ms;
       }
       else
       {
          if (system_get_ms() > time_value_ms)
          {
             time_value_ms = (uint64_t)0;
             state = STATE_ONE;
          }
       }
}
.
.
.
else
{
   return;
}

But the problem in the code above is that when the state changes, the time_value_ms variable has to be set at 0 in order to be available to count a delay again when the state return in the STATE_ONE afterwards.

Therefore when you need a non blocking delay in every state in a big state machine with too many states (10 states for example) the code becomes complex with very bad readability.

The delay is used as timeout timer for deadlock or an event timer.

Best Answer

This can be solved by using function pointers.

void (*FP)(void*, uint64_t);//FP = Function Pointer
//The void means that whatever function FP will point 
// to will always return nothing.
//The *FP means that FP is a pointer named FP.
//The void* means that whatever function FP will point 
// to, its first argument will be yet another pointer of type void.
//The unsigned long means that whatever function FP will point 
// to, its second argument will be an unsigned long variable.

uint64_t next_time_pulse=0;
//time variable that we compare system clock with

void state_0(void* FP(void*, uint64_t), uint64_t t){
    //custom code that should always happen if state_0 is active
    if(t<system_get_ms()){
        //custom code that should happen when state_0 transitions to state_1
        FP = &state_1;
        t = system_get_ms()+delay;
    }
    return;
}

void state_1(void* FP(void*, uint64_t), uint64_t t){
    //custom code that should always happen if state_1 is active
    if(t<system_get_ms()){
        //custom code that should happen when state_1 transitions to state_0
        FP = &state_0;
        t = system_get_ms()+delay;//this will update "next_time_pulse"
    }
    return;
}

void init(){//initiate your system, only called once
    FP=&state_0;//Set FP to state 0
    next_time_pulse = system_get_ms()+delay; 
    //when should the absolutely first clock happen?
}

void loop(){//your main loop of your system
    //some code that won't be blocked. 
    FP(&FP,&next_time_pulse);//This will call either state_1 or state_2
    //if you want to know which state FP is in
    //then write whatever that knowledge would give you, in the functions
}

As you can see, I'm just using pointers, so if you need several finite state machines happening independently then you can just make more variables, and most likely just store them in an array.

If you need more states then you just add more functions.


Another way of solving it would be with just simple arrays, because that's the essence of the above solution.

uint64_t machine[2];
uint8_t next_state[2]={1,0};
//state 0's next state is 1
//state 1's next state is 0
//=> {1,0}

void init(){//initiate your system, only called once
    state=0;
    //Set state to 0
    machine[0]=system_get_ms()+delay; 
    //when should the absolutely first clock happen?
}

void loop(){//your main loop of your system
    //some code that won't be blocked. 
    if(machine[state]<system_get_ms()){
        state=next_state[state];
        machine[state]=system_get_ms()+delay;
    }
}

This way it's more difficult to make custom things happen depending on a state (could be solved with a switch case... ), but your simple example is now simpler to understand code wise.


Though, with more information regarding how you are going to use your finite state machines, more elegant / optimized code could be made.