multitask in pic

Thread Starter

bug13

Joined Feb 13, 2012
2,002
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):
C:
// function pointer
typedef void (*TASK)(void);

// TICK is my structure for timer tick, the structure is not show
    typedef struct {
        TICK LastRun;                      // ticks of this task is last executed
        TICK interval;                        // how often the task need to be executed
        TASK task;                            // pointing to the task
        uint8_t isTaskEmpty : 1;        // to tell if this structure is assigned a task or not
    } TASK_T;

TASK_T task[MAX_TASK];

void createTask(TASK t, TICK interval)
{
   // a function to:
   // loop through task[]
   // find an empty slot
   // assign the values to task[] and initial conditions
}

// create some tasks
createTask(task_01, TICK_10MS);
createTask(task_02, TICK_20MS);
createTask(task_03, TICK_30MS);
createTask(task_04, TICK_40MS);

void taskScheduler(void)
{
  // loop through all tasks
  for (uint8_t i = 0; i < MAX_TASK; i++)
  {
      // if task is valid
      if (!task[i].isTaskEmpty)
      {
          // if task expired
          if ((TICK_NOW - task[i].LastRun) > task[i].interval)
          {
              task[i].LastRun = TICK_NOW;      // update time
              task[i].task();                                 // run task
          }
      }
  }
}

// main loop
while(1)
{
  taskScheduler();
}
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!!
 

nsaspook

Joined Aug 27, 2009
13,312
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:

NorthGuy

Joined Jun 28, 2014
611
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.
 

joeyd999

Joined Jun 6, 2011
5,287
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.
 

ci139

Joined Jul 11, 2016
1,898
main_loop_entry
// 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​
main_loop_end

versus:

main_loop_entry
// if i remember this right -- simplified
for x = MaxTask dnto 1
ex=1 shl (x-1)
doTasks(ex)
XthBitClear(ex,TaskMask)​
end_for
// 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​
main_loop_end
(long ago)
 
Last edited:

Thread Starter

bug13

Joined Feb 13, 2012
2,002
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.
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. :)

PS:
And usually a watchdog timer kicker task to monitor all the tasks.
 
Last edited:

NorthGuy

Joined Jun 28, 2014
611
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.
 

Thread Starter

bug13

Joined Feb 13, 2012
2,002
I don't think the scheduler is necessary for something time-critical - there are interrupts for that.
Yes, my time critical tasks still use interrupts. My driver tasks are usually interrupt driven, along with my other time critical tasks.
 

nsaspook

Joined Aug 27, 2009
13,312
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.
C:
typedef struct hidtype { // task structure
uint8_t bled_on : 1; // blink flags
uint8_t bled_flash : 1;
uint8_t bled_flash_fast : 1;
void (*t_on)(void); // blink procedures
void (*t_off)(void);
} hidtype;

volatile hidtype hid0 = {0, 0, 0, b0_on, b0_off},
hid1 = {0, 0, 0, b1_on, b1_off};
volatile hidtype *hid0_ptr = &hid0, *hid1_ptr = &hid1;

void b0_on(void)
{
BLED0 = S_ON;
}

void b1_on(void)
{
BLED1 = S_ON;
}

void b0_off(void)
{
BLED0 = S_OFF;
}

void b1_off(void)
{
BLED1 = S_OFF;
}

/*
* This is the low priority ISR routine, the high ISR routine will be called during this code section
*/
void work_handler(void)
{
static int8_t task = 0;

if (PIR1bits.TMR1IF) {

PIR1bits.TMR1IF = LOW; // clear TMR1 interrupt flag
WriteTimer1(PDELAY);
/*
* task work stack
*/
switch (task) {
case 0:
if (hid0_ptr->bled_on) {
hid0_ptr->t_on();
} else {
hid0_ptr->t_off();
}
break;
case 1:
if (hid1_ptr->bled_on) {
hid1_ptr->t_on();
} else {
hid1_ptr->t_off();
}
break;
default:
task = -1; // null task
break;
}
task++;
}
}
#pragma tmpdata
 
Last edited:

joeyd999

Joined Jun 6, 2011
5,287
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.
C:
typedef struct hidtype { // task structure
uint8_t bled_on : 1; // blink flags
uint8_t bled_flash : 1;
uint8_t bled_flash_fast : 1;
void (*t_on)(void); // blink procedures
void (*t_off)(void);
} hidtype;

volatile hidtype hid0 = {0, 0, 0, b0_on, b0_off},
hid1 = {0, 0, 0, b1_on, b1_off};
volatile hidtype *hid0_ptr = &hid0, *hid1_ptr = &hid1;

void b0_on(void)
{
BLED0 = S_ON;
}

void b1_on(void)
{
BLED1 = S_ON;
}

void b0_off(void)
{
BLED0 = S_OFF;
}

void b1_off(void)
{
BLED1 = S_OFF;
}

/*
* This is the low priority ISR routine, the high ISR routine will be called during this code section
*/
void work_handler(void)
{
static int8_t task = 0;

if (PIR1bits.TMR1IF) {

PIR1bits.TMR1IF = LOW; // clear TMR1 interrupt flag
WriteTimer1(PDELAY);
/*
* task work stack
*/
switch (task) {
case 0:
if (hid0_ptr->bled_on) {
hid0_ptr->t_on();
} else {
hid0_ptr->t_off();
}
break;
case 1:
if (hid1_ptr->bled_on) {
hid1_ptr->t_on();
} else {
hid1_ptr->t_off();
}
break;
default:
task = -1; // null task
break;
}
task++;
}
}
#pragma tmpdata
Damn. I must be doing something wrong. My LED blink code looks like:

Code:
    btfsc    tc128ms
    btg    led
 

ErnieM

Joined Apr 24, 2011
8,377
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.
 

Thread Starter

bug13

Joined Feb 13, 2012
2,002
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.
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
 
Thread starter Similar threads Forum Replies Date
S Microcontrollers 5
Top