1. 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.
    Dismiss Notice

Here are some PIC multi-tasking systems

Discussion in 'Microcontrollers' started by Mr RB, Jan 2, 2010.

  1. Mr RB

    Mr RB Well-Known Member

    Joined:
    Jul 22, 2008
    Messages:
    4,716
    Likes:
    191
    Location:
    Out there
    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.

    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: Jan 6, 2010
  2. 3v0

    3v0 Coop Build Coordinator Forum Supporter

    Joined:
    Jul 14, 2006
    Messages:
    9,403
    Likes:
    227
    Location:
    OKLAHOMA USA
    Roman,

    I wrote a substantial reply but the computer ate it.
    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.
     
  3. smanches

    smanches New Member

    Joined:
    Mar 5, 2009
    Messages:
    986
    Likes:
    8
    Location:
    Oregon, USA
    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: Jan 6, 2010
  4. dave

    Dave New Member

    Joined:
    Jan 12, 1997
    Messages:
    -
    Likes:
    0


     
  5. Mr RB

    Mr RB Well-Known Member

    Joined:
    Jul 22, 2008
    Messages:
    4,716
    Likes:
    191
    Location:
    Out there

    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 (text):


    // 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.
     
  6. 3v0

    3v0 Coop Build Coordinator Forum Supporter

    Joined:
    Jul 14, 2006
    Messages:
    9,403
    Likes:
    227
    Location:
    OKLAHOMA USA
    I am rather suprised you did not see these things in my code/tutorial. Perhaps I need to revisit it.

    Code (text):

    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 (text):

    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 (text):

     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: Jan 7, 2010
  7. Mr RB

    Mr RB Well-Known Member

    Joined:
    Jul 22, 2008
    Messages:
    4,716
    Likes:
    191
    Location:
    Out there
    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 (text):


    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 (text):

     blah
     blah
     blah
     blah
     blah
     blah
     blah
     blah
     
    And they just need to insert a one-liner throughout the code;
    Code (text):

     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 (text):

     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. :)
     
  8. gabeNC

    gabeNC Member

    Joined:
    Feb 14, 2009
    Messages:
    559
    Likes:
    4
    Location:
    North Carolina
    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.
     
  9. 3v0

    3v0 Coop Build Coordinator Forum Supporter

    Joined:
    Jul 14, 2006
    Messages:
    9,403
    Likes:
    227
    Location:
    OKLAHOMA USA
    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 (text):

     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 (text):

    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: Jan 8, 2010
  10. Mr RB

    Mr RB Well-Known Member

    Joined:
    Jul 22, 2008
    Messages:
    4,716
    Likes:
    191
    Location:
    Out there
    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 (text):

       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)

    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: Jan 9, 2010
  11. gramo

    gramo New Member

    Joined:
    Oct 2, 2006
    Messages:
    1,221
    Likes:
    23
    Location:
    Newcastle, Australia
    Wow there is some serious heat going on here! What ever happened to simple time splicing?
    Code (text):
    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?
     
  12. 3v0

    3v0 Coop Build Coordinator Forum Supporter

    Joined:
    Jul 14, 2006
    Messages:
    9,403
    Likes:
    227
    Location:
    OKLAHOMA USA
    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: Jan 9, 2010
  13. 3v0

    3v0 Coop Build Coordinator Forum Supporter

    Joined:
    Jul 14, 2006
    Messages:
    9,403
    Likes:
    227
    Location:
    OKLAHOMA USA
    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.

    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.

    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.


    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.

    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.
     
  14. Mr RB

    Mr RB Well-Known Member

    Joined:
    Jul 22, 2008
    Messages:
    4,716
    Likes:
    191
    Location:
    Out 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: Jan 10, 2010
  15. Mr RB

    Mr RB Well-Known Member

    Joined:
    Jul 22, 2008
    Messages:
    4,716
    Likes:
    191
    Location:
    Out there
    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 (text):

    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 (text):

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

    :)
     
  16. Mr RB

    Mr RB Well-Known Member

    Joined:
    Jul 22, 2008
    Messages:
    4,716
    Likes:
    191
    Location:
    Out there
    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 (text):

    // 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 (text):

    ;  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. :)
     
  17. Mr RB

    Mr RB Well-Known Member

    Joined:
    Jul 22, 2008
    Messages:
    4,716
    Likes:
    191
    Location:
    Out there
    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.
     
  18. 3v0

    3v0 Coop Build Coordinator Forum Supporter

    Joined:
    Jul 14, 2006
    Messages:
    9,403
    Likes:
    227
    Location:
    OKLAHOMA USA
    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.
     
  19. Mr RB

    Mr RB Well-Known Member

    Joined:
    Jul 22, 2008
    Messages:
    4,716
    Likes:
    191
    Location:
    Out there
    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 (text):

        //-----------------------------------------------------
        // 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 (text):

    //-----------------------------------------------------
    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 (text):

      //-----------------------------------------------------
      // 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.
     
  20. smanches

    smanches New Member

    Joined:
    Mar 5, 2009
    Messages:
    986
    Likes:
    8
    Location:
    Oregon, USA
    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?
     
  21. Mr RB

    Mr RB Well-Known Member

    Joined:
    Jul 22, 2008
    Messages:
    4,716
    Likes:
    191
    Location:
    Out there
    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.
     

Share This Page