• 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

Mr RB

Well-Known Member
Thread starter #21
...
Your system is not preemptive. To do that you have to stop the task without it cooperation, and save the program counter and registers. Your tasks are cooperative which is OK.
...
I got that. I also clearly covered that in my post (#3) that it's not worth trying to do TRUE pre-emptive multitasking on a 16F/18F PIC.

Preemptive or cooperative is in the spirit of the difference. Wiki says "In simple terms: Preemptive multitasking involves the use of an interrupt mechanism which suspends the currently executing process and invokes a scheduler to determine which process should execute next." meaning that the control mechanism is external to the task itself where as in cooperative systems the tasks have a much greater ability to determine the multitasking themselves.

In the middle there is a whole world of grey, in the same way that "parallel processing" and "multitasking" exist in the grey area that they are not TRUE parallel at all but executed sequentially. However they can be called "parallel" because they function ENOUGH like parallel that for the term to be acceptable.

So you could say something is "multitasking" even though it is not TRUE mutitasking, and you could say it performs more in a "preemptive" manner (more controlled externally) than in a "cooperative" manner (more controlled by the tasks themselves) without requiring true preemptivity any more than it requires true parallel processing.

If you don't like my definition of something in a grey area being "more preemptive" or "more cooperative" in the nature of it's operation that is fine you are entitled to use the terms you prefer.

...
The task examples you show are rather on the simple side. Some tasks are but many are not. For the longer tasks we need to use state machines. Exectute a bit (state) of each task then yeild.
...
You obviously didn't see the project I did here;
Some BIGPIC6 projects (see Project 3)
which is a larger more involved C project where one of the tasks is performed incrementally so that each bar of a 62-bar bargraph is drawn in "parallel" with the other tasks executing at high speeds. I won't be as rude to say "You should do your research" as you said to me but it does get annoying when people criticise what I have done without actually knowing what I have done.

I stated clearly on my page that I am "not an expert in multithreading" and I also made that quite clear in post #3 in this thread. Nobody has commented on the things I may have done differently to other mutlitasking systems, or provided input on what features may be good to add to multitasking systems or suggestions to imrpove systems for the future. This thread has basically degenerated into "your multitasking demo doesn't gain anything", "multitasking is no good anyway" and "let's pompously argue over exact meanings of multitasking terms".

Feel free to read through everything on my PIC-thread page including my 2 larger baudrate converter projects (where the C source is in ZIP form) and if there is one thing there that can spark someone to a new idea or some tiny thing of value then it is not wasted. If you don't gain anything from it that's fine too, I like to read through other peoples code and systems and many times don't gain anything from it. But a thread full of criticism and waffling semantics is not likely to benefit anybody.
 
Last edited:

3v0

Coop Build Coordinator
Forum Supporter
#22
Roman,

I wrote a substantial reply but the computer ate it.
Your submission could not be processed because the token has expired.

Please push the back button and reload the previous window.
This area has been of interest to me for a long time. If there is any particular aspect you would like to discuss I would enjoy the privilege.
 
#23
I also wouldn't mind being in the discussion, so just keep it going and ignore the critics.

The one thing I've learned in 30+ years of programming. There is no right way; it's all opinion in the end.

But why should that stop us from learning?
 
Last edited:

Mr RB

Well-Known Member
Thread starter #24
Sure. Something constructive to talk about. :)

It looks like there are many easy reliable ways to externally control the tasks and divide the processor time by timeslice/iteration/frequency etc.

I think the big challenge would be to approach the level of functionality offered by true preemptive multitasking where tasks are easily divided by the preemptive interrupt or similar mechanism. So if we have decided not to use a true preemptive system (as an example), then what is the closest thing and/or a close enough thing which is fast, reliable and makes for easy to undertsand code?

My 2 bargraph examples just used the most obvious approach, ie draw one bar each time the task is executed. That's an easy case because the task itself is obviously divisable into small equal tasks.

