0
votes

I'm using a RTOS distribution on an ARM embedded device. Currently i'm in need of toggling a signal like this

GPIO_Write(PIN_1, LOW);
vTaskDelay(msec_to_ticks(1));

GPIO_Write(PIN_1, HIGH);
vTaskDelay(msec_to_ticks(1));
GPIO_Write(PIN_1, LOW);
vTaskDelay(msec_to_ticks(3));

GPIO_Write(PIN_1, HIGH);
if (option1){
    vTaskDelay(msec_to_ticks(3));
    GPIO_Write(PIN_1, LOW);
    vTaskDelay(msec_to_ticks(1));
} else {
    vTaskDelay(msec_to_ticks(1));
    GPIO_Write(PIN_1, LOW);
    vTaskDelay(msec_to_ticks(3));
}
GPIO_Write(PIN_1, HIGH);
if (option2){
    vTaskDelay(msec_to_ticks(3));
    GPIO_Write(PIN_1, LOW);
    vTaskDelay(msec_to_ticks(1));
} else {
    vTaskDelay(msec_to_ticks(1));
    GPIO_Write(PIN_1, LOW);
    vTaskDelay(msec_to_ticks(3));
}
GPIO_Write(PIN_1, HIGH);
vTaskDelay(msec_to_ticks(3));
GPIO_Write(PIN_1, LOW);

What i noticed is that based on where and when i call this function (separate thread/task) the signal can be completely wrong (1-2ms off sometimes).

I'm wondering if using taskENTER_CRITICAL() and taskEXIT_CRITICAL can help me to ensure a somewhat accurate signal.

One idea i have is that interrupts with higher priority may kick in and delay the code somewhere (although i've set max priority on the task). The other one is that the timings are correct but GPIO_Write is not taking place immediately.

Any other idea on how can i ensure the code is not interrupted?

Regards,

2

2 Answers

0
votes

Here are a few things to think about.

1) What is the period of the timer used by vTaskDelay()? The amount of error in the delay period could be up to one tick. For example if you start the delay just prior to when the next tick is about to occur then the delay could be up to one tick too short.

If the tick period is 1 millisecond then you cannot accurately measure 1 millisecond delays because the relatively low resolution of the timer means those delays could be up to one millisecond or 100% too short. Hopefully the tick period is 0.01 milliseconds or less because that would allow it to measure a 1 millisecond with 1% error or less.

2) The FreeRTOS documentation explains why vTaskDelay() is not a good method of controlling the frequency of periodic tasks.

vTaskDelay() specifies a time at which the task wishes to unblock relative to the time at which vTaskDelay() is called. For example, specifying a block period of 100 ticks will cause the task to unblock 100 ticks after vTaskDelay() is called. vTaskDelay() does not therefore provide a good method of controlling the frequency of a periodic task as the path taken through the code, as well as other task and interrupt activity, will effect the frequency at which vTaskDelay() gets called and therefore the time at which the task next executes. See vTaskDelayUntil() for an alternative API function designed to facilitate fixed frequency execution. It does this by specifying an absolute time (rather than a relative time) at which the calling task should unblock.

3) Using taskENTER_CRITICAL and taskEXIT_CRITICAL around calls to vTaskDelay() seems like a bad idea to me. The reason for calling vTaskDelay() is to give other tasks a chance to run while this task is delaying. So it seems counterproductive to disable interrupts with taskENTER_CRITICAL. If you don't want other tasks to run during the delays then call a non-blocking delay function rather than vTaskDelay(). Then if the timing of your non-blocking function is still being affected by interrupts then maybe you could consider putting it into a critical section.

0
votes

One problem is that the first pin set occurs asynchronously to the RTOS clock, so may occur anywhere in the clock period:

Tick:    |          |          |          |
       ________      __________
Signal:        |____|          |__________________
          <--------->
       First transition
     may occur anywhere here. 

This can be resolved by inserting a single tick delay before the first transition.

Tick:    |          |          |          |
       __            __________
Signal:  |__________|          |__________________
Delay(1)-^         
         Transition occurs on the tick boundary

Higher priority tasks and interrupts may delay transitions and cause jitter, but if you lock the scheduler or disable interrupts, then those tasks will not run, and if your application can take that then those tasks are have been assigned inappropriate priority. Equally is the jitter is significant, then again you have tasks with inappropriately high priority or conversely inappropriate behaviour and schedulability for a high priority task.

Task priority is not about "importance" it is about schedulability and deadlines. As a guide, tasks that have hard-real-time deadlines and run for short deterministic periods should have high priority. In this case the task spends most of its time in delay - the GPIO switches will take very few clock cycles, so this task can probably safely be assigned a high priority.

Tasks that do not behave deterministically should have a low priority in order not to affect time-critical tasks. If this fragment is part of some task that does other perhaps non-deterministic things, then your task partitioning may need re-thinking. Task partitioning is not always about the "job", again it is about schedulability and an intuitively single feature or behaviour may need to be split amongst more that one task. For example this signal might be generated by a high-priority task waiting on an event triggered by some lower priority task rather then implemented directly in the low priority task.