Simple C18 Cooperative Multitasking

A simple cooperative multitasking system by Harold Hallikainen.

See "PIC 18C Microcontoller - Preemptive real-time multitasking kernel" /techref/microchip/18c/multitask-rtk-ba.htm for a more complex multutasker for the PIC18C. See "multitasking" /techref/method/multitask.htm for multitasking in general.


// main.c
// SIMPLE COOPERATIVE MULTITASKING SYSTEM
// Harold Hallikainen
// harold@hallikainen.org
// December 2006
// This is a test of a SIMPLE cooperative multitasker. For each task, the system keeps a copy of the
// stack pointer and a copy of the stack.
// Initialization -
// Each task starts with a call of InitTask(n) where n is the task number. The task follows the InitTask
// call with any other required initialization, then enters a loop. InitTask saves the return address into
// the task in the SavedStack array. It then pops the return address off the stack so we return to main()
// instead of the function that called InitTask(). This allows us to continue setting up other tasks in main().
// Running-
// After each task has been initialized, main calls StartMultiTask(). This loads the stack with the values
// stored for task 0 by InitTask(). 
// Task Switching-
// Whenever a task is waiting for something (typically input, perhaps waiting for an output device to be ready),
// the loop contains a call of NextTask(). NextTask saves the current stack, loads the stack for the next task,
// then returns into that function where IT had called NextTask. Note that this return may be several 
// function call levels down. It need not be in the original task loop, but, instead, in a function called
// by that loop. This is makes multitasking much simpler than use of state machines for each task and function
// within that task.
// Variables and Function Parameters
// If a particular function contains a NextTask call, local variables and parameters to the function MUST
// be static. This keeps them from being located on the software stack (as automatic variables would be).
// Automatic variables and function parameters are lost during a task switch. In addition, the software stack
// may undeflow or overflow if you do a task switch while automatic variables or parameters exist. However,
// functions that do NOT include a task switch may use automatic parameters and functions since these are
// destroyed before the NextTask is called.
 
#include <p18f8627.h>
#include "types.h"
 
#define MaxTask 3
 
// Function Prototypes
void InitTask(static uint8_t TaskNum); 	// Save return address for this function to continue,
				//   then throw it out, dropping to main()
void StartMultiTask(void); 		// loads stack with initial data for task 0, then returns to task 0.
void NextTask(void); 			// save current stack and load stack for next task
void task0(void);
void task1(void);
void task2(void);
void task3(void);
uint8_t Task3SubFunction(static uint8_t n); // testing a subfunction
uint8_t Task3subSub(uint8_t n);
 
#pragma udata MultiTask 		// generally requires more than 256 bytes or ram, so put in bigger section
union{
    uint24_t u24b; 			// access as 24 bit unsigned int
    uint8_t u8b[sizeof(uint24_t)]; 	// or array of bytes
}SavedStack[MaxTask+1][31]; 		// Each stack entry is 3 bytes and there are 31 of them in each stack.
// MaxTask is highest task number. Since they start at zero, dimension to MaxTask+1 
 
static uint8_t SavedStackPointer[MaxTask+1]; // stack pointer saved for a particular task
#pragma udata 			// let linker allocate rest of stuff
 
void main(void){
    task0(); 
    task1();
    task2();
    task3(); 			// init each task
    StartMultiTask(); 		// go run multitask
}
 
void InitTask(static uint8_t TaskNum){
// Call from within a task. Puts the Saves the return address in SavedStack[TaskNum][1] so a later call
// of NextTask will continue from this address. Uses static parameter to avoid use of parameter stack which
// would be messed up when we do a return to main instead of calling function.
    INTCONbits.GIEH=0; 		// disable high priority interrupts
    INTCONbits.GIEL=0; 		// and low priority interrupts while messing with stack
    SavedStackPointer[TaskNum]=STKPTR; 	// save the current stack pointer
    SavedStack[TaskNum][STKPTR & 0b11111].u24b=TOS; // save current tos, which is where this function should start
    _asm POP _endasm; 		// remove return address so we drop back to main()
    INTCONbits.GIEL=1; 		// interrupts ok now
    INTCONbits.GIEH=1;
} 
 