So what about exploring systems that can execute part of a task each time the task is executed, but trying to be unintrusive in the code itself.

Just off the top of my head;
Code:
// thread 4 (a long calculation)
do some calcs
blah

thread_end_test(0);
blah
blah 

thread_end_test(1);

blah
blah

thread_end_test(2);

// start thread 5 etc
some other code
blah
blah

thread_end_test(0);
blah

thread_end_test(1);

// start thread 6 (etc)
So a function or macro would be very easy to intersperse through the threads and could work with the interrupt engine that does the thread sequencing. So when the interrupt says its time for the next thread the function or macro will jump to the next thread. Also when starting the next thread, that same function or macro would jump to the right place in that thread.

This would obviously require a RAM var to record the current place in each thread and (more difficult) some way for that function to jump forward in code to the correct version of itself.

This is unusual coding but not impossible. If it was a macro it could contain something as simple as gotoX labels which will jump to the correct version of itself based on the currently active thread, and the currently active position in thread.
 

3v0

Coop Build Coordinator
Forum Supporter
#25
I think the big challenge would be to approach the level of functionality offered by true preemptive multitasking where tasks are easily divided by the preemptive interrupt or similar mechanism. So if we have decided not to use a true preemptive system (as an example), then what is the closest thing and/or a close enough thing which is fast, reliable and makes for easy to undertsand code?
I am rather suprised you did not see these things in my code/tutorial. Perhaps I need to revisit it.

Code:
void taskBlink(void)
{
  static byte seq=0;
  switch (seq)
  {
    state 0:
    ledBitOn();
    seq=1;
    break;
    state 1:
    ledBitOff();
    seq=0;
  }
  kTimer[BLINK]=2000;
  return;
}

Void main()
{
  while(1) // main loop
  {
    if (!kTimer[BLINK])  taskBlink();
    // (!kTimer[BOOM])  taskBoom();
    // additional tasks

    while(waitForTimer);  // ensure that next task starts at top of a kTimer perios
    waitForTimer = TRUE;  
  }
}
Code 6:
Each task keeps track of what code segment/state to execute the next time it gains control. This is stored in the local static variable seq.

The line

static byte seq=0;

is a very useful, it preforms much of what Roman is looking for. People who know C will already know this but others may be following the thread.

Initialization: Variable seq is initialized to zero. But because the variable is static this initialization is preformed once in c0 code (init.c) prior to the execution of main(), never within the function in which it is defined!

Persistance: Variable seq that is not stored on the system stack. It is stored in regular RAM and maintains it value even when it is out of scope. This allows each execution of the the current state to set the state to be executed the next time the function runs in this persistent variable.

Scope: Because seq is a local variable the compiler will know it internally as taskBlink_seq (or similar). That is to say the variable seq in taskblink is not the same as a similar variable declared in another function. The beauty is that we only have to use the name seq.

TaskBlink could have been written
Code:
void taskBlink(void)
{
  static byte seq=0;
  switch (seq)
  {
   [B] state 0:[/B]
    ledBitOn();
    seq=1; 
    kTimer[BLINK]=2000;
    return;
    [B]state 1:[/B]
    ledBitOff();
    seq=0;
    kTimer[BLINK]=2000;
    return;
  }
}
I think the more simplistic rewrite better illustrates what is happening.
The correct state is selected via the switch statement.
The code for that state is executed.
Seq is set to point to the state that will be executed next time.
A kTimer value to let the scheduler know when to next run the task

We could easily replace the last 3 things with a macro
#define BLOCK_ON_TIME(state,time,index) seq=state; kTimer[index]=2000; return;

Code:
 void taskBlink(void)
 {
   static byte seq=0;
   switch (seq)
   {
    [B] state 0:[/B]
     ledBitOn();
     BLOCK_ON_TIME(1,2000,BLINK);
     [B]state 1:[/B]
     ledBitOff();
     BLOCK_ON_TIME(0,2000,BLINK);
   }
 }
