HAL库中使用us级延时方法

在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));
}
### HAL中的微秒延时函数实现及用法 #### 1. 微秒延时的需求背景 现代嵌入式开发中,ST官方主推的HAL虽然提供了丰富的功能接口,但在某些场景下仍显不足。例如,尽管有基于SysTick的毫秒延时函数`HAL_Delay()`[^1],却未提供原生支持的微秒延时函数。这使得开发者在需要高精度短时间延迟的应用场合需自行设计解决方案。 --- #### 2. 使用定时器实现微秒延时 一种常见的方法是利用STM32内部的通用定时器(如TIMx),通过设置合适的预分频系数和计数值来完成精确到微秒的延时操作。以下是具体实现方式: ##### (1) 配置定时器参数 为了达到微秒分辨率,通常会将定时器的工作频率设定为较高值。假设系统时钟为72MHz,则可以按照如下计算得出预分频系数: \[ \text{Prescaler} = (\frac{\text{System Clock}}{\text{Desired Timer Frequency}}) - 1 \] 对于每微秒触发一次中断的情况,目标频率应设为1 MHz,即周期为1 μs。此时, \[ \text{Prescaler} = (\frac{72\,\mathrm{MHz}}{1\,\mathrm{MHz}}) - 1 = 71 \] ##### (2) 编写延时函数 以下是一个典型的微秒延时函数实现示例,使用了上述配置逻辑: ```c void Delay_us(uint16_t nus) { __HAL_TIM_SET_COUNTER(&htim1, 0); // 将定时器计数器清零 __HAL_TIM_ENABLE(&htim1); // 启动定时器 while (__HAL_TIM_GET_COUNTER(&htim1) < nus) { } __HAL_TIM_DISABLE(&htim1); // 关闭定时器 } ``` 此代码片段展示了如何借助硬件资源构建精准的时间间隔控制机制[^3]。其中,`nus`表示期望等待的微秒数量;而`__HAL_TIM_*`系列宏则封装了底层寄存器访问细节,简化编程流程并增强可移植性。 --- #### 3. 注意事项 - **避免干扰其他模块**:由于整个项目可能广泛依赖于默认初始化后的SysTick行为,因此不宜随意调整其相关属性以免影响全局性能表现。 - **合理选择定时器实例**:考虑到不同外设间可能存在资源共享冲突,在实际部署前务必确认所选定时器不会与其他重要组件争抢通道使用权。 - **优化效率考量**:纯轮询模式下的阻塞型延时可能会降低CPU利用率甚至引发实时响应滞后风险,故针对更复杂应用场景推荐探索回调事件驱动或者DMA传输相结合的技术路线进一步提升整体效能水平。 --- ### 示例程序结构说明 下面给出一段完整的测试代码框架供参考学习之用: ```c #include "stm32f1xx_hal.h" // 定义使用的定时器句柄 TIM_HandleTypeDef htim1; /** * @brief 初始化 TIM1 的配置用于生成微秒延时 */ static void MX_TIM1_Init(void){ TIM_ClockConfigTypeDef sClockSourceConfig; TIM_MasterConfigTypeDef sMasterConfig; htim1.Instance = TIM1; htim1.Init.Prescaler = 71; // 设置 Prescaler 使计数频率为 1MHz htim1.Init.CounterMode = TIM_COUNTERMODE_UP; htim1.Init.Period = 65535; // 自由运行模式最大值 if(HAL_TIM_Base_Init(&htim1)!= HAL_OK){ } sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; if(HAL_TIM_ConfigClockSource(&htim1,&sClockSourceConfig)!= HAL_OK){ } sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if(HAL_TIMEx_MasterConfigSynchronization(&htim1,&sMasterConfig)!= HAL_OK){ } } int main(void){ HAL_Init(); SystemClock_Config(); // 系统时钟配置省略... MX_TIM1_Init(); uint16_t delay_time = 1000; // 设定延时时长(单位μs) while(1){ GPIO_TogglePin(GPIOA,GPIO_PIN_5); Delay_us(delay_time); // 调用自定义微秒延时函数 } } ``` 以上例子演示了一个简单的LED闪烁实验过程,验证了前述理论分析成果的实际可行性。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值