Task vs timer

Perhaps a basic question but when would you use a timer over a task? say, you want to constantly read sensor data every X seconds. You could have a task with relevant read functions inside it and constantly read it in a loop (not quite sure about how to read at certain intervals though) whereas, with a timer, you could specify the callback along with the rate at which it’d be invoked.

Valid question.

If you want periodic actions, you might as well create a separate task. All FreeRTOS timers are serviced by one dedicated timer task, so what you gain by using a timer (if what you need to do periodically is lean and simple enough) is that you don’t “waste” the resources of a dedicated task (stack space, PCB and so on).

In addition to RAc’s explanation reading a sensor, doing some post-processing and perhaps forwarding the value to another task might be a lengthy operation which might not be suited done as timer callback. Because it’d stall the timer task state machine and other timers running might miss their deadlines etc.
A good rule is that timer callbacks should be short and don’t block, of course. Pretty similar to ISRs.

I think a simple guideline is that IF the operations really are suitable for a timer callback, then that is a much simpler and lower cost solution. But, if the operation is a bit more involved, it might not be suitable for a timer callback and thus need to be a task.

The basic rules for a timer callback is that they must not block, or take a ‘significant’ amount of time to execute (that is a bit of a fuzzy definition, as it depends on other usages of timers, and pending operations). The priority of the operations should be compatible with the priority of the timer task, which I almost always consider to be near the top of my task priority list.

If reading the sensor takes a bit of time, that operation might not be good for a timer, unless the timer just kicks off the operation with a simple GPIO operation, and then the sensor reading being finished returns an interrupt which triggers a processing task/callback.

Thanks.
In my current implementation, I am processing the raw data inside an ISR, along with setting a boolean flag.
Following read() is what I call inside the thread periodically. I’m using an observer pattern that notifies its subscribers of the change in the value which take care of storing it, so not quite IPC stuff

uint16_t Sensor::read()
{
     m_xfer_done = false;
    ret_code_t err_code = nrf_drv_twi_rx(&m_twi, ADDR, _buffer, 2);
    
    APP_ERROR_CHECK(err_code);
    while(!m_xfer_done);    // m_xfer_done flag is set within IRQ -- IRQ is done after this
   
    notify();                   // notify observers of the change in value
    return _value;	   // processed value         
}

so instead of calling read() inside a thread, it would still not make sense to call it via a timer?
Actually I read your last point again, and you mentioned it doesn’t block.

read() does block till the flag is set inside an IRQ so that may be a convincing point to not use a timer?

Lastly, how would you specify the rate at which the task should run? Say you want to read every 5s as opposed to however many seconds a thread runs at? I reckon it’s configured via configTICK_RATE_HZ? But what if you want different frequencies for different tasks?

Rather than having the task just spin wait on the flag variable, why not have it block on a notification given by the ISR.

Note, configTICK_RATE_HZ sets the base rate of the system tick for timing. This will be the rate that tasks of the same priority will switch if the are both ready, but it is best for tasks to block so they only run when they really have something to do.

To run task at a given rate, normally you put in the task loop a call to vTaskDelay or vTaskDelayUntil to make it block until the next time it should run. You can also use a timer to activate some code at a programmed period, but as I mentioned, timer callbacks should be short and non-blocking.

1 Like

thanks for your input.

Rather than having the task just spin wait on the flag variable, why not have it block on a notification given by the ISR.

read() needs to be invoked to initiate the interrupt.

void task()
{
   sensor.transfer();   // send register info
   while(true)
   { 
       uint16_t data = sensor.read(); 
   }
}

configTICK_RATE_HZ sets the base rate of the system tick for timing. This will be the rate that tasks of the same priority will switch if the are both ready

tasks of the same priority? so if there are two tasks of the same priority, if you modify configTICK_RATE_HZ, that would apply to both the tasks and not just the desired one?

Maybe you have some fundamental misunderstanding on how FreeRTOS works.

Sensor::read() can still do what it does, just that rather then have a spin loop just checking m_xfer_done continuously, it blocks on a notification, to let other tasks run. Use of spin waits like this are a sign of thinking like a single tasked program.

With you shown program, no tasks of a priority lower than this one will EVER run, as the task never actually blocks, it just spins waiting on the conversion, but that won’t let FreeRTOS switch to other tasks, only blocking will.

