Multi-stage task
Context
● You are developing an embedded application using one or more members of the 8051 family of microcontrollers.
● The application has a time-triggered architecture, constructed using a scheduler.
Problem
How do you make sure that a long task does not interfere with the normal operation of the scheduler?
Background
See CO – OPERA TIVE SCHEDULER [page 255] for relevant background information.
Solution
As we have seen, co-operative schedulers have many advantages when compared to pre-emptive or even hybrid schedulers and we generally wish to use a co-operative approach when it is feasible to do so. However, a key design challenge when using this approach is to ensure that each task is completed before the next scheduler tick occurs. One approach that can help us to achieve this goal is to make use of timeouts (see Chapter 15).
Another approach is to use of multi-stage tasks. To understand the need for multi- stage tasks, consider an example (adapted from Pont 1996,Chapter 13). Suppose that as part of a ‘backup’ temperature monitoring system for a metal furnace we are required to send temperature samples at regular, 5-second, intervals to a desktop PC (Figure 16.1).
The required PC output takes the form shown in Figure 16.2.
At first inspection, the obvious way of sending this information to the PC at the required 5-second interval is to create a function (we will call it Send_Temp_Data()), which will be run by the scheduler every five seconds.
However, this is probably a rather bad solution. The problem is that we need to send at least 43 characters to the PC using Send_Temp_Data(). At a common baud rate of 9,600 (see Chapter 18 for details), each of the characters will take approxi- mately 1 millisecond to send: as a result, the task duration will be around 40 ms. If we use this (long) task, we will need to set the system tick interval at a value greater than 40 ms, which may have a detrimental impact on the overall responsiveness of the application.
The multi-stage alternative solution avoids this problem. Rather than sending all the data at once, we can simply store the data we want to send to the PC in a buffer. Every ten milliseconds (say) we schedule a task to check the buffer and send the next character (if there is one ready to send). In this way, all the required 43 characters of data will be sent to the PC within 0.5 seconds: we can reduce this time if necessary (it rarely is) by scheduling the task to check the buffer every millisecond. Note that because we do not have to wait for each character to be sent, the process of sending data from the buffer will be very fast (typically a fraction of a millisecond).
Overall, the use of multi-stage tasks in this application allows us the opportunity to use a much shorter tick interval and therefore make more efficient use of the microcontroller’s processing power.
The ‘RS232’ library used as an example here is discussed in detail in Chapter 18.
Hardware resource implications
In general, the use of multi-stage tasks allows much more efficient use of the available microcontroller processing power.
Reliability and safety implications
Use of multi-stage tasks can make your system more responsive, by allowing the user of a shorter tick interval.
Portability
This technique can be (and is) applied in a wide range of different embedded systems.
Overall strengths and weaknesses
The use of multi-stage tasks allows the use of shorter tick intervals and, hence, can help to make the system more responsive.
The use of multi-stage tasks allows much more efficient use of the available microcontroller processing power.
Related patterns and alternative solutions
This basic architecture is applied in various patterns in this collection, including PC LINK (RS -232 ) [page 362], LCD CHARACTER P ANEL [page 467] and SWITCH INTER – F ACE ( SOFTW ARE ) [page 399].
Example: Measuring rotational speed
Suppose we wish to measure the speed of a rotating shaft and display the results on a (multiplexed) LED display. This could be part of an automotive or industrial application.
As we will see in Chapter 30, an effective way of measuring the speed is to attach an appropriate rotary encoder to the shaft (Figure 16.3), and count the number of pulses that occur over a fixed period of time (say 100 ms or 1 second). From the count, and having details of the rotary encoder, we can calculate the average speed of rotation.
We have seen various people try to apply this basic approach in a scheduled envi- ronment as follows:
● Create a task of (say) 100 ms duration
● In this task, count the pulses
● Set the value of a (global) Pulse_count_G variable at the end of the task
● Use the global variable to update the LED display
The problem with this approach is that 100 ms taken to measure the speed is a long task duration and can be difficult to support in many embedded applications. Here, for example, we suggested that we wished to display the speed on a multiplexed LED display. We might well have to update the LEDs every 5 ms to avoid flickering (see MX LED DISPLA Y [page 450] for details).
Of course, a co-operative scheduler cannot support tasks called every 5 ms and tasks of 100 ms duration.
To solve this problem, consider a multi-stage approach to this problem. In the first approach, we waited in the task to count the pulses from the encoder. However, this is not necessary. Timer 0 or Timer 1 (or Timer 2 where available) will count pulses (strictly, falling edges of pulses) on external pins without user intervention, without generating interrupts and without interfering with any other processing. We can use this fact to make this speed measurement system into a multi-stage task as follows:
● Create a very short (<0.1 ms task)
● Schedule this task (say every 100 ms, to be compatible with the first example)
● In this task, read the current pulse count. Store the result in a global variable (for use elsewhere in the program)
● Reset the pulse count to 0
● Repeat 100 ms later etc.
The details of the technique will be considered in Chapter 30, where we will see that it is possible for similar solutions to be created without the need for the timer hardware.
Example: LCD library
Suppose we wish to update an LCD display. As we will see in LCD CHARACTER P ANEL [page 467], updating each character in a typical LCD display can take around 0.5 ms: to update the whole of a 40-character display can therefore take around 20 ms. This is often prohibitively long.
However, suppose we schedule a LCD_Update() function every 20 ms and, each time, update only a single display position. At worst, it will take us a total of 800 ms to update the whole display – and we will be able to complete numerous other tasks at the same time. In addition, in most circumstances, only some of the display will have changed: if we keep track of characters that need to be updated, we can usually keep the display fully up to date with a multi-stage task of 0.5 ms duration called every 100 ms. Overall, the multi-stage approach to this single task can make a major difference to the architecture of the whole application.