Sunteți pe pagina 1din 6

PIC micro and C

Search

Search this site (www.microchipC.com).

Expand All | Collapse All PIC and Hi-Tech C Archive

Home Q. How do I handle many things at once on a PIC?

+ PIC Code in C (55) A. Go to www.pumpkininc.com

+ PIC C FAQ (68) Look at their 'Salvo' real time operating system . The quote
below says it all.
+ PIC bootloaders (5)
" ... I use this system for near on 2 years and I can tell only
- PIC Articles (3) the best things for it (even I can say that my life like PIC
Hi-Tech C Speed Optimization programmer has 3 periods - assembler, HiTech C and RTOS SALVO - the difference between assembler and C is the same like
Hi-Tech C Multitasking between no RTOS and RTOS)." - Luben Christov
Solderless RS232 Crossover
Neither the author of this web page nor Luben Christov have any affiliation with PumpkinInc - but we both share a liking for it!
+ PIC tips (7)
See also http://www.bknd.com/cc5x/multitasking.shtml for a discussion on multitasking and state machines.
+ Reviews (7)
I wrote the rest of the document below before I came across Salvo. The technique works nicely enough, but Salvo is has far
+ UVa tutorial (2) superior power.
Advertise
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:

1. There is multiple tasks.


2. 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.

1. For this example, set up the simulator for a PIC16F876.


2. Set up timer 1 to overflow and trigger and generate an interrupt every 500us.
3. The interrupt service routine will get executed every 500us.
4. 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.
5. Thus, we can set the frequency and priority of how often each task gets performed.
6. 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 couldn’t have the PWM stopping for 100ms.
1. 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.
2. 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.
7. In summary:

http://www.microchipc.com/Hi-Tech_C_multitask/[12/2/2010 8:23:44 PM]


PIC micro and C

1. We can execute any number of tasks at regular intervals.


2. We can choose the frequency of executing each task by altering TASKX_COUNTER_MAX.
3. Choose the order of execution by having the most important tasks first in the interrupt routine. This alters the
priority.
4. Have slow, infrequent background tasks executing in main(), triggered by the interrupt setting ‘taskX_go=true’ at
regular intervals.
8. 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 the complete C source code which illustrates this time-sliced method, follow the link to the "Complete
Design for Giant 8-foot LCD Counter" (see the picture showing the completed product). The sample project
includes C source code , hardware, and VB controller.

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*/

#define TICKS_BETWEEN_INTERRUPTS 2000


#define INTERRUPT_OVERHEAD 19
#define TMR1RESET (0xFFFF-(TICKS_BETWEEN_INTERRUPTS-INTERRUPT_OVERHEAD))
#define TMR1RESET_HIGH TMR1RESET >> 8
#define TMR1RESET_LOW TMR1RESET & 0xFF
TMR1ON=0;
TMR1H=TMR1RESET_HIGH;
TMR1L=TMR1RESET_LOW;
TMR1ON=1;
TMR1IF=0;
TMR1IE=1;
PEIE=1;
GIE=1;
}

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

http://www.microchipc.com/Hi-Tech_C_multitask/[12/2/2010 8:23:44 PM]


PIC micro and C

}
}
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

On Wed, Jun 10, 2009 at 12:56 AM, Mauricio Gendelman wrote:

> Shane,
>
> Thank you for putting available your website to the whole PIC community, it
> is really well done and I do appreciate your efforts.

You are welcome.

> I was checking in detail your description of the multitasking system but
> still have a few doubts. Sorry for my ignorance and having to ask them.

I'll do my best to answer them.

> The concrete questions refer to some paragraphs.


>
> 1) .....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.

> The whole paragraph cause me problems. It is more or less evident for me
> that the whole processes inside the ISR must be executed between ticks.

Yes, this is correct.

There are two types of tasks: short tasks, and long tasks.

A "short task" is so fast, that it can always be executed entirely within the ISR, i.e. it takes less than 500us to execute. An
example is "increment a timer, and set a flag if the timer goes over a certain value".

A "long task" is so slow that it cannot be executed entirely within the ISR, e.g. it takes 50ms to execute. An example of a long
task would be "calculated the cosine of an an angle, send it out the serial port".

Here is the key: put the short tasks inside the ISR, and move the long tasks into main() so they will run in the background.

The short tasks get executed immediately, entirely within the ISR.

The long tasks are executed in main(), in the background. The long tasks can be triggered to run by the short tasks in the ISR.

> Q1. Are you referring (with this paragraph) to the case in which one single

http://www.microchipc.com/Hi-Tech_C_multitask/[12/2/2010 8:23:44 PM]


PIC micro and C

> task inside the ISR takes too long and a new tick appears before finishing
> all tasks?

Yes.

> Q2. Why you mention one task per tick? (when there is no guarrantee about a
> task duration...).

