2017-11-09 12:04 PM
Using the STM32F413 Nucleo-144 and STM32F091 Nucleo-64 evaluation boards. I am using a combination of HAL_GetTick() and a read of the countdown timer to generate higher resolution time stamps for events. Using the CubeMX emitted code so no funny business there - 1ms resolution.
So, I have a test system that timestamps a GPS PPS signal. In general, there aren't any problems - intervals between timestamps are constant and close to 1000msec (typically within 40us). All good.
However, about 0.5% of the time, the value of uwTick is off by one count (equivalent to 1msec error). The next interval 'catches up' with an extra millisecond to maintain the correct _average_ interval (i.e. 999msec and the next is 1001msec). Really quite stable over long periods. Anyway, unsurprisingly, this happens when the program is accessing the global uwTick while it is being changed by the ISR. This is not an unusual problem with reading timers and I have tried to implement the software equivalent of a 'dirty' bit but the 1msec error stubbornly persists.
Specifically:
1) In SysTick_Handler declare a global value 'uwDirtyTimer' that is set upon entry into the ISR. The app calling HAL_GetTimer() first clears this, reads uwTick, and then checks to see if the value changed during the read. This did not eliminate the issue.
2) Sample uwTick multiple times (quickly) and use a 'voting' scheme. This helped but there appear to be corner cases that still sneak through.
3) _inline_ everything to eliminate the overhead of function calls. Would have though optimization would do this but this cut down on the numbers of lagging reads the most.
On a 48MHz clock, it takes very nearly 1usec to execute the ISR not including getting into or returning from the ISR and including the GPIO port set can clear (for the o-scope). It takes 500ns (GPIO2_HIGH to GPIO2_LOW) to execute the following code:
#pragma inline = forced
void HAL_Precision_GetTick(PrecisionSysTick_t *time){GPIO2_HIGH; bSysTickDirty = 0; time->msec = uwTick; time->fraction = SysTick->VAL; if (bSysTickDirty) {GPIO3_HIGH; time->msec = uwTick;GPIO3_LOW; }GPIO2_LOW;}The scope triggers on GPIO2 and GPIO3 being high but the value that ends up read from uwTick is still one count off - almost as if it still hasn't finished updating - but the ISR has completed at this point.
To date, the best solution has been an inelegant one that involves looking that the value of the SysTick counter timer (SysTick->VAL). If this value is greater than 0x0000BB1C then I assume that a roll has occurred and add one to the value. However, no surprise here, instead of losing a tick, sometimes I gain one. I noticed this from the observation that timestamps that were 1ms off consistently reported a fractional part rounded to 0.002 (2us). Hence, 'best' solution but not 'fixed'.
Has anyone out there figured out a working way to implement a 'safe' read of uwTick? Basically, my approaches are band-aids for the fact that for whatever reason, I cannot ascertain when the value of uwTick is correct in RAM or at least has changed since I read it.
2017-11-09 03:10 PM
Update: Check your compiler optimizations - instruction scheduling was the issue. Turned that off and variables were getting set when they were supposed to.
2017-11-09 03:47 PM
On the F4 I'd use either TIM2 or TIM5->CNT as a one micro-second ticker, on the F0 a free 16-bit TIM at one millisecond.
The F0 at 48 MHz will not run 2x the 24 MHz speed, the flash wait states dominate
2017-11-09 03:50 PM
do {
a = uwTick;
time->fraction = SysTick->VAL;
b = uwTick;
} while(a != b);
time->msec = a;
2017-11-09 04:04 PM
My modified SysTick Handler. With all optimizations off, bSysTickDirty gets set properly and indicates that a countdown rollover event happened during the read.
void SysTick_Handler(void)
{ /* USER CODE BEGIN SysTick_IRQn 0 */bSysTickDirty = 1;
/* USER CODE END SysTick_IRQn 0 */
HAL_IncTick(); HAL_SYSTICK_IRQHandler(); /* USER CODE BEGIN SysTick_IRQn 1 *//* USER CODE END SysTick_IRQn 1 */
}With all optimizations off, this code works as expected - if the ISR fires, the dirty bit gets trapped and the second read captures the correct value.
#pragma inline=forced
void HAL_Precision_GetTick(PrecisionSysTick_t *time){ bSysTickDirty = 0; time->msec = uwTick; time->fraction = SysTick->VAL; time->bDirty = bSysTickDirty; if (time->bDirty) { time->msec = uwTick; }}I will try another alternative where if the read of uwTick occurs 'too close' to the rollover, I will just stall and wait until uwTick becomes valid. On the order of a couple of microseconds which is easily within my application tolerance.
2017-11-09 04:58 PM
Tried that early on as the obvious solution and 'a' would always equal 'b' even though a rollover clearly happened between the reading of 'a' and 'b'. Instruction re-ordering in IAR likely put the reads right next to each other so uwTick never got a chance to change. Disabling all optimizations fixes the problem but this is an unsatisfactory solution. Currently trying to figure out the optimization to blame - instruction reordering is part but not all of the problem.
2017-11-09 05:09 PM
uwTick should be volatile to make the compiler physically read it, ditto with
bSysTickDirty. Would be a serious problem if the compiler ignores that flagging.
2017-11-10 09:32 AM
UPDATE:
After exploring many different configurations in the IAR environment it was nearly impossible to guarantee that the time read happening during a rollover event would be able to use any variable to be told that the ISR had interrupted it. But, there is one signal that was always reliable - the GPIO that I was using to indicate that I was in the ISR. So, I now use a GPIO pin as a non-volatile 'memory' to indicate that the ISR fired. The new code is:
#pragma inline=forced
void HAL_Precision_GetTick(PrecisionSysTick_t *time){ GPIO1_LOW; // Clear the NV bit - probably should do a check on this first time->msec = uwTick; time->fraction = SysTick->VAL; if ( GPIO1_GPIO_Port->IDR & GPIO1_Pin ) { time->msec = uwTick; GPIO1_LOW; // Clear the NV bit - unnecessary, but I use it for the oscope to track that this condition happened }}
2017-11-13 08:47 AM
UPDATE:
Using the GPIO port as a 'safe' signal that the ISR fired, ran multiple hardware devices for 70+ hours with no missed SysTick events.