A Better Formatted Technique for Delaying Multiple Functions

Delaying Function Execution

In the FreeRTOS implementation for the LPC1347 I could not find a method to queue functions for execution at some later time. There were functions defined but not implemented to use the system tick, but none that suited our purpose. This required being able to add functions with their parameters to a queue for execution at some given number of ticks later. This short paper describes our solution. While undoubtedly not unique it is a working solution.

A public function called DoDelay is provided that takes as its parameter a message structure that includes the name of the task to run, how many ticks to wait and the required priority, along with an optional parameter to be passed on when the function runs. An example structure is:

typedef struct
{
MessageType message;
TaskBase* task;
uint32_t delay;
uint16_t priority;
} DelayMessageType;

The MessageType can be a simple integer or itself a structure. An example of filling the DelayMessageType structure and calling the DoDelay function is given here:

DelayMessageType d;

d.delay = timerTicks;
d.task = MotorClass.TimedEvent_StartMotor();
d.priority = uxTaskPriorityGet(NULL);
DelayQueue.DoDelay(&d);

The DoDelay function copies the DelayMessageType structure contents to a local variable, pushes that onto a FreeRTOS queue accessible via xDelayQueHandle, and then exits. The message copy means the original data can be transient.

//-----------------------------------------------------------------------------
// Receive a request to run a task sometime later. The various details are
// in the message. Only the delay, function address, and priority are necessary.
//
void CDelayQueue::DoDelay(DelayMessageType* dp)
{
DelayMessageType dm;

if (uxQueueSpacesAvailable(xDelayQueHandle) < DELAY_QUEUE_SIZE)
{
// Copy the message to local storage.
memcpy((void*)&dm, (void*)dp, sizeof(DelayMessageType));

  // Send it to the delay control task. 
  xQueueSendToBack(xDelayQueHandle, &dm, 5000); 

}
}

A task called DelayQueueManager runs at a high priority every system tick. It wakes up and peeks at the xDelayQueHandle queue to check for entries. If so it is read until empty with each DelayMessageType being pushed onto a timer queue by the private method InsertDelta. This is repeated until the input queue is empty. The timer queue is organised so the shortest delayed message is always at the start of the queue and all messages are ordered by their time of arrival.

//-----------------------------------------------------------------------------
// This task runs every clock tick. It first examines the input queue for any
// contents. If so they (it) are consecutively extracted and put on the task
// delay queue by the InsertDelta class method. It then calls the TickDelayQueue
// method to decrement the queue. Any entries that go to zero will be run.
//
void CDelayQueue::Task_DelayQueueManager(void* p)
{
DelayMessageType dm;
TaskBase* task;

while (true)
{
// Check if there are any new tasks for the delay queue.
while (uxQueueMessagesWaiting(xDelayQueHandle) > 0)
{
// If so extract it from the queue.
xQueueReceive(xDelayQueHandle, &dm, 0);

     // Add this task to the delay queue.
     if (!DelayQueue.InsertDelta(&dm))
     {
        mDelayQueOverrun++;
     }
  }

  // Now tick the delay queue to check if any are ready to run.
  DelayQueue.TickDelayQueue();

  // Off to sleep again until the next tick.
  vTaskDelay(1);

}
}
Once the xDelayQueHandle queue is empty another private method called TickDelayQueue is called. If the queue has contents the timer queue head entry tick count is decremented. If it goes to zero that entry is taken off the timer queue and the delayed function called with its given parameter. This is repeated until either the queue is empty or the head tick value is non-zero, at which point the method returns to the task which goes off to sleep for another
tick.

//-----------------------------------------------------------------------------
// Tick the delay queue if it has any contents. Execute any tasks coming off the
// queue in their queue order.
//
void CDelayQueue::TickDelayQueue(void)
{
uint32_t taskOriginalPriority;
DelayEntryType entry;

if (mDelayQueueCount > 0)
{
mDelayQueue[0].ticks–;
while ( (mDelayQueueCount > 0) && (mDelayQueue[0].ticks == 0) )
{
entry = mDelayQueue[0]; // save the task pointer
mDelayQueueCount–; // done with this entry

     // Fix up any remaining entries on the delay list.
     if (mDelayQueueCount > 0)
     {
        // The delay list is linear.
        memcpy((void*)&mDelayQueue[0], (void*)&mDelayQueue[1], sizeof(DelayEntryType) * mDelayQueueCount);
     }

     // Unless the task was deleted, the head of the delay list was
     // a valid task and the associated delay time his reached zero,
     // execute this task at its given priority.
     if (entry.task)
     {
        taskOriginalPriority = uxTaskPriorityGet(NULL);
        if (entry.priority <= HIGHEST_PRIORITY)
        {
            // Use the given task’s priority.
            vTaskPrioritySet(NULL, entry.priority);
        }
        (*entry.task)(&entry.message); // run the task
        vTaskPrioritySet(NULL, taskOriginalPriority);
     }
  }

}
}

The only downside of this method it that all delayed tasks must use the same parameter. We have found that using a structure as that parameter and judicious use of the union keyword in the definition of the structure solves most problems.

Oops. That should be:
if (uxQueueSpacesAvailable(xDelayQueHandle) > 0)
Added before testing. Mea culpa.
Ron