STM32 Microsecond Delay

From Stm32World Wiki
Jump to navigation Jump to search

The STM32 HAL libraries include a HAL_Delay function which will cause a delay for X milliseconds (1/1000th of a second). While the necessity of such a function is debatable (see STM32 LED Blink), there are times when they are useful, for example when having to wait for an external peripheral to be ready after power up.

In Arduino a delayMicroseconds() function also exist, creating a delay measured in microseconds (μs delay, or us delay) and well, following the same argument as for the millisecond delay, it is possible that there are valid use cases for such a function. This article will explain how such a function could be implemented using a timer.

Tutorial Video

Timer Configuration

First step is to configure a timer running at exactly 1 MHz (clock cycle of 1 μs).

Using a STM32F411 Black Pill board as an example assume the following clock tree:

STM32F411 Clock Tree.png

The important part here is that both the APB clocks are running at 100 MHz - or 100 pulses each μs.

Keeping that information in mind, we can now configure one of the timers:

Microsecond timer.png

Notice that most of the timers on most STM32 MCUs uses 16-bit timers. Most of the STM32 MCUs include one or two 32-bit timers and on the STM32F411 Timer 5 is one such timer.

Using a 32-bit timer makes it possible to make much longer delays. A 16-bit timer running at 1 MHz can count 65536 μs, or 65.5 ms. That is probably enough for most use cases of such a timer, but if using a 32-bit timer can count 4294967296 or 4294 seconds.

Implementation

There are multiple ways the μs delay can be implemented. In the first example in the above video, a free running timer was used. This timer was using the default timer configuration counting UP.

First the timer was started:

HAL_TIM_Base_Start(&htim5);

The delay_us function can now be implemented like this:

void delay_us(uint32_t us) {
    uint32_t start = htim5.Instance->CNT;
    uint32_t end = start + us;

    while (htim5.Instance->CNT < end); // Do nothing - will fail to delay at wraparound

}

The drawback of this approach - as mentioned in the comment - is that it will fail when the timer wraps around having reached the full range of the counter.

A much better approach is to leave the timer stopped and only start it when needed. In the timer configuration screen shot above, the timer have been configured to count down rather than up. The function now becomes really simple:

void delay_us(uint32_t us) {

    volatile uint32_t *cnt = &htim5.Instance->CNT;

    __HAL_TIM_SET_COUNTER(&htim5, us); // Set the counter to number of us
    HAL_TIM_Base_Start(&htim5);        // Fire up the timer
    while (*cnt != 0);                 // Just wait until 0
    HAL_TIM_Base_Stop(&htim5);

}

Notice the use of a pointer to the counter. The htim5.Instance->CNT could be used directly in the while loop but by de-referencing it first a few instructions are saved.

That is all there is to it really. We now have a delay_us() function which will create a delay of almost exactly the requested number of μ seconds. The actual delay will be a few instructions shorter as the function does not take the overhead of the function call into consideration.

Miscellaneous Links