We would need other macros to block on flags and resources.

I think the tutorial covers how to break tasks into states. In short each time a task needs to delay or wait on some flag/resource the current state will end. If any state is longer then the norm you find a logical way to split it into one or more states.

Another thing I do not like about my example is that it seems to indicate that each state should be a function. That is not the case. It can be any code fragment that compiles and makes sense. What is does show correctly is that each task needs to be a function with a switch statement to select the correct state.
 
Last edited:

Mr RB

Well-Known Member
Thread starter #26
I am rather suprised you did not see these things in my code/tutorial. Perhaps I need to revisit it.

Code:
void taskBlink(void)
{
  static byte seq=0;
  switch (seq)
  {
    state 0:
    ledBitOn();
    seq=1;
    break;
    state 1:
    ledBitOff();
    seq=0;
  }
  kTimer[BLINK]=2000;
  return;
}
...
I saw that in your code 3V0! But its still very "manual" in that you have to have add the switch/state statement and manually control the seq variable. It's also basically the same high level of intrusiveness and same basic functionalty as my original clumsy method;
Code:
if(seq==2)
{
  blah
  blah
  seq++;
}
else if(seq==3)
{
  blah
  blah
  seq++;
}
What I was suggesting in post #24 is a complete level above your example and my example shown here.

A system where the user could take *preexisting code* and just insert a common one liner macro or whatever throughout it. Where all the multitasking is handled invisibly and unintrusively.

Imagine the user already has this code;
Code:
 blah
 blah
 blah
 blah
 blah
 blah
 blah
 blah
And they just need to insert a one-liner throughout the code;
Code:
 macro(2);   // thread 2
 blah
 blah
 macro(2);
 blah
 blah
 macro(2);
 blah
 blah
 macro(3);   // thread 3
 blah
 blah
 macro(3);
So that the macro handles the multitasking. So then it is practically invisible to the user and all happens automatically (as it would be in a "proper" multitasking system).

Imagine the macro does *something* like this procedure;
if thread> macrothread; goto next thread
if thread == macrothread;
if seq == macroseq; macroseq++, then do nothing (it will execute the following code after this macro)
if seq < macroseq; goto next macro
if seq > macroseq; goto next thread

So for every loop, it will basically run one chunk of code between 2 macros in that thread. Same basic functionality, but is automatic.

It would be much easier to implement if the macro defined the thread and seq, similar to this;

Code:
 macro(2,0);
 blah
 blah
 macro(2,1);
 blah
 blah
 macro(2,2);
 blah
 blah
 macro(3,0);
 blah
 blah
 macro(3,1);
However I think the true goal would be to separate the user as completely as possible from the nuts and bolts workings of the actual seq sequencing. They should not have to manually sequence anything within a thread, or know what the seq value is etc.

They should just be able to separate code into threads, and the nuts and bolts takes care of itself. Have another read through what I said in post #24 and then start thinking above and beyond the code you have already done. :)
 
#27
Not to derail your thread, but I find this stuff fascinating since I do a form of threading using perl. Of course I also have the luxury of a multi-proc machine with lots of ram and don't really worry about the limitations of hardware. That mindset of course will eventually get us programmers into trouble as we tend to fix things by throwing more hardware at it. :eek:

Anyway back to your thread, understanding multitasking from a "lowest common denominator" or a PIC in this case I think will help me approach my stuff from another point of view. thanks gents.
 

3v0

Coop Build Coordinator
Forum Supporter
#28
I now understand what you are looking form Roman. It is manual in that I was attempting to show people how it worked. Perhaps we can use macros but would it really make it any easier to use ? Not sure so lets give it a try.

The example you gave

Code:
 macro(2);   // thread 2
 blah
 blah
 macro(2);
 blah
 blah
 macro(2);
 blah
 blah
 macro(3);   // thread 3
 blah
 blah
 macro(3);
I expect to see each thread in its own function. That allows it to have local variables and return value.

