Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

Here are some PIC multi-tasking systems

Status
Not open for further replies.
The use of the ADC is a good example of what is missing here.

Reading an ADC has two parts. You start the conversion then wait for the conversion to finish. The read_adc() calls does both, not good in a multitasking system. The amount of time is small here but we could just as easily be waiting for a button push.

In terms of this system we only want to bump sync[current] when the conversion is finished. As it exists the bumping in done by the THREAD_END macro. The thread should not advance past the test state/chunk unless the condition is satisfied.

Code:
#define AD0 0
#define SW0 1

THREAD_START(AD0) // adc related process
adc_start_conversion(0);
THREAD_BREAK
[B]if (!adc_finished(0)) noBump=FALSE;[/B]
THREAD_BREAK
peocess_adc_value():
THREAD_END(AD0)

THREAD_START(SW0)  // a process using a switch
// whatever setup is required
THREAD_BREAK
[B]if (not_switch_active) noBump=FALSE;[/B]
THREAD_BREAK
// post press peocessing
THREAD_END(SW0)

THREAD_START nees to clear the noBump flag
THREAD_END should not bump the seq[] if noBump is set
 
Well a manual system like the macros provides some user control over exactly what part of code is performed one each iteration of the loop. That potentially has value.

The main thing that puts me off doing a true preemptive kernel is that it needs quite a bit of overhead, as you need to do the normal int context saving, then the previous program counter, then save the entire stack for that thread (which can be large in PIC24 or PIC32). Then obviously restore all those for the next thread before exiting the int.

There's a heap of overhead for no obvious gain, personally I would prefer to insert a few macros through the code, in places where I want them, and have the option to have some code with no macros (for speed). Or be able to make small changes to it as the code is changed.

With a true preemptive system you are stuck with the same overhead for everything. Sure it's another level up in invisibility, but how far up do you need to go? Approaching that level you could just use an object oriented langage, even an interpreted one.

Actually, since they have a Stack Pointer, Stack Frame pointer and a stack limit register, you do not need to save the stack at all, just those registers. The stack is one big area of memory that you can pre-allocate (for lack of a better word) and just restore the pointers for each thread.

Other than saving/restoring registers, and updating thread priority lists, there's not a lot of overhead on those PICs. The biggest advantage to a fully pre-emptive system is the user does not have to compile the tasking system into their code. It's not quite as efficient, as with combining all code, but gives the flexibility to run any relocatable code transparently.

Since you can only do relocatable code, it would be perfect for on-demand loadable modules.
 
The use of the ADC is a good example of what is missing here.

Reading an ADC has two parts. You start the conversion then wait for the conversion to finish. The read_adc() calls does both, not good in a multitasking system. The amount of time is small here but we could just as easily be waiting for a button push.

In terms of this system we only want to bump sync[current] when the conversion is finished. As it exists the bumping in done by the THREAD_END macro. The thread should not advance past the test state/chunk unless the condition is satisfied.

Code:
THREAD_START(AD0) // adc related process
adc_start_conversion(0);
THREAD_BREAK
[B]if (!adc_finished(0)) noBump=FALSE;[/B]
THREAD_BREAK
peocess_adc_value():
THREAD_END(AD0)

THREAD_START nees to clear the noBump flag
THREAD_END should not bump the seq[] if noBump is set

Hmm. Good catch! I didn't imagine that would be needed, but I can see how it could be very useful. I think your suggested implementation is close to perfect, they only change I would suggest is maybe to set the noBump variable within the chunk itself;

Code:
THREAD_START(AD0) // adc related process
adc_start_conversion(0);  // this chunk sets noBump, if it needs encores
THREAD_BREAK
process_adc_value():
THREAD_END(AD0)

I think that is more elegant as the need to re-iterate a chunk is really a property of that chunk itself, not a property of that thread which will have many chunks with differing needs.

If anything what it really means is any thread chunk/state has the ability to call for an "encore" if it chooses, so each chunk can call for as much timeslice as it likes before the next chunk gets a go. It is a big step towards the co-operative model though with the chunks and/or threads having more control of timeslice and away from a preemptive (or "externally controlled") model where the chunks have little or no control.

I lean towards that externally controlled model because to me the best multitasking system is to have multiple fast loops that can run at the same time. But I'm beginning to see you lean more towards an "object oriented" model where threads can just halt and wait for a button press etc. (Please correct me if I'm wrong there!)

