How to use state machines with VxWorks

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.

Using state machines with a real-time operating system

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.

Simple state machine used for evaluation

Simple state machine 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.

Setting up the system

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; }

VxWorks Task 1

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 ); } }

VxWorks Task 2

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); } }

Generated output on the qemu console

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

Wrap-up

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.