Code:
void thread2(void)
{
   macro(2);   // thread 2
   blah
   blah
   macro(2);
   blah
   blah
   macro(2);
   blah
   blah
}

void thread3(???)
{
  macro(3);   // thread 3
  blah
  blah
  macro(3);
  blah
  blah
}
If you are going to object that now we need to call the threads from a scheduler you are right. But it is the only way that makes real sense.

But now that we have each thread in a function we use a seq var to keep track of the state.

#define THREAD_START static unsigned char seq=0; ...
and
#define STATE preProcessorStateCounter++;..

If we had a way to create labels at the preprocessor level and then goto them we could do it just as you suggested.

The switch statement does not have this problem. But we still need a way for the preprocessor to generate the case labels/numbers. I do not know how to use a variable at the preprocessor level of even if it can be done.

If the C preprocessor can not do the job I would be tempted to write a program to adjust the C code.



.... more to come... need to post while I still can ...
The programmer has to be smart enough to know where to put the macro. For performance the most the most important thing is to use them to prevent delays.
 
Last edited:

Mr RB

Well-Known Member
Thread starter #29
...
I expect to see each thread in its own function. That allows it to have local variables and return value.
...
Spoken like a true C purist. :)

Yep i am going to argue that point. If the threads are inline, any macro will have the ability to jump to the next thread. I suggested a very crude macro procedure;
Code:
   if seq > macroseq; goto next thread
As you can see once the seq "chunk" has been performed in thread 2 then the next macro would jump right ahead to the following thread. My macro procedure is buggy as it is written in post #26 but it still outlines the basic things that need to be done.

To me that seems to have a certain neatness where the macros basically ARE the sequencing engine, so it looks attractive because the macro could be the complete solution to turning code into a multithreaded application.

But you are right in that it would be easier to code if each thread is in a function and to end that thread the macro just needs to return, instead of another goto.

(edit)Actually, on re-thinking that it might be better to have the threads in a while(1) loop, then the macro could just do a break. That removes the overhead of functions, and the need to have separate code that calls the functions.(/edit)

...
If we had a way to create labels at the preprocessor level and then goto them we could do it just as you suggested.

The switch statement does not have this problem. But we still need a way for the preprocessor to generate the case labels/numbers. I do not know how to use a variable at the preprocessor level of even if it can be done.

If the C preprocessor can not do the job I would be tempted to write a program to adjust the C code.
...
You know I had the same thought. A separate program to manually insert the mutltithread control in the users source code is tempting, it would be enormously powerful. It could even be programmed to analyse the code and roughly compute the execution time of each line of C code (even just done with simple rules) so it could manually insert the thread sequencing in places where the code is slow.

But it's nasty in a few ways. Especially when the user will need to debug why something strange is happening.

My grasp of C macros is pretty poor. But maybe it would be possible to have a modifiable label in the macro? Like a label++ if you get me. Maybe even something like #IFDEF then ++.

Failing that there's always the standby option of a computed jump. So each seq chunk of code is exactly X bytes, and the macro instead of using "goto label" could just do a computed jump. That's not so far fetched, each thread chunk should be small anyway and anything complex in a thread chunk should probably be in an external function anyway.
 
Last edited:

gramo

New Member
#30
Wow there is some serious heat going on here! What ever happened to simple time splicing?
Code:
Interrupt TMR2_INT()
    Inc(mS)
    If mS = 1000 Then
        mS = 0
    Endif
    If mS = Event_1_Interval Then
        Event_1 = True
    Endif
    If mS = Event_2_Interval Then
        Event_2 = True
    Endif
    If mS = Event_3_Interval Then
        Event_3 = True
    Endif    
End Interrupt

While True
    If Event_1 Then
        Service_Event_1
    EndIf
    If Event_2 Then
        Service_Event_2
    EndIf
    If Event_3 Then
        Service_Event_3
    EndIf