I don't like that object oriented model much. When I was reading through the XMOS examples they have 8 threads, and their object oriented model means that a UART thread will be completely halted waiting for a byte, and a keyboard thread will be halted waiting for a button press. That can make sense when the micro is REALLY good at passing that processor time on to the other (working) threads. But in a PIC system that is simulated multi-tasking and really NOT good at passing that time onto other threads it seems more sensible to steer away from a pure object oriented approach and use the multi-threads more like a few simultaneous small PICs.

Where you might halt a thread waiting for a button press I would do something like run that thread every 250mS, and just poll the button as ONE of the thread chunks as there are probably other tasks that would be good to run every 250mS. Definitely a more vertical model with a few threads running a number of tasks each, rather than a pure horizontal model with a lot of threads doing one task each.

(edit) Actually if a chunk wanted an encore it could just directly decrement the seq[] variable, so the macros could remain standard and not get larger and incur additional overhead.
 
Last edited:
Actually, since they have a Stack Pointer, Stack Frame pointer and a stack limit register, you do not need to save the stack at all, just those registers. The stack is one big area of memory that you can pre-allocate (for lack of a better word) and just restore the pointers for each thread.

Other than saving/restoring registers, and updating thread priority lists, there's not a lot of overhead on those PICs. The biggest advantage to a fully pre-emptive system is the user does not have to compile the tasking system into their code.
...

Nice! I didn't know the stack operated like that and could be handled that easily. That definitely makes it sound a bit more attractive.

But I'm not sure you are right about the user not having to compile the multitasking system into their code. They still need to choose the number of threads, and write the code as separate threads, and be aware of the threads.

Then all the thread stacks will manually have to be loaded at the start of execution, with the starting program counters for each thread. So that will require some effort by the user to org each thread code at specific start location in ROM, or have some constants for the starting org locations for each thread. I guess if the code is setup as proper "relocatable" then maybe it can auto calc all the thread start program counters.

It would be a cool project but I'm not sure it will be THAT much more user friendly than the other systems. Seeing that the user still has to define threads, the only extra benefit is they don't need to insert thread breaks!
 
@RB:
The idea of having the blocking thread stat decrement the seq var is good.


Code:
THREAD_START(AD0) // adc related process
adc_start_conversion(0);
THREAD_BREAK
[B]if (!adc_finished(0)) seq[AD0]--;[/B]
THREAD_BREAK
peocess_adc_value():
THREAD_END(AD0)
I lean towards that externally controlled model because to me the best
multitasking system is to have multiple fast loops that can run at the
same time. But I'm beginning to see you lean more towards an "object
oriented" model where threads can just halt and wait for a button press
etc. (Please correct me if I'm wrong there!)
I was under the impression that we were talking about cooperative
multi tasking. By "Externally controlled" I assume you mean that there
is some code outside the tasks that control execution. This can be the
case in both cooperative and preemptive systems. Can we agree that this
not task code is called a kernel?

Structured programing and OOP are philosophies, we can code
in their styles using any procedural language.
Structured programming was developed in response to spaghetti
code. OOP is an effort to make large systems more manageable
and has its detractors.
So if you are saying that I lean in the direction of good
coding practices you are correct. But I did not suggest
that we use objects.

At the most fundamental level the difference between what weW
are doing is in the placement of the state guard statement.
The line of code that determines if any given state will execute.

Your method embeds a guard statement for each state within the
thread. NAOS uses a single guard statement per thread in the kernel
(main loop). The state within the thread is selected via a
switch/case, a n way branch.

NAOS is a thread per task system. It allows one to set priority and
blocking on a per task basis. The use of this
in conjunction with ISR routines that unblock when resources become
available is a powerful mechanism.

As I said earlier a well designed cooperative system can out preform
a preemptive one.
 
I lean towards that externally controlled model because to me the best multitasking system is to have multiple fast loops that can run at the same time. But I'm beginning to see you lean more towards an "object oriented" model where threads can just halt and wait for a button press etc. (Please correct me if I'm wrong there!)

