Task scheduler library  1.0
Arduino library for simply executing tasks in parallel
Tasks.cpp
Go to the documentation of this file.
1 
7 #include "Tasks.h"
8 
9 
10 // check Arduino controller only once
11 #if !defined(__AVR__) && !defined(__SAM3X8E__)
12  #error board not supported, error
13 #endif
14 
15 
16 /**************************************/
17 /******* start skip in doxygen ********/
18 /**************************************/
20 
21 // measure speed via pin D9(=PB1). Is configured as OUTPUT in Scheduler_Start()
22 #define TASKS_MEASURE_PIN 0
23 #if (TASKS_MEASURE_PIN)
24  #define SET_PIN (PORTB |= B00000010)
25  #define CLEAR_PIN (PORTB &= ~B00000010)
26  #define TOGGLE_PIN (PORTB ^= B00000010)
27 #endif
28 
29 
30 // macro to pause / resume interrupt (interrupts are only reactivated in case they have been active in the beginning)
31 uint8_t oldISR = 0;
32 #if defined(__AVR__)
33  #define PAUSE_INTERRUPTS { oldISR = SREG; noInterrupts(); }
34  #define RESUME_INTERRUPTS { SREG = oldISR; interrupts(); }
35 #elif defined(__SAM3X8E__)
36  #define PAUSE_INTERRUPTS { oldISR = ((__get_PRIMASK() & 0x1) == 0 && (__get_FAULTMASK() & 0x1) == 0); noInterrupts(); }
37  #define RESUME_INTERRUPTS { if (oldISR != 0) { interrupts(); } }
38 #endif
39 
40 
41 // task container
42 typedef struct SchedulingStruct
43 {
44  Task func; // function to call
45  bool active; // task is active
46  bool running; // task is currently being executed
47  int16_t period; // period of task (0 = call only once)
48  int16_t time; // time of next call
49 };
50 
51 
52 // global variables for scheduler
53 SchedulingStruct SchedulingTable[MAX_TASK_CNT]; // array containing all tasks
54 bool SchedulingActive; // false = Scheduling stopped, true = Scheduling active (no configuration allowed)
55 int16_t _timebase; // 1ms counter (on ATMega 1.024ms, is compensated)
56 int16_t _nexttime; // time of next task call
57 uint8_t _lasttask; // last task in the tasks array (cauting! This variable starts is not counting from 0 to x but from 1 to x meaning that a single tasks will be at SchedulingTable[0] but _lasttask will have the value '1')
58 
59 
60 #if defined(__SAM3X8E__)
61  /*
62  Code from https://forum.arduino.cc/index.php?topic=130423.0
63  ISR/IRQ TC Channel Due pins
64  TC0 TC0 0 2, 13
65  TC1 TC0 1 60, 61
66  TC2 TC0 2 58
67  TC3 TC1 0 none
68  TC4 TC1 1 none
69  TC5 TC1 2 none
70  TC6 TC2 0 4, 5
71  TC7 TC2 1 3, 10
72  TC8 TC2 2 11, 12
73  */
74  void startTasksTimer(Tc *tc, uint32_t channel, IRQn_Type irq, uint32_t frequency)
75  {
76  pmc_set_writeprotect(false);
77  pmc_enable_periph_clk((uint32_t)irq);
78  TC_Configure(tc, channel, TC_CMR_WAVE | TC_CMR_WAVSEL_UP_RC | TC_CMR_TCCLKS_TIMER_CLOCK1);
79  uint32_t rc = (SystemCoreClock >> 1)/frequency; //2 because we selected TIMER_CLOCK4 above
80  //TC_SetRA(tc, channel, (rc >> 1)); //50% high, 50% low
81  TC_SetRC(tc, channel, rc);
82  TC_Start(tc, channel);
83  tc->TC_CHANNEL[channel].TC_IER=TC_IER_CPCS;
84  tc->TC_CHANNEL[channel].TC_IDR=~TC_IER_CPCS;
85  NVIC_SetPriority(SysTick_IRQn, 8);
86  NVIC_SetPriority(irq, 15);
87  NVIC_EnableIRQ(irq);
88  }
89 #endif // __SAM3X8E__
90 
91 
92 void Scheduler_update_nexttime(void)
93 {
94  // stop interrupts, store old setting
95  PAUSE_INTERRUPTS;
96 
97  // find time of next task execution
98  _nexttime = _timebase + INT16_MAX; // Max. possible delay of the next time
99  for (uint8_t i = 0; i < _lasttask; i++) {
100  if ((SchedulingTable[i].active == true) && (SchedulingTable[i].func != NULL)) {
101 
102  /*
103  Serial.print(i);
104  Serial.print(" ");
105  Serial.println(SchedulingTable[i].time);
106  */
107 
108  if ((int16_t)(SchedulingTable[i].time - _nexttime) < 0) {
109  _nexttime = SchedulingTable[i].time;
110  }
111  }
112  }
113 
114  /*
115  Serial.print("timebase ");
116  Serial.println(_timebase);
117  Serial.print("nexttime ");
118  Serial.println(_nexttime);
119  Serial.println();
120  */
121  /*
122  Serial.print(_timebase);
123  Serial.print(" ");
124  Serial.println(_nexttime - _timebase);
125  */
126 
127  // resume stored interrupt setting
128  RESUME_INTERRUPTS;
129 
130 } // Scheduler_update_nexttime()
131 
132 
134 /************************************/
135 /******* end skip in doxygen ********/
136 /************************************/
137 
138 
139 void Tasks_Init(void)
140 {
141  static bool flagDone = false;
142 
143  // clear tasks schedule only for first call to avoid issues when calling multiple times
144  if (flagDone == false) {
145  flagDone = true;
146  Tasks_Clear();
147  }
148 
149 } // Tasks_Init()
150 
151 
152 
153 void Tasks_Clear(void)
154 {
155  uint8_t i;
156 
157  // stop interrupts, store old setting
158  PAUSE_INTERRUPTS;
159 
160  // init scheduler
161  SchedulingActive = false;
162  _timebase = 0;
163  _nexttime = 0;
164  _lasttask = 0;
165  for(uint8_t i = 0; i < MAX_TASK_CNT; i++)
166  {
167  //Reset scheduling table
168  SchedulingTable[i].func = NULL;
169  SchedulingTable[i].active = false;
170  SchedulingTable[i].running = false;
171  SchedulingTable[i].period = 0;
172  SchedulingTable[i].time = 0;
173  } // loop over scheduler slots
174 
175  // resume stored interrupt setting
176  RESUME_INTERRUPTS;
177 
178 } // Tasks_Clear()
179 
180 
181 
182 bool Tasks_Add(Task func, int16_t period, int16_t delay)
183 {
184  // stop interrupts, store old setting
185  PAUSE_INTERRUPTS;
186 
187  // Check range of period and delay
188  if ((period < 0) || (delay < 0))
189  return false;
190 
191  // workaround for 1.024ms timer period of Arduino ATMega
192  #if defined(__AVR__)
193  delay = (uint16_t)(((((int32_t)delay) * 250) + 128) >> 8); // delay = delay / 1.024 <-- with up/down rounding
194  period = (uint16_t)(((((int32_t)period) * 250) + 128) >> 8); // period = period / 1.024 <-- with up/down rounding
195  #endif
196 
197  // Check if task already exists and update it in this case
198  for(uint8_t i = 0; i < _lasttask; i++)
199  {
200  // stop interrupts when accessing any element within the scheduler (also neccessary for if checks!), store old setting
201  PAUSE_INTERRUPTS;
202 
203  // same function found
204  if (SchedulingTable[i].func == func)
205  {
206  SchedulingTable[i].active = true;
207  SchedulingTable[i].running = false;
208  SchedulingTable[i].period = period;
209  SchedulingTable[i].time = _timebase + delay;
210 
211  // resume stored interrupt setting
212  RESUME_INTERRUPTS;
213 
214  // find time for next task execution
215  Scheduler_update_nexttime();
216 
217  // return success
218  return true;
219  }
220 
221  // resume stored interrupt setting
222  RESUME_INTERRUPTS;
223 
224  } // loop over scheduler slots
225 
226  // find free scheduler slot
227  for (uint8_t i = 0; i < MAX_TASK_CNT; i++)
228  {
229  // stop interrupts when accessing any element within the scheduler (also neccessary for if checks!), store old setting
230  PAUSE_INTERRUPTS;
231 
232  // free slot found
233  if (SchedulingTable[i].func == NULL)
234  {
235  // add task to scheduler table
236  SchedulingTable[i].func = func;
237  SchedulingTable[i].active = true;
238  SchedulingTable[i].running = false;
239  SchedulingTable[i].period = period;
240  SchedulingTable[i].time = _timebase + delay;
241 
242  // update _lasttask
243  if (i >= _lasttask)
244  _lasttask = i + 1;
245 
246  // resume stored interrupt setting
247  RESUME_INTERRUPTS;
248 
249  // find time for next task execution
250  Scheduler_update_nexttime();
251 
252  // return success
253  return true;
254 
255  } // if free slot found
256 
257  // resume stored interrupt setting
258  RESUME_INTERRUPTS;
259 
260  } // loop over scheduler slots
261 
262  // did not change anything, thus no scheduler_update_nexttime neccessary
263  // no free slot found -> error
264  return false;
265 
266 } // Tasks_Add()
267 
268 
269 
270 bool Tasks_Remove(Task func)
271 {
272  // stop interrupts, store old setting
273  PAUSE_INTERRUPTS;
274 
275  // find function in scheduler table
276  for (uint8_t i = 0; i < _lasttask; i++)
277  {
278  // stop interrupts when accessing any element within the scheduler (also neccessary for if checks!), store old setting
279  PAUSE_INTERRUPTS;
280 
281  // function pointer found in list
282  if (SchedulingTable[i].func == func)
283  {
284  // remove task from scheduler table
285  SchedulingTable[i].func = NULL;
286  SchedulingTable[i].active = false;
287  SchedulingTable[i].running = false;
288  SchedulingTable[i].period = 0;
289  SchedulingTable[i].time = 0;
290 
291  // update _lasttask
292  if (i == (_lasttask - 1))
293  {
294  _lasttask--;
295  while(_lasttask != 0)
296  {
297  if(SchedulingTable[_lasttask - 1].func != NULL)
298  {
299  break;
300  }
301  _lasttask--;
302  }
303  }
304 
305  // resume stored interrupt setting
306  RESUME_INTERRUPTS;
307 
308  // find time for next task execution
309  Scheduler_update_nexttime();
310 
311  // return success
312  return true;
313 
314  } // if function found
315 
316  // resume stored interrupt setting
317  RESUME_INTERRUPTS;
318 
319  } // loop over scheduler slots
320 
321  // did not change anything, thus no scheduler_update_nexttime neccessary
322  // function not in scheduler -> error
323  return false;
324 
325 } // Tasks_Remove()
326 
327 
328 
329 bool Tasks_Delay(Task func, int16_t delay)
330 {
331  // stop interrupts, store old setting
332  PAUSE_INTERRUPTS;
333 
334  // Check range of delay
335  if (delay < 0)
336  return false;
337 
338  // Workaround for 1.024ms timer period of Arduino MEGA
339  #if defined(__AVR__)
340  delay = (uint16_t)(((((int32_t)delay) * 250) + 128) >> 8); // delay = delay / 1.024 <-- with up/down rounding
341  #endif
342 
343  // find function in scheduler table
344  for (uint8_t i = 0; i < _lasttask; i++)
345  {
346  // stop interrupts, store old setting
347  PAUSE_INTERRUPTS;
348 
349  // function pointer found in list
350  if (SchedulingTable[i].func == func)
351  {
352  // if task is currently running, delay next call
353  if (SchedulingTable[i].running == true)
354  SchedulingTable[i].time = SchedulingTable[i].time - SchedulingTable[i].period;
355 
356  // set time to next execution
357  SchedulingTable[i].time = _timebase + delay;
358 
359  // resume stored interrupt setting
360  RESUME_INTERRUPTS;
361 
362  // find time for next task execution
363  Scheduler_update_nexttime();
364 
365  // return success
366  return true;
367 
368  } // if function found
369 
370  // resume stored interrupt setting
371  RESUME_INTERRUPTS;
372 
373  } // loop over scheduler slots
374 
375  // did not change anything, thus no scheduler_update_nexttime neccessary
376  // function not in scheduler -> error
377  return false;
378 
379 } // Tasks_Delay()
380 
381 
382 
383 bool Tasks_SetState(Task func, bool state)
384 {
385  // stop interrupts, store old setting
386  PAUSE_INTERRUPTS;
387 
388  // find function in scheduler table
389  for (uint8_t i = 0; i < _lasttask; i++)
390  {
391  // stop interrupts when accessing any element within the scheduler (also neccessary for if checks!), store old setting
392  PAUSE_INTERRUPTS;
393 
394  // function pointer found in list
395  if(SchedulingTable[i].func == func)
396  {
397  // set new function state
398  SchedulingTable[i].active = state;
399  SchedulingTable[i].time = _timebase + SchedulingTable[i].period;
400 
401  // resume stored interrupt setting
402  RESUME_INTERRUPTS;
403 
404  // find time for next task execution
405  Scheduler_update_nexttime();
406 
407  // return success
408  return true;
409 
410  } // if function found
411 
412  // resume stored interrupt setting
413  RESUME_INTERRUPTS;
414 
415  } // loop over scheduler slots
416 
417  // did not change anything, thus no scheduler_update_nexttime neccessary
418  // function not in scheduler -> error
419  return false;
420 
421 } // Tasks_SetState()
422 
423 
424 
425 void Tasks_Start(void)
426 {
427  #if (TASKS_MEASURE_PIN)
428  pinMode(9, OUTPUT);
429  #endif
430 
431  // enable scheduler
432  SchedulingActive = true;
433  //_timebase = 0; // unwanted delay after resume, see time-print() output! -> likely delete
434 
435  _nexttime = _timebase; // Scheduler should perform a full check of all tasks after the next start
436 
437  // enable timer interrupt
438  #if defined(__AVR__)
439  TIMSK0 |= (1<<OCIE0A); // Enable OC0A Interrupt
440  #elif defined(__SAM3X8E__)
441  startTasksTimer(TC1, 0, TC3_IRQn, 1000); // TC1 channel 0, the IRQ for that channel and the desired frequency
442  #endif
443 
444  // find time for next task execution
445  Scheduler_update_nexttime();
446 
447 } // Tasks_Start()
448 
449 
450 
451 void Tasks_Pause(void)
452 {
453  // pause scheduler
454  SchedulingActive = false;
455  //_timebase = 0; // unwanted delay after resume, see time-print() output! -> likely delete
456 
457  // disable timer interrupt
458  #if defined(__AVR__)
459  TIMSK0 &= ~(1<<OCIE0A); //Disable OC0A Interrupt
460  #elif defined(__SAM3X8E__)
461  NVIC_DisableIRQ(TC3_IRQn);
462  #endif
463 
464 } // Tasks_Pause()
465 
466 
467 
468 /**************************************/
469 /******* start skip in doxygen ********/
470 /**************************************/
472 
473 #if defined(__AVR__)
474  ISR(TIMER0_COMPA_vect) // Timer0 interrupt is called each 1.024ms before the OVL interrupt used for millis()
475 #elif defined(__SAM3X8E__)
476  void TC3_Handler(void)
477 #else
478  void Scheduler_dummy_handler(void) // avoid compiler error for unsupported boards
479 #endif
480 {
481  uint8_t i;
482 
483  // measure speed via GPIO
484  #if (TASKS_MEASURE_PIN)
485  SET_PIN;
486  #endif
487 
488  #if defined(__SAM3X8E__)
489  TC_GetStatus(TC1, 0);
490  #endif
491 
492  // Skip if scheduling was stopped or is in the process of being stopped
493  if (SchedulingActive == false) {
494  #if (TASKS_MEASURE_PIN) // measure speed via GPIO
495  CLEAR_PIN;
496  #endif
497  return;
498  }
499 
500  // increase 1ms counter
501  _timebase++;
502 
503  // no task is pending -> return immediately
504  if ((int16_t)(_nexttime - _timebase) > 0) {
505  #if (TASKS_MEASURE_PIN) // measure speed via GPIO
506  CLEAR_PIN;
507  #endif
508  return;
509  }
510 
511  // loop over scheduler slots
512  for(i = 0; i < _lasttask; i++)
513  {
514  // disable interrupts
515  noInterrupts();
516 
517  // function pointer found in list, function is active and not running (arguments ordered to provide maximum speed
518  if ((SchedulingTable[i].active == true) && (SchedulingTable[i].running == false) && (SchedulingTable[i].func != NULL))
519  {
520  // function period has passed
521  if((int16_t)(SchedulingTable[i].time - _timebase) <= 0)
522  {
523  // execute task
524  SchedulingTable[i].running = true; // avoid dual function call
525  SchedulingTable[i].time = _timebase + SchedulingTable[i].period; // set time of next call
526 
527  // re-enable interrupts
528  interrupts();
529 
530  // execute function
531  SchedulingTable[i].func();
532 
533  // disable interrupts
534  noInterrupts();
535 
536  // re-allow function call by scheduler
537  SchedulingTable[i].running = false;
538 
539  // if function period is 0, remove it from scheduler after execution
540  if(SchedulingTable[i].period == 0)
541  {
542  SchedulingTable[i].func = NULL;
543  }
544 
545  // re-enable interrupts
546  interrupts();
547  } // if function period has passed
548  } // if function found
549 
550  // re-enable interrupts
551  interrupts();
552 
553  } // loop over scheduler slots
554 
555  // find time for next task execution
556  Scheduler_update_nexttime();
557 
558  // measure speed viaGPIO
559  #if (TASKS_MEASURE_PIN)
560  CLEAR_PIN;
561  #endif
562 
563 } // ISR()
564 
565 
567 /************************************/
568 /******* end skip in doxygen ********/
569 /************************************/
void(* Task)(void)
Example prototype for a function than can be executed as a task.
Definition: Tasks.h:62
void Tasks_Pause(void)
Pause the task scheduler.
Definition: Tasks.cpp:451
void Tasks_Start(void)
Start the task scheduler.
Definition: Tasks.cpp:425
bool Tasks_SetState(Task func, bool state)
Enable or disable the execution of a task.
Definition: Tasks.cpp:383
bool Tasks_Add(Task func, int16_t period, int16_t delay)
Add a task to the task scheduler.
Definition: Tasks.cpp:182
#define MAX_TASK_CNT
Maximum number of parallel tasks.
Definition: Tasks.h:54
void Tasks_Init(void)
Initialize timer and reset the tasks scheduler at first call.
Definition: Tasks.cpp:139
bool Tasks_Delay(Task func, int16_t delay)
Delay execution of a task.
Definition: Tasks.cpp:329
Library providing a simple task scheduler for multitasking.
void Tasks_Clear(void)
Reset the tasks schedulder.
Definition: Tasks.cpp:153
bool Tasks_Remove(Task func)
Remove a task from the task scheduler.
Definition: Tasks.cpp:270