Precise delay with counter
If you work at high speed, as STM32F4 devices do (84MHz or more) then this tutorial is not right for you. But, you are able to descrease system speed to any frequency basicly you want.
If you want to use delay with systick down timer, look at my library here.
You can use great solution, Systick timer to make an interrups for you. But you can also decrease speed to let’s say 42Mhz, and that systick every 42ticks checks for variable in interrupt handler and increase/decrease it if neccessary, that can take up to 10cycles. In practise, that’s the same if we have 32MHz clock instead of 42MHz.
I was making some experiments with delay functions with only one variable (counter) and delay in simple way like
1 2 3 |
void Delay(uint32_t delay) { while (delay--); } |
but this method is not accuracy, because this works with system ticks and depends on core clock. But how we can make a delay like this, to be independent of core clock?
This can be done, of course. We need to make some mathematics at the beginning and then we are ready.
First, we have to know, how many ticks our while loop actually  takes. I create a simple program with while loop inside, and start debugger. I got result, that while loop “while (variable–);” takes exactly 4 clock cycles. We are getting closer.
Let’s say, our system clock is 180000000 Hz (STM32F429 Discovery). If we want to make a simple 1us delay, then we have to count 180 ticks to get this. If one while loop takes 4 cycles, then we have to divide our counter by 4.
1 2 3 4 5 6 |
/* 180ticks = number of ticks for 1 us 4ticks = 4 ticks while loop takes counter = 45 we have to be in while loop 45 times */ counter = 180ticks / 4ticks = 45 while loops |
This will be great, if we are making this inside program. But, calculating takes some time (with floating point unit activated only 1 cycle), then calling function and returning back also takes some time. My debugger says that I have to always substract 10cycles. I make some tests with different delay numbers and results are below.
Test code:
1 2 3 4 5 6 |
while (1) { /* Toggle leds */ TM_DISCO_LedToggle(LED_GREEN); /* Delay 1000 micros */ TM_DelayMicros(1000); } |
In example above is delay fixed to 1000us (1ms), but I tried different values. I made test on STM32F429 Discovery board (180MHz), FPU activated.
Delay I wanted | Ticks I got @ 180MHz | Ticks I got @ 168MHz | Ticks I got @ 84MHz |
---|---|---|---|
50us | 9010 | 8410 | 4210 |
125us | 22510 | 21010 | 10510 |
250us | 45010 | 42010 | 21010 |
1000us | 180010 | 168010 | 84010 |
So, always is “xxx10” at the end. If we assume that this is because of function calls and calculating, we can say that our equation is very simple. If we substract 10 from result, and divide that by number of delay we want, then we will see that we get exactly our system clock in MHz.
To calculate proper ticks for our delay, we first have to get ticks from device. This can be simply done with RCC_GetClocksFreq in CMSIS libraries.
1 2 3 4 5 6 7 8 9 10 11 12 |
uint32_t multiplier; void TM_Delay_Init(void) { RCC_ClocksTypeDef RCC_Clocks; /* Get system clocks */ RCC_GetClocksFreq(&RCC_Clocks); /* While loop takes 4 cycles */ /* For 1 us delay, we need to divide with 4M */ multiplier = RCC_Clocks.HCLK_Frequency / 4000000; } |
In function below, we divide with 4M. That’s because 4 is from while loop takes 4cycles and if we want to get 1us, we have to divide with 1M. This multiplier we use then in our pre calculations for delay.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
void TM_DelayMicros(uint32_t micros) { /* Multiply micros with multipler */ /* Substract 10 */ micros = micros * multiplier - 10; /* 4 cycles for one loop */ while (micros--); } void TM_DelayMillis(uint32_t millis) { /* Multiply millis with multipler */ /* Substract 10 */ millis = 1000 * millis * multiplier - 10; /* 4 cycles for one loop */ while (millis--); } |
That’s all. We are ready to make an example. I put it to oscilloscope and results were really great.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
/** * Keil project for delay with Systick timer * * Before you start, select your target, on the right of the "Load" button * * @author Tilen Majerle * @email tilen@majerle.eu * @website http://stm32f4-discovery.net * @ide Keil uVision 5 */ /* Include core modules */ #include "stm32f4xx.h" /* Include my libraries here */ #include "defines.h" #include "tm_stm32f4_disco.h" uint32_t multiplier; void TM_Delay_Init(void) { RCC_ClocksTypeDef RCC_Clocks; /* Get system clocks */ RCC_GetClocksFreq(&RCC_Clocks); /* While loop takes 4 cycles */ /* For 1 us delay, we need to divide with 4M */ multiplier = RCC_Clocks.HCLK_Frequency / 4000000; } void TM_DelayMicros(uint32_t micros) { /* Multiply micros with multipler */ /* Substract 10 */ micros = micros * multiplier - 10; /* 4 cycles for one loop */ while (micros--); } void TM_DelayMillis(uint32_t millis) { /* Multiply millis with multipler */ /* Substract 10 */ millis = 1000 * millis * multiplier - 10; /* 4 cycles for one loop */ while (millis--); } int main(void) { /* Initialize system */ SystemInit(); /* Initialize delay */ TM_Delay_Init(); /* Initialize onboard leds */ TM_DISCO_LedInit(); while (1) { /* Toggle leds */ TM_DISCO_LedToggle(LED_GREEN); /* Delay 1000 micros */ TM_DelayMicros(1000); } } |
For that example, my DISCO library will be great for you. Download below.
Leds and buttons for Discovery boards
Recent comments