Exercise-003 Nested switch implementation of an FSM part 1
In this article, let’s continue the coding. Write one function to implement a state machine.
Go to the file ‘protimer_state_mach.cpp’, and let’s create a function.
event_status_t protimer_state_machine(protimer_t *mobj, event_t *e){ switch (mobj->active_state){ case IDLE: { protimer_state_handler_IDLE(mobj,e); } case TIME_SET: { protimer_state_handler_TIME_SET(mobj,e); }
protimer_state_mach.cpp
The function is a protimer; I will call this protimer_state_machine. This state_machine will receive protimer_t; its main application object is mobj. And the state machine also receives the event. And the generalized event structure is event_t, a pointer to the event structure.
And here we are using a Nested switch. Let’s use the switch case to switch between different states. switch (mobj->active_state).
Now, let’s implement the different cases—Case IDLE (the first active_state IDLE). If the case is IDLE, then let’s call one state handler function here, protimer_state_handler_IDLE. And for this, we have to send ‘mobj‘ and ‘e’ (the received event) like you have to implement in different cases. So, one case for each state of the state machine. And inside the protimer_state_handler_IDLE() function, you have to implement one more switch case, which switches between different events to take different actions.
And we will use the ‘return’ type to return the status of the event handling. The status could be whether the event is really handled or ignored or caused any transition. That’s why I will use ‘event_status_t.’ Each event handler should also return the event status. Like this(as shown above), you implement different cases for different states.
And now, let’s implement for individual State handlers. The first individual state handler is protimer_state_handler_IDLE. They receive a pointer to the main application object(mobj) and a pointer to the event, and this individual state handler returns the event_status_t.
The first state handler is an IDLE state. IDLE state processes various signals. Increment time, a TIME_TICK, START_PAUSE, etc., It also has an Internal activities Entry and Exit. We will also treat them as signals for the implementation purpose. We will call it an Entry event, an Exit event.
Let’s use one more switch case. That’s why it’s called a Nested switch. First code snippet shows the upper switch, which is to switch between different states of the state machine, and second ccode snippets shows the inner switch is to switch between different signals the state processes.
switch(e->sig). We have to implement one case for each signal, the state processes. Start with ENTRY. We are not using the ‘break’ statements because we have to return the status of the event handling.
And one more case for EXIT, one more case for Increment time (INC_TIME), one more case for START_PAUSE, one more case for TIME_TICK. Like that, you have to complete another state handler’s implementation.
Let’s define the event_status_t.
typedef enum{
EVENT_HANDLED,
EVENT_IGNORED,
EVENT_TRANSITION
}event_status_t;
Implementation of event_status_t in main.h
Go to the main.h, and here create a typedef enum. The status is EVENT_HANDLED, EVENT_IGNORED, and EVENT_TRANISTION. This indicates the status of event handling; After that, event_status_t.
As per our diagram, the ENTRY action is c_time=0 and e_time=0. mobj->curr_time=0; mobj->elapsed_time=0; And after that, display time. We will use one helper function called display_time(0); After that, display_message(“Set time”). That’s the Entry action. After completing the Entry action, it should return EVENT_HANDLED.
event_status_t protimer_state_handler_IDLE(protimer_t *mobj, event_t *e){ switch(e->sig){ case ENTRY:{ mobj->curr_time = 0; mobj->elapsed_time = 0; display_time(0); display_message("Set",0,0); return EVENT_HANDLED; }
Entry action implementation in protimer_state_mach.cpp
It has to return the status( as shown below).
event_status_t protimer_state_machine(protimer_t *mobj, event_t *e){ switch (mobj->active_state){ case IDLE: { return protimer_state_handler_IDLE(mobj,e); } case TIME_SET: { return protimer_state_handler_TIME_SET(mobj,e); }
return the status in protimer_state_mach.cpp
Next, let’s implement the EXIT case.
case EXIT:{ display_clear(); return EVENT_HANDLED; }
Exit action implementation in protimer_state_mach.cpp
And after that, Increment time. Increment time it has to add 60 seconds to c_time. mobj->curr_time += 60;
The increment time causes the Transition, mobj->active_state=TIME_SET (the new state is assigned to active_state); return the status as EVENT_TRANSITION.
case INC_TIME:{ mobj->curr_time += 60; mobj->active_state = TIME_SET; return EVENT_TRANSITION; }
INC_TIME implementation
Next START_PAUSE. For START_PAUSE, there is no Action, just a Transition.
mobj->active_state = STAT; here also return the status as EVENT_TRANSITION.
case START_PAUSE:{ mobj->active_state = STAT; return EVENT_TRANSITION; }
START_PAUSE implementation
The next one is TIME_TICK. The TIME_TICK is the Internal Transition. So, there is a Guard. How to implement this? You have to access the sub-second field of the event. The TIME_TICK implementation is shown below.
case TIME_TICK:{ if(((protimer_tick_event_t*)(e))->ss == 5){ do_beep(); return EVENT_HANDLED; } return EVENT_IGNORED; } }//END OF SWITCH return EVENT_IGNORED; }
TIME_TICK Implementation
If(protimer_tick_event *) you have to typecast the pointer, you must access the ‘ss’ field. If it = 5, then you have to call do_beep(); then you have to return EVENT_HANDLED.
Suppose, if this condition fails, then you return EVENT_IGNORED. This is the end of the switch case. If this state receives any other event, then it is ignored. That’s why at the end, you return EVENT_IGNORED by default.
That’s the implementation of the handler of the state IDLE. Likewise, you have to implement similar functions for other states.
Look at Figure 2; These are helper functions to implement our actions. We will implement that at the end.
The first helper function is display_time. And after that, display_message, so this is a string value. ‘String’ is a user-defined data type provided by the Arduino framework to represent string values. I will use String s. After that, do_beep().
Now you give the prototypes of these functions as shown below.
#include "main.h" #include "lcd.h" //prototypes of helper functions static void do_beep(void); static void display_clear(void); static void display_message(String s); static void display_time(uint32_t time);
Prototypes of helper functions
Provide the prototypes of those helper functions. And after that, you have to provide the prototypes of state handlers functions as well. So, let’s make them static because these are private to this file. All functions you can make it as static, no problem.
Look at Figure 3; mobj is a const pointer, pointing to variable data. Data can be modified. But this pointer cannot be modified. That’s why, as a best practice, you can use ‘const’ here.
How to interpret this?
‘e’ is a const pointer pointing to const data of this type. Data, as well as pointers, are constant here. The handler should not modify that; it just uses it. But in Protimer_t, the data can be modified. That’s why mobj is a const pointer pointing to variable data.
Now you have modified the function declarators. That’s why these function prototypes also have to be modified.
FastBit Embedded Brain Academy Courses
Click here:https://fastbitlab.com/course1