Staggering Task Start Times

nobody wrote on Wednesday, November 30, 2005:

Hello,

I have several tasks - of which I’d like to start
at intervals relative to each other - let’s say 10ms for the sake of argument and let’s assume we have a 1ms clock.

Ex:

xFrequency = 250;

portTASK_FUNCTION( task1, pvParameters)
{

    xLastWakeTime = xTaskGetTickCount();

for (EVER)
{
        vTaskDelayUntil( &xLastWakeTime, xFrequency);

}
}

portTASK_FUNCTION( task2, pvParameters)
{

    xLastWakeTime = xTaskGetTickCount();
    xLastWakeTime+=10;

for (EVER)
{
        vTaskDelayUntil( &xLastWakeTime, xFrequency);

}
}
portTASK_FUNCTION( task3, pvParameters)
{

    xLastWakeTime = xTaskGetTickCount();
    xLastWakeTime+=20;

for (EVER)
{
        vTaskDelayUntil( &xLastWakeTime, xFrequency);

}
}

Why doesn’t this work?  It seems to try to work
the first couple of cycles - but then just one task will run eventually - and not always the same one…

Thanks In Advance.

nobody wrote on Wednesday, November 30, 2005:

Does this have something to do with adding a value to xLastWakeTime before the 1st call to vTaskDelayUntil() is trying to schedule something in the future?

nobody wrote on Wednesday, November 30, 2005:

There does not seem anything obviously wrong with this, except how can you guarantee the order in which the tasks execute the first time?  You are making an assumption as to the initial order in which the tasks execute, and if this is wrong then the following pattern will also be wrong.

Could this be the problem?

nobody wrote on Wednesday, November 30, 2005:

Looking at the code I don’t think this would be a problem.  But if there was a reschedule between setting the wake time and the first call to vTaskDelayUntil then maybe the timing would not be as you would expect.  Does this scheme assume there is no reschedule of the task prior to the call to vTaskDelayUntil?  If there were a few milliseconds between setting the wake time and calling vTaskDelayUntil then the period it actually delays would be shorter than your intended delay.

Also thinking about it, if you really want the delay to be exact between the tasks the time each task woke would have to be recorded autonomously (at one point).  Recording the time individually in each task will cause the reference to be different for each task also.

nobody wrote on Wednesday, November 30, 2005:

You could take a single point in time prior to the tasks being started, then have each task use this as a reference.  This ‘single point’ time would have to be available globally, or passed into the tasks as a parameter.  Each task would then add a different time to this.  The first would be Time + 10.

xFrequency=250;

xGlobalRef = xTaskGetTickCount();

portTASK_FUNCTION( task1, pvParameters )
{
  
xWakeTime = xGlobalRef + 110;

// rest of task code.
}

portTASK_FUNCTION( task2, pvParameters )
{
  
xWakeTime = xGlobalRef + 120;

// rest of task code.
}

You must ensure the task starts prior to the first wake time, hence the 110, and 120 increments rather than the 10 and 20.  This may mean your system does noting for the first 100ms, but then is in sync.

HOWEVER, the wake time is the time the task unblocks and is ready to execute.  It is not necessarily the time the task actually starts as this depends on the prioritisation of the tasks within the system.  Therefore the exact synchronisation of times would only be expected at the beginning, following that it is application dependent and would be unlikely to maintain the exact synchronisation.

nobody wrote on Friday, December 02, 2005:

Hello,

This is the original poster.

I appreciate the discussion - but after reading
all of this - is there a way to know which task starts first?

Let’s say you have a dozen or so tasks - all of which will start as soon as the scheduler starts.
How do you know who’s on first so to speak first?

Thanks!

nobody wrote on Friday, December 02, 2005:

Hello,

This is the original poster again - maybe the ‘best’ way to do this is to use an event or semaphore as in the demo. code to try and stagger the tasks?

Thanks!

rtel wrote on Friday, December 02, 2005:

If the tasks you want to run in this manner are the highest priority tasks in the system then you should be able to provide some accurate staggering - provided a single task does not overrun into the time at which the next task should start.

