打印
[应用相关]

基于STM32的us延时的多种方法实现

[复制链接]
109|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
观海|  楼主 | 2025-5-8 21:29 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
硬件环境:STM32F103

软件环境:Keil5

实现方式:HAL/寄存器

1.问题背景
在我重新学习温湿度传感器的时候,发现HAL库的延时函数的最小单位时ms,我之前学习温湿度时基于RTThread操作系统的,本身就有us延时,学习完各博客的方法发现一些有意思的并且整理了出来。

说是HAL库,但有些写的很好的方法原理已经接触到了寄存器部分了,只是实现在HAL库中。因此本章我会将我的理解讲解出来。当然如果只是要用的话跟着配置完基础配置后直接复制本文代码就可以直接编译了。

2.定时器实现
在我看来,定时器和轮询一样简单,因为定时器有一个准确的公式转换,而且cubemx可以直接设置psc和arr,对于本章节来说不需要了解原理。且轮询由于可能会有其他中断产生会打断主程序的循环,精度会比定时器差。

2.1定时器时钟频率设置
我们以TIM6为例,首先查出TIM6是挂载在APB1中的。



我们的cubemxAPB1定时器时钟频率设置为60MHz



2.2 周期间隔计算
首先我们要T=1us的基准时钟,换算成频率就是1MHz,根据频率计算公式



得出(PSC+1)*(ARR+1)=60即可,满足前面条件的情况下,至于你PSC和ARR取多少都可以。

2.3 定时器参数设置



设置完了以后,你的定时器中的计数器就可以每隔1us加1了。当加到指定值的时候退出就行了。

2.4 代码实现
/*
    TIM6实现us延时
*/
void delay_us_tim(uint32_t nus)
{

    uint16_t  differ = 0xffff-nus-5;
    //设置定时器6的技术初始值
  __HAL_TIM_SetCounter(&htim6,differ);
  //开启定时器
  HAL_TIM_Base_Start(&htim6);

  while( __HAL_TIM_GetCounter(&htim6)<0xffff-5);
//关闭定时器
  HAL_TIM_Base_Stop(&htim6);
}

3.基于滴答定时器的积分法实现(推荐)
上面的定时器还要打开关闭定时器,HAL库封装好的打开函数也是需要时间的,因此在1-5us左右的情况下误差会比较大,因此更适用50us以上的延时将定时器打开和关闭的函数稀释掉。

而滴答定时器是HAL库一开始就打开的,因为HAL库的ms级延时函数就是基于滴答定时器的心跳。无需打开和关闭。

3.1 滴答定时器寄存器描述



3.2 滴答定时器原理
滴答定时器启动后,你的滴答定时器将会每隔1/CK_PSC s跳变一次。每跳变一次,systick->VAL寄存器的值就减1,当减到0的时候,systick->VAL值重装载变回SysTick->LOAD



那么我们只需要读取在一段时间内VAL寄存器的值的变化我们就知道我们延时了多少了(重点!!)

而本章方法他的思路跟积分定义很相似(数学好的可以和积分定义相互印证)

该方法时先取一段时间,然后对VAL的值进行对比,VAL的值的变化*次数变化的时间就是该段时间的大小了,许多取值相加加到我们需要的延时值的时候我们就可以退出延时函数了。



而根据积分的定义可知,当代码运行的越快,取的段数越多,延时代码就越精确。

3.3 滴答定时器参数设置
我以120MHz为例,说明VAL跳变120次的时候为1us



3.4 代码实现
static uint32_t g_fac_us = 0;            /* us 延时倍乘数 */
/**
* @brief    初始化延时函数
* @param    无
* @retval   无
*/
void delay_init()
{
        g_fac_us = HAL_RCC_GetHCLKFreq() / 1000000;   //获取MCU的主频
}
/**
* @brief    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;/*防止VAL减到0重装载了还没延时完*/
                        told = tnow;
                        if(tcnt >= ticks)
                                break;            /*时间超过/等于要延时的时间,则退出*/
                }
        }
}


4.使用NOP函数延时
这个就是在定时器实现的时候将的轮询延时。在没有中断的时候,精确度很高,但有中断的时候,有时候还在执行的时候就被中断抢占了,导致延时时长不正确。且该方法一些高级的编译器会优化掉

4.1 STM32中的定义



4.2 代码实现
uint32_t g_fac_us ;
/**
* @brief    初始化延时函数
* @param    无
* @retval   无
*/
void delay_init()
{
        g_fac_us = HAL_RCC_GetHCLKFreq() / 1000000;   //获取MCU的主频
}


/*
for循环实现延时us
*/
void for_delay_us(uint32_t nus)
{
    uint32_t Delay = nus * g_fac_us /4;
    do
    {
        __NOP();
    }
    while (Delay --);
}

————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/cgw15019854423/article/details/147638702

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

129

主题

4263

帖子

1

粉丝