Also, configTICK_RATE_HZ is a GLOBAL setting, not something that applies to a given task. It basically sets the unit that the time is measured in for functions like vTaskDelay().

I understand your point but where else am I going to constantly invoke read()? Sure I could block a task on m_xfer_done which even makes sense but where in the program is read() getting called if not constantly in the task itself?

That’s what I could think one but here read is invoked only once.

uint16_t data;

void IRQ()
{
    // once data is read, call xQueueSendFromISR(sensorQueue, data, 0)
}

uint16_t MCP9808::read()
{
     m_xfer_done = false;
    ret_code_t err_code = nrf_drv_twi_rx(&m_twi, MCP9808_ADDR, _buffer, 2);
    APP_ERROR_CHECK(err_code);    
}

void task()
{
   sensor.transfer();   // send register info
   sensor.read();

   while(true)
   { 
       if (xQueueReceive(sensorQueue, &data, 0) == pdPASS)
       { 
          // do stuff with data
   }
}

The first question is that as written, you are reading the sensor as fast as possible, the question becomes is that the rate you want/need to read it.

Another point, a FreeRTOS application rarely has just A task. The whole purpose is to allow you to create multiple tasks that share resources to get the jobs needed to be done completed by the time they need to be done.

It is quite likely that there would end up being A task to handle this sensor, and it would initiate the sensor.read() function at the rate that you need to read the sensor. You would then have possible other tasks to handle other jobs that need to get done in the system.

Furx,

this is standard pseudo code RTOS (freshman level):

while(1)
{
    read_sensor();
    do something with the result value()*;
    vTaskDelay(<your interval between sensor reads>);
}

*this may or may not involve signaling other tasks

Note that the two lines without the vTaskDelay() would be exactly the contents of the corresponding timer callback, so this is a blueprint for a conversion from timers to dedicated tasks. Note also (as others have pointed out) that those two lines pretty much determine whether a timer is sufficient or not (if the computation time of the two lines is deterministic and always shorter than the timer period, you’ll be fine with a timer, but risk convoying out other timers if the computation takes a sigificant amount of time). There are other subtle issues to discuss if the computation time of the two lines does require a dedicated task (you might want to look at the DelayUntil() family of functions).

I’m sure there is a pattern name for this code fragment, but I don’t bother to find out. It’s just off the shelf code so basic it doesn’t need a name IMHO.

this may or may not involve signaling other tasks

true, but I have an observer pattern that takes care of notifying the subscriber (which isn’t a task) as soon as the value is read.

Your point about the computation time taking more than the timer value is valid; if something goes wrong in the driver that causes extra delay, it could be a problem. But deadlines are fairly important in RTOS, what would the approach look like in case of unexpected delays in computation time? I was thinking of merely using the m_xfer_done but that sort of defeats the purpose particularly when we need to read a value every X seconds

My comment is that your structure seems to have the ISR just set a flag in memory that some task is spinning on to see that things are done. This is NOT how you normally do things like this in an RTOS.

Generally, an ISR will set some notification primitive that tasks can wait on to get resumed when the event happens, or data is put into some sort of queue that the task is waiting to get data out of. This allows the task that is waiting to let the RTOS know that it is waiting, and what it is waiting on, allowing the RTOS to go run something else for the time.

The key is that ISR to Task and Task to Task communications where the receiver needs to wait for the event, should be done using a synchronizing primitive from the RTOS so the RTOS can do its job.

The just setting a flag is only used when the receiving task is primarily doing something else, and it just wants to test if there is data to do something extra with it, or would continue on with out it. (You still may need something to make the ‘access’ an ‘atomic’ operation).

Hm, so it’s never really a good idea to keep the task spinning on a certain condition as opposed to being blocked till an event occurs or something is passed via queues etc.

So perhaps I could use something like task notifications for unblocking the task as soon as the value is read from within an ISR, and once unblocked, task goes onto process the raw data, and notifies the observers of the change in the data

I wouldn’t say ‘never’, but usually. If the variable will likely be set quicker than the time it take to swap out and back, it might make sense to spin wait, but that would be waiting for VERY short time periods. Another case is if you can’t get an interrupt to occur when something happens, then you may need to spin wait for it.

1 Like