/* RCS revision control
$Header: c:/cvsroot/bart/rt_task.c,v 1.4 2004/03/09 22:09:10 mjames Exp $
*/
/* RCS Log file
$Log: rt_task.c,v $
Revision 1.4 2004/03/09 22:09:10 mjames
Hardware flow control implemented
Revision 1.3 2004/03/09 00:45:20 mjames
Corrected mistakes, made task numbers visible
Revision 1.2 2004/03/06 12:17:48 mjames
Moved headers around, made it clearer that there are no configurable
parts to the OS unless it is rebuilt
Revision 1.1.1.1 2004/03/03 22:54:33 mjames
no message
*/
/*******************
* INCLUDE FILES *
********************/
#include "mcs51reg.h"
#include "rt_int.h"
#include "rt_ext.h"
/*******************
* LOCAL MACROS *
********************/
/** compute timer rolling over for T0 */
#define ROLLOVER_RATE (PRESCALE2/8192)
/* this is 112.5 Hz */
#define INT_TASK_BIT(tasknum) (1<<(tasknum))
/* this code is for the main uart of the 8051 */
/* timer registers used by T0 IRQ */
/* 11.25Hz counter bank */
volatile unsigned char T0ctr;
/* this 'register' is exported */
volatile unsigned char T100ms;
volatile unsigned char T10sec;
/* first idata */
STACK_TYPE stack0[STACK0SIZE];
STACK_TYPE stack1[STACK1SIZE];
#if TASKS >=3
STACK_TYPE stack2[STACK2SIZE];
#endif
#if TASKS >=4
STACK_TYPE stack3[STACK3SIZE];
#endif
volatile STACK_PTR_TYPE stack_save[TASKS];
volatile SIGNAL_TYPE task_signals[TASKS];
volatile SIGNAL_TYPE task_masks[TASKS];
volatile TIMER_TYPE task_timer[TASKS];
volatile TASKID_TYPE ready ;
volatile TASKID_TYPE run ;
/* almost round robin with a modulo-4 task counter */
#define RUN_TOG 0x10
#define RUN_MASK 0x3F
/* 3 tasks including idle */
#if TASKS == 3
const STACK_PTR_TYPE start_stack[]={
stack0,
stack1,
stack2};
#define VALID_TASK_NUM
#endif
#if TASKS == 4
const STACK_PTR_TYPE start_stack[]={
stack0,
stack1,
stack2,
stack3};
#define VALID_TASK_NUM
#endif
#if !defined VALID_TASK_NUM
#error Invalid TASK_NUM declaration
#endif
/** This task table describes a round robin with priority :
there are 4 phases to the round robin
regardless of the number of tasks , so one task wins more often
with 3 tasks tasks 1 and 2 are high priority,
task 3 runs when 1 and 2 are not running. If there are less than 4 tasks then
this table will still apply . */
const char priotab[] =
{
/** round 00 : leftmost task bit wins*/
/*T3210 */
0,0,1,1, /**< 0000,0001,0010,0011 */
2,2,2,2, /**< 0100,0101,0110,0111 */
3,3,3,3, /**< 1000,1001,1010,1011 */
3,3,3,3, /**< 1100,1101,1110,1111 */
/** round 01 : second task bit wins */
/*T3210 */
0,0,1,0, /**< 0000,0001,0010,0011 */
2,0,1,1, /**< 0100,0101,0110,0111 */
3,0,1,1, /**< 1000,1001,1010,1011 */
2,2,2,2, /**< 1100,1101,1110,1111 */
/** round 10 : third task bit wins */
/*T3210 */
0,0,1,1, /**< 0000,0001,0010,0011 */
2,2,2,0, /**< 0100,0101,0110,0111 */
3,3,3,0, /**< 1000,1001,1010,1011 */
3,0,1,1, /**< 1100,1101,1110,1111 */
/** round 11 : fourth or cycle again */
/*T3210 */
0,0,1,0, /**< 0000,0001,0010,0011 */
2,0,1,2, /**< 0100,0101,0110,0111 */
3,0,1,3, /**< 1000,1001,1010,1011 */
2,3,3,0, /**< 1100,1101,1110,1111 */
};
/** Sets up the scheduler state variables to a clean initial state, but indicate a
task identified as MAIN_TASK_ID (the task under which main() will run)
is actually running and ready to run. Use with EA off !!*/
void rt_tasks_init (void)
{
char i;
run = MAIN_TASK_ID;
ready = TASK_BIT(MAIN_TASK_ID);
for(i=0;i<TASKS;i++)
{
stack_save[i] = 0;
task_signals[i]=0;
task_masks[i] =0;
task_timer[i] =0;
}
}
/** the 100ms counter is one every 11.25 timeouts of the T0 counter
* so we will count 12 timeouts when T100ms MOD 4 = 0 and 11 otherwise
* this means that 10 counts of T100ms are almost exactly 1 second */
/** Timer interrupt running about once every 10ms */
bit T0_interrupt_ea;
void T0Interrupt(void) interrupt T0_INTVEC using T0_INTERRUPT_BANK
{
/* TF0 = 0; This Interrupt is cleared automatically by jumping down this vector */
T0ctr--;
if(T0ctr==0)
{
int i;
T100ms++;
if(T100ms & 3)
T0ctr= 11;/* 01,10,11 */
else
T0ctr= 12;/* 00 */
/* and now decrement all of the 100 ms timers */
for (i=0;i<TASKS;i++)
{
if (task_timer[i] != 0)
{
task_timer[i]--;
if (task_timer[i] == 0)
{
task_signals[i] |= TIMER_SIG;
if(task_signals[i] & task_masks[i])
{
ready |= TASK_BIT(i); /* Mapping here */
}
}
}
}
/* wrap T100ms around an integer number of seconds
The wrap value should be divisible by 4 because of
the fractional N counting */
if(T100ms>=MAXT100ms)
{
T10sec++;
T100ms=0;
}
}
/* use this interrupt to repetitively sample the flow
control lines for the primary UART */
#if defined HARD_FLOW
/* if nCTS goes low and we are not watching it then retrigger TI */
if(!SIO1_CTS && !SIO1_TxBusy && SIO1_TxCount)
{
TI=1;
}
#endif
#if !defined NO_IRQ_SCHEDULE
_asm
; use assembler form of atomic operation for keeping ea
jbc ea,0001$
clr _T0_interrupt_ea
sjmp 0002$
0001$:
setb _T0_interrupt_ea
0002$:
; These are in bank 0
push 7
push 6
push 5
push 4
push 3
push 2
push 1
push 0
;
;
; Determine which task was running
; and save the task SP
;
mov a,_run
add a,#_stack_save
mov r0,a
mov a,sp
mov @r0,a
; *********** SCHEDULE CODE HERE
;
; look at the task ready flags
mov a,_ready
add a,#RUN_TOG
anl a,#RUN_MASK
mov _ready,a
;
; get new running task ID
;
mov dptr,#_priotab
movc a,@a+dptr
;
mov _run,a
;
add a,#_stack_save
mov r0,a
mov a,@r0
mov sp,a
;
;
; Bank 0 registers in use here
;
pop 0
pop 1
pop 2
pop 3
pop 4
pop 5
pop 6
pop 7
;
; this is done in the ISR
; pop psw
; pop dph
; pop dpl
; pop b
; pop acc
;
;
;
jnb _T0_interrupt_ea,0010$
setb ea
0010$:
_endasm;
#endif
}
/********************************************************************************
** Name : reschedule
This function determines which task to run, by using the ready variable as
an index into the scheduler table. Each call to reschedule increments the upper
2 bits of the ready variable, so as to use a different part of the table on each call.
This allows a round-robin with priority scheduling to take place.
\break
To set a bit in the ready variable, the task's signal (task_signal[task]) variable is
masked with the task's mask (task_masks[task]) variable. If the result is non-zero then the
bit corresponding to the tasks ID number is set in the ready variable.
\break
Task timers reaching zero from a non-zero value will assert the TIMER_SIG signal
in the task's signal variable. If the mask in task's masks has TIMER_SIG set at that time
then the task will have its bit set in the ready variable.
\break
A task will never run again if it has a zero task_masks variable and a zero task_timer
variable, and its ready bit is zero. In all other cases the task will run again in the
future
*********************************************************************************/
bit reschedule_ea;
void reschedule(void)
{
_asm
; use assembler form of atomic operation for keeping ea
jbc ea,0001$
clr _reschedule_ea
sjmp 0002$
0001$:
setb _reschedule_ea
0002$:
; this is done in any ISR
push acc
push b
push dpl
push dph
push psw
;
; These are in bank 0
;
push 7
push 6
push 5
push 4
push 3
push 2
push 1
push 0
;
;
;
; Determine which task was running
; and save the task SP
;
mov a,_run
add a,#_stack_save
mov r0,a
mov a,sp
mov @r0,a
; *********** SCHEDULE CODE HERE
;
; look at the task ready flags
mov a,_ready
add a,#RUN_TOG
anl a,#RUN_MASK
mov _ready,a
;
; get new running task ID
;
mov dptr,#_priotab
movc a,@a+dptr
;
mov _run,a
;
add a,#_stack_save
mov r0,a
mov a,@r0
mov sp,a
;
;
; Bank 0 registers in use here
;
pop 0
pop 1
pop 2
pop 3
pop 4
pop 5
pop 6
pop 7
;
; this is done in the ISR
pop psw
pop dph
pop dpl
pop b
pop acc
;
jnb _reschedule_ea,0010$
setb ea
0010$:
;
_endasm;
}
/********************************************************************************
** Name : start_task
* the stack for the task is built and then its run flag is set
* This uses the real task code for the task
*********************************************************************************/
start_rc start_task(task_p f,char tasknum)
{
short fp;
STACK_PTR_TYPE csp;
USE_CRITICAL;
if (tasknum >= TASKS)
return FAILED;
BEGIN_CRITICAL;
csp = start_stack[tasknum];
stack_save[tasknum] = (STACK_PTR_TYPE)csp+16 ;
/* catch task return by pointing it at fallback termination function */
fp = (int)&end_run_task;
*csp++ = (char)(fp & 0xff);
*csp++ = (char)(fp >> 8) ;
/* and put a pointer to the return address which is function to call */
fp = (int)f;
*csp++ = (char)(fp &0xff);
*csp++ = (char)(fp >> 8) ;
/* then zero out the PSW image otherwise there will be a strange crash
as code tries to run in a random register bank .... */
*csp++ = 0; /* csp[4] = acc could pass argument here */
*csp++ = 0; /* csp[5] = b */
*csp++ = 0; /* csp[6] = dpl */
*csp++ = 0; /* csp[7] = dph */
*csp++ = 0; /* psw VITAL */
*csp++ = 0; /* csp [9] = r7 */
*csp++ = 0; /* csp [10] = r6 */
*csp++ = 0; /* csp [11] = r5 */
*csp++ = 0; /* csp [12] = r4 */
*csp++ = 0; /* csp [13] = r3 */
*csp++ = 0; /* csp [14] = r2 */
*csp++ = 0; /* csp [15] = r1 */
*csp = 0; /* csp [16] = r0 */
/* Set its task state as ready to run */
ready |= TASK_BIT(tasknum);
END_CRITICAL;
return STARTED;
}
/********************************************************************************
** Name : end_run_task
*********************************************************************************/
void end_run_task(void)
{
USE_CRITICAL;
/* whatever is running, stop it */
BEGIN_CRITICAL;
ready &= ~TASK_BIT(run);
task_masks[run] = 0; /* Stop any further signals making task ready to run */
task_timer[run] = 0; /* kill off pending task timeout */
END_CRITICAL;
reschedule();
/* Should never get here as this task cannot be rescheduled */
while(1);
}
/** schedules a sleep with early return on any signals. Need to include TIMER_SIG in
signal list */
char wait_timed(char signal,char ticks)
{
USE_CRITICAL;
BEGIN_CRITICAL;
ready &= ~TASK_BIT(run); /* this task is going to sleep */
task_timer[run] = ticks;
task_masks[run] = signal; /* accept these signals as a validto wait on */
END_CRITICAL;
reschedule();
return task_signals[run];
}
/** A sleep of 0 means a reschedule call */
void sleep(char ticks)
{
USE_CRITICAL;
if (ticks)
{
BEGIN_CRITICAL;
ready &= ~TASK_BIT(run); /* this task is going to sleep */
task_timer[run] = ticks;
task_masks[run] = TIMER_SIG; /* accept this signal as a valid one to wait on */
task_signals[run] &=~TIMER_SIG;
/* this is actually a virtual idle task here in the loop, allowing all tasks to sleep */
do {
END_CRITICAL;
reschedule();
BEGIN_CRITICAL;
}
while((task_signals[run] & TIMER_SIG) == 0);
task_signals[run] &= ~(TIMER_SIG);
task_masks[run] &= ~TIMER_SIG; /* accept this signal as a valid one to wait on */
END_CRITICAL;
}
else
{
reschedule();
}
}
/** the current running task acknowledges the signal bits in the argument */
void clear_signal(char pattern)
{
USE_CRITICAL;
BEGIN_CRITICAL;
task_signals[run] &= ~(pattern);
END_CRITICAL;
}
/** Sends a signal to the task referred to. Does not actually cause rescheduling
until either a T0 interrupt, or a reschedule(), sleep() or wait_timed()
call made by this task */
void signal(char task,char pattern)
{
USE_CRITICAL;
BEGIN_CRITICAL;
/* this used in ISR context !! */
INT_SIGNAL(task,pattern);
END_CRITICAL;
}
/*******************************************************************/
/* System initialisation call */
void rt_system_init(void)
{
AUXR = M0 |XRS1 | XRS0 ;
CKCON = WdX2 | PcaX2 | SiX2 | T2X2 | T0X2 | X2; /* T1X2 bit is '0' to double UART speed */
TMOD = 0x20; /* timer1 mode2 timer0 mode0 */
EA = 0;
}