Wend
Sure there's always time splicing edicate, such as no delays in functions etc (make them separate events), and keeping tabs on function completion times.

Don't get me wrong, I know where you guys are heading, but when theory vs reality kicks in there's really no need to go that far given the type of mcu in use, is there?
 

3v0

Coop Build Coordinator
Forum Supporter
#31
gramo,

It is nice to talk about something other then blinking an led for a change. :)

The code you provided works if your events are of very short duration. We are looking at tasks of undetermined length.

To make that work you have to break each task into a number of chunks. The task becomes a state machine which is able to run a short time, exit, then pick up where it left off the last time it was executed.

My cooperative multitasking tutorial in 3v0's Tutorials is written to show how it works and can be done.

Roman is unhappy with it so we are exploring where we could go from there.
 
Last edited:

3v0

Coop Build Coordinator
Forum Supporter
#32
Spoken like a true C purist. :)
It would be fair to say that I place high value on well structured code. I am thankful that spaghetti code has been recognized as evil.

I try to stay language neutral. I use the best language for the job. As a PIC hobbyist the best language is often C because most of the examples, app notes, and libs are written in C.

If I had to pick a favorite language it would be forth which has very good support for cooperative multitasking. But if I used it I would have write nearly everything from scratch.

Computer language and programming is evolving. Even when using old languages we can incorporate good methodologies (when useful) as we code.

Yep i am going to argue that point. If the threads are inline, any macro will have the ability to jump to the next thread. I suggested a very crude macro procedure;
Code:
   if seq > macroseq; goto next thread
As you can see once the seq "chunk" has been performed in thread 2 then the next macro would jump right ahead to the following thread. My macro procedure is buggy as it is written in post #26 but it still outlines the basic things that need to be done.

To me that seems to have a certain neatness where the macros basically ARE the sequencing engine, so it looks attractive because the macro could be the complete solution to turning code into a multithreaded application.

But you are right in that it would be easier to code if each thread is in a function and to end that thread the macro just needs to return, instead of another goto.

(edit)Actually, on re-thinking that it might be better to have the threads in a while(1) loop, then the macro could just do a break. That removes the overhead of functions, and the need to have separate code that calls the functions.(/edit)
I do not know of any high level language that mixes codes from more then one thread in the same procedure. It defeats the notion of scope which makes debugging more difficult. You would be giving in to a lot of ugly for that neatness.

You know I had the same thought. A separate program to manually insert the mutltithread control in the users source code is tempting, it would be enormously powerful. It could even be programmed to analyse the code and roughly compute the execution time of each line of C code (even just done with simple rules) so it could manually insert the thread sequencing in places where the code is slow.

But it's nasty in a few ways. Especially when the user will need to debug why something strange is happening.
This problem exists with macros as well. Unless the compiler expands the macros in the src used by the debugger you can not easily debug them.

For a translator program that expanded tags as per our need the code passed on to the compiler will contain the code executed. The user may not understand it. We could add comments to show what the expansion was to help him out.


My grasp of C macros is pretty poor. But maybe it would be possible to have a modifiable label in the macro? Like a label++ if you get me. Maybe even something like #IFDEF then ++.
I try to stay away from all but simple C macros. I like to see what I am debugging. Unfortunately I have not seen where the C pre processor has the ability to to support variables. I expect one could modify an open source version and add the feature.

Failing that there's always the standby option of a computed jump. So each seq chunk of code is exactly X bytes, and the macro instead of using "goto label" could just do a computed jump. That's not so far fetched, each thread chunk should be small anyway and anything complex in a thread chunk should probably be in an external function anyway.
This could be done. If I were asked to do it I would modify the compiler to generate the modified C code. Maybe make it an additional first pass. The program counter would tell me where to insert the task-switch code.

One could do it by using rules to estimate time based on operators on each line. Have it descend into functions.

In my book both of these are way too much work for the return. Maybe write a translator that runs prior to the preprocessor. Write the needed tags as comments or even #pragmas (if undefined ones only generate warnings in the compiler) in the C source code.
 

