multitask in pic

Discussion in 'Embedded Systems and Microcontrollers' started by bug13, Oct 17, 2016.

  1. bug13

    Thread Starter Well-Known Member

    Feb 13, 2012
    Hi guys

    I usually need to run multiple tasks in a PIC, but recently I found myself need to assign some task with higher priority, and some task with lower priority.

    Here is how I normally do it (with no priority):
    Code (C):
    2. // function pointer
    3. typedef void (*TASK)(void);
    5. // TICK is my structure for timer tick, the structure is not show
    6.     typedef struct {
    7.         TICK LastRun;                      // ticks of this task is last executed
    8.         TICK interval;                        // how often the task need to be executed
    9.         TASK task;                            // pointing to the task
    10.         uint8_t isTaskEmpty : 1;        // to tell if this structure is assigned a task or not
    11.     } TASK_T;
    13. TASK_T task[MAX_TASK];
    15. void createTask(TASK t, TICK interval)
    16. {
    17.    // a function to:
    18.    // loop through task[]
    19.    // find an empty slot
    20.    // assign the values to task[] and initial conditions
    21. }
    23. // create some tasks
    24. createTask(task_01, TICK_10MS);
    25. createTask(task_02, TICK_20MS);
    26. createTask(task_03, TICK_30MS);
    27. createTask(task_04, TICK_40MS);
    29. void taskScheduler(void)
    30. {
    31.   // loop through all tasks
    32.   for (uint8_t i = 0; i < MAX_TASK; i++)
    33.   {
    34.       // if task is valid
    35.       if (!task[i].isTaskEmpty)
    36.       {
    37.           // if task expired
    38.           if ((TICK_NOW - task[i].LastRun) > task[i].interval)
    39.           {
    40.               task[i].LastRun = TICK_NOW;      // update time
    41.               task[i].task();                                 // run task
    42.           }
    43.       }
    44.   }
    45. }
    47. // main loop
    48. while(1)
    49. {
    50.   taskScheduler();
    51. }
    I can see there is limitation with this method, and one thing is, with this, there is no priority. How do you guys do it if you need to add priority to the tasks. Or what is the better way to do this?

    thanks guys!!
  2. nsaspook

    AAC Fanatic!

    Aug 27, 2009
    With the scale of programming on a 8-bit PIC a can't see much of an advantage in using a user Task parallel programming model instead of a big loop state machine. Normally a higher priority in a RTOS system decides who gets to access a fixed resource first not who gets more programming time from the task scheduler.
    Last edited: Oct 17, 2016
    bug13 likes this.
  3. NorthGuy

    Active Member

    Jun 28, 2014
    You can do it in lots of different ways, the simplest way is to sort the tasks in your list.

    I'm working on a development tool which also does multitasking for PICs. I deal with priorities by having two separate queues - high priority queue and low priority queue. The tasks from low priority queue gets executed only when high priority queue is empty.
    bug13 likes this.
  4. joeyd999

    AAC Fanatic!

    Jun 6, 2011
    I'm curious. I've developed dozens of PIC based designs, most performing multiple tasks simultaneously and many of them time critical, yet I've never found a need for an explicit multitasking structure.

    What kinds of tasks are you scheduling, and why do you think you need such a scheduling system?

    I ask this not to argue, but perhaps to learn something.
    bug13 likes this.
  5. ci139


    Jul 11, 2016
    // actually there was 3 v. of which i found 1 for now
    get hwEvents(events)
    if events and Exeption2Brun1st Run1st
    if events and 1thBit do Tasks Clear1
    if events and NthBit do Tasks ClearN​


    // if i remember this right -- simplified
    for x = MaxTask dnto 1
    ex=1 shl (x-1)
    // if i remember this right -- ends
    ct0=(ct0+Nbits-1)mod Nbits
    MaxBit=GetMaxB(ct0) // might've been tracked differently
    MaxTask = Setbits(-1 xor (MaxBit-1) , TasksFlag)
    // i'm not sure must find the src -- but it gives some idea to it​
    (long ago)
    Last edited: Oct 17, 2016
  6. bug13

    Thread Starter Well-Known Member

    Feb 13, 2012
    Most of the task I run is pretty simple, flashing LEDs on different rate, scan buttons, CLI, moving buffer from/to UART/SPI/I2C etc, I agree it can be done with state machine or even simple loop.

    For me, the benefit for using a task scheduler is I can easily add/remove a feature without re-designing a state machine. I usually separate my tasks into two types, driver tasks and application tasks, just like a windows PC. The driver tasks are responsible for talking to the hardware. Application tasks are for application only, and they are independent (most of the time) of the driver tasks.

    It works for me so far, but I am open to new ideas/suggestions /criticism. :)

    And usually a watchdog timer kicker task to monitor all the tasks.
    Last edited: Oct 17, 2016
  7. NorthGuy

    Active Member

    Jun 28, 2014
    I don't think the scheduler is necessary for something time-critical - there are interrupts for that.

    Otherwise a formal scheduler interface is very handy.

    Imagine, you have a task which needs to do something now, then something 50us later etc. Then, another task needs to debounce a button. Now you need to put several instances of such tasks together. You need to do lots of little things for this - you need to figure out your clock speed, you need to make sure that all your tasks are called as needed and when needed. Perhaps, you need to change the code of individual tasks.

    If you have a formal scheduler interface, you don't need any of these. Your tasks are written to interface the scheduler. You just get the code for the task and move it into your project. The scheduler figures out how to translate your clock to the real time. It calls your tasks as specified, goes to sleep when allowed etc. And you can re-use your scheduler for all your projects. Makes the life easier.
    bug13 likes this.
  8. bug13

    Thread Starter Well-Known Member

    Feb 13, 2012
    Yes, my time critical tasks still use interrupts. My driver tasks are usually interrupt driven, along with my other time critical tasks.
  9. nsaspook

    AAC Fanatic!

    Aug 27, 2009
    I've made several (static) round-robin task cooperative schedulers using the PIC18 low interrupt for something non-critical. It's usually more trouble than it's worth to create unless you just like to program that way. The 8-bit PIC controllers are designed for quick hardware module multitasking using the limited interrupts to trigger user code not user tasks and queues with priorities. The PIC controllers small stack and register architecture makes hardware module multitasking fast but it usually limits real-time user task multitasking to mainly slow and low resource procedures.

    Some code fragments from a simple LED blink system.
    Code (C):
    3. typedef struct hidtype { // task structure
    4. uint8_t bled_on : 1; // blink flags
    5. uint8_t bled_flash : 1;
    6. uint8_t bled_flash_fast : 1;
    7. void (*t_on)(void); // blink procedures
    8. void (*t_off)(void);
    9. } hidtype;
    11. volatile hidtype hid0 = {0, 0, 0, b0_on, b0_off},
    12. hid1 = {0, 0, 0, b1_on, b1_off};
    13. volatile hidtype *hid0_ptr = &hid0, *hid1_ptr = &hid1;
    15. void b0_on(void)
    16. {
    17. BLED0 = S_ON;
    18. }
    20. void b1_on(void)
    21. {
    22. BLED1 = S_ON;
    23. }
    25. void b0_off(void)
    26. {
    27. BLED0 = S_OFF;
    28. }
    30. void b1_off(void)
    31. {
    32. BLED1 = S_OFF;
    33. }
    35. /*
    36. * This is the low priority ISR routine, the high ISR routine will be called during this code section
    37. */
    38. void work_handler(void)
    39. {
    40. static int8_t task = 0;
    42. if (PIR1bits.TMR1IF) {
    44. PIR1bits.TMR1IF = LOW; // clear TMR1 interrupt flag
    45. WriteTimer1(PDELAY);
    46. /*
    47. * task work stack
    48. */
    49. switch (task) {
    50. case 0:
    51. if (hid0_ptr->bled_on) {
    52. hid0_ptr->t_on();
    53. } else {
    54. hid0_ptr->t_off();
    55. }
    56. break;
    57. case 1:
    58. if (hid1_ptr->bled_on) {
    59. hid1_ptr->t_on();
    60. } else {
    61. hid1_ptr->t_off();
    62. }
    63. break;
    64. default:
    65. task = -1; // null task
    66. break;
    67. }
    68. task++;
    69. }
    70. }
    71. #pragma tmpdata
    Last edited: Oct 17, 2016
  10. joeyd999

    AAC Fanatic!

    Jun 6, 2011
    Damn. I must be doing something wrong. My LED blink code looks like:

    Code (ASM):
    1.     btfsc    tc128ms
    2.     btg    led
    ErnieM likes this.
  11. nsaspook

    AAC Fanatic!

    Aug 27, 2009
    How can billions in profits be made from large memory, high speed micro-controller led blinkers with code like that?
    NorthGuy, bug13 and joeyd999 like this.
  12. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    I do most of this stuff in my main loop with the assistance of a regular heartbeat ISR. Let’s take each in turn:

    scan buttons: When I have this task to do I set up a timer ISR to trigger at a 1mS rate; that is a bit too fast but typically the slowest I can get Timer 2 to trigger at a uniform rate. Every so often (like every 25 mS) I will read the buttons. If the pattern is the same as the last reading I present the data as stable. Otherwise it is ignored. In either case the present pattern is stored as the old pattern to be used on the next scan.

    Data is presented via a global variable the main loop can read as it requires. Also, a global tick counter is incremented; use this value at your peril as it can overflow and produce incorrect timings. If just one task needs timing, the tick counter can be cleared to prevent overflow.

    flashing LEDs on different rate: Similar to the above tick counter multiple counters can be incremented. Main loop code would check to see if LEDs need be enabled or disabled. The whole task could be done in the ISR but I prefer to offload as much work as possible from the ISR itself

    CLI: I have no idea what this is.

    moving buffer from/to UART/SPI/I2C: these have their own interrupt vector that can update (global) buffers. A global variable can be used to see if a transfer is complete and thus flag the main loop.
  13. bug13

    Thread Starter Well-Known Member

    Feb 13, 2012
    That's exactly what I do. I usually think of these as driver tasks, or maybe I should call it a physical layer. My application tasks are usually consume the data from UART/SPI/I2C, buttons inputs etc.. Driver tasks are usually interrupt driven, and the data are put into a global buffer. Application tasks use the data in the buffer when they are ready.

    For example driving LEDs, an application task set a flag (it could be on and off only, or PWM), a LED driver task does it in the background (usually in timer interrupt).

    Or sending data to UART/SPI/I2C, an application task put the data want to send into a TX buffer, and send the flags accordingly. A UART/SPI/I2C driver task just look at the flags and send data in the background (usually in their interrupt too).

    CLI: I was meaning to say command line interrupter