Using a Task Based Dispatcher (cont)

Ron Kreymborg

Example One

The first example shows the two tasks plugged into the task scheduler. In the following discussions I will call the computing task Task1 and the state change task Task2. While the timers run at different frequencies, I have arranged the periods so the tasks move in and out of synchronization. You can cut and past directly from here along with the matching makefile. Or you can download the complete set of files where the tasks are defined in modules.

#include <stdlib.h>
#include <interrupt.h>
#include <signal.h>
#include "Taskr.h"

int main(void);
void InitComputeTask(void);
void InitStateChangeTask(void);
void Task_Computation(void);
void Task_ManageEvent(void);

#define  CLK0_DIVIDER      0x04     // PCK0 / 64
#define  CLK0_COUNT        256-8    // 15.625 mSec tick
#define  CLK2_DIVIDER      0x05
#define  CLK2_COUNT        256-101

volatile BYTE  ComputationTicks;
BYTE           LED_State;

int main(void)
{
   // PORTA
   outp(0x00, PORTA);               // all low
   outp(0xff, DDRA);                // all output
   // PORTB
   outp(0xff, PORTB);               // all low
   outp(0xff, DDRB);                // all output
   
   sbi(ACSR, ACD);                  // disable comparator
   
   InitTaskR();                     // init TaskR
   InitComputeTask();               // init the "compute" task
   InitStateChangeTask();           // init the state change task
   sei();                           // hello world
   
   while (1)
      Dispatch();                   // loop forever
      
   return 0;
}

//---------------------------------------------------------------------
//
void InitComputeTask(void)
{
   outp(CLK2_DIVIDER, TCCR2);
   outp(CLK2_COUNT, TCNT2);
   sbi(TIMSK, TOIE2);               // enable Timer2 interrupts
}

//---------------------------------------------------------------------
void InitStateChangeTask(void)
{
   sbi(ASSR, AS0);                  // clock from external crystal
   outp(CLK0_DIVIDER, TCCR0);       // 32768 / 64 = 512 ticks/sec
   outp(CLK0_COUNT, TCNT0);
   sbi(TIMSK, TOIE0);               // enable Timer0 interrupts
}

//---------------------------------------------------------------------
void Task_Computation(void)
{
   int i, j;
   
   sbi(PORTA, 3);                   // for scoping
   cbi(PORTB, 7);                   // the event start
   for (i=0; i<5000; i++)
      j = 1;
   sbi(PORTB, 7);                   // the event stop
   cbi(PORTA, 3);                   // for scoping
}

//---------------------------------------------------------------------
void Task_ManageEvent(void)
{
   if (LED_State)
   {
      cbi(PORTA, 5);                // for scoping
      sbi(PORTB, 6);                // the state change
   }
   else
   {
      sbi(PORTA, 5);                // for scoping
      cbi(PORTB, 6);                // the state change
   }
   
   LED_State = ~LED_State;          // flip state
}

//---------------------------------------------------------------------
// The simulated event that causes the state change.
//
SIGNAL(SIG_OVERFLOW0)
{
   outp(CLK0_COUNT, TCNT0);         // reset counter
   sbi(PORTA, 1);                   // for scoping
   QueTask(Task_ManageEvent);       // schedule the task
   cbi(PORTA, 1);                   // for scoping
}

//---------------------------------------------------------------------
//  The simulated event that initiates the compute task.
//
SIGNAL(SIG_OVERFLOW2)
{
   outp(CLK2_COUNT, TCNT2);         // reset the timer
   if (++ComputationTicks %gt 5)
   {
      ComputationTicks = 0;
      QueTask(Task_Computation);    // schedule the task
   }
}

You can see the tasks running by connecting a scope to the port A pins 3 and 5. Trigger on pin 5 and set a timebase of 5mSec/div. Notice how the Task2 on time (pin 5) varies as its execution period coincides with that of Task1. Even though the interrupt for Task2 may occur during Task1's on time and the task put on the ready queue at that time, it must wait until the other task completes before it can run. The queued Task2 runs immediately Task1 exits, but the timer controlling this task has been ticking all this time, and so when next it interrupts and changes the state, the preceding on or off time for Task2 will consequently be something less than the required 50% duty cycle.

While this will not be a problem in most cases, it would be if the 50% duty cycle was a specification of the output. Where a task must run immediately, simply execute the task from the interrupt. In our case Task2 is very fast, so the only change is to the interrupt function:

//---------------------------------------------------------------------
// The simulated event that causes the state change.
//
SIGNAL(SIG_OVERFLOW0)
{
   outp(CLK0_COUNT, TCNT0);         // reset counter
   sbi(PORTA, 1);                   // for scoping
   Task_ManageEvent();              // execute the task directly
   cbi(PORTA, 1);                   // for scoping
}

On to Example 2