I mention one task per tick, because there is only enough time to do extremely quick tasks within the ISR. Longer tasks should
be moved to execute in the background, in main(). See above.

> Q3. What do you exactly mean with "time slot" here?

The "time slot" is the amount of time allocated to the ISR.

For example, if you set up the ISR to execute 10 times per second, then there would be 100ms time between each interrupt. If
you set up the ISR to execute 100 times per second, then there would be 10ms between each interrupt. The time between
each interrupt can be lengthened or shortened, depending on the time that it takes everything within the ISR to execute.

> Q4. Why a task can be executed again before the previous one had a chance to
> finish?

If this is happening, then you have to reduce the number of ticks per second, in the ISR. Another way of thinking about it is
that you have to lengthen the amount of time that the ISR has available to execute its tasks.

> Q5. Where exactly would have to be added the line you mention to check if
> TMR1IF has aleady been set before the interrupt exits?...Aren´t you
> referring with this to the first line of the ISR?

This should be checked at the very end of the ISR.

Here is a worked example to illustrate this:

Imagine we set up a timer to trigger an interrupt every 10ms.

At time=10ms, the timer trips, and the program flow jumps into the ISR.

Under normal circumstances, the ISR would take 2ms to execute, and exit at time=12ms.

However, if the ISR took 25ms to execute, then nothing would ever work. The reason is that the interrupt happens every 10ms,
and if the ISR takes 25ms, then the program would never get to execute anything running in the background, in main().

How about this situation: 99% of interrupts take 2ms, but the odd interrupt takes 25ms. How would we detect this? Well, at
the end of the interrupt, just before we exit and program control returns to main(), we check the flag TMR1IF. If this is set,
then its clear that the interrupt must have taken more than 10ms, because the timer tripped the interrupt flag before the
interrupt code could exit. In this situation, we would set a variable to let us know. Then, we could react by increasing the
amount of time for each interupt to say 50ms.

> 2) In ISR routine


>
> Q1. If in Q5 of 1) you are not referring to the first instruction of the
> ISR: Then, what is this TMR1IF check for? (the one you included in the ISR).

The TMR1IF check is a piece of code to detect if we need to allocate more time to handle each interrupt. See the previous
answer.

> 3) Overhead
>
> Q1. I understand it tries to compensate some TMR1 counts before tmr
> overflow, with some time spent in processing other instructions. Which are
> they? Could you please expand your explanation a little bit?

Its just an effort to make the interrupts happen on a regular basis.

Here is an example.

1. Imagine that we set up timer 2 to trigger every 10ms, and set the interrupt flag.
2. When this interrupt flag was set, program flow would jump into the interrupt service routine (ISR).
3. Once we are in the ISR, we reset timer 2 to trigger in another 10ms. This means that another interrupt will occur in 10ms.

However, if we actually measured the time between interrupts, we would find that they are occuring about once every 11ms.
Where did this extra 1ms come from? Well, it took 1ms to actually get into the interrupt, to the point where we could set the
timer to tick in another 10ms. Another way of saying this is that it took 1ms betweensteps 2 and 3, which would increase the
total amount of time to 11ms between interrupts.

So, all we do is set timer 2 to trigger in another 9ms (see step 3). This adjusts the error so that the ISR will trip precisely
every 10ms.

> 4) In your answer to Mr. Clive Wilson: 03/07/2003 you write: Work it out so
> that even if all the interrupts happen at once, there's still enough time

http://www.microchipc.com/Hi-Tech_C_multitask/[12/2/2010 8:23:44 PM]


PIC micro and C

> for everything.


>
> Q1. What do you mean with "all the interrupts" happening at once?

I mean that in the event that every single line of code in the interrupt is executed, there should still be enough time to finish
the interrupt before the next one happens. Another way of saying this is that the longest code path should still be quicker than
the time allocated to the interrupt.

As an example, imagine that you set up the interrupts to happen every 10ms. This means that program flow would jump into
the interrupt routine every 10ms.

Imagine that normally, the interrupt took 2ms. Now imagine that we added an "if" statement in this interrupt which only
triggered occasionally - but when it triggered, it added an additional 15ms of processing time to the interrupt. This wouldn't
work, as the entire interrupt would now take 17ms, which is 7ms more than the maximum available time of 10ms. To fix this,
we would have to move this code within the "if" statement into main(), and trigger this code running in main() with a flag
which is set in the interrupt.

> Thank you in advance for your reply.


>
> Mauricio
>
> Argentina

-----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

http://www.microchipc.com/Hi-Tech_C_multitask/[12/2/2010 8:23:44 PM]


PIC micro and C

is copyrighted by Shane Tolmie 1999-2009. Click to advertise on this website - $29.90 for a banner ad which will reach 55,000 user sessions per month. One months
free trial!

http://www.microchipc.com/Hi-Tech_C_multitask/[12/2/2010 8:23:44 PM]

S-ar putea să vă placă și