在STM32 HAL库中,有毫秒级延时函数HAL_Delay(),但是没有微妙级别的延时,目前有的几种延时方法有:
DWT延时
这是一个系统外设,专门用来为Cortex-M3及其以上芯片提供调试和追踪的硬件辅助功能,一种Cortex-M内核中的精确延时方法 | 杰杰的博客 (jiejietop.github.io)这个方法好是好,但缺点也是非常突出的:
- DWT 根本就不是设计给用户用的,它是Cortex-M处理器预留给上位机调试软件(例如MDK)进行调试和追踪的。换句话说,上位机调试软件觉得这是自己的私人财产,从来没想过用户会去使用它——这就导致调试过程中,IDE会按照自己的意思随意修改它的配置——啥时候会改呢?这要看IDE的心情。如果你的程序依赖了DWT进行延时,那么调试的时候,IDE的一个无心之举可能就会毁了你的时序——这一知识点非常容易忽略掉,从而导致很多人遇到调试的时候,系统随机性的功能不正常的坑,从而浪费大把的时间,往往还想不到是DWT导致的——说这一方法是天坑可能一点也不为过
- DWT 不是所有 Cortex-M 芯片都有……(Cortex-M0/M0+就没有)
NOP延时
- 有的人习惯于直接用软件方法堆积NOP() 来实现——这种方法所产生的延时效果“可能”容易受到编译器优化等级的影响——据说这也是很多人惧怕开启编译器的原因之一,因为一开优化,很多对时间敏感的硬件时序就因为延时函数的不稳定而一起变得不可捉摸;
定时器延时
再多使用一个定时器进行延时,这个办法要多再耗费一个定时器资源。例如使用TIM7,配置TIM7的频率为1M,对应计时一次1us,向上计数模式,通过操控定时器的CNT寄存器实现延时,代码如下:
void delay_us(uint16_t nus)
{
uint16_t differ = 0xffff - nus - 5;
//设置定时器7的初始值
___HAL_TIM_SetCounter(&htim7,differ);
//开启定时器
HAL_TIM_Base_Start(&htim7);
while(___HAL_TIM_GetCounter(&htim7) < 0xffff - 6)
{
;
}
//关闭定时器
HAL_TIM_Base_Stop(&htim7);
}
不过这还要多耗费一个定时器资源,感觉还是有些浪费。
SYSTICK 时钟摘取法延时
这是参考正点原子的代码,发现很巧妙的地方,他只对SYSTICK进行了读取VAL和LOAD寄存器的操作,没有改变其他寄存器的值。
代码如下:
static uint32_t g_fac_us = 0; /* us 延时倍乘数 */
/**
* @brief 初始化延时函数
* @param 无
* @retval 无
*/
void delay_init()
{
g_fac_us = HAL_RCC_GetHCLKFreq() / 1000000; //获取MCU的主频
}
/**
* @brief us延时函数
* @note 使用时钟摘取法来做us延时
* @param nus:要延时的us数
* @note nus取值范围:0 ~ (2^32 / fac_us)(fac_us一般等于系统主频)
* @retval 无
*/
void delay_us(uint32_t nus)
{
uint32_t ticks;
uint32_t told,tnow,tcnt = 0;
uint32_t reload = SysTick->LOAD; /*LOAD的值*/
ticks = nus * g_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; /*时间超过/等于要延时的时间,则退出*/
}
}
}
}
/**
* @brief ms延时函数
* @param nms:要延时的ms数
* @note nms取值范围:0 ~ (2^32 / fac_us / 1000)(fac_us一般等于系统主频)
* @retval 无
*/
void delay_ms(uint32_t nms)
{
delay_us((uint32_t)(nms * 1000));
}