0
\$\begingroup\$

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.

\$\endgroup\$
  • \$\begingroup\$ Looks like a lot of code duplication. Each state should be broken up and called from a main state machine routine. The timer code looks the same save for a state transition which can be a parameter to the function. As far as the non-blocking goes, maybe this would be a better task for an RTOS. \$\endgroup\$ – Ron Beyer Jan 7 '18 at 17:56
  • \$\begingroup\$ Why not just reset the timer when you transition between states? \$\endgroup\$ – The Photon Jan 7 '18 at 17:57
  • \$\begingroup\$ @ThePhoton, This is what I was thinking. What is your proposal? \$\endgroup\$ – MrBit Jan 7 '18 at 18:14
  • \$\begingroup\$ @RonBeyer, my main has got other similar state machines like this I posted. This state machine is inside a function (say "my_finite_state_machine()") which is called from the main. \$\endgroup\$ – MrBit Jan 7 '18 at 18:17
  • \$\begingroup\$ Since you haven't shared the code that does state transitions, it's up to you to figure out how to change it to reset a timer. \$\endgroup\$ – The Photon Jan 7 '18 at 18:24
2
\$\begingroup\$

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.

\$\endgroup\$
  • \$\begingroup\$ Fascinating piece of pointer work delivered. Although variable function pointers should be avoided in critical embedded systems. For other use cases (eg: UI) this could be an elegant method for sure. \$\endgroup\$ – Jeroen3 Jan 8 '18 at 21:04
  • \$\begingroup\$ @Jeroen3 "Although variable function pointers should be avoided in critical embedded systems", huh, okay. \$\endgroup\$ – Harry Svensson Jan 8 '18 at 21:12
  • \$\begingroup\$ I know. I didn't make the rules. Maybe if you have a chip with ECC-SRAM you are allowed to. \$\endgroup\$ – Jeroen3 Jan 8 '18 at 21:14
  • \$\begingroup\$ @Jeroen3 "I didn't make the rules.", got it. \$\endgroup\$ – Harry Svensson Jan 8 '18 at 21:16
1
\$\begingroup\$

The typical solution as used in some real-time systems, is to execute the state and then "burn" the remaining time after that, so that each state is always executed on fixed time intervals, with a given time slice. If you will, a "poor man's RTOS":

start_timer(x); // x miliseconds
  state = STATE_MACHINE[state]();
while(timer_running) // blocking or non-blocking, busy-wait or preferably wake-up interrupt
{}

In either case you should not mix neither the state machine logic nor the delays with the application logic. Doing so doesn't make any sense and will lead to obfuscation and code bloat. That's bad design, period.

\$\endgroup\$
1
\$\begingroup\$

I don't understand why you are using a different instance of time_value_ms for each state. The state machine is in only one state at a time. So just use the same instance of time_value_ms for all states. And set time_value_ms at each state transition (although don't reset through zero as if it's some sort of flag).

Also there is a bug in your implementation when system_get_ms() is within delay_ms of rolling over. Adding delay_ms will cause the value of time_value_ms to roll over. Then subsequently the comparison system_get_ms() > time_value_ms will be true instantly and the desired delay will not occur. Instead you should subtract the start_time from the current_time and compare the result with delay_time.

In my example I'm using the STATE_INIT state simply to initialize the value of time_start_ms on the very first invocation. You could return to STATE_ONE directly from another state and not have to go through STATE_INIT.

Finally, I prefer the switch construct rather than if-else-if in this situation.

static uint8_t state = STATE_INIT;
static uint64_t time_start_ms;

uint64_t current_ms = system_get_ms();

switch (state)
{
   case STATE_INIT:
      time_start_ms = current_ms;
      state = STATE_ONE:
      // break omitted intentionally to fall through into STATE_ONE.

   case STATE_ONE:
      if ((current_ms - time_start_ms) > delay_ms)
      {
          state = STATE_TWO;
          time_start_ms = current_ms;
      }
      break;

   case STATE_TWO:
      if ((current_ms - time_start_ms) > delay_ms)
      {
          state = STATE_THREE;
          time_start_ms = current_ms;
      }
      break;
   .
   .
   .
}
\$\endgroup\$
1
\$\begingroup\$

You can use a transition state.

Inside a state function I always return with the next state the machine has to got to, and take previous as argument.
Instead of waiting inside each state, which will become messy, you can jump to a wait state. The wait state waits a number of state machine evaluations (or until specified system time), and have the wait state return to its caller.

If you rely on state machine arguments that contain the previous state, you can implement the wait state to be implicit. And thus you can jump or return to a state without the state knowing it came from a wait state. Like I do.

Below I've put some snippets of my typical state machine.

My rules:
- The machine is evaluated at a constant rate. (eg: 1 KHz)
- There is a system timer counting system ticks.
- Variable functions pointers should be avoided.

void evaluate(void){
    previous = current;
    current = next;

    switch(current){
    case WAIT_STATE:
    {
        int elapsed = systick() - wait_entry_time;
        if( previous != current){
            wait_previous = previous;
        }
        if( elapsed >= wait_time){
            next = wait_next;
            current = wait_previous;
        }
    }  
    break;
    case STATE_INIT:   next = init(previous); break;
    case STATE_ENTRY:  next = entry(previous); break;
    // .. other states ...

Preparing to wait, or jump to the next with a delay can be done with a macro.

#define WAIT_PREP(s, w)   next = WAIT_STATE;           \
                          wait_next = s;               \
                          wait_entry_time = systick(); \
                          wait_time = w;

This is assuming it is setup so that each state is a function, takes as argument the previous state, and returns the next state.

state_t init(state_t prev){
    state_t next = current;
    // Delay 2000 ms then jump to ENTRY
    WAIT_PREP(STATE_ENTRY, 2000);
    return next;
}

You'll need some static variables on top of the module, like the wait_ arguments. And the state machine variables current, next and previous.

Proper discipline prevents you from bypassing intended operation using the globals.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.