Tics Realtime -----


 
Home Services Products Tutorials Contact Us
- - - - -

A Cyclical Executive for Small Systems

A simple cyclical executive is adequate for a variety small non-critical real-time applications. This article presents the concept and the code for the executive.

Concept

This technique is known by various names including executive, cyclical executive, round robin executive, round robin kernel, super loop, and probably others. The concept is shown below.
void main(void)
{
	for (;;) {
		task0();
		task1();
		task2();
		/* and so on... */
	}
}
Each "task" (C function) is called in turn. For this technique to work, the following rules must be followed:
  1. Tasks may not employ busy waiting.
  2. Tasks must do their work quickly and return to the main loop so that other tasks can run.
  3. If necessary, tasks must save their place by using a state variable.
The executive provides no services to the tasks. For example, there are no priorities, no timers, no inter-task communication mechanisms, no queuing, no task switching, and no suspension mechanisms provided.

Timers

The timer isr for the executive is shown below.
void timerIsr(void)
{
	Tics++;
}
The timer isr has one function and that is to bump a global counter (unsigned integer). Tasks use this counter to implement timers. For this article we will assume that the hardware timer has been programmed to generate an interrupt every 10 milliseconds (a typical value for many real-time systems).
void readAnalogDataTask(void)
{
	static int state = 0, timer;

	switch (state) {
	case 0:
		timer = Tics + 10;
		state = 1;
		break;

	case 1:
		if (Tics == timer) {
			readAndProcessAnalogData();
			timer = Tics + 10;
		}
		break;
	}
}
The task reads an analog channel about every 100 milliseconds (every 10 tics). It implements its own timer by adding to "Tics" the number of timer tics to wait, and then checks "Tics" against the timeout value each time the task is called. Note also that the "if" test checks for ==, not >= because if the value of "timer" added to "Tics" forces a wrap around, then ">=" will succeed on the first try which is incorrect. This also means that the round trip time through the loop should not exceed 10 milliseconds (the hardware timer interval) otherwise, by the time the timeout test is performed, the value of "Tics" may have gone past the timeout value. Note also the use of the state variable. This is necessary because the first time the function is called, a timer must be started.

Inter-task Communication

Inter-task communication is done through global data. A signal is simply a global flag. A message with data is a global data structure and a flag that signals the receiving task that data is available. It is up to the receiving task to check for any data that it may receive and to reset the flag after data is read.
void displayDataTask(void)
{
	if (DisplayData.flag == 1) {
		displayOnScreen(&DisplayData);
		DisplayData.flag = 0;
	}
}
Note that the receiver of the data must set the flag back to 0, otherwise, the flag will stay set.

Priorities

There are no priorities. Tasks are called in their turn in round robin fashion. High priority items are handled inside isr's. For example, if a line must be toggled at a very accurate 10 millisecond rate, then the appropriate code is added to the timer isr.

Queueing

There are no message queues. If your application needs queuing, consider using a kernel rather than implementing queues here. The idea with the cyclical executive is to keep things simple.

Task Switching

The concept of task switching is not part of the cyclical executive. If a task is at a point where it must save its place and return to the executive loop, then it must save its place in a static or global state variable. The next time the task is called, the task can resume where it left off by switching on the state variable.

Instance Data

Consider a system that has 5 analog channels, each with a different sample rate, gain, IO address, etc. Each channel must be sampled every "n" milliseconds, where "n" is different for each channel. One solution is to have a separate task for each channel and call each from the executive loop.
	channel0Task();
	channel1Task();
	channel2Task();
	channel3Task();
	channel4Task();
Another approach is to code one generic task that would acquire its sample rate, IO address, etc. from a C structure passed to it as an argument.
	genericChannelTask(&channel0);
	genericChannelTask(&channel1);
	genericChannelTask(&channel2);
	genericChannelTask(&channel3);
	genericChannelTask(&channel4);
The benefit gained is that only one function needs to be written.

Advantages

The cyclical executive has the following advantages.
  1. Easy to use and understand.
  2. With the exception of interrupts, the system is completely deterministic. Tasks are always called in the same order. The worst case loop latency can be computed.
  3. Minimal code and data requirements.
  4. Because tasks cannot be preempted, time dependent errors cannot occur.
  5. Only one stack is required. Preemptive systems require a stack for each task.

Disadvantages

The cyclical executive has the following disadvantages.
  1. More responsibility is placed on the programmer.
  2. Coding is more difficult than with other models.
  3. No priorities.
  4. No queues.
  5. A form of busy waiting is employed, since tasks are called regardless of whether they have work to do or not.

Comments

For small systems, especially single chip micro-controllers, the cyclical executive is a good choice. Larger applications that require priorities and queuing may require a standard real-time kernel.


We welcome comments. Let us know what subjects you would like written up. Send comments to Mike@TicsRealtime.com

Copyright © 2000, Tics Realtime