Cos.mpas, a Cooperative Operating System.


-- with thanks to Jo Vandale for his "JoCos" that I could use as an example of a COS --

Introduction

An operating system is capable of executing tasks that have been submitted to it. It can play the role of a scheduler and takes the tedious "execute this after xxx seconds" out of the developers hands.
A cooperative operating system is the simplest one imaginable. The tasks running under this type of COS are not interrupted by COS to give execution time to another task (as with the preemptive Real Time OS), no, tasks always run to completion before COS gives execution to another task.
So, simple enough. This means however that each single task must finish execution in an acceptable time. If a task is too time consuming then it has to be split up in several subtasks, each with an acceptable execution time. Hence the term cooperative: the tasks have to cooperate to make things work well.

Details

This unit contains a COS with the following characteristics: (*) ISR: Interrupt Service Routine
(**) The COS itself checks, in each iteration (the "Cos_Step"), the content of the 3 queues and executes them if appropriate.

The Interface

The COS unit's interface looks like this:
unit Cos;

uses __Lib_String;
     
// interface
     
type Cos_TaskType = procedure(Par: Word);
     Cos_TaskPointer = ^Cos_TaskType;
     
procedure Cos_Init;
// Initialises Cos

procedure Cos_Start;
// make Cos scheduler running (the routine "Cos_Step" is to be called regurarly by the using code)

procedure Cos_Step;
// make the Cos sheduler make one step (if Started)

procedure Cos_Run;
// make the Cos scheduler running in an endless loop (the routine "Cos_Step" is NOT to be called regurarly by the using code)

procedure Cos_Stop;
// make Cos scheduler stop

function Cos_SubmitTask(Task_: Cos_TaskPointer; Parameter: word): boolean;
// Task_ will be executed asap
// Can NOT be called from within an Interrupt Service Routine (ISR)
// Returns TRUE if the task is added to the queue, else false (queue full)

function Cos_SubmitTask_From_ISR(Task_: Cos_TaskPointer; Parameter: word): boolean;
// Task_ will be executed asap
// Can ONLY be called from within an Interrupt Service Routine (ISR)
// All ISR's calling this routine must have the same interrupt priority
// Returns TRUE if the task is added to the queue, else false (queue full)

function Cos_SubmitTask_Timed(Task_: Cos_TaskPointer; Parameter, Tme: word; Repetitive: boolean): boolean;
// Task_ will be executed after "Tme" time (millisecs)
// If "Repetitive" is true the task will resubmit itself after it has run
// Can NOT be called from within an Interrupt Service Routine (ISR)
// Returns TRUE if the task is added to the queue, else false (queue full)

procedure Cos_TimerInterrupt;
// the handling if the Cos timer interrupt, to be called from a 1ms timer ISR

implementation

Usage


The 1 millisecs timer

  // Define the COS interrupt bit
var CosTimerInterruptbit  : sbit at TMR2IE_bit; // define the actual COS timer interrupt bit (e.g. here Timer 2 is used)
  // The timer interrupt service routine that calls the one in the COS (again timer2 is used here):
procedure Interrupt;
begin
  if TestBit(PIR1,TMR2IF) = 1 then     // 1 ms TMR2
  begin
    PIR1.TMR2IF := 0;
    Cos_TimerInterrupt;                // <--- has to be called every millisecond
  end;
end;

Initialisation

  // Start up the 1 ms timer (again timer2 is used here):
  T2CON   := %01011001; // prescaler 4, postcaler 12
  PR2     := 207;       // preload value, interrupt every millisec <-- MPU clock speed dependant!!!
  TMR2    := 0;         // timer 2 register reset
  T2CON.TMR2ON := 1;    // start TMR2
  INTCON  := %11000000; // enable interrupts (PEIE and GIE)
  TMR2IE_bit := 1;      // Enable the timer2 interrupts
The init code of the timer has to be executed before Cos can be "started" or "runned".
  // Initialise Cos
  Cos_Init; // Empties the queues. To be called before cos is "started" of "runned".

Starting Cos

2 methods can be used:

Submission of non timed tasks

  Cos_SubmitTask(@Task1, 54321);  // Task1 will be executed asap (according priorities), with parameter value = 54321

Submission of non timed tasks from within an ISR

  Cos_SubmitTask_From_ISR(@Task1, 100 + Cnt);  // Task1 will be executes asap (according priorities), with parameter value = 100 + Cnt (some variable)
The user can submit any task from an ISR using this submission routine, also the ones that are submitted from a non ISR or another ISR.
Important to know: All tasks are executed outside the ISR's, only the submission of them happens inside the ISR.

Submission of timed tasks

  Cos_SubmitTask_Timed(@Task1, 12345,  1000, false);  // Task1 will be executed after 1000 ms, with parameter value = 12345
                                                      //  and the execution is not repeating
  Cos_SubmitTask_Timed(@Task2, 23456,  250, true);    // Task2 will be executed after 250 ms, with parameter value = 23456
                                                      //  and the execution is repeated every 250 ms.

Note: Tasks Addresses
As you can see, submitting a task requires the address of that task. The task should have been declared before the submission takes place (in reality or "forward"), otherwise the task address is not known.
If a tasks wants to re-submit itself for execution, the "forward" technique doe not work. In this case a DWord variable, filled with the tasks address, is to be used.

The tasks

The tasks are the routines where the actual work is done in the project. A task should have always the same "signature" as follows:
procedure Task1(Par: word);
where "Task1" was the one submitted to the COS in previous examples (you can choose any name you want, it does not have to start with "Task").
The "Par" parameter the task receives is the value that was used when the task was submitted (see above examples).

Tasks may submit other tasks to the COS (if fact, they have to do so -- if no tasks are submitted from ISR's -- because the the COS would run out of tasks to execute if they don't):
procedure Task1(Par: word);
begin
  IntToStr(Par, Str);
  Str := 'Task1 ' + Str + #13 +#10;
  Uart1_Write_Text(Str);
  Cos_SubmitTask(@Task2, 20);             // <---- here, task 2 will be executed asap
  Cos_SubmitTask_Timed(@Task3, 30, 1000); // <---- here, task 3 will be executed after 1 second
end;
Eventually, somewhere in all tasks there will be one that will submit Task1 (otherwise the COS would run out of work), and the project keeps running...

Indirect calls: warn the linker

Since the tasks to be executed by COS are called indirectly from it (via a pointer to them), the linker should be warned of that. This is accomplished by calling the mP pseudo routine "SetFuncCall", in the initialisation part of the unit where-in the tasks are defined, e.g.:
 // for the linker (tasks are called indirectly, the linker must be warned):
  SetFuncCall(Task1);
  SetFuncCall(Task2);
  ...

-------------------------------------------