Look
at their 'Salvo' real time operating system. The quote below says it all.
" ... I use
this system for near on 2 years and I can tell only the best things for it (even
I can say that my life like PIC programmer has 3 periods - assembler, HiTech
C and RTOS SALVO - the difference between assembler and C is the same like between
no RTOS and RTOS)."
- Luben Christov
Neither the author of
this web page nor Luben Christov have any affiliation with PumpkinInc - but
we both share a liking for it!
I wrote the rest of the
document below before I came across Salvo. The technique works nicely enough,
but Salvo is has far superior power.
A. Alternatively,
use a State Machine or Time sliced multi-tasking system
A state machine is recommended for most solutions where there is non time-critical
tasks to perform. However, the time sliced multi-tasking is useful when:
There is multiple tasks.
Some of the tasks are
low-priority, and can execute in the background, being interrupted by higher
priority tasks. For example, low-frequency PWM with 300ms of mathematical
calculations happening every 1 second.
Using a State
Machine
The topic of this tutorial is not to teach state machines - they are very
common - look them up on the web.
Using
a Time Sliced Multi-tasking system
The multitasking system below was used to implement, on an 8-pin 12CE674 and
28-pin 16F876, software polling of a pin to get serial characters, perform
maths at regular intervals on the serial character buffer, write the data
to a 24LC256 I2C EEPROM, and generate 50Hz variable duty cycle PWM. This
was all performed simultaneously, so the PIC could accept a continuous serial
stream. It works beautifully - the code is stable.
Heres the explanation of how this was achieved. This technique that allows
one to harness the power of a PIC micro. It is definitely worth learning,
and probably the single most important technique in this FAQ.
For this example,
set up the simulator for a PIC16F876.
Set up timer 1 to
overflow and trigger and generate an interrupt every 500us.
The interrupt service
routine will get executed every 500us.
For each individual
task, have three things:
i.A counter that increments every time the interrupt happens every
500us
ii.A task counter maximum, called TASKX_COUNTER_MAX. When the counter
gets to this maximum, it executes the task. Thus, one can set the frequency
that each task gets performed by changing this maximum. One task may have
a maximum set to 1, which means it executes every 500us, and another set
to 2000 which means it only gets executed every 1000ms.
iii.A task enable boolean flag, TASKX_ENABLE.
Thus, we can set the
frequency and priority of how often each task gets performed.
We can extend this
further what happens if we have to regularly do a task that takes
ages, say 100ms of mathematical calculations, but it only happens every
second? It we execute it right in the interrupt, it will stop all the high
frequency, important tasks from executing every 500us. We couldnt
have the PWM stopping for 100ms.
So, all we do is
trigger the slow, complex task from the interrupt, by setting taskX_go=true.
Then, the interrupt immediately finishes ready for the next task.
In the meantime,
a loop in main() is polling taskX_go. If it is true, it sets it to
false ready for next time, then executes the task at its leisure. While
this is going on, the high priority tasks still happen at a normal rate.
In summary:
We can execute
any number of tasks at regular intervals.
We can choose the
frequency of executing each task by altering TASKX_COUNTER_MAX.
Choose the order
of execution by having the most important tasks first in the interrupt
routine. This alters the priority.
Have slow, infrequent
background tasks executing in main(), triggered by the interrupt setting
taskX_go=true at regular intervals.
There is one more
tip to avoid problems. The time inbetween the 500us ticks must
be enough to execute all the tasks, in the worst case. Either that, or
only one task is executed per 500us interrupt tick, and the
others have to wait until a free slot comes along. To tell whether the
timing is too tight and tasks are executed again before the previous one
had a chance to finish, add a line to check whether the timer interrupt
flag has aleady been set before the interrupt exits.
Download
Complete Example Project
For a sample project illustrating this time-sliced method, follow
this link. View Plans.
Sample Code Outline The sample code below briefly outlines the code described above:
//(c)Shane Tolmie, http://www.microchipc.com/,
distribute freely for non commercial use on the condition that you include this
web link somewhere in your document.
//*****
//multitasking system handle multiple tasks with one microprocessor
//task counters used to tell when a task is ready to be executed
//all these counters are incremented every time a 500us interrupt happens
//every task has its own counter that is updated every time a 500us interrupt
happens
unsigned int task0_counter=0;
unsigned int task1_counter=0;
unsigned int task2_counter=0;
//this tells
when a task is going to happen again
//for example, when task0_counter==TASK0_COUNTER_MAX, set task0_counter=0
and do task
#define TASK0_COUNTER_MAX 1 //high frequency task every 500us,
maybe PWM
#define TASK1_COUNTER_MAX 2000 //low frequency task every 1000ms
#define TASK2_COUNTER_MAX 5000 //low frequency and low priority task,
every 2500ms
//Note: every
variable referenced in both interrupt and main() must be declared volatile.
You have been warned!
//this enables/disables a task
volatile unsigned char task0_enable=TRUE;
volatile unsigned char task1_enable=TRUE;
volatile unsigned char task2_enable=TRUE;
//this allows tasks triggered by interrupt to run in the background in main()
volatile unsigned char task2_go=FALSE;
void setup_multitasking(void)
{
//set up tmr1 to interrupt every 500us
TMR1CS=0;
T1CKPS0=0;
T1CKPS1=0;
/*We want
to wait 2000 clock cycles, or 500us @ 16MHz (instructions are 1/4 speed of
clock). Timer 1 interrupts when it gets to 0xFFFF or 65535. Therefore, we
set timer 1 to 65535 minus 2000 = 63535, then wait 2000 ticks until rollover
at 65535. To test, use simulator to find that its exactly correct*/
void interrupt
isr(void)
{
//one tick every 500us at 16Mhz
if (TMR1IF)
{
//set up timer 1 again to interrupt 500us in future
TMR1IF=0;
TMR1ON=0;
TMR1H=TMR1RESET_HIGH;
TMR1L=TMR1RESET_LOW;
TMR1ON=1;
task0_counter++;
if (task0_counter>=TASK0_COUNTER_MAX) //high frequency task
every 1 tick
{
task0_counter=0;
if (task0_enable==TRUE)
{
//do high frequency important task 0, for example PWM
}
}
task1_counter++;
if (task1_counter>=TASK1_COUNTER_MAX) //low priority task - every
2000 ticks
{
task1_counter=0;
if (task1_enable==TRUE)
{
//do low frequency yet important task 1
}
}
/*this task
takes a long time, 100ms for example, lots of maths. Is extremely low priority,
but has to be done at regular intervals, so all this does is trigger it.
In main(), it will, at leisure, poll task2_go and then execute
it in the background.*/
task2_counter++;
if (task2_counter>=TASK2_COUNTER_MAX) //every 250ms
{
task2_counter=0;
if (task2_enable==TRUE)
{
//every 250ms take 100ms to do maths, do this in main() so
the we can get back to doing the high frequency tasks.
task2_go=TRUE;
}
}
} //if (TMR1IF)
} //interrupt routine
main()
{
setup_multitasking();
while(1)
{
if (task2_go==TRUE)
{
task2_go=FALSE;
//take our time, doing heaps of complex maths at our leisure in
the background
}
}
}
User
Feedback
-----Original
Message-----
From: Clive Wilson
Sent: 07 March 2003 09:01
To: support@microchipc.com
Subject: Time-sliced multitasking system for Hi-Tech C
Shane,
Many thanks for giving
us the MicrochipC web site - it is a truly invaluable resource.
I have a question for
your regarding the time-sliced multitasking system for Hi-Tech C which you
discuss, at http://www.microchipc.com/Hi-Tech_C_multitask.htm. I understand
how the time slicing works, along with the variable execution rates (determined
by the TASKX_COUNTER_MAX value for each task).
I see that task0 (or whatever
the first 'task' in the ISR is called) gets executed at precisely the time
expected. However, what happens if task0 has some conditional code which causes
the execution path through the task0 code to vary from one interrupt to the
next? Then, the next task in the ISR call, e.g. task1, would get executed
at varying times after the point when the ISR was triggered. The same goes
for any task due to run in the current interrupt - the timeliness of each
task is dependent upon all previous
tasks in this interrupt instance having non-varying execution times.
Have I got the wrong end
of the stick or is my theory correct? How do you cope with this when doing
serial comms? Presumably the variable latency limits the speed at which serial
comms can be performed?
Many thanks again for
the great web site. I look forward to hearing your opinion on my query.
Kind regards,
Clive Wilson
-----Original
Message-----
From: Shane Tolmie
Sent: 07 March 2003 10:47
To: Clive Wilson
Subject: RE: Time-sliced multitasking system for Hi-Tech C
Hi Clive,
Yes, you're right. This
is known as interrupt jitter. However, with serial, there is a 2 byte internal
buffer, so this can cope with a lot of variance in the exact time that the
time-sliced interrupts happen.
The trick is to do all
the processing in the background, triggered from the interrupts, and have
minimal processing done in the actual interrupt. Work it out so that even
if all the interrupts happen at once, there's still enough time for everything.
Regards,
Shane Tolmie
www.microchipc.com
We welcome any suggesions
or comments! Send them to Shane Tolmie on support@microchipc.com.
This site is a completely separate site to www.microchip.com, and is maintained
independently of Microchip Ltd., manufacturers of the PIC micro.
All code on this site is free for non-commercial use, unless stated otherwise.
Commercial use normally free, however, it is prohibited without contacting support@microchipc.com for permission.
All content on this site created by Shane Tolmie is copyrighted by Shane Tolmie
1999-2007.