I don't like that object oriented model much. When I was reading through the XMOS examples they have 8 threads, and their object oriented model means that a UART thread will be completely halted waiting for a byte, and a keyboard thread will be halted waiting for a button press. That can make sense when the micro is REALLY good at passing that processor time on to the other (working) threads. But in a PIC system that is simulated multi-tasking and really NOT good at passing that time onto other threads it seems more sensible to steer away from a pure object oriented approach and use the multi-threads more like a few simultaneous small PICs.

I think more of what you are talking about here is signal handling. Having a thread block on a slow call until the state of whatever you are monitoring changes. This is the one place where an "ouside" influence would be good. For many of the events, you could tie the signal right to an interrupt, which can "wake-up" the thread in question. Although then you need someway to remove it from the active processing list and put it back on later.

You could also do a Sleep() model where a thread can basically schedule itself. This could be used for polling routines without much of a change in the co-op code. Again, would need an actual scheduler though, so another outside influence.

EDIT: I just realized that 3v0 already said much of this. :p
 
Last edited:
If anyone is interested in an example of a non intrusive multitasking system look at the one used by forth interpreters. Forth is a stack based language consisting of words and operators used with reverse Polish notation. As I recall it forth includes a thread switch at the end of each word. The down side is that it does a lot of unrequired thread switching.

The NAOS system operates much as smanches suggested regarding sleeping. If one embraces the nature of this system you can do some interesting and powerful things using the sleep timers.

The sleep timers in NAOS are located in array kTimer[]. An ISR decrements each kTimer element 4000 times per second. The use of the kTimer array allows us to use a single timer as it were many. Tasks may use one or more kTimers. kTimer values are polled.

In the tutorial taskBLINK uses a single kTimer to blink an led.

In the tutorial taskBEEP uses two kTimers to produce an alternating tone and rest without the use of loops. Using the timers alow us to set the duration of the sound independent of the frequency.

Task BEEP uses two kTimers BEEP and DUR (duration). At the start of the task DUR is set to 750
which is a bit under 1/2 second. kTimer BEEP is set to 10 in the state that switches the speaker on and off resulting in a (4000/(10+10)) or 1000Hz tone. The state that turns the speaker off checks to see if kTime DUR is zero. If not it goes back to the on state, If zero it sets kTimer BEEP to 750 which causes the task to sleep with the speaker off, musical rest. It also set the next state to init which causes it to start over.

Code:
JuneBug – BoostC - C18 Tutorials : Cooperative Multitasking (1st Draft, 4rd Edit)
void taskBeep(void)
{
  static byte seq=0;
  switch (seq)
  {
     state 0: // state INIT
    kTimer[DUR]=750;
    seq=1;
    break;
    state 1: // state ON
    SPEAKER_BIT_HI;
    kTimer[BEEP]=2; // 1000 Hz
    seq=2;
    break;
    state 2: // state OFF
    SPEAKER_BIT_LO;;
    if(kTimer[DUR]) // another pulse
   {
      kTimer[BEEP]=2;
      seq=1;
    }
    else // generate a rest
   {
      kTimer[BEEP]=750;
      seq=0;
    }
  }
}
 
Hi guys,

I read the 3v0'tutorial, it was good and clear.
I have used the switch case technique a long time ago without task scheduler at work. It is very simple to make progress the program and each piece of code is very quick.
I have a remark about your source code NAOS. You decrement into interruption the timer of all task with a loop "for", I think it was not a good choice. Because an interruption should be the quickest than possible, if you have a lot tasks to manage, you will stay a lot of time in interruption, and your program won't be able to run correctly. And if you need to manage SPI, UART and CAN protocols and so forth with interruptions, you microcontroller would be full and not real time.

So, I would like to share my tiny kernel in C programming language to manage tasks on a microcontroller. You can create a task with a period, suspend, resume and change a period of task at any time. This kernel can delete all task to create another sequencer as you want.
A TickGet function is supply by the kernel to manage all timers as you want.
You have to create just one interrupt function and replace the function Timer() to get your tiny-kernel for your own application.
To resume this kernel is based on a circular linked list, to switch task on task. It is willingly written in a general way to help people to customise for theirs owns applications. There is no priority between task like a round-robin task scheduling. And I wrote this source code in respect of the MISRA guidelines (automotive norm)



I hope to help people to manage tasks on microcontroller.
 
Last edited by a moderator:
Status
Not open for further replies.

Latest threads

New Articles From Microcontroller Tips

Back
Top