A couple of thoughts:

+ If the tasks will never overlap then is there a case for making them a single task?  The three tasks could be made into simple functions, then a single task used to call the relevant function for the particular time.

+ You could use a tick hook to schedule the tasks.  If the tasks suspend themselves, then the tick hook (which always knows what time it is) could selectively unsuspend tasks at the appropriate time relative to each other.

Regards.

nobody wrote on Saturday, December 03, 2005:

Could you give an example of the tick hook?

rtel wrote on Saturday, December 03, 2005:

First you need to know how to place a task on a ready list from a suspended list.  This can be done by the following code.  This is written as a macro as the code will be called in the tick interrupt so wants to be as fast as possible:

#define prvReadyTask( xTaskHandle pxTaskToResume )
{
tskTCB *pxTCB; \

____pxTCB = ( tskTCB * ) pxTaskToResume;

    /* No NULL check! */
____vListRemove(  &( pxTCB->xGenericListItem ) );
____prvAddTaskToReadyQueue( pxTCB );
}

Next we need a function that will resume the tasks at the appropriate time spacing:

void vTickHook( void )
{
static portBASE_TYPE xCallCount = 0;

____/* We know the frequency at which this
____function is called. */
____xCallCount++;
   
____/* Is it time to start a task? */
____switch( xCallCount )
____{
________case 5    :    prvReadyTask( TaskA );
____________________break;
                   
________case 10    :    prvReadyTask( TaskB );
____________________break;
                   
________case 15        /* When we start task C
____________________we reset the call count
____________________so TaskA is started again
____________________in 5 ticks. */
____________________prvReadyTask( TaskC );
____________________xCallCount = 0;
____________________break;
    }
}

This function needs to be called every tick.  It keeps track of how many times it has been called and starts a different task every filth time it is called (adjust this to whatever is required for you).

We don’t really want to change the core FreeRTOS code so ideally the new function should be called from the port layer.  I’m not sure which processor you are using, but the tick interrupt code in the port layer will will call vTaskIncrementTick() followed by a call to vTaskSwitchContext().  You should place a call to vTickHook() between these to functions.  This way any tasks unsuspended by vTickHook() will get scheduled by vTaskSwitchContext().

The tasks being started should suspend themselves each cycle.  This code does not take into account the situation of a task still running when vTickHook() tries to unsuspend it so you must guard against this eventuality.

Regards.

nobody wrote on Sunday, December 04, 2005:

Richard,

Thanks for this.

Do any of the ports have a tickHook coded?
Just wondering.

I’ll give this a try and let you know how it works.

I think this should work OK from looking at your
example.

Thanks Again!

nobody wrote on Sunday, December 04, 2005:

What if you want to keep a task suspended?  How do you keep the tickHook from enabling it?

nobody wrote on Tuesday, December 06, 2005:

I know changing the kernel isn’t something desired - but adding something like:

#if ( configUSE_TICK_HOOK == 1 )

is something that could be useful…

Thanks.

nobody wrote on Tuesday, December 06, 2005:

OK, coded it up and I’m getting the same results
as when using a queue to try and do this.

The tasks are running - I must be having a problem
with a shared resource.

Thanks.

nobody wrote on Friday, December 16, 2005:

Here’s my actual code for the tick hook - I
thought this could be useful for someone else:

I put this in tasks.c since that was the easiet place to put it:

#if ( configUSE_TICK_HOOK == 1 )

// #define  prvReadyTask( xTaskHandle pxTaskToResume )
#define  prvReadyTask( pxTaskToResume )

  tskTCB *pxTCB;
  pxTCB = ( tskTCB * ) pxTaskToResume;
  vListRemove ( &( pxTCB->xGenericListItem ) ); 
  prvAddTaskToReadyQueue( pxTCB );
}

// our 16 tasks that we want to control relative to one another:

