【开源实战笔记】STM32 HAL库实现微秒级别延时
STM32 HAL库实现微秒级别延时
1 前言
HAL库函数中有延时函数HAL_Delay()(只是简单的减值等待),TencentOS tiny的tos_task_delay()(可以调度其他任务)进行毫秒级别的延时,但是在实际的开发中编写时序时有时需要进行较为准确的微秒级别延时,例如IIC协议等。
在导师vitoswwang指导下实现了us延时,
下面介绍一些实现步骤做出优缺点分析。
2 时间检测
方法一:示波器
通过引脚高低电平变化,检测间隔时间即可查出us延时是否精确
方法二:Keil 5的Debug模式查看执行时间
解决mdk优化问题
选择不进行优化 Level 0
相关配置
晶振
时钟并使能跟随
计算
点击设置断点,开始执行(F5)
再次运行(F5),观察右下角
两者相减即可算出延时时间,此处是使用下面介绍的方法一实现的延时效果0.00631573 - 0.00516788 = 0.00114785约为1us
3 实现方法
方法一:通过系统滴答时钟实现
System tick Timer是Cotex-M内核的24位计数的系统定时器,具有自动重载和溢出中断功能。
所有基于ARM Cortex_M内核的控制器都带有SysTick定时器,这样就方便了程序在不同的器件之间的移植。而使用RTOS的第一项工作往往就是将其移植到开发人员的硬件平台上,由于SYSTICK的存在无疑降低了移植的难度。
SysTick定时器除了能服务于操作系统之外,还能用于其它目的:如作为一个闹铃,用于测量时间等。
代码实现:
main.c
void HAL_Delay_us(uint32_t us)
{
HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000000);
HAL_Delay(us-1);
HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);
}
实现原理:
假设我们的时钟频率设为了180MHz
HAL_RCC_GetHCLKFreq()用于获取当前系统中的计数值
HAL_SYSTICK_Config()用于设置多少时间中断一次
180M / 1000 = 180000
HAL_SYSTICK_Config(180000)代表了延时了180000 / f主频 = 1 /1000s 即 1ms 中断一次
同理HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000000) 即 1us 中断一次
为何输入延时时间在HAL_Delay(us-1);处要减1呢?由于进出函数,配置寄存器都需要时间,经过测试大约为1us,故而要减去1
HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);重新将中断设为 1ms 中断一次,退出函数
优点:可省下一个硬件定时器资源,功耗相较于多个硬件定时器使用时较低
缺点:此延时函数不适合在中断、某些RTOS中调用
方法二:传统的for、while循环延时
/*
for循环实现延时us
*/
void for_delay_us(uint32_t us)
{
uint32_t Delay = us * 168/4;
do
{
__NOP();
}
while (Delay --);
}
优点:代码简单
缺点:调试出us延时较为耗时
方法三:通过硬件定时器TIM
为了获取精确延时,STM32芯片中往往会使用内核中的嘀嗒时钟Systick作为参考时钟。当嘀嗒时钟被占用做其他用途时,比如RTOS中的节拍器;就需要采用其他方法进行延时。
这里提出一种采用基本定时器TIM6(STM32F4)作为延时的参考时钟的方法。
通用、高级定时器应该留给更重要的功能使用
1 TIM6挂在APB1
2 配置定时器
配置定时器参数,主要关注预分频器寄存器TIMx_PSC,由于设置了TIM6的时钟为45MHz,所以TIM6_PSC设置为45-1,进行45分频那么得出分频后定时器的时钟为1MHz即1us进行数据变化一次,根据这个思想,我将定时器配置为向上计数,自动重装
打开定时器TIM6中断
3 代码实现
代码方式一(开关定时器):
void HAL_Delay_us(uint16_t us)
{
uint16_t differ=0xffff-us-5; //设定定时器计数器起始值
__HAL_TIM_SET_COUNTER(&htim6,differ);
HAL_TIM_Base_Start(&htim6); //启动定时器
while(differ<0xffff-6) //补偿,判断
{
differ=__HAL_TIM_GET_COUNTER(&htim6); //查询计数器的计数值
}
HAL_TIM_Base_Stop(&htim6);
}
代码方式二(定时器始终开启):
如果主函数里面启动一次后面不再关闭该定时器
HAL_TIM_Base_Start(&htim6);
延时函数
void HAL_Delay_us(uint16_t us) {
uint16_t startCnt = __HAL_TIM_GET_COUNTER(&htim6);
while ((__HAL_TIM_GET_COUNTER(&htim6) - startCnt) <= us);
}
代码方式三(回调函数):
Counter Mode设为Down
Counter Period设为0,其他保持不变
volatile bool elapsed = false; //用于判断设置的计数值是否耗尽(向下计数模式),耗尽时,在中断回调中会设置为true
void setState(bool state)
{
elapsed = state;
}
bool getState()
{
return elapsed;
}
void usDelay(uint32_t time)
{
__HAL_TIM_SetCounter(&htim2,time); //设置计数值
setState(false);
HAL_TIM_Base_Start_IT(&htim2); //开启定时器
while(!getState()); //判断计数值是否耗尽
HAL_TIM_Base_Stop_IT(&htim2); //关闭定时器
}
计数值耗尽进入回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM2) {
setState(true);
}
}
参考文章:
https://blog.csdn.net/haohaojian/article/details/69946544
STM32F4探索者https://blog.csdn.net/qq_33974167/article/details/110413882
STM32 HAL 库 uS 延时的 3 种实现方式https://zhuanlan.zhihu.com/p/349024420
- 点赞
- 收藏
- 关注作者
评论(0)