cancel
Showing results for 
Search instead for 
Did you mean: 

Inaccurate us delay function

CGalv
Associate III

Hi,

I'm trying to implement a `delay(microseconds)` function on an STM32 F745 running at 216 MHz.

I want to keep it dummy and simple without involving interrupts. My strategy is as follows:

-Setup TIM2 to run at 1 MHz. Since it has a 32-bit counter it will overflow after a bit more than 1h which is enough for my application.

-Implement delay() as follows:

inline void delay(const uint32_t wait_time_us)
{
    volatile const uint32_t t_start = TIM2->CNT;
    while ((TIM2->CNT - t_start) < wait_time_us);
}

After implementing it, I try it by simply turning on an off a GPIO pin every 100 ms:

GPIOC->BSRR = GPIO_BSRR_BR_9;
delay(100'000U);
GPIOC->BSRR = GPIO_BSRR_BS_9;
delay(100'000U);

However when I inspect it in my logic analyzer (typical 24 MHz 8-channel), I obtain 99.8 ms instead of 100 ms. That's 200 us difference or 200 clock ticks that were missed. And the CPU should be running at 216 MHz.

If I try other delay values (e.g. 100 us) I don't get an exact timing either, but the difference is not the same as before (200 us), but something else (smaller). So it doesn't seem to be an off-by-x kind of problem.

I have verified that the TIM2 is correctly setup with this code (inside an infinite while loop):

if (TIM2->CNT & 0x0000'0001)
{
       GPIOC->BSRR = GPIO_BSRR_BR_9;
}
else
{
       GPIOC->BSRR = GPIO_BSRR_BS_9;
}

In the logic analyzer I see a perfect 1us delay all the time.

So, what can be wrong with my delay function? I've also tried inlining the code to discard delays due to function calls but the result is the same. The larger the delay, the larger the difference. I'm compiling with -O3 optimization level on gcc 8.3, C++14.

Thanks!

9 REPLIES 9

Perhaps use a finer granularity time source. Depending when you enter you've got +/-1us error now.

Are you sure you can perceive a 0.2% error on the 1us?

Perhaps have a TIM with Prescaler=216-1, Period=100000-1, and have it toggle the TIMx_CHx pin, and measure that.

You're using what clock sources? Have you measured those?

The start value doesn't need to be volatile, the TIM2->CNT should already be volatile.

Tips, buy me a coffee, or three.. PayPal Venmo Up vote any posts that you find helpful, it shows what's working..
CGalv
Associate III

Thanks for the quick reply!

Perhaps I should mention that I don't expect perfect accuracy on 1us level. You are absolutely right that I can have +/-1 us errors and that's fine for my application. However I think 200 us error or even 1 ms error (on a delay of 1 second) seem really strange to me given a granularity of 1us. And I still don't understand why the error changes based on the amount of delay.

I'm using the "internal clock" as described in the reference manual, which I assume is the peripheral clock for TIM2. Currently I have APB1 clock as 54 MHz (maximum value according to the reference manual). Looking at the clock tree picture the "APB1 timer clock" would be APB1 x 2 = 108 MHz, which is what I assume is the clock source for the timer (again, the manual never clarifies what CK_INT actually means).

Given that, I set a prescaler value PSC of 107 to achieve a timer counter running at 1 MHz.

How can I measure the clock sources directly? Can they be output on some pin? In any case I don't have access to a higher frequency oscilloscope so I can measure 24 MHz at most.

As I said I measured the counter with the code shown above and I can see a perfect 1us delay so I think the timer configuration should be fine.

0690X00000AQtNqQAL.png

CGalv
Associate III

I did some research and found out that the input HSE is a 8MHz ceramic resonator with 0.5% tolerance. Can this explain the 0.2% error I'm seeing in my measurements? I realized that it's consistently 0.2% error always. Does it make any sense that I pursue a smaller error given the 0.5% error at the input?

At the same time this could not explain the perfect 1us delay I get in the picture above. Also, my delay() function creates a 0.2% smaller delay than requested, even though it would make more sense to produce a larger delay (due to extra cycles in the while loop). The 1us delay executes fewer cycles and returns a higher relative delay compared to the delay function.

This is confusing =) It's probably not worth it in my application but I just would like to understand what's really happening under the hood.

I think that the error accumulates to the point where it becomes obvious at your sampling frequency, perhaps use an analogue scope, or a proper frequency counter.

But like I said, configure the TIM in a Toggle or PWM mode, with a 100ms period and see how accurate that is, my expectation is it will track your long term observation with the timing loop.

The HSI performance depends on voltage and temperature, the PLL/VCO will depend more on the loop filter, which might hide short term variations.

Things like USB, CAN and Ethernet need a crystal based source, for radios TCXOs.

Tips, buy me a coffee, or three.. PayPal Venmo Up vote any posts that you find helpful, it shows what's working..
S.Ma
Principal

Your reference clock tolerance will affect the whole chip, including issues when for example UART serial interface baud rate gets high and bit sampling period shrinks.

You can output the internal clock on MCO alternate function pin.

Always use oscilloscope first, then digital analyser.

Remember also you don't need to care about timer overflow, if the timer is 16 bit, just calculate time in 16 bit. It will limit to here 65msec max delay only.

Clive's suggestion is good, use PWM mode or just toggle an IO every overflow (you can make it overflow at 10000 = 10msec) and chec the time drift vs MCO.

For usec SW delays, I usually use a LPTIM when available. In the L4 family, the MSI clock generated from precise LSE 32768 Hz does quite a good job.

CGalv
Associate III

Duplicate answer, please delete.

CGalv
Associate III

Hi again!

Sorry for the silence, haven't had time to look into it until now.

I have followed your advice and here's my results. NOTE: I still haven't managed to get hold of an oscilloscope so perhaps all my logic analyzer measurements aren't very meaningful, but here it goes:

  • Configuring the timer in output toggle mode as @Community member​ suggested I obtain exactly the same 0.2% error as before.
  • Checking the HSE clock (8MHz) on MCO2 as per @S.Ma​ I obtain 8 MHz sharp, as shown below. Not sure if the duty cycle != 50% has any impact:

0690X00000AqZfSQAV.png

Any ideas what else I could look into? In the meantime I will try to find an oscilloscope and repeat my measurements, together with SYSCLK.

Thanks!

CGalv
Associate III

Hi again!

I finally got hold of a 200MHz - 2 GSa/s oscilloscope, which confirmed what we suspected - the HSE oscillator is 0.2% faster than the nominal value.

We can see that in the following pictures:

1) HSE without division:

0690X00000AtMQGQA3.jpg

Nominal: 8 MHz

Real: 8.02 MHz

2) SYSCLK divided by 4:

0690X00000AtMQfQAN.jpgNominal: 216 / 4 = 54 MHz

Real: 54.11 MHz

This solves the issue - I just need to either accept or get a more accurate HSE.

Thanks a lot for the help!

Thanks for following up..

Tips, buy me a coffee, or three.. PayPal Venmo Up vote any posts that you find helpful, it shows what's working..