Arduino Playground is read-only starting December 31st, 2018. For more info please look at this Forum Post

TimerScheduler

 
  1. /*
  2.  * Randall Schmidt
  3.  * A task scheduler that keeps an array of tasks
  4.  * and uses timer interrupts at regular intervals
  5.  * to execute those tasks at approximately the time
  6.  * specified by the caller. In this implementation
  7.  * the caller must know an upper limit for how many
  8.  * tasks will be scheduled at once. This kind of
  9.  * scheme works best with a small maximum number of
  10.  * scheduled tasks and a low time resolution.
  11.  * Otherwise you may be using more clock cycles
  12.  * than you'd like.
  13.  *
  14.  * Thanks to amandaghassaei @ https://www.instructables.com/id/Arduino-Timer-Interrupts/step2/Structuring-Timer-Interrupts/
  15.  * for the timer setup code.
  16.  */
  17.  
  18. const int prescaler = 1024; // How many clock cycles it takes before the underlying timer (Timer1) increments
  19. const int timerHertz = 16000000 / prescaler; // How many times per second the underlying timer (Timer1) increments. 16,000,000 is the Arduino's clock speed
  20.  
  21. int maxTasks; // The maximum number of tasks that can be scheduled at once
  22. unsigned long tickIntervalMs; // The number of milliseconds between two instances of the scheduler looking for due tasks to execute
  23. int compareMatchRegister; // When the underlying timer (Timer1) hits this value, it interrupts and then resets to zero
  24.  
  25. // A task consists of:
  26. // a pointer to a function that returns void and accepts a void* argument,
  27. // that void* argument,
  28. // and how many ticks before the task should be executed.
  29. struct Task
  30. {
  31.         void (*action)(void*);
  32.         void* argument;
  33.         unsigned long delayTicks;
  34. };
  35.  
  36. struct Task** tasks;
  37.  
  38. // _maxTasks = you cannot have more than this many tasks scheduled
  39. // at any given time. Additional tasks will be ignored. If you have
  40. // the maximum number of tasks scheduled, you must wait for one to
  41. // execute. Then you can schedule another task.
  42. // _tickIntervalMs = how often in milliseconds the scheduler
  43. // checks to see if any tasks are due to execute. The higher this number,
  44. // the less accurate the scheduler (your tasks won't be run exactly when
  45. // you want them to) but the overhead is also lower. A lower number makes
  46. // the scheduler more accurate but introduces more overhead.
  47. // You probably only want to call this function once
  48. void setupTaskScheduler(int _maxTasks, unsigned long _tickIntervalMs)
  49. {
  50.   // Get rid of any existing tasks.
  51.   if (tasks != NULL)
  52.   {
  53.     for (int i = 0; i < maxTasks; i++)
  54.     {
  55.       Task* task = tasks[i];
  56.       free(task);
  57.     }
  58.  
  59.     free(tasks);
  60.   }
  61.  
  62.   tickIntervalMs = _tickIntervalMs;
  63.   double interruptHertz = 1000 / _tickIntervalMs; // interruptHertz = How many times per second the caller is asking to check for and execute due tasks
  64.   maxTasks = _maxTasks;
  65.   tasks = (Task**)calloc(maxTasks, sizeof(Task*));
  66.  
  67.   compareMatchRegister = timerHertz / interruptHertz; // compareMatchRegister = when the underlying timer (Timer1) hits this value, it interrupts and then resets to zero
  68. }
  69.  
  70. int scheduleTimer1Task(void (*action)(void*), void* argument, unsigned long delayMs)
  71. {
  72.   // Look for an empty spot in the task array and put the new task there
  73.   for (int i = 0; i < maxTasks; i++)
  74.   {
  75.     if (tasks[i] == NULL)
  76.     {
  77.       Task* ptr = (Task*)malloc(sizeof(struct Task));
  78.       ptr->action = action;
  79.       ptr->argument = argument;
  80.       ptr->delayTicks = delayMs / tickIntervalMs;
  81.       tasks[i] = ptr;
  82.       return true; // Found an empty spot in the task list for this task
  83.     }
  84.   }
  85.  
  86.   return false; // No spot for this task found, scheduling request rejected
  87. }
  88.  
  89. // Thanks to amandaghassaei @ https://www.instructables.com/id/Arduino-Timer-Interrupts/step2/Structuring-Timer-Interrupts/ for the code in this function
  90. void startSchedulerTicking()
  91. {
  92.   noInterrupts();
  93.  
  94.   TCCR1A = 0;// set entire TCCR1A register to 0
  95.   TCCR1B = 0;// same for TCCR1B
  96.   TCNT1  = 0;//initialize counter value to 0
  97.   // set compare match register for 1hz increments
  98.   OCR1A = compareMatchRegister;// = (16*10^6) / (1*1024) - 1 (must be <65536)
  99.   // turn on CTC mode
  100.   TCCR1B |= (1 << WGM12);
  101.   // Set CS10 and CS12 bits for 1024 prescaler
  102.   TCCR1B |= (1 << CS12) | (1 << CS10);  
  103.   // enable timer compare interrupt
  104.   TIMSK1 |= (1 << OCIE1A);
  105.  
  106.   interrupts();
  107. }
  108.  
  109. // This is the timer interrupt
  110. ISR( TIMER1_COMPA_vect )
  111. {
  112.   // Look for tasks that are due to be executed
  113.   for (int i = 0; i < maxTasks; i++)
  114.   {
  115.     if (tasks[i] != NULL)
  116.     {
  117.       Task* task = tasks[i];
  118.       unsigned long ticks = task->delayTicks;
  119.  
  120.       if (ticks == 0) // It's due!
  121.       {
  122.          void (*action)(void*) = task->action;
  123.          void* argument = task->argument;
  124.          free(task);
  125.          tasks[i] = NULL;
  126.          action(argument);
  127.        }
  128.       else
  129.       {
  130.         task->delayTicks--; // If the task it not due, decrement its remaining tick count
  131.       }
  132.     }
  133.   }
  134. }
  135.  
  136. void tellMeYouLoveMe(void*)
  137. {
  138.   Serial.print("I love you! The time is: ");
  139.   Serial.println(millis());
  140.   scheduleTimer1Task(&tellMeYouLoveMe, NULL, 5000); // Keep calling this method every 5 seconds just for the heck of it
  141. }
  142.  
  143. void setup()
  144. {
  145.   Serial.begin(9600);
  146.  
  147.   setupTaskScheduler(5, 100); // Setup the task scheduler with a maximum task count of 5 and a tick rate of 10 Hz
  148.   startSchedulerTicking();
  149.   scheduleTimer1Task(&tellMeYouLoveMe, NULL, 5000); // Schedule a call to tellMeYouLoveMe() to occur in five seconds
  150. }
  151.  
  152. void loop()
  153. {
  154.  
  155. }