With our YAKINDU Statechart Tools Professional Edition we introduced support for arrays and pointers in the C domain. Using these new features you can reference arrays and pointers from your existing header files in your statechart and use them there as well. In this blog post you are going to learn what you can do with arrays, and how to handle them efficiently in your statechart.
Robotics: State-driven systems
Ultrasonic sensors are frequently used in robotics to measure the distance to the next solid object. They generate an ultrasonic sound, and when the soundwave is reflected by a solid object back to the sensor, the time taken for the roundtrip can be used to determine the distance. The quality of the result can vary greatly with the geometric orientation and the material of the object. Surfaces not parallel to the sensor can generate multiple echos, and soft tissues like carpet and similar can absorb the signal completely.
To rule out these effects, the measurements are often filtered. This statechart shows one of the simplest methods of filtering – the mean and the standard deviation are calculated and the mean is returned as long as the standard deviation is not greater than a threshold value, indicating that all measurements were in a close margin to each other. You can try it out at Wolfram Alpha. Just play a bit with the values and see how the standard deviation changes.
The sensor subsystem can be modeled as a state machine. It has three basic states: Waiting, measuring and filtering. As you can see in the diagram, there’s one more state – the filtering part is split into two for implementation reasons.
Modelling the system with a statechart
The array the statechart uses is declared in the header file sct_utils.h (note the import statement in the declaration section), together with
#define ARRAY_SIZE 5. This allows you to use both values directly in the statechart. As of now you can’t declare arrays in the statechart, only import them.
The state machine cycles between the state wait and a measuring and filtering process. In measure_distance, the state machine acquires some measurements, proceeds to calculate the mean value and the standard deviation and then returns back to the wait state, raising a
done- or an
When the statechart receives the event
sensor.measure, the measure_distance state is entered. Note how the
index variable is set to 0 on entry. The state iterates over the array with the local reaction
[index < ARRAY_SIZE] – this specifies that the following action should be executed every cycle when the
index is smaller than the array size. Basically this is a standard FOR loop. On each iteration, one measurement is taken. Currently, the function is only a mock that generates random values around a random mean, simulating a sensor. It is declared in sct_utils.h as well and implemented in sct_utils.c, so it can be used directly from the statechart. In a real-world-application this function can talk to the sensor directly and return the measured value. In every loop cycle, the
index is incremented – note that this happens after the assignment to the array element.
Instead of the local reaction you could also use a transition reentering the same state with the same guard. This would have two downsides – the
entry action would be executed every time, so the
index can’t be set to 0 there, and there is more clutter in the statechart.
As soon as the
index reaches the constant
ARRAY_SIZE, the state is left via the transition to the state calc_mean. This state shows how to use an external function with an array – you can pass it as an argument, just like you’re used to from your C projects. Remember that you need to pass the size of the array as well because C itself does not have any knowledge of that.
The return value of the function is assigned to the statechart variable
stddev.sum and the mean value is calculated by dividing through the number of elements. After that (note that all these operations are the entry action of the state), the state is left on the next cycle to calculate the standard deviation.
The principle of the array iteration in calc_stddev is exactly the same as in measure_distance. On each iteration the state accumulates the squares of the difference of all values to the mean value. Again, the state is left when the variable
index is equal to
ARRAY_SIZE. The state’s exit-action is then triggered and takes the square root of the result. (Technically, it would be okay to omit the square root – the value would be the variance then, and root(x) is a monotonous function for positive values.)
After the calculations are done, the statechart raises the
sensor.done or the
sensor.error event, depending on the standard deviation. The
sensor.error event would instruct the application to ignore this measurement, while the
sensor.done event indicates a valid measurement that can be used to plan further movements of the robot. Another statechart could react on this
sensor.error event, for example raising a more global error indicating the sensor might be faulty when there are more than three erroneous measurements in a row – just like a car’s fault memory works.
Summary and outlook
To summarize what you have learnt today:
- You can declare arrays in separate header files and use them after importing the header file.
- You can iterate over the array using local reactions inside of a state.
- Local reactions can often help you save transitions reentering the same state, removing clutter from the statechart diagram.
- You can pass arrays as arguments to functions just like in C.
In the next article, you will learn how to use pointers. We will integrate the statechart of this post with another one that uses the measurement to control the movement of a robot.