Mr RB

Well-Known Member
Thread starter #33
...
My cooperative multitasking tutorial in 3v0's Tutorials is written to show how it works and can be done.

Roman is unhappy with it so we are exploring where we could go from there.
Hey I never said that! I believe in post #18 I was quite complimentary about your tutorial.

But just because something has already been done by one person doesn't mean that other people should never try to do it in their own way. And when I wrote my PIC-thread page I didn't know you had already done it (or didn't remember). Actually I was inspired to "have a go at some PIC multi-thread systems" after reading through some XMOS .XC source code and seeing their way of doing things.

Maybe a custom preprocessor is the best way to do this? With the XMOS devices they have the multi-thread stuff in .XC files but they can also have .C files. Maybe the PIC code could be written by the user in a .?C format which is the same as C but has aditional simple indicators where threads start and end. Then the preprocessor is run, that inserts the multi-thread if statements or switch statements and creates a .C file.

I would still like some input from a C macro expert, on how to get a different label in each iteration of the same macro, or even just some clever way of getting one macro to jump or goto the next copy of itself... Surely that's not impossible!
 
Last edited:

Mr RB

Well-Known Member
Thread starter #34
OK, I think I might have solved it.

My proposal was for a one-liner macro that the user could just intersperse through their code, so the workings of the multi-thread intra-thread sequencing was hidden and automatic.

The user can just do this;

Code:
THREAD_START(2)
blah
blah
THREAD_BREAK
blah
blah
THREAD_BREAK
blah
blah
THREAD_BREAK
blah
blah
THREAD_END(2)
For each thread.

Basic operation of the macros;
Code:
----------------------------------
THREAD_START(2)
thseq = thseq[2];
thseqtemp = 0;
if(thseq == thseqtemp)
{
----------------------------------
THREAD_BREAK
}
thseqtemp++;
if(thseq == thseqtemp)
{
----------------------------------
THREAD_END(2)
}
thseq[2]++;
if(thseq[2] > thseqtemp) thseq[2] = 0;
----------------------------------
So the first macro loads the current seq for THAT thread. Then the code between macros will only run for the right seq, or when seq==seqtemp.

And the clever part at the end, is that the seq value is reset to zero ONLY if the last code chunk was the one that was just run! So it automatically adjusts for any number of THREAD_BREAK macros.

:)
 

Mr RB

Well-Known Member
Thread starter #35
I just had a read though the MikroC v7.0 help file on macros, and put this together.

Amazingly enough it seems to work ok right off the bat.

This is the MikroC source for PIC 16Fxxx;
Code:
// Romans PIC-thread automatic multi-thread macros
// first test 11/01/2010

unsigned char seq[10];
unsigned char thseq;
unsigned char thseqtemp;

#define THREAD_START(A) thseq=seq[A]; thseqtemp=0; if(thseq==thseqtemp) {

#define THREAD_BREAK } thseqtemp++; if(thseq==thseqtemp) {

#define THREAD_END(B) } seq[B]++; if(seq[B]>thseqtemp) seq[B]=0;

unsigned char i, x, y;

//-----------------------------------------------------------------------------
void main()
{
  i=3;
  x=y;

  THREAD_START(2)
  y++;
  THREAD_BREAK
  x++;
  THREAD_BREAK
  y=x;
  THREAD_END(2)

}
//-----------------------------------------------------------------------------
And this is how it ended up after compilation;

Code:
;  ASM code generated by mikroVirtualMachine for PIC - V. 7.0.0.3
;  Date/Time: 11/01/10 9:58:51 AM