void StartMultiTask(void){
// loads stack with initial data for task 0, then returns to task 0.
    INTCONbits.GIEH=0; 		// disable high priority interrupts
    INTCONbits.GIEL=0; 		// and low priority interrupts while messing with stack
    STKPTR=0; 			// start with clear stack pointer
    while(STKPTR<=SavedStackPointer[0]){
        //TOS=SavedStack[0][STKPTR & 0b11111]; // restore stack value - use below code to avoid movff to tos
        WREG=SavedStack[0][STKPTR & 0b11111].u8b[0];
        TOSL=WREG;
        WREG=SavedStack[0][STKPTR & 0b11111].u8b[1];
        TOSH=WREG;
        WREG=SavedStack[0][STKPTR & 0b11111].u8b[2];
        TOSU=WREG;
        _asm PUSH _endasm; 		// and increment stack pointer
    }
    _asm POP _endasm; 		// get rid of extra stack pointer increment at end 
    INTCONbits.GIEL=1; 		// interrupts ok now
    INTCONbits.GIEH=1;
} 
 
 
void NextTask(void){
// save away the current stack, load the one for the next task, then exit
static uint8_t TaskNum=0; 
    INTCONbits.GIEH=0; 		// disable high priority interrupts
    INTCONbits.GIEL=0; 		// and low priority interrupts while messing with stack
    SavedStackPointer[TaskNum]=STKPTR; 	// save the current stack pointer
    while(STKPTR & 0b11111){ 		// while stuff still on the stack
        SavedStack[TaskNum][STKPTR & 0b11111].u24b=TOS; // save current tos
       _asm POP _endasm; 		// decrement the stack pointer
    }
    TaskNum++; 			// on to next task
    if(TaskNum>MaxTask)
    TaskNum=0; 			// roll over if past last
    while(STKPTR<=SavedStackPointer[TaskNum]){
        //TOS=SavedStack[TaskNum][STKPTR & 0b11111]; // restore stack value - use below code to avoid movff to tos
        WREG=SavedStack[TaskNum][STKPTR & 0b11111].u8b[0];
        TOSL=WREG;
        WREG=SavedStack[TaskNum][STKPTR & 0b11111].u8b[1];
        TOSH=WREG;
        WREG=SavedStack[TaskNum][STKPTR & 0b11111].u8b[2];
        TOSU=WREG;
        _asm PUSH _endasm; 		// and increment stack pointer
     }
    _asm POP _endasm; 		// get rid of extra stack pointer increment at end 
    INTCONbits.GIEL=1; 		// interrupts ok now
    INTCONbits.GIEH=1;
    } 
 
 
 
void task0(void){
static uint8_t n=0;
    InitTask(0); 			// set up return address for here
    while(1){ 			// task loop
        ClrWdt();
        n++;
        NextTask();
    } // end while
} // end function 
 
void task1(void){
static uint8_t n=0;
    InitTask(1); 			// set up return address for here
    while(1){ 			// task loop
        ClrWdt();
        n++;
        NextTask();
    } // end while
}
 
void task2(void){
static uint8_t n=0;
    InitTask(2); 			// set up return address for here
    while(1){ 			// task loop
        ClrWdt();
        n++;
        NextTask();
    } // end while
}
 
void task3(void){
static uint8_t n=0;
     InitTask(3); 			// set up return address for here
     while(1){ 			// task loop
          ClrWdt();
          n++;
          n=Task3SubFunction(n);
          ClrWdt();
     } // end while
}
 
uint8_t Task3SubFunction(static uint8_t n){
// testing a subfunction. Make parameters and local variables static on any functions that include NextTask.
     ClrWdt();
     n=3*n;
     NextTask();
     ClrWdt();
     n=Task3subSub(n);
     return(n);
}
 
uint8_t Task3subSub(uint8_t n){
// since this function does not call NextTask, we don't need to use statics
     uint8_t y=3;
     uint8_t z;
     z=n*y;
     return(z);
}