How to use state machines with a real-time operating system

State machines can be used in various places of your system. In this example you will learn how to use a state machine in the context of a RTOS (real-time operating system). For this exercise it does not really matter which RTOS you have in place. Because the general approach is always the same. Let's use FreeRTOS here.

FreeRTOS is a market-leading real-time operating system (RTOS) for microcontrollers and small microprocessors.

There is a Windows port layer which allows to quickly test how to use the state machine code generated by the SinelaboreRT code generator on your computer.

In this design pattern state machines run in the context of an own task. The principle design is shown in the following figure.

Using state machines in a main loop with event queue

Each task executes a state machine (often called active object) in an endless while loop. The tasks wait for new events to be processed from the state machine. In case no event is present the task is set in idle mode from the RTOS. In case one or more new events are available the RTOS wakes up the task. The used RTOS mechanism for event signaling can be different. But often a message queue is used. Events might be stored in the event queue from various sources. E.g. from within another task or from inside an interrupt service routine from a timer which is shown in the code below. This design can be realised with every real-time operating system. Mainly the exact syntax differs from OS to OS.

Simple State Machine

Simple state machine using a timer to toggle between two states The simple state machine below reacts on a cyclic timer. It just toggles between the two states SA and SB. It prints some text as demo when enter or leaving a state and when the transition takes place. In more complex systems more than one timer might be needed. If different timers are needed different timeout events and timers can be setup easily. See below.

Setup the system

This simple main code creates a task a queue and a timer and then starts the RTOS scheduler.

int main( void ){ prvInitialiseHeap(); vTraceEnable( TRC_START ); xTaskCreate(tA, "TA", 1000 /*Bytes stack*/, NULL /* no parameters */, tskIDLE_PRIORITY + 1, NULL); /* Create the queue. */ queueHandle = xQueueCreate(32, sizeof(USER_EVENT_T)); timerHandle = xTimerCreate("Timer", /* name for debugging */ 1000, /* ticks -> 1s */ true, /* true = repetitive */ NULL, /* The timer's ID is not used. */ timerCallback); /* The function executed when the timer expires. */ xTimerStart(timerHandle, 0); /* The scheduler has not started so use a block time of 0. */ vTaskStartScheduler(); }

TA task

The tasks waits blocking for elements to appear in the queue. If data is present in the queue we dequeue it and call the state machine.

static void tA(void* xTimerHandle){ /* Avoid compiler warnings resulting from the unused parameter. */ (void)xTimerHandle; USER_EVENT_T data = { SM_NO_MSG , 0 }; while(true){ printf("TA task.\r\n"); /* Wait until something arrives in the queue - this task will block indefinitely. */ // call once to init sm(&instDataSM, &data); while (true) { xQueueReceive(queueHandle, &data, portMAX_DELAY); sm(&instDataSM, &data); } } }

Timer

Each timer has a callback function called from the RTOS in case of a timeout happened. In the timer handler we simply enqueue a message with a timeout event (here evTimeout). We decided to sent some data in addition to the event. Therefore we defined an own data type (USER_EVENT_T) that is sent to the state machine. Otherwise it would be possible to just sent the event itself.

For clearness it is good to use some kind of guideline how events should be named. Here we call it evTimeout. If you have multiple timers call them e.g. according to their purpose.

/* This is the software timer callback function. This callback function will execute if the timer expires */ void timerCallback(TimerHandle_t xTimerHandle){ static uint8_t cnt = 0; cnt++; USER_EVENT_T data = {evTimeout, cnt}; // evTimeout is used in the state diagram xQueueSend(queueHandle, &data, 0U); }

Wrap-up

We showed how to use state machines in the context of FreeRTOS. This pattern can be universally used with any other RTOS too. Using this pattern allows a very flexibly system design and allows to design robust easy to understand real-time embedded systems. The relevant files are available here: freertos-code.zip