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 VxWorks. VxWorks is a market-leading real-time operating system (RTOS) used in many different applications since many years. You can follow this tutorial by using the VxWorks Software Development Kit on of of the supported platforms. We use QEMU (x86-64) running on a Debian host.
In the most typical design, each state machine runs in the context of an own task. The design of the fully running example is shown in the following figure and available for download at the end of the article.
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 VxWorks. In case one or more new events are available the VxWorks wakes up the task. The used VxWorks 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. This design can be realised with every real-time operating system. Mainly the exact syntax differs from OS to OS.
This state machine is a representative of a simplified control task. The state machine was designed with the built-in state machine editor. And the state machine code was completely generated.
This simple main code creates two tasks, a message queue, the timeout timer and the state machine.
#include <stdio.h> #include <stdint.h> #include <vxWorks.h> #include <taskLib.h> #include <msgQLib.h> #include <time.h> #include "state_machine_ext.h" #include "state_machine.h" TASK_ID tid1,tid2,tidLowPrio; MSG_Q_ID qid; timer_t timerID; struct itimerspec value, ovalue; STATE_MACHINE_INSTANCEDATA_T smInstance = STATE_MACHINE_INSTANCEDATA_INIT; // Helper to start timer from state machine. // In practise you might use a more sophisticated routine considering multiple timers ... void startTimer(void){ printf("Start timer!\n"); timer_settime (timerID, TIMER_RELTIME, &value, &ovalue); } // helper to stop timer void stopTimer(void){ printf("Stop timer!\n"); timer_cancel(timerID); } int main(void) { qid = msgQCreate(20, 1 /* msg len in byte */,MSG_Q_FIFO); // create a cyclic timer for demo purposes used in the state machine timer_create (CLOCK_REALTIME, NULL, &timerID); timer_connect (timerID, (VOIDFUNCPTR)timerhandler, NULL); value.it_value.tv_nsec = 1; value.it_value.tv_sec = 0; value.it_interval.tv_nsec = 500*1000*1000; // 500ms value.it_interval.tv_sec = 0; // init state var once state_machine(&smInstance, STATE_MACHINE_NO_MSG ); tid1 = taskSpawn("/task1" , 107 , VX_NO_STACK_FILL , 2000 , (FUNCPTR)task1, 0,0,0,0,0,0,0,0,0,0); tid2 = taskSpawn("/task2" , 106 , VX_NO_STACK_FILL , 2000 , (FUNCPTR)task2, 0,0,0,0,0,0,0,0,0,0); tidLowPrio = taskSpawn("/taskidle" , 255 , VX_NO_STACK_FILL , 2000 , (FUNCPTR)lowPriTask, 0,0,0,0,0,0,0,0,0,0); printf("init finished!\n"); while(1){ taskDelay (CLOCKS_PER_SEC); } taskSuspend (0); return 0; }
The task1
waits blocking for elements to appear in the queue. If data is present in the queue we dequeue it and call the state machine.
void task1() { #define BUF_LEN 5 char buf[BUF_LEN]; printf("task 1 started!\n"); while(1) { msgQReceive(qid, buf,1,WAIT_FOREVER); state_machine(&smInstance, *buf ); } }
For simplicity task2
sends events to task1
via the created message queue. In practise also from timers or interrupt service routines events might be sent.
char msgs[]= {evStart,STATE_MACHINE_NO_MSG, STATE_MACHINE_NO_MSG,STATE_MACHINE_NO_MSG,STATE_MACHINE_NO_MSG, STATE_MACHINE_NO_MSG,STATE_MACHINE_NO_MSG,STATE_MACHINE_NO_MSG,evStop,evStart,evStop,evShutdown, STATE_MACHINE_NO_MSG,STATE_MACHINE_NO_MSG}; void task2() { printf("task 2 started!\n"); for(uint8_t i=0; i<sizeof(msgs); i++) { msgQSend(qid, &msgs[i],1,WAIT_FOREVER,MSG_PRI_NORMAL); taskDelay(CLOCKS_PER_SEC); } }
The following output is generated from the above code. Note that a hierarchical state machine with history is used.
[vxWorks *]# example.vxe Launching process 'example.vxe' ... Process 'example.vxe' (process Id = 0xffff800000312010) launched. Enter STOP In STOP task 1 started! task 2 started! In STOP Exit STOP Start timer! Enter RUN Enter Slow init finished! In RUN Exit Slow Enter Fast In RUN Exit Fast Enter Slow In RUN In RUN Exit Slow Enter Fast ... In RUN Exit Slow Exit RUN Stop timer! Enter STOP In STOP Exit STOP Start timer! Enter RUN Enter Slow In RUN Exit Slow Enter Fast In RUN Exit Fast Enter Slow In RUN Exit Slow Exit RUN Stop timer! Enter STOP In STOP Exit STOP Entering Final
We showed how to use state machines in the context of VxWorks. 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 and easy to understand real-time embedded systems. The relevant files are available here.