Compatible Library Modules with Independent State Machines
Each module runs as an independent state machine. These modules effectively run in the background, without any need for the client (the module that calls its API) to manage the module's state machine.
Different modules (even ones that depend on each other) cannot assume any relationship as to when or how their task functions are called. They are effectively independent of each other. The “Tasks” function of each module just checks what state it is currently in, and when the right conditions are met, performs whatever task is necessary to move to the next state.
In this way, the over-all system runs like a single state machine, consisting of individual, independent, but cooperating state machines.
Cooperative (non-blocking) State Machines
Each state machine is not only independent, but it is also cooperative. Cooperative state machines make it possible to function together simultaneously.
A cooperative state machine will break longer tasks into shorter ones. Doing this helps ensure that one task will not "block" other tasks. This allows the system to move quickly from one module to another, giving all modules in a system time to perform the tasks they need to perform, when they need to perform them.
Tasks are broken down into steps, largely based on how long it takes to perform each step. For example, a simple UART transmit driver (state machine) could implement two states:
- Write data to a transmit FIFO and set a bit to start transmitting
- Wait for the UART transmit to complete (wait for interrupt or poll for a flag)
Each of these states takes a small amount of time for the core to implement. The core will not spend any time waiting for the data to be transmitted. As the hardware (UART in this case) performs each module's task, the core is free to start and monitor tasks performed by other modules.
State machines can be used to build some fairly complex systems. But, it’s not ideal for all systems and it gets restrictive when modules have to respond with very low latency to external events or in a system where there are too many modules that have to be appropriately prioritized. In this case, an RTOS can be used to ensure certain tasks run when they need to run. Harmony's RTOS abstraction layer (OSAL) can help implement this.
Compatible State Machine Examples
Each state machine is initialized and driven by the system. This greatly simplifies the usage model. The basic idea is that each module implements a function that initializes its state machine and a function that manages the tasks of the state machine. In a polled system, these modules are ultimately called from the main function.
After all the modules have been initialized, each module’s “Tasks” function checks to see what state it is in, and if the appropriate conditions are met, it does whatever task is necessary to move to the next state. This process continues as the module’s state machine operates. When the right conditions are met, a module does whatever is necessary to transition to the next state.
This code shows an example of what a simple system might look like. The first thing “main” does is call “SYS_Initialize” to initialize the clock, the board (via BSP_Initialize()), all modules in the system (here, just the USART and timer drivers and a timer system service), and the application. Then, “main” drops into the system-wide “super loop”, repeatedly calling “SYS_Tasks” which in turn calls the “Tasks” functions for each module and the application. This keeps the system running and is part of a system configuration.
This shows a partial example of what a simple application’s “Tasks” function might do. Notice the initial state attempts to open the USART driver. If it succeeds the state switches to wait for the USART driver to indicate it is ready. Other states might transfer a message to the USART, depending on whether or not the “APP_TMRCallBack’ function has been called since the last message was received. This example isn’t complete, but you can see how the state machine of this application calls the API routines of other modules. All modules are “maintained” and continue to operate as long as the over-all “super loop” continues to run.