; ADDRESS	OPCODE	ASM
; ----------------------------------------------
$0000	$2804			GOTO	_main
$0004	$	_main:
;test.c,17 :: 		void main()
;test.c,19 :: 		i=3;
$0004	$3003			MOVLW	3
$0005	$1303			BCF	STATUS, RP1
$0006	$1283			BCF	STATUS, RP0
$0007	$00A0			MOVWF	_i
;test.c,20 :: 		x=y;
$0008	$0822			MOVF	_y, 0
$0009	$00A1			MOVWF	_x
;test.c,22 :: 		THREAD_START(2)
$000A	$0826			MOVF	_seq+2, 0
$000B	$00A3			MOVWF	_thseq
$000C	$01AE			CLRF	_thseqtemp, 1
$000D	$0826			MOVF	_seq+2, 0
$000E	$3A00			XORLW	0
$000F	$1D03			BTFSS	STATUS, Z
$0010	$2812			GOTO	L_main_0
;test.c,23 :: 		y++;
$0011	$0AA2			INCF	_y, 1
;test.c,24 :: 		THREAD_BREAK
$0012	$	L_main_0:
$0012	$0AAE			INCF	_thseqtemp, 1
$0013	$0823			MOVF	_thseq, 0
$0014	$062E			XORWF	_thseqtemp, 0
$0015	$1D03			BTFSS	STATUS, Z
$0016	$2818			GOTO	L_main_1
;test.c,25 :: 		x++;
$0017	$0AA1			INCF	_x, 1
;test.c,26 :: 		THREAD_BREAK
$0018	$	L_main_1:
$0018	$0AAE			INCF	_thseqtemp, 1
$0019	$0823			MOVF	_thseq, 0
$001A	$062E			XORWF	_thseqtemp, 0
$001B	$1D03			BTFSS	STATUS, Z
$001C	$281F			GOTO	L_main_2
;test.c,27 :: 		y=x;
$001D	$0821			MOVF	_x, 0
$001E	$00A2			MOVWF	_y
;test.c,28 :: 		THREAD_END(2)
$001F	$	L_main_2:
$001F	$0AA6			INCF	_seq+2, 1
$0020	$0826			MOVF	_seq+2, 0
$0021	$022E			SUBWF	_thseqtemp, 0
$0022	$1803			BTFSC	STATUS, C
$0023	$2825			GOTO	L_main_3
$0024	$01A6			CLRF	_seq+2, 1
$0025	$	L_main_3:
;test.c,30 :: 		}
$0025	$2825			GOTO	$
Looking through the assembler it looks perfect, at least at first glance. :)
 

Mr RB

Well-Known Member
Thread starter #36
I've done some more messing with these automatic multi-tasking macros, and can't find any significant ways of improving them besides the obvious goal of making the macros jump directly forward to the right code or putting in a "return" directly after the working macro which is easy enough to implement but probably not worth it.

So I've written it up, it's at the top of my page;
PIC-thread automatic multi-tasking macros

This seems to have turned out very well, the more I look at it the stronger it seems, with the ability to easily turn code into a "thread" by inserting some macros, or easily turn a thread back to normal code and the ease of moving threads around in code and adding/removing thread_breaks while the system automatically compensates.

Apart from the obvious "wasted overhead" of executing the macros, it does look like 3 simple macros makes an entire multitasking system, and is easier to use than anything I have seen so far.
 

3v0

Coop Build Coordinator
Forum Supporter
#37
Apart from the obvious "wasted overhead" of executing the macros, it does look like 3 simple macros makes an entire multitasking system, and is easier to use than anything I have seen so far.
It is very good in that it easy easily added or removed from code.

But you are not quite yet done. :)

An important feature of multitasking is to not wast time in loops waiting or checking for flags.

You system can be modified to handle this. Place the check for the expired timer or other flag in state/chunk by itself. If the flag is not set do not advance the seq counter for that thread.

By not using a function per task you are using C as if it were assembler. I like my variables to have scope, like the idea of function input parameters and return values. The ability to call a function from several places in the code.

If this were not the case I would be much more enthused about the system.
 

Mr RB

Well-Known Member
Thread starter #38
...
An important feature of multitasking is to not wast time in loops waiting or checking for flags.

