The article is released by WeChat official account "firmware workers".
In the program development of single chip microcomputer, delay is generally used. When the delay accuracy is not high, it is generally realized by software delay, which is to execute NOP empty statements in a cycle. If you want to achieve relatively high-precision delay, you can consider using a timer.
Taking STM32F429I-DISCO as the basic hardware, this paper explains how to use the ARM MCU core System Tick timer to achieve relatively high-precision microsecond and millisecond delay.
The official routine of STM32F429I-DISCO development board configures and starts the System Tick timer by default. The default timing cycle is 1ms. The STM32F429I-DISCO development board uses an 8MHz external crystal oscillator. Here, the main frequency of the single chip microcomputer of the development board is set to 168MHz. The clock source of the System Tick timer comes from the main frequency of the single chip microcomputer. Therefore, the corresponding time is 1 / 168 microseconds for every change in the count value of the timer. In this way, for every 168 changes in the count value of the timer, the corresponding time is 1 microsecond. We can convert the microseconds to be delayed t into the count value n of the timer. When the count value of the timer changes n, it indicates that the corresponding delay time has expired.
What needs to be noticed here is that the timer's timing cycle is 1ms, i.e. 1000us. After 1000us, the timer will automatically overload and restart counting. Therefore, in order to avoid the possible impact of timer overflow on the delay accuracy, here, take half of the timer cycle, i.e. 500us, as a delay cycle p to design the microsecond delay function vDelayUs().
The design idea of microsecond delay function is as follows.
(1) According to the microseconds to be delayed t, calculate the number n of 500us delay cycles, and then obtain the remaining microseconds r.
(2) First delay n 500us delay cycles, and then delay the remaining microseconds r after completion.
The program code is implemented as follows. There are some precautions when using this delay function.
-
If you need to transplant the code to other MCU, you need to modify the relevant macro definition at the top according to the actual setting of MCU timer.
-
Some initialization codes of the delay function will also occupy a certain execution time, so the delay here will also have a certain error, but the theoretical error of microsecond delay should be controlled within 1us.
-
In actual use, when delaying, try to call the function only once, and try not to call the function multiple times in a circular way to realize some long delays, because this will accumulate the execution time of the initialization code of the function itself, resulting in large delay error.
-
The function will also be interrupted, so the execution time of the interrupt should be as short as possible, otherwise it will increase the delay error here.
Since this function only reads the VAL count value register of the timer, it does not affect the 1ms interrupt timing function of the timer itself, and can make full use of the timer resources to realize a more accurate delay function.
#include "stm32f4xx.h" /* Cortex-M4 processor and core peripherals */ #define US_ Tips 168 / / the count value of the timer per microsecond needs to be modified according to the working frequency of MCU. Here, the main frequency is 168MHz #define MAX_ US_ PER_ Period (1000 / 2) / / the maximum number of microseconds in one cycle, which needs to be modified according to the System Tick cycle, and set to half of the System Tick cycle to prevent the inaccuracy caused by the overflow of microsecond delay #define MAX_ TICKS_ PER_ Period (max_us_per_period * us_ticks) / / the timer count value corresponding to the maximum microseconds of 1 cycle #define MAX_US_DELAY 4294967295 // uint32_t type Max void vDelayUs(uint32_t p_dwUs) { uint32_t l_dwReloadValue = SysTick->LOAD; uint32_t l_dwUsPeriodNum = p_dwUs / MAX_US_PER_PERIOD; uint32_t l_dwUsRemainTicks = (p_dwUs % MAX_US_PER_PERIOD) * US_TICKS; uint32_t l_dwDeltTicks = 0; uint32_t l_dwCurTicks, l_dwPreTicks, l_dwIntervalTicks, i; l_dwPreTicks = SysTick->VAL; for(i = 0; i < l_dwUsPeriodNum; i++) { while(1) { l_dwCurTicks = SysTick->VAL; // The System Tick of Cortex-M4 counts down if(l_dwCurTicks <= l_dwPreTicks) { l_dwIntervalTicks = l_dwPreTicks - l_dwCurTicks + l_dwDeltTicks; } else { l_dwIntervalTicks = l_dwPreTicks + (l_dwReloadValue - l_dwCurTicks + 1) + l_dwDeltTicks; } if(MAX_TICKS_PER_PERIOD <= l_dwIntervalTicks) { l_dwPreTicks = SysTick->VAL; l_dwDeltTicks = l_dwIntervalTicks - MAX_TICKS_PER_PERIOD; break; } } } if(0 < l_dwUsRemainTicks) { while(1) { l_dwCurTicks = SysTick->VAL; if(l_dwCurTicks <= l_dwPreTicks) { l_dwIntervalTicks = l_dwPreTicks - l_dwCurTicks + l_dwDeltTicks; } else { l_dwIntervalTicks = l_dwPreTicks + (l_dwReloadValue - l_dwCurTicks + 1) + l_dwDeltTicks; } if(l_dwUsRemainTicks <= l_dwIntervalTicks) { break; } } } }
Based on the microsecond delay function above, the millisecond delay function can be further designed. According to the principle of minimizing the number of microsecond delay function calls, the millisecond delay function vDelayMs() is designed with the maximum microsecond delay time as the basic unit. The design idea is as follows.
(1) Convert the number of milliseconds t to be delayed into microseconds, and calculate the number of maximum microseconds delay time n and the remaining microseconds delay time r.
(2) Call vDelayUs() function to complete n maximum microsecond delays first, and then call vDelayUs() to complete the remaining microsecond delays r.
Similarly, in order to improve the delay accuracy as much as possible, the millisecond delay function vDelayMs() should be called only once when delaying.
The program code of the millisecond delay function vDelayMs() is implemented as follows.
void vDelayMs(uint32_t p_dwMs) { uint64_t p_llUs = (uint64_t)p_dwMs * 1000; uint32_t p_dwMaxUsNum = p_llUs / MAX_US_DELAY; uint32_t p_dwRemainUs = p_llUs % MAX_US_DELAY; uint32_t i; for(i = 0; i < p_dwMaxUsNum; i++) { vDelayUs(MAX_US_DELAY); } if(0 < p_dwRemainUs) { vDelayUs(p_dwRemainUs); } }
If you want to further improve the delay accuracy, you can measure the initialization time ti of some variables at the beginning of the delay function, and then compensate these additional execution time in the delay. You can also convert C code into assembly code to evaluate the actual execution time of the code.
The method of converting from C code to assembly code can refer to the following link.
Some instructions commonly used by arm none EABI cross compiling tool:
https://blog.csdn.net/a13526758473/article/details/54982817
Here, the delay function is placed in delay C file, you can generate the corresponding assembly code file through the following command.
arm-none-eabi-objdump -d -S delay.o >delay1.txt
It should be noted that to embed C code in assembly code, you need to use the - g parameter in the compilation stage to make the assembly code correspond to C code. That is, the following commands are used at compile time.
arm-none-eabi-gcc -g ...