Special usage

Special tasks

Timer task

The timer task is implemented in TaskTimer class. A timer task provides some internal mechanisms allowing you to execute some code at a specific milliseconds interval. The difference with a simple delay loop is that the timer task tries to compensate the time consumed by your code.

Example of a delay loop:

void fnc1()
{
  for(;;)
  {
    task()->sleep(100);
    //some code here
  }
}

The above example illustrates the implementation of a bad timer. Suppose the sleep function takes an extra of 10 milliseconds (the time interval between the wake up of the task (new state: StQueuing) and the effective code execution (new state: StRunning)) and the code replaced by the comment "some code here" takes 50ms.

Then if t1 is just before sleep and t1 = 0:

But you expected: t1 = 0, t2 = 100, t3 = 200!

That's possible with a timer task. See the next example to have an overview of how use it.

#include <os48.h>

using namespace os48;

Scheduler* scheduler = Scheduler::get();
TaskTimer* goodTaskTimer = NULL;

void setup() {
  Serial.begin(9600);
  goodTaskTimer = scheduler->createTaskTimer(&goodFnc, 60, 1000); //period of 1000ms 

  scheduler->start();
}

bool goodFnc()
{
  uint32_t m;
  OS48_NO_CS_BLOCK
  {
    m = millis();
    Serial.print ("Good timer task:\t");
    Serial.println(m);
  }

  return true;
}

void loop() {}

Unlike a normal task, you have to return a boolean with true to continue the timer or false to stop it.

warning A timer task can not ensure you your code will be executed exactly at the specified interval. This timer is still a software timer! You can use hardware timers provided by the MCU to have a better accuracy. See Timer1 for example. A timer task can ensure only that the task will be woken up exactly at the specified interval (state is set to ::StQueuing).

The function to get the current timer task is taskT()-> instead of task()->.

Work queue task

A work queue task allows you to execute some fast code in an uncertain future. TaskWorkQueue is the class that implements this concept. This task can offload some code from other tasks whose time is not an important criterion. Create a work object with a C function as argurment. Then call TaskWorkQueue::addWork(Work& work) to add the work to the waiting queue.

Work is an object to monitoring the execution of the function passed to the constructor. You can cancel a work through this object or get its state. A work is attached to a function returning a databag_t type. That means you can return 32bits data or less including string, integer, float, data pointer + size.

In the same way as the message box, you can limit the number of works in the queue with a semaphore.

You should instanciate this object dynamically with the new operator or the malloc function and delete it when the execution will be done. You can also use the memory pool by setting the rights values in Advanced_parameters.h.

The function should be fast and should return. Avoid long loops, delay functions, synchronization functions... Otherwise the next work won't be able to start.

The function to get the current work queue task is taskWQ()-> instead of task()->.

#include <os48.h>

using namespace os48;

Scheduler* scheduler = Scheduler::get();
Task* task1 = NULL;
Task* task2 = NULL;
TaskWorkQueue* taskWorkQueue = NULL;

void setup() {
  Serial.begin(9600);

  task1 = scheduler->createTask(&func1, 60);
  task2 = scheduler->createTask(&func2, 60);
  taskWorkQueue = scheduler->createTaskWorkQueue(60, PrLow);

  randomSeed(millis());

  scheduler->start();
}

Sync sync1;
Work* w1 = NULL;

void func1()
{
  for (;;)
  {
    Serial.println("Wait...");

    //simuates a long process
    task()->sleep(random(3000, 6000));

    OS48_NO_CS_BLOCK
    {
      Serial.println("-------------------------------");
      Serial.println("task 1: ADDING a work to the work queue task...");
    }

    OS48_NO_CS_BLOCK
    {
      w1 = new Work(&work1, &sync1);
      taskWorkQueue->addWork(*w1);
    }

    OS48_NO_CS_BLOCK
    {
      Serial.println("task 1: waiting for work completion...");
    }

    w1->join();

    OS48_NO_CS_BLOCK
    {
      if (w1->getState() == WkStTerminated)
      {
        Serial.println("task 1: work done :).");
        Serial.print("task 1: result: ");
        Serial.println(w1->getResult().bInt16);
      }
      else if (w1->getState() == WkStCancelled)
      {
        Serial.println("task 1: work has been cancelled by task 2 :(.");
      }

      delete w1;
      w1 = NULL;
    }
  }
}

void func2()
{
  for (;;)
  {
    //simulates a long process
    task()->sleep(random(50, 2000));

    OS48_NO_CS_BLOCK
    {
      if (w1 != NULL)
        w1->cancel();
    }
  }
}

databag_t work1()
{
  //simuates a SHORT process
  task()->sleep(random(300, 1000));

  OS48_NO_CS_BLOCK
  {
    if (w1 == NULL || w1->isCancellationRequested()) //cancellation token has been generated from task 2
    {
      databag_t res;
      res.bInt16 = -1;
      return (databag_t) { .bInt16 = -1};
    }

    Serial.println("Execution of the work");
  }

  return (databag_t) { .bInt16 = 42};
}

void loop() {}

Advanced configuration

The kernel is provided with a default configuration enabling a lot of features. You may change some parameters in order to save memory or CPU time. You can take a look to the file Advanced_parameters.h in order to have an overview of parameters and their default values. If you have installed this kernel as a lib of the Arduino IDE, you can find the file in the libraries folder located in your personal directory (MyDocuments on Windows for example).

Redefine the Arduino yield function

You can redefine the yield function of the Arduino lib because yield() is defined as a weak function. For example, the function delay() calls inside the yield() function.

#include <os48.h>

using namespace os48;

Scheduler* scheduler = Scheduler::get();

Task* task1 = NULL;
Task* task2 = NULL;

void setup() {
  Serial.begin(9600);
  Serial.println("Creating tasks...");

  task1 = scheduler->createTask(&task1Func, 80); //id 1
  task2 = scheduler->createTask(&task2Func, 80); //id 2

  scheduler->setSchedulingPolicy(SchPolicyCoop);

  Serial.println("Starting...");

  scheduler->start();
}

//UNCOMMENT that!
/*void yield()
{
  scheduler->yield();
}*/

void task1Func()
{
  for (;;)
  {
    Serial.println("task1");
    delay(2000);
  }
}

void task2Func()
{
  delay(1000);
  for (;;)
  {
    Serial.println("task2");
    delay(2000);
  }
}

void loop() {}

In the above example, if you let the comments, the task 1 won't be able to start. It's your choice to decide if you allow yields inside the Arduino functions or if you prefer call the Scheduler::yield() function after the delay calls. This is an implementation choice.