You system can be modified to handle this. Place the check for the expired timer or other flag in state/chunk by itself. If the flag is not set do not advance the seq counter for that thread.
...
I agree completely 3V0, I didn't include any function calls in the code examples because I thought it was obvious.

Because the macros work very independently they are "function proof" so they can be used within functions for those times when you need to have a thread in a function;

So this;
Code:
    //-----------------------------------------------------
    // do the adc tests in a thread
    THREAD_START(0)
    read_adc(0);
    THREAD_BREAK
    read_adc(1);
    THREAD_BREAK
    read_adc(2);
    THREAD_BREAK
    read_adc(3);
    THREAD_END(0)

    //-----------------------------------------------------
    // do the calcs, display etc in a thread
    THREAD_START(1)
    calc_adc_temperature();
    THREAD_BREAK
    calc_adc_battery_voltage();
    THREAD_BREAK
    adc3int = (adc3 * 51) / 1024;
    THREAD_BREAK
    format_data_to_transmit();
    THREAD_BREAK
    process_menu_buttons();
    THREAD_BREAK
    display_data_on_lcd();
    THREAD_END(1)
    //-----------------------------------------------------
Works just as well like this (in functions);
Code:
//-----------------------------------------------------
void Thread_0(void)
{
    THREAD_START(0)
    read_adc(0);
    THREAD_BREAK
    read_adc(1);
    THREAD_BREAK
    read_adc(2);
    THREAD_BREAK
    read_adc(3);
    THREAD_END(0)
}
//-----------------------------------------------------
void Thread_1()
{
    THREAD_START(1)
    calc_adc_temperature();
    THREAD_BREAK
    calc_adc_battery_voltage();
    THREAD_BREAK
    adc3int = (adc3 * 51) / 1024;
    THREAD_BREAK
    format_data_to_transmit();
    THREAD_BREAK
    process_menu_buttons();
    THREAD_BREAK
    display_data_on_lcd();
    THREAD_END(1)
}
//-----------------------------------------------------
And it works just as well as part of a timed multitasking system;

Code:
  //-----------------------------------------------------
  // do the adc tests in a thread
  if(time_for_thread0)
  {
    THREAD_START(0)
    read_adc(0);
    THREAD_BREAK
    read_adc(1);
    THREAD_BREAK
    read_adc(2);
    THREAD_BREAK
    read_adc(3);
    THREAD_END(0)
    time_for_thread0 = 0;
  }
  //-----------------------------------------------------
  // do the calcs, display etc in a thread
  if(time_for_thread1)
  {
    THREAD_START(1)
    calc_adc_temperature();
    THREAD_BREAK
    calc_adc_battery_voltage();
    THREAD_BREAK
    adc3int = (adc3 * 51) / 1024;
    THREAD_BREAK
    format_data_to_transmit();
    THREAD_BREAK
    process_menu_buttons();
    THREAD_BREAK
    display_data_on_lcd();
    THREAD_END(1)
    time_for_thread1 = 0;
  }
  //-----------------------------------------------------
Which I think satisfies your other point about not wasting time in loops while checking for flags etc, so the thread sequencing can be controlled by an interrupt timer or other system as the main thread control engine, with the macros acting as a independent secondary (intra-thread?) engine.

Considering the time we spent discussing the other thread control processes I thought it was obvious that the macros would integrate right into the other thread control systems with minimal fuss.
 
#39
I know you guys are focusing on the 8-bit PICs, but I was just looking at the dsp30's as well as the PIC24s and they could support a fully pre-emptive multi-tasking kernel. However, without virtual memory mapping, you would have to make sure all the user code was relocatable.

The registers that make this possible:
Stack pointer
Stack frame pointer
Stack pointer limit
Program counter

With those accessible, you should be able to go fully pre-emptive, or am I missing something?
 

Mr RB

Well-Known Member
Thread starter #40
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.
 

Latest threads

EE World Online Articles

Loading

 
Top