extern xTaskHandle xpcolHandle1;
extern xTaskHandle xpcolHandle2;
extern xTaskHandle xpcolHandle3;
extern xTaskHandle xpcolHandle4;
extern xTaskHandle xpcolHandle5;
extern xTaskHandle xpcolHandle6;
extern xTaskHandle xpcolHandle7;
extern xTaskHandle xpcolHandle8;
extern xTaskHandle xpcolHandle9;
extern xTaskHandle xpcolHandle10;
extern xTaskHandle xpcolHandle11;
extern xTaskHandle xpcolHandle12;
extern xTaskHandle xpcolHandle13;
extern xTaskHandle xpcolHandle14;
extern xTaskHandle xpcolHandle15;
extern xTaskHandle xpcolHandle16;

void vTaskTickHook(void);
   
#endif

/*-----------------------------------------------------------*/
// vTaskTickHook
/*-----------------------------------------------------------*/

#if ( configUSE_TICK_HOOK == 1 )

void vTaskTickHook( void )
{
static portBASE_TYPE xCallCount = 0;

/* We know the frequency at which this function is called. */

/* Is it time to start a task? */

// assumption - time here is in ms - note we can fix this using our
// port definitions…
     
      switch ( xCallCount )
      {
          case 0: prvReadyTask( xpcolHandle1 );
               break;
          case 25: prvReadyTask( xpcolHandle2 );
            break;
          case 50: prvReadyTask( xpcolHandle3 );
            break;
          case 75: prvReadyTask( xpcolHandle4 );
            break;
          case 100: prvReadyTask( xpcolHandle5 );
            break;
          case 125: prvReadyTask( xpcolHandle6 );
            break;
          case 150: prvReadyTask( xpcolHandle7 );
            break;
          case 175: prvReadyTask( xpcolHandle8 );
            break;
          case 200: prvReadyTask( xpcolHandle9 );
            break;
          case 225: prvReadyTask( xpcolHandle10 );
            break;
          case 250: prvReadyTask( xpcolHandle11 );
            break;
          case 275: prvReadyTask( xpcolHandle12 );
            break;
          case 300: prvReadyTask( xpcolHandle13 );
            break;
          case 325: prvReadyTask( xpcolHandle14 );
            break;
          case 350: prvReadyTask( xpcolHandle15 );
            break;
          case 375: prvReadyTask( xpcolHandle16 );
            break;
          case 499: /* When we start task C
              we reset the call count 
              so TaskA is started again
              in 5 ticks. */
          //      prvReadyTask( xpcolHandle1 );
          // we bias xCallCount to -1 since xCallCount gets
          // incremented below the switch and we start our cycle
          // with case 0:
              xCallCount = -1;
              break;

          default: // post error condition - maybe not
              break;
             
      }    // end switch

      xCallCount++;    //
     
} // end vTaskTickHook()
   
#endif

in FreeRTOSConfig.h:

#define configUSE_TICK_HOOK        1

in the tick isr:

                #if configUSE_TICK_HOOK == 1
                        call    #vTaskTickHook
                #endif

Regards…

nobody wrote on Thursday, April 13, 2006:

Hello Richard,

Is it possible to miss a ‘tick’ in this tickHook example?

For instance, can xCallCount++ be missed periodically?

Thanks,
John W.

rtel wrote on Thursday, April 13, 2006:

I don’t think so, unless the ticks themselves are missing.

If the hook is in the port layer then it will get called all the time.  If the hook is in the task code then it could get missed if the scheduler was suspended.

V4.0.x has a configurable hook in the task code, but in a safe place.

Regards.

nobody wrote on Thursday, April 13, 2006:

Richard,

The tickHook I’ve done gets called directly from the tickISR() in the port code - but the ‘function’ itself resides in my task.c file - just because it was easier (at first) to put it there.  So as long as the tickISR is called (which in turn increments xTickCount) - it should always be called.  If something can cause the tickISR to be missed - then it could be missed I suppose.

Thanks,
John W.

nobody wrote on Friday, April 14, 2006:

Richard,

I’ve made some measurements and the times between tickHook calls seems to vary somewhat.

Should tickHook be ‘wrapped’ in a critical section?

Thanks,
John W.