赞
踩
说到单片机的延时函数,对于很多人来说并不陌生,在F4Cube Hal库中不就是HAL_Delay()函数而已么,但是实际上,简单的HAL_Delay函数的实现原理,并不是那么容易看明白。
HAL库中SystemCoreClock的变化过程如下表所示(最终的值取决于在CubeMX中的时钟树配置中的系统时钟SYSCLK)
函数【所在文件】 | SystemCoreClock |
SystemInit【startup_stm32f407xx.s】 | 16,000,000 |
SystemClock_Config【main.c】 HAL_RCC_ClockConfig 【stm32f4xx_hal_rcc.c】 | 168,000,000(SYSCLK) |
最终SystemCoreClock的更新值在stm32f4xx_hal_rcc.c的第708-712行完成,如下
- /* Update the SystemCoreClock global variable */
- SystemCoreClock = HAL_RCC_GetSysClockFreq() >> AHBPrescTable[(RCC->CFGR & RCC_CFGR_HPRE)>> RCC_CFGR_HPRE_Pos];
-
- /* Configure the source of time base considering new system clocks settings */
- HAL_InitTick (TICK_INT_PRIORITY);
STM32F407的时钟树配置如下图:
要明白HAL_Delay函数的原理,就不得先去理解SysTick的概念
Cortex -M内核的系列处理器,内部包含了一个SysTick (System tick)定时器,SysTick 是一个24 位的向下计数(倒计)定时器,它的时钟可以是系统时钟HCLK,也可以是对HCLK分频后的时钟。
当计到0 时,将从RELOAD 寄存器中自动重装载定时初值。在倒计时完成时,就会产生一个中断,中断服务函数为SysTick_Handler。当SysTick的频率和RELOAD重装载值(计数值)保持不变,中断就会以固定的时间间隔发生,此时如果在中断服务函数在对其内部预设的某一变量进行累加(或递减),就可以产生延时效果。只要不把它在SysTick 控制及状态寄存器中的使能位清除,就永不停息。
HAL_Delay函数就是利用的SysTick的上述特点
详细分析:https://stm32f4-discovery.net/2014/09/precise-delay-counter/
要使用while等循环函数进行延时,就得要知道while一次会执行多长时间,也就是消耗多少个MCU Clock Cycle,对于F4来说,经过测试,一次while(variable--)为4个tick,因此对于毫秒级的延时来说,如果采用16Mhz的SysTick,则16tick为1us,4次简单的while循环为1us。
- uint32_t multiplier;
- void Delay_Init(void)
- {
- /* while loop takes 4 cycles */
- /* for 1 us delay, we need to divide with 4M */
- multiplier = HAL_RCC_GetHCLKFreq() / 4000000;
- }
- }
最后一行的意思是1us延时,需要的while运行次数(System clock / (1MHz * 4 Tick))。经过测试,在while中的运算与函数调用会消耗10个tick,因此可以在最终延时结果中减去者10个tick就可以降低误差
- void DelayUs(uint32_t micros)
- {
- /* multiply micro with multipliter */
- micros = multiplier * micros - 10;
- /* 4 cycles for one loop */
- while(micros--);
- }
-
-
- void DelayMs(uint32_t mills)
- {
- /* multiply mills with multipliter */
- mills = multiplier * mills * 1000 - 10;
- /* 4 cycles for one loop */
- while(mills--);
- }

参考正点原子delay函数,改写为HAL库适用
- #include "delay.h"
- #include "stm32f4xx_hal.h"
- //初始化延迟函数
- //当使用OS的时候,此函数会初始化OS的时钟节拍
- //SYSTICK的时钟需要通过时钟树配置,可选为HCLK系统时钟,也可8分频HCLK_DIV8系统时钟
- //SYSCLK:系统时钟频率HCLK
-
- static uint8_t fac_us=0; //us延时倍乘
-
- void delay_init(void)
- {
- HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK); //SysTick频率为HCLK
- fac_us=(uint32_t)HAL_RCC_GetHCLKFreq()/1000000; //不论是否使用OS,fac_us都需要使用
- }
-
- //延时nus
- //nus为要延时的us数.
- //nus:0~190887435(最大值即2^32/fac_us@fac_us=22.5)
- void delay_us(uint32_t nus)
- {
- uint32_t ticks;
- uint32_t told,tnow,tcnt=0;
- uint32_t reload=SysTick->LOAD; //LOAD的值
- ticks=nus*fac_us; //需要的节拍数
- told=SysTick->VAL; //刚进入时的计数器值
- while(1)
- {
- tnow=SysTick->VAL;
- if(tnow!=told)
- {
- if(tnow<told)tcnt+=told-tnow; //这里注意一下SYSTICK是一个递减的计数器就可以了.
- else tcnt+=reload-tnow+told;
- told=tnow;
- if(tcnt>=ticks)break; //时间超过/等于要延迟的时间,则退出.
- }
- };
- }
-
- //延时nms
- //nms:要延时的ms数
- void delay_ms(uint16_t nms)
- {
- uint32_t i;
- for(i=0;i<nms;i++) delay_us(1000);
- }

在STM32F4Discovery 开发板的实测结果如下
预期 | 实测 |
10us | 15.19us |
100us | 105.31us |
关于DWT的内容,可以参考这里:
预期 | 实测 |
10us | 11.81us |
100us | 102.06us |
DWT延时函数源码下载地址:https://controllerstech.com/create-1-microsecond-delay-stm32/
参考资料:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。