A periodic task that could be suspended

Periodic task A makes some things in the endless loop and then calls vTaskDelayUntil(). Task B can call set_delay() that changes the amount of time task A delays and, if it is zero, put task A in a blocked state until a new set_delay() is called.

volatile unsigned int delay_ms;

// Task A
prevTick = xTaskGetTickCount();
while(1) {
  // some stuff
  vTaskDelayUntil(&prevTick, pdMS_TO_TICKS(delay_ms));
}

// Task B
void set_delay(unsigned int new_delay) {
  delay_ms = new_delay;
  if (new_delay == 0) {
    ????
  }
}

I’d like to not block task A during “some stuff”. How to do it in a reliable way?

I was thinking to vSuspend()/vResume(), semaphore… without finding a good approach.

One approach is for TaskA to wait on a semaphore with a timeout. That gives the effect of vTaskDelay instead of vTaskDelayUntil.

If you want to wake up task A now, you can use vTaskAbortDelay() to wake up A now.

I don’t think I got your point.

This isn’t what I want to do. TaskA reads a sample from a sensor and the sampling times should be at a regular spaces. I need vTaskDelayUntil(), when delay_ms is greater than 0.

Another thing. Who calls xSemaphoreGive()? Please, explain with some pseudo-code, I’m a newbie with FreeRTOS.

The idea of doing a SemaphoreTake with a timeout is that most of the time, the take just times out, and that gives you your period. The give is only done if you need it to run right now.

It does say that you get Delay, not DelayUntil semantics, but if TaskA can do its operations in less than a tick, and has a high enough priority that this happens even with the interference of other tasks, then Delay will be repeatable. If this is not the case, then you can’t use this method if you need strictly controlled periods (but TaskA still will need to be a high priority to make the timing of readings precise. I will sometimes use this sort of thing for reading a button, where an interrupt will trigger an immediate reading for a fast response, but if held down for an extended time, I want to re-read and process, so the timeout changes depending on the button being up or down.

The second method gets around the timing issue but has a failure mode that if after TaskA reads the sensor, but before it finishes processing and delays, you get the need to read immediately again, then there vTaskAbortDelay won’t abort the delay (since TaskA isn’t in the delay). Even if you check a flag just before the delay, there is a window of a race condition between the test and the delay.

Richard thank you for your explanation, but I think I didn’t explain well what I want to do.

It’s not important to wake up taskA right now and I can live with vTaskDelay() instead of vTaskDelayUntil().

What I’m not able to do is to put taskA in blocked state, if taskB calls set_delay(0). TaskA shouldn’t block during processing, but only after “some stuff” in my original code.

After that, taskB can call set_delay(not zero) and taskA should unblock.

TaskB needs to communicate to TaskA that is shouldn’t block, so that it doesn’t block. Perhaps use a global variable of some sort (possible just a variable local to the file with TaskA and the set_delay function which sounds like part of TaskAs API.

TaskB then probably wants to the use vTaskAbortDelay to start TaskA immediately.

Don’t try to think in terms of one task doing changes to the behavior of another task without it being told.

I’m not able to find a good solution that doesn’t suffer of race conditions. The only solution is to create a queue and push events in the queue during set_delay().

taskA wait forever for an event. Even the sampling is managed by a SAMPLE event generated by a software periodic timer.

// taskA
startTimer(delay_ticks, timer_callback);
while(1) {
  struct event_t ev;
  if (get_event(&ev, portMAX_DELAY) == pdTRUE) {
    if (ev.type == SAMPLE) {
      // get sample and process
    } else if (ev.type == NEW_DELAY) {
      delay_ticks = ev.delay_ticks;
      if (delay_ticks == 0) {
        stopTimer();
      } else {
        changeTimerPeriod(delay_ticks);
        startTimer();
      }
    }
  }
}

// taskB
void set_delay(unsigned char new_ticks) {
  struct event_t ev = { .type = NEW_DELAY, .delay_ticks = ticks };
  push_event(&ev);
}

void timer_callback(...) {
  struct event_t ev = { .type = SAMPLE };
  push_event(&ev);
}

I can’t see any other approach without a queue.

One comment, you have described methods that don’t work but haven’t given a real specification of what you are trying to achieve. (Such a specification should make NO reference to FreeRTOS as such a specification is mixing in implementation details)

Such a specification would begin something like, The program should read and process a sample at a rate specified by … and when … occurs, it should …

Knowing the actual goal makes it easier to provide solutions.

Hi Richard, I’m sorry if specifications weren’t sufficient. They are very simple.

I have a task that polls a sensor on SPI at a regular intervals. The user can change the sampling frequency from a serial port. A particular case is when the user set zero as the sampling interval, causing no sampling at all.

I started with two tasks: taskA is the sampling task that polls the sensor at regular intervals; taskB implements the serial port protocol and calls set_delay() to change the sampling interval of taskA.

The big issue is to stop/restart taskA from taskB, without race conditions. Every effort I made arrived to a not reliable implementation.

I see two possible answers depending on how you want to handle rate changes. The simpler, which may cause an irregularity at the switching of rates, is you have a queue to send that salmi rate by TaskB. TaskA waits on the queue with a timeout set by sampling rate (adjusted by the time spent in processing), with an infinite timeout if the last rate was set to 0. If the queue return data, you update the rate, and depending on what how you want the changes handled, either read immediately (which says that this sample may be earlier than desired) or immediately wait again (which says that the next sample may be later than previously desired).

A more complicated method would be for TaskA, if it is sampling, to use the vTaskDelayUntil for the period, do the sampling, then check the queue with a 0 timeout. If you get a new value, update the period, and if still sampling go back to the vTaskDelayUntil again. If not sampling, go back to the check queue, but now with an infinite timeout.

If you are shortening the time significantly, and don’t want to wait for the next sample, then TaskB can use vTaskAbortDelay() to end the timeout immediately. If TaskB has a higher priority than TaskA, then there is a small chance that TaskB will post the new time and the delay abort between TaskA checking the queue and hitting the delay. To handle this, after TaskB has sent the delay abort, it should delay a tick (or long enough for TaskA to do a sampling) and see if TaskA has taken the change from the queue, and if not, resend the delay abort.

If TaskB has a lower priority than TaskA (which is likely, TaskA is doing time critical operations, while TaskB sounds to be a user interface) then this timing hazard can’t occur.

TaskB NEVER needs to stop TaskA, and only needs to be able to kickstart TaskA if you might use a delay longer than you want to wait for a speed change (maybe if the old time was long on human scale)

If you want to avoid the queue, you can use xTaskNotify() and xTaskNotifyWait(), using eNotifyAction eSetBits. Each bit is essentially an event. You’ll need a volatile field for passing the new sampling interval, and set_delay() must set the field before calling xTaskNotify().

It would be just like the solution you proposed with queues, just lighter.

1 Like