Task scheduler library  1.3
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 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 struct SchedulingStruct SchedulingTable[MAX_TASK_CNT] = { {(Task)NULL, false, false, 0, 0} }; // 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  {
101  if ((SchedulingTable[i].active == true) && (SchedulingTable[i].func != NULL))
102  {
103  //Serial.print(i); Serial.print(" "); Serial.println(SchedulingTable[i].time);
104 
105  if ((int16_t)(SchedulingTable[i].time - _nexttime) < 0)
106  {
107  _nexttime = SchedulingTable[i].time;
108  }
109  }
110  }
111 
112  //Serial.print("timebase: "); Serial.println(_timebase);
113  //Serial.print("nexttime: "); Serial.println(_nexttime);
114  //Serial.println();
115 
116  //Serial.print(_timebase); Serial.print(" "); Serial.println(_nexttime - _timebase);
117 
118  // resume stored interrupt setting
119  RESUME_INTERRUPTS;
120 
121 } // Scheduler_update_nexttime()
122 
123 
125 /************************************/
126 /******* end skip in doxygen ********/
127 /************************************/
128 
129 
130 void Tasks_Init(void)
131 {
132  /*static bool flagDone = false;
133 
134  // clear tasks schedule only for first call to avoid issues when calling multiple times
135  if (flagDone == false) {
136  flagDone = true;
137  Tasks_Clear();
138  }*/
139 
140 } // Tasks_Init()
141 
142 
143 
144 void Tasks_Clear(void)
145 {
146  uint8_t i;
147 
148  // stop interrupts, store old setting
149  PAUSE_INTERRUPTS;
150 
151  // init scheduler
152  SchedulingActive = false;
153  _timebase = 0;
154  _nexttime = 0;
155  _lasttask = 0;
156  for(i = 0; i < MAX_TASK_CNT; i++)
157  {
158  //Reset scheduling table
159  SchedulingTable[i].func = NULL;
160  SchedulingTable[i].active = false;
161  SchedulingTable[i].running = false;
162  SchedulingTable[i].period = 0;
163  SchedulingTable[i].time = 0;
164  } // loop over scheduler slots
165 
166  // resume stored interrupt setting
167  RESUME_INTERRUPTS;
168 
169 } // Tasks_Clear()
170 
171 
172 
173 bool Tasks_Add(Task func, int16_t period, int16_t delay)
174 {
175  // Check range of period and delay
176  if ((period < 0) || (delay < 0))
177  return false;
178 
179  // workaround for 1.024ms timer period of Arduino ATMega
180  #if defined(__AVR__)
181  delay = (uint16_t)(((((int32_t)delay) * 250) + 128) >> 8); // delay = delay / 1.024 <-- with up/down rounding
182  period = (uint16_t)(((((int32_t)period) * 250) + 128) >> 8); // period = period / 1.024 <-- with up/down rounding
183  #endif
184 
185  // Check if task already exists and update it in this case
186  for(uint8_t i = 0; i < _lasttask; i++)
187  {
188  // stop interrupts when accessing any element within the scheduler (also neccessary for if checks!), store old setting
189  PAUSE_INTERRUPTS;
190 
191  // same function found
192  if (SchedulingTable[i].func == func)
193  {
194  SchedulingTable[i].active = true;
195  SchedulingTable[i].running = false;
196  SchedulingTable[i].period = period;
197  SchedulingTable[i].time = _timebase + delay;
198 
199  // resume stored interrupt setting
200  RESUME_INTERRUPTS;
201 
202  // find time for next task execution
203  Scheduler_update_nexttime();
204 
205  // return success
206  return true;
207  }
208 
209  // resume stored interrupt setting
210  RESUME_INTERRUPTS;
211 
212  } // loop over scheduler slots
213 
214  // find free scheduler slot
215  for (uint8_t i = 0; i < MAX_TASK_CNT; i++)
216  {
217  // stop interrupts when accessing any element within the scheduler (also neccessary for if checks!), store old setting
218  PAUSE_INTERRUPTS;
219 
220  // free slot found
221  if (SchedulingTable[i].func == NULL)
222  {
223  // add task to scheduler table
224  SchedulingTable[i].func = func;
225  SchedulingTable[i].active = true;
226  SchedulingTable[i].running = false;
227  SchedulingTable[i].period = period;
228  SchedulingTable[i].time = _timebase + delay;
229 
230  // update _lasttask
231  if (i >= _lasttask)
232  _lasttask = i + 1;
233 
234  // resume stored interrupt setting
235  RESUME_INTERRUPTS;
236 
237  // find time for next task execution
238  Scheduler_update_nexttime();
239 
240  // return success
241  return true;
242 
243  } // if free slot found
244 
245  // resume stored interrupt setting
246  RESUME_INTERRUPTS;
247 
248  } // loop over scheduler slots
249 
250  // did not change anything, thus no scheduler_update_nexttime neccessary
251  // no free slot found -> error
252  return false;
253 
254 } // Tasks_Add()
255 
256 
257 
258 bool Tasks_Remove(Task func)
259 {
260  // find function in scheduler table
261  for (uint8_t i = 0; i < _lasttask; i++)
262  {
263  // stop interrupts when accessing any element within the scheduler (also neccessary for if checks!), store old setting
264  PAUSE_INTERRUPTS;
265 
266  // function pointer found in list
267  if (SchedulingTable[i].func == func)
268  {
269  // remove task from scheduler table
270  SchedulingTable[i].func = NULL;
271  SchedulingTable[i].active = false;
272  SchedulingTable[i].running = false;
273  SchedulingTable[i].period = 0;
274  SchedulingTable[i].time = 0;
275 
276  // update _lasttask
277  if (i == (_lasttask - 1))
278  {
279  _lasttask--;
280  while(_lasttask != 0)
281  {
282  if(SchedulingTable[_lasttask - 1].func != NULL)
283  {
284  break;
285  }
286  _lasttask--;
287  }
288  }
289 
290  // resume stored interrupt setting
291  RESUME_INTERRUPTS;
292 
293  // find time for next task execution
294  Scheduler_update_nexttime();
295 
296  // return success
297  return true;
298 
299  } // if function found
300 
301  // resume stored interrupt setting
302  RESUME_INTERRUPTS;
303 
304  } // loop over scheduler slots
305 
306  // did not change anything, thus no scheduler_update_nexttime neccessary
307  // function not in scheduler -> error
308  return false;
309 
310 } // Tasks_Remove()
311 
312 
313 
314 bool Tasks_Delay(Task func, int16_t delay)
315 {
316  // Check range of delay
317  if (delay < 0)
318  return false;
319 
320  // Workaround for 1.024ms timer period of Arduino MEGA
321  #if defined(__AVR__)
322  delay = (uint16_t)(((((int32_t)delay) * 250) + 128) >> 8); // delay = delay / 1.024 <-- with up/down rounding
323  #endif
324 
325  // find function in scheduler table
326  for (uint8_t i = 0; i < _lasttask; i++)
327  {
328  // stop interrupts, store old setting
329  PAUSE_INTERRUPTS;
330 
331  // function pointer found in list
332  if (SchedulingTable[i].func == func)
333  {
334  // if task is currently running, delay next call
335  if (SchedulingTable[i].running == true)
336  SchedulingTable[i].time = SchedulingTable[i].time - SchedulingTable[i].period;
337 
338  // set time to next execution
339  SchedulingTable[i].time = _timebase + delay;
340 
341  // resume stored interrupt setting
342  RESUME_INTERRUPTS;
343 
344  // find time for next task execution
345  Scheduler_update_nexttime();
346 
347  // return success
348  return true;
349 
350  } // if function found
351 
352  // resume stored interrupt setting
353  RESUME_INTERRUPTS;
354 
355  } // loop over scheduler slots
356 
357  // did not change anything, thus no scheduler_update_nexttime neccessary
358  // function not in scheduler -> error
359  return false;
360 
361 } // Tasks_Delay()
362 
363 
364 
365 bool Tasks_SetState(Task func, bool state)
366 {
367  // find function in scheduler table
368  for (uint8_t i = 0; i < _lasttask; i++)
369  {
370  // stop interrupts when accessing any element within the scheduler (also neccessary for if checks!), store old setting
371  PAUSE_INTERRUPTS;
372 
373  // function pointer found in list
374  if(SchedulingTable[i].func == func)
375  {
376  // set new function state
377  SchedulingTable[i].active = state;
378  SchedulingTable[i].time = _timebase + SchedulingTable[i].period;
379 
380  // resume stored interrupt setting
381  RESUME_INTERRUPTS;
382 
383  // find time for next task execution
384  Scheduler_update_nexttime();
385 
386  // return success
387  return true;
388 
389  } // if function found
390 
391  // resume stored interrupt setting
392  RESUME_INTERRUPTS;
393 
394  } // loop over scheduler slots
395 
396  // did not change anything, thus no scheduler_update_nexttime neccessary
397  // function not in scheduler -> error
398  return false;
399 
400 } // Tasks_SetState()
401 
402 
403 
404 void Tasks_Start(void)
405 {
406  #if (TASKS_MEASURE_PIN)
407  pinMode(9, OUTPUT);
408  #endif
409 
410  // enable scheduler
411  SchedulingActive = true;
412  //_timebase = 0; // unwanted delay after resume, see time-print() output! -> likely delete
413 
414  _nexttime = _timebase; // Scheduler should perform a full check of all tasks after the next start
415 
416  // enable timer interrupt
417  #if defined(__AVR__)
418  TIMSK0 |= (1<<OCIE0A); // Enable OC0A Interrupt
419  #elif defined(__SAM3X8E__)
420  startTasksTimer(TC1, 0, TC3_IRQn, 1000); // TC1 channel 0, the IRQ for that channel and the desired frequency
421  #endif
422 
423  // find time for next task execution
424  Scheduler_update_nexttime();
425 
426 } // Tasks_Start()
427 
428 
429 
430 void Tasks_Pause(void)
431 {
432  // pause scheduler
433  SchedulingActive = false;
434  //_timebase = 0; // unwanted delay after resume, see time-print() output! -> likely delete
435 
436  // disable timer interrupt
437  #if defined(__AVR__)
438  TIMSK0 &= ~(1<<OCIE0A); //Disable OC0A Interrupt
439  #elif defined(__SAM3X8E__)
440  NVIC_DisableIRQ(TC3_IRQn);
441  #endif
442 
443 } // Tasks_Pause()
444 
445 
446 
447 /**************************************/
448 /******* start skip in doxygen ********/
449 /**************************************/
451 
452 #if defined(__AVR__)
453  ISR(TIMER0_COMPA_vect) // Timer0 interrupt is called each 1.024ms before the OVL interrupt used for millis()
454 #elif defined(__SAM3X8E__)
455  void TC3_Handler(void)
456 #else
457  void Scheduler_dummy_handler(void) // avoid compiler error for unsupported boards
458 #endif
459 {
460  uint8_t i;
461 
462  // measure speed via GPIO
463  #if (TASKS_MEASURE_PIN)
464  SET_PIN;
465  #endif
466 
467  #if defined(__SAM3X8E__)
468  TC1->TC_CHANNEL[0].TC_SR; //Read status register to delete status flags
469  #endif
470 
471  // Skip if scheduling was stopped or is in the process of being stopped
472  if (SchedulingActive == false) {
473  #if (TASKS_MEASURE_PIN) // measure speed via GPIO
474  CLEAR_PIN;
475  #endif
476  return;
477  }
478 
479  // increase 1ms counter
480  _timebase++;
481 
482  // no task is pending -> return immediately
483  if ((int16_t)(_nexttime - _timebase) > 0) {
484  #if (TASKS_MEASURE_PIN) // measure speed via GPIO
485  CLEAR_PIN;
486  #endif
487  return;
488  }
489 
490  // loop over scheduler slots
491  for(i = 0; i < _lasttask; i++)
492  {
493  // disable interrupts
494  noInterrupts();
495 
496  // function pointer found in list, function is active and not running (arguments ordered to provide maximum speed
497  if ((SchedulingTable[i].active == true) && (SchedulingTable[i].running == false) && (SchedulingTable[i].func != NULL))
498  {
499  // function period has passed
500  if((int16_t)(SchedulingTable[i].time - _timebase) <= 0)
501  {
502  // execute task
503  SchedulingTable[i].running = true; // avoid dual function call
504  SchedulingTable[i].time = _timebase + SchedulingTable[i].period; // set time of next call
505 
506  // re-enable interrupts
507  interrupts();
508 
509  // execute function
510  SchedulingTable[i].func();
511 
512  // disable interrupts
513  noInterrupts();
514 
515  // re-allow function call by scheduler
516  SchedulingTable[i].running = false;
517 
518  // if function period is 0, remove it from scheduler after execution
519  if(SchedulingTable[i].period == 0)
520  {
521  SchedulingTable[i].func = NULL;
522  }
523 
524  // re-enable interrupts
525  interrupts();
526  } // if function period has passed
527  } // if function found
528 
529  // re-enable interrupts
530  interrupts();
531 
532  } // loop over scheduler slots
533 
534  // find time for next task execution
535  Scheduler_update_nexttime();
536 
537  // measure speed viaGPIO
538  #if (TASKS_MEASURE_PIN)
539  CLEAR_PIN;
540  #endif
541 
542 } // ISR()
543 
544 
546 /************************************/
547 /******* end skip in doxygen ********/
548 /************************************/
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:430
void Tasks_Start(void)
Start the task scheduler.
Definition: Tasks.cpp:404
bool Tasks_SetState(Task func, bool state)
Enable or disable the execution of a task.
Definition: Tasks.cpp:365
bool Tasks_Add(Task func, int16_t period, int16_t delay)
Add a task to the task scheduler.
Definition: Tasks.cpp:173
#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:130
bool Tasks_Delay(Task func, int16_t delay)
Delay execution of a task.
Definition: Tasks.cpp:314
Library providing a simple task scheduler for multitasking.
void Tasks_Clear(void)
Reset the tasks schedulder.
Definition: Tasks.cpp:144
bool Tasks_Remove(Task func)
Remove a task from the task scheduler.
Definition: Tasks.cpp:258