Hi guys,
CPU: Cortex-M4, STM32F4, 1 kHz kernel tick, FreeRTOS v10.4.x
The short version:
Is there some rule that forbids calling vTaskSetTimeOutState() or reading the systick counter register when the kernel is disabled by vTaskSuspendAll()?
The lengthy version:
My code is confidential unfortunately, but I try to give you the idea of my problem as simple as possible.
I have some function now() which gives me the current time(stamp) in microsecond resolution and is also overflow safe (or at least that was the idea). This function now() is based on the FreeRTOS kernel tick.
For this you’d normally use xTaskGetTickCount(), but instead I use vTaskSetTimeOutState() because this function does not only return the 32 bit TickType_t but also returns the number of overflows of this 32 bit value as BaseType_t, i.e. effectively I have a 64 bit counter which is overflow-safe until I’m dead and beyond.
Since the kernel tick is 1 millisecond only, I combine the kernel tick counter values I get from xTaskGetTickCount() with the Systick counter register value which is running at CPU frequency. This way I get the microsecond resolution as well.
The C++ pseudo code of now() looks like:
MyTimeType now()
{
// vTaskSuspendAll(); // Problematic, keep in mind, explained later.
curSysticks = getSystickRegisterAndSubstractReloadReg(); // takes care of down-counting systick
TimeOut_t millisecsAndOverflows{0,0};
vTaskSetTimeOutState(&millisecsAndOverflows)
// vTaskResumeAll(); // Problematic, keep in mind, explained later.
latestSysticks = getSystickRegisterAndSubstractReloadReg();
if(latestSysticks >= curSysticks)
{
// no overflow of systick counter reg
microsecs = calcMicrosecsFromSysticks(curSysticks);
millisecs = combineOverflowsAndMillisecTicks(millisecsAndOverflows);
return nowTimeInMicrosecs = millisecs * 1000 + microsecs;
}
else
{
// There was an overflow of systick counter reg, refetch FreeRTOS kernel tick values
// Would usually mean +1 millisecond, but we do it clean ...
vTaskSetTimeOutState(&millisecsAndOverflows); // ... and refetch the FreeRTOS values.
microsecs = calcMicrosecsFromSysticks(latestSysticks );
millisecs = combineOverflowsAndMillisecTicks(millisecsAndOverflows);
return nowTimeInMicrosecs = millisecs * 1000 + microsecs;
}
}
Hope this makes sense, now() so far worked rather OK for me.
Now the current problem:
I use my function now() to measure time differences in my application in the sub-millisecond range, i.e. an accuracy of 10us or so is acceptable.
One of those time diff calculations looks somehow like this:
vTaskSuspendAll(); // MAD boy!
setDebugLED();
MyTimeType startTime{ now() }; // Can be WRONG!
MyTimeType expectedTime {somePreviousTime_MemberVar - startTime};
vTaskResumeAll(); // MAD boy!
/* ... Some other code in this task is running, the task might block and other tasks might run. ... */
MyTimeType endTime { now() }; // Kernel not suspended here!
resetDebugLED();
MyTimeType actualTime {endTime - startTime};
printf("actual time: %ld, expected time: %ld", actualTime, expectedTime);
}
For some reason I thought at some point, that I need to suspend my scheduler there as a “soft critical section” so that expectedTime would be calculated correctly (no context switch, no delay), considering that my application has many tasks. Suspending the scheduler has now been proven to be unnecessary and without any benefit in this case, anyway …
The printed actualTime showed some weird behaviour that the times were mostly correct, but occasionally were exactly 1 ms off the real value I could measure via the LED with an oscilloscope. When I changed the kernel tick frequency to 500 Hz, that offset value changed to 2 ms. So the effect is dependant on the kernel tick rate.
I found that startTime was causing the wrong time diff.
When I removed the “soft critical section”, i.e. the vTaskSuspendAll() and vTaskResumeAll() pair from above calculation of startTime, everything was fine. And I cannot not explain why …
Now I remembered that if-clause in my function now() and was able to track down the problem of the “soft critical section” to the point that I pointed out as reminder in the two comments in the code of now().
- One big “soft critical section” around getSystickRegisterAndSubstractReloadReg() and vTaskSetTimeOutState() → Problem, wrong values for startTime!
- One “soft critical sections” around each getSystickRegisterAndSubstractReloadReg() and vTaskSetTimeOutState() → No problem.
And now my question from above again:
Is there some rule that forbids calling vTaskSetTimeOutState() or reading the systick counter register when the kernel is disabled by vTaskSuspendAll()?
Or is this just some task timing thing of my application I don’t understand?
EDIT:
I have seen that general warning in the API docu: “API functions that have the potential to cause a context switch (for example, vTaskDelayUntil(), xQueueSend(), etc.) must not be called while the scheduler is suspended.”
But it doesn’t seem relevant to me in case of vTaskSetTimeOutState(). Or am I wrong here?
Thanks for your patience and help!
Regards,
Flo