Multitasking Basics
The PDQ Board includes a multitasking executive that can concurrently execute a number of tasks, making it ideal for automation and instrument control applications. While the processor only executes one instruction at a time, the multitasker enables the processor to switch rapidly among tasks to give the impression that several tasks are being performed simultaneously. The tasks are linked in a round-robin circular list, with each task switch running the next task in the list.
Multitasking Advantages
Without a multitasking executive, multiple functions can be performed by coding them into a single loop that performs all of the needed functions. This approach suffers from lack of modularity and poorly controlled real-time performance. Using a multitasking approach overcomes these drawbacks.
Real-time systems must be able to perform tasks within specified periods of time. For example, an automation instrument might have to perform a mathematical calculation that requires half a second to complete, and it might also have to update a display every tenth of a second. If these functions are performed in a non-multitasked system in a single large loop, the time requirements of all of the statements in the loop interact to determine the overall timing of the functions. In the example just cited, the mathematical computation might have to be broken into several parts and interspersed with calls to the display update function to ensure that the display is updated frequently enough. But then the timing changes every time another function is added to the loop or the computation is re-coded. This is due to lack of modularity. Such a program is extremely difficult to maintain and modify because all of the details of the implementation of the loop affect the other functions in the loop.
Another solution is to code the time-critical display update as an interrupt routine. This will work, but if the application has many time-critical tasks the number of interrupts grows, making the code less manageable.
Use of multitasking provides the optimal solution. The multitasking executive in the PDQ Board handles task switching for you. This makes it easy for you to design modular code with well specified real-time behavior. Each task is defined as a separate environment with access to all of its needed resources. Each task is individually debugged, and then installed into the round robin task loop to enable concurrent execution. A properly designed multitasked application program allows the tasks to be modified without lowering system performance, and more modular tasks can be added to the round-robin loop if an upgrade is required.
Multitasking lexicon
To properly describe the PDQ Board's multitasking executive, we must first define a few terms.
- A task is not a program, process or routine. Rather, a task is an environment capable of running an assigned program. The task environment contains at the minimum a user area (which holds variables and pointers critical to the operating system's interface to the task) and a return stack, both in common ram.
- Declaring a task gives the task a name that is usable by the application program, and allocates a block of RAM that is typically decimal 2000 bytes long. Tasks typically have a full user area (186 byte default size), a return stack used by all functions invoked by the action program, as well as a data stack and various ram buffers used by any built-in driver functions that might be invoked.
- Building a task initializes these task areas in common RAM. A user-defined program that contains an infinite loop can be specified as the action program or activation routine for a task.
- Activating a task associates an action program with a specified task.
- A shared resource is a hardware or software resource that is used by two or more tasks. For example, a serial I/O port could be a shared resource, as could an A-to-D (analog to digital) converter or an area of memory. Access to a shared resource is controlled by a resource variable.
- A mailbox is a variable that is used to pass messages between tasks using functions such as SEND() and RECEIVE().
The multitasking executive provides functions that make it easy to declare tasks, associate action programs with tasks, and switch control among tasks. It also provides functions to pass messages and manage access to shared resources so that the state of each resource is not corrupted by simultaneous access from several tasks.
Timesliced and cooperative task switching
There are two kinds of task switching implemented by the PDQ Board's multitasker. The first is software-invoked cooperative task switching. To use cooperative task switching, the operating system function Pause() (declared in the include\mosaic\mtasker.h
header file) must be invoked by each task. Each time Pause() is called, the multitasker switches to the next active task in the round-robin task loop. Non-time-critical tasks can call Pause() often to allow time-critical tasks to use more processor time.
The PDQ Board also supports timesliced task switching. This forces a task switch with every tick of an interrupt-driven timeslice clock. The default period of the timeslice clock is 1 millisecond; this can be changed by the programmer using the MsecTimeslicePeriod() function declared in the mtasker.h
header file.
Timesliced task switching allows the programmer to guarantee that each task is executed within a specified maximum time. For example, in a 4-task system with one task that must update a display every 50 milliseconds (msec), a timeslice period of up to 12 msec guarantees that the update operation is accomplished in a timely fashion.
The PDQ Board operating system lets you choose timesliced or cooperative multitasking, or to mix them in any combination. The multitasking executive also supports the passing of messages among tasks via mailboxes for data sharing and task synchronization, as well as controlled access to shared resources.
Initializing the multitasker and using the elapsed time clock
A 32-bit timeslice clock is driven by the RTI (Real-Time Interrupt) in the Freescale HCS12 (9S12) processor. If the timeslicer is enabled, the multitasker switches to the next task in the round-robin task loop with each tick of the timeslice clock, and also increments a long 32-bit variable named TIMESLICE_COUNT once each timeslice period. Note that the timeslice clock is not itself a task; rather, it is an interrupt service routine that interrupts the processor only briefly every timeslice period.
Your application program controls the timeslice clock using the following functions:
- StartTimeslicer()
- InitElapsedTime()
- FetchTSCount()
- MsecTimeslicePeriod()
- CountToMsec()
- ReadElapsedSeconds()
- StopTimeslicer()
- TIMESLICE_COUNT
To start the timeslice clock, invoke the function StartTimeslicer(). This function also globally enables interrupts. InitElapsedTime() sets TIMESLICE_COUNT equal to zero. Note that TIMESLICE_COUNT must not be accessed directly while the timeslicer is running; use FetchTSCount() to read its current value.
The default timeslice period is 1.024 millisecond (ms), or approximately 1 ms (we round to the nearest millisecond for simplicity in this discussion). The timeslice/multitasker period can be varied over the range from just over 1 millisecond to just over 15 ms by invoking the MsecTimeslicePeriod() function as described in the C Glossary. Because the processor's real-time-interrupt system counts in multiples of 1.024 ms, the number of milliseconds that you specify is multiplied by 1.024. For example, to switch tasks every 5 ms, execute MsecTimeslicePeriod(5) which sets the actual timeslice period to 5 * 1.024 = 5.12 ms. With this period, the elapsed-time clock can run for over 245 days before rolling over to zero. The maximum valid parameter that can be passed to MsecTimeslicePeriod() is 15.
Two functions make it easy to convert from timeslice counts to real time measured in milliseconds. CountToMsec() accepts as its input a 32-bit elapsed number of timeslice counts and returns the corresponding 32-bit number of elapsed milliseconds. This function is used in the example program described below. The function ReadElapsedSeconds() returns a 32-bit long result equal to the number of seconds since InitElapsedTime() was called.
ReadElapsedSeconds() and CountToMsec() are aware of the exact time value of each count as set by MsecTimeslicePeriod(), including the 1.024 multiplication factor. These functions report times with an accuracy better than 0.02%, although the resolution is of course limited to 1 second for ReadElapsedSeconds() and to 1 ms for CountToMsec(). At the default 1.024-ms resolution, the 32-bit TIMESLICE_COUNT rolls over to zero in just under 50 days, and this sets a maximum limit on the elapsed time that these functions can report.
A C function that computes the elapsed seconds as well as the number of milliseconds since the last integral second corresponding to the TIMESLICE_COUNT is described in the PDQ Board Manual's chapter on task switching and timekeeping.
To turn off the timeslicer, execute StopTimeslicer(). This locally disables the RTI interrupt but does not alter the contents of TIMESLICE_COUNT.
See also: