KM3NeT CLB  2.0
KM3NeT CLB v2 Embedded Software
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
Using the Scheduler

The scheduler is an important piece in the embedded software.

The main function of the sys module will invoke schdRunForever() at the end. schdRunForever(), as the name says it, never returns.

This implies that any code you wish to run (called a task) periodically or at some event, you will need to add to the scheduler. Examples are network handlers, monitoring functions, software timers.

First a task needs to register itself. Usually this is done in the init routine of the specific module. Than it can optionally schedule itself to be run at an interval.

The following piece of code shows how to schedule a task which says 'one second please' every second:

#include <stdio.h>
#include "kernel/scheduler.h" // include the scheduler header file
#include "kernel/err.h"
int taskId;
void saySecond()
{
puts("One second please...");
}
void initSecond()
{
if (schdRegister(saySecond, false, &taskId)) // register with scheduler
{
schdRunPeriodic(taskId, 1000); // when succesful, configure interval
} else
{
puts("Task could not be scheduled!"); // on failure
errPrint(true); // print the error
}
}

What is important to note is that the module is a simple collaborative multitasking scheduler. This means that if you do not terminte your code, you will prevent other tasks from running, so never schedule a task to do this:

void myTask()
{
while (1)
{
// ... your code here
};
}

It is however possible to schedule a task to run for as long as it can, but it is not adviced:

void myTask()
{
while (!schdTaskPending())
{
// ... your code goes here.
};
}

The scheduler and IRQs

Another use for the scheduler is to prevent long-running IRQ handlers. Since no other IRQs will be serviced as long as your IRQ handler is running, you do not wish to block the queue when a long running task needs to be performed on the data. For this we will use the schdRun(taskId) function, which schedules the task for running at a later time.

For example, we have a fictional digitizer (ADC) which uses two buffers. Each second it gives a data ready IRQ, and than the data from one buffer can be processed, while it continues writing in the other.

#include "lm32soc/irq.h"
int averagerTaskId;
int outstanding = 0;
// task averages data and stores the total.
void averageData()
{
int i;
uint32_t total = 0;
for (i = 0; i < DIGITIZER_SAMPLES; ++i)
{
total += DIGITIZER->SAMPLE[i];
}
total /= DIGITIZER_SAMPLES;
storeTotal(total); // implemented somewhere else.
}
// called in the initApp() function.
void initDigitizer()
{
// schedule this as high priority, as we want it to run asap.
schdRegister(averageData, true, &averagerTaskId);
}
// this is the actual IRQ handler. We don't want to average in here!
void IRQ_HANDLER(DIGITIZER_IRQ)
{
schdRun(averagerTaskId);
DIGITIZER->INTC |= DIGITIZER_IRQ_DATA_READY; // clear interrupt
}

This code will be executed as is shown in the image below:

  1. The task is registered. This must always be done before it can be used
  2. The IRQ is issued somewhere during the run of the application.
  3. The task is executed at some later point.
scheduler.png
Example of IRQ handling
Attention
It is possible for a second IRQ to arrive before the application was done with averaging. Though this can not be prevented, it can be detected. A additional variable can be created which tracks if this is the case. However, is so, it is probably a design issue, and an analysis should be done of why the data was not processed before the next IRQ. Possibly another tasks is not keeping itself to the procedure, or the LM32's processing capabilities are just not enough.