Task Switching and Time Keeping
This page provides an introduction to real time programming. Here you'll learn how to use the PDQ Board's timeslice clock, which provides an elapsed time clock and guarantees the timeliness of pre-emptive task switching. Understanding it will enable you to write fast, responsive C language programs for real-time control of your software application.
Understanding the mulitasker timeslice clock will enable you to write fast, responsive programs to control your instrument.
The timeslicer and task switching
Many instrument design, automation and control application programs can be logically conceived of in terms of a set of distinct tasks that cooperate to solve the problem at hand. For example, a program that manages a hand-held sensing instrument might have one task that acquires sensory data, another that performs calculations to process the data, and a third task that displays the results on a liquid crystal display.
Using the PDQ Board’s built-in multitasking executive confers significant advantages when designing real-time systems. Breaking up a complex program into easily understood modular tasks speeds debugging, improves maintainability, and prevents source code modifications of one task from adversely affecting the required real-time performance of another task.
In a multitasking environment, a task is an environment capable of running a program, comparable to a thread in a UNIX environment for example. After declaring (naming) a new task (which also allocates a 1 Kbyte task area), its environment is built by initializing its required stacks, buffers and pointers in the 1 Kbyte task area. Then the task is activated by associating it with an activation routine that performs a specified set of actions.
The built-in elapsed time clock
The PDQ Board is a Single Board Computer (SBC) with a built-in real-time operating system (RTOS) that includes a multitasking executive. It maintains an elapsed time clock whenever the timeslicer is active. Please open the "Timekeep Demo" from within the Mosaic IDE Plus to follow along with this chapter:
You control the timeslice clock with the following functions:
- StartTimeslicer()
- StopTimeslicer()
- FetchTSCount()
- MsecTimeslicePeriod()
- InitElapsedTime()
- ReadElapsedSeconds()
- CountToMsec()
- TIMESLICE_COUNT
This elapsed time clock counter is based on the RTI (Real-Time Interrupt) in the Freescale 9S12 (HCS12) processor, so it does not use any of the timing resources provided by the processor’s Enhanced Capture/Timer (ECT) system. Your program can start the timeslice clock by calling the function:
StartTimeslicer()
The timeslicer increments the long (32-bit) variable named TIMESLICE_COUNT each timeslice period. Note that TIMESLICE_COUNT should not be accessed directly while the elapsed time clock is running; use FetchTSCount() to read its value. The default timeslice period is approximately one millisecond (ms) (it’s 1.024 ms, to be exact). The timeslice/multitasker period can be changed from just over 1 ms to just over 15 ms by calling MsecTimeslicePeriod() as described in the C Glossary. In fact, because the RTI system works in multiples of 1.024 ms as configured, the number of milliseconds that you specify will by multiplied by 1.024.
Two convenient functions save you the trouble of converting from the TIMESLICE_COUNT timer contents to real time measured in milliseconds. CountToMsec() converts a 32-bit elapsed number of timeslice counts into the corresponding 32-bit number of milliseconds. The function ReadElapsedSeconds() returns a long result representing the number of elapsed seconds since InitElapsedTime() was called; InitElapsedTime() sets TIMESLICE_COUNT equal to zero. CountToMsec() and ReadElapsedSeconds() handle the 1.024 conversion factor, and are aware of the exact time value of each count as set by MsecTimeslicePeriod(); they report times with an accuracy better than 0.02%. At the default 1-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.
We can write a simple function that converts timeslice counts into elapsed seconds as well as the number of milliseconds since the last integral second. For example, let’s examine some code from the "Timekeep Demo":
1: // *********** Using the Timeslice Clock to Calculate Elapsed Time ****** 2: 3: // 1.024 ms is the default timeslice period as set by the RTI (Real-Time Interrupt). 4: // This value can be changed using the MsecTimeslicePeriod() function. 5: // Note that the underlying RTI (Real-Time Interrupt) runs at multiples 6: // of 1.024 ms, not 1.000 ms. If uncompensated, this results in 7: // a 2.4% error in absolute time. 8: // Fortunately, the ReadElapsedSeconds() and CountToMsec() 9: // functions compensates for this timing factor, 10: // reducing the absolute timing error to only 0.02%, 11: // equivalent to reporting 17 seconds too few per day, 8.64 minutes per month. 12: // Note that the timeslice clock rolls over from 0xFFFFFFFF to 0 in just under 13: // 50 days at the default 1.024 ms resolution. 14: 15: #include <mosaic\allqed.h> 16: 17: #define MS_PER_SECOND 1000 18: 19: long start_time; // saves starting count of TIMESLICE_COUNT 20: 21: _Q void MarkTime( void ) 22: { 23: start_time = FetchTSCount(); 24: } 25: 26: _Q void PrintElapsedTime( void ) 27: // prints elapsed time since MarkTime() was executed until this function 28: // was called; prints as seconds and ms; 29: // resolution is equal to the period of the timeslice clock (approx. 1 ms default) 30: { 31: unsigned long absolute_count = FetchTSCount(); // read timeslice counts only once 32: unsigned long absolute_ms = CountToMsec( absolute_count ); 33: unsigned long elapsed_ms = CountToMsec( absolute_count - start_time ); 34: 35: iprintf( "\n\nAbsolute time is: %ld seconds and %ld ms.\n", absolute_ms / MS_PER_SECOND, absolute_ms % MS_PER_SECOND ); 36: iprintf( "Time since mark is: %ld seconds and %ld ms.\n\n", elapsed_ms / MS_PER_SECOND, elapsed_ms % MS_PER_SECOND ); 37: } 38: 39: int main( void ) 40: { 41: // Disable libc output buffering, which causes unexpected behavior on embedded systems. 42: // If I/O buffering would benefit your application, see the Queued Serial demo. 43: setvbuf( stdout, NULL, _IONBF, 0 ); 44: 45: InitElapsedTime(); // init TIMESLICE_COUNT to zero 46: start_time = FetchTSCount(); // init timeslice marker (also to zero) 47: StartTimeslicer(); // start timeslicer; also calls ENABLE_INTERRUPTS 48: PrintElapsedTime(); // print calculated elapsed time 49: 50: return 0; 51: }
The MarkTime()
function simply stores the current elapsed timeslice counts in the start_time
variable. After StartTimeslicer() has been executed (by main
in this example program), the timeslicer continually increments its counter. When you later call PrintElapsedTime()
, start_time
is subtracted from the latest elapsed timeslice counts retrieved with FetchTSCount(), and CountToMsec() converts the resulting elapsed count into the elapsed number of milliseconds. This is converted into the elapsed seconds by dividing by 1000, and the remainder is the number of milliseconds since the last integral elapsed second.
Note that if the elapsed timeslice counts roll over from the maximum to minimum value, the subtraction in PrintElapsedTime()
will still correctly calculate the elapsed time since start_time
. Only when the elapsed timeslice counts completely rolls around to again be equal to start_time
will a calculation of elapsed time no longer be possible.
To try it out, make a new Timekeep Demo
project, click Build→ Build, and use the terminal to send timekeep.dlf
to the PDQ Board. At your terminal, type:
main↓
to start the timeslicer and initialize the program. Now at any time you can mark a starting time by typing at the terminal:
MarkTime( )↓
and you can print the elapsed seconds and ms since the last mark by typing:
PrintElapsedTime( )↓
which will produce a response of the form:
Time since mark is: 3 seconds and 45 ms.
See also → Loading Your Program into Memory