Finite state machines¶
Learning goals: The student will understand how state machines can be used in the design and implementation of an embedded program.
Finite state machines (FSM) are generally used in digital techniques to implement sequential logic. In addition to this, state machines are used widely in software engineering as a way of modeling appliction behaviour. In embedded systems a finite state machine is a great way to model the functionality of the program, even during the design phase, and to manage and drive the control logic of an event-driven program. In this course, finite state machines are extremely useful, when we are implementing the responses to different events in our program, shared between different RTOS-tasks of Pico.
In this material we are not going to dive deep into the secrets of state machines (formal representations etc.), but we are going to present how to take them as a part of the design and implementation process of a program. The professionals of the field have development environments and tools, that can be used to create a state machine as a part of code. Some tools can even automate the process of programming by generating code from a state machine representation.
A finite state machine¶
A finite state machine is a model of a system or a process, that is used to visualize functionality with states and changes of states. For example, Mealy-style state machine changes its state in a following way: a state transition depends on the current state and inputs. During state transitions, it is possible to execute functions, that produce an output. Here, the state machine is deterministic, which means that one state with one input can produce only one state transition. State transition graph visualizes the possible states of a system, the transitions between them and the events, that cause the transition from one state to another.
In the picture below, the state machine has two states
State a and State b. The possible inputs and outputs are attached to the state transitions depicted by arrows.
This simple description of a finite state machine is sufficient for this course.
Example of a state machine¶
Below, the functionality of an embedded program is visualized as a state machine. Functionality, state transitions and a global state variable, the value of which (=state) changes in response to events, have been defined to the state machine. The logic of the program is based on the state variable so that the execution of the program progresses according to the state transitions, sequentially
- In state IDLE we just wait for events in infinite loop
- Once in a second, we get an event (=timer interrupt), that causes the state transition from IDLE to READ_SENSOR
- State transition from the state READ_SENSOR, where the new sensor data is being read, to the state UPDATE, where the new sensor data is updated to the screen of the device
- State transition from state UPDATE back to waiting state IDLE.
- In an infinite loop, checking if there are new messages for the device (event). If there are, perform state transition to state NEW_MSG.
- In this state, the message is handled
handle_msgand a response is sentsend_reply - After which we return back to IDLE
File tkj-state-machine.jpg not found.
Implementation in C-language¶
In programming of a state machine, there are four principles:
- Place the functionality of a state in functions
- Assign state transitions to the state variable. For this reason, the state variable is a global variable, so that its value can be modified everywhere in the program!
- In the infinite loop of the program, the value of the state variable is checked and an appropriate state transition is executed.
- There can be multiple nested state machines in a program. For example, a task in RTOS can hold one internal state machine, while another one is running the whole system.
Keeping these principles in mind, let's look at one event-driven implementation of the above state machine, based on the task architecture of FreeRTOS.
// Introducing state
enum state {IDLE=1, READ_SENSOR, UPDATE, NEW_MSG};
// Global state variable, initialized to waiting state
enum state myState = IDLE;
// Timer interrupt once per second
void clkFxn(void *pvParameters) {
// We change the state to a desired one
// If-clause is used to check, that the state transition is possible
// Now we allow only the state transition IDLE -> READ_SENSOR
if (myState == IDLE) {
// State transition IDLE -> READ_SENSOR
myState = READ_SENSOR;
}
}
// Communications task
void commTask(void *pvParameters) {
while (1) {
// Function is_message_waiting is used to check
// are there new messages in the buffer
// Additionally, we allow only the state transition IDLE -> NEW_MSG
if (is_message_waiting() == TRUE && myState == IDLE) {
// State transition IDLE -> NEW_MSG
myState = NEW_MSG;
// Functionality of state
handle_message();
send_reply();
// State transition NEW_MSG -> IDLE
myState = IDLE;
}
}
}
// Handling sensors
void sensorTask(void *pvParameters) {
while (1) {
if (myState == READ_SENSOR) {
// Functionality of state
read_sensor_values();
// State transition READ_SENSOR -> UPDATE
myState = UPDATE;
}
vTaskDelay(..);
}
}
// Handling display update
void displayTask(void *pvParameters) {
while (1) {
if (myState == UPDATE) {
// Functionality of state
update_screen();
// State transition UPDATE -> IDLE
myState = IDLE;
}
vTaskDelay(..);
}
}
Note Above implementation of a state machine does not work (purposefully) in the device of the course without modification.
In the program, we have set a timer interrupt to run once per second, with a handler
clkFxn, that changes the global state to READ_SENSOR. Like this, with the help of a state machine, we can read the value of a sensor once per second. Similarly in the commTask, we change the state to value NEW_MSG, if there are new messages waiting in the buffer.Please notice one important thing. Now, with
if-clause we check if the state transition is legal. Without checks, the program might perform state transitions that are not meant to happen, which in turn would break the program. In general, checking the legality of a state transition is an important part of using state machines in programs!!When designing a state machine, it is beneficial to order its states somehow, for example in a increasing numbered order, so that the checking of state transitions can be easily done with comparison operators (smaller than, etc.) Of course, here you should avoid complex
if-else-structures. Another important factor in improving the structure of the code is minimizing the amount of states. However, we are not going to explore that in this course. Instead, we will think which things are really necessary in our program. In the example, the UPDATE-state is not necessary, but it is needed, because we are updating the screen in its own task. In this way, we can manage the control logic of our program between different tasks.Another use case for a state machine in embedded programs is the implementation of control logic for user interface. With the help of a state machine, we can define (for a restricted resource), like the button integrated to Pico's extension board, different functionalities tied to the state of a program. For example, in the state MENU, the button could be used to change between menu items, when in state GAME it could be used for something entirely different. How convenient!
To conclude¶
State machines will be used in course project.
Give feedback on this content
Comments about this material