你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

​STM32延时函数的四种方法

[复制链接]
STMCU 发布时间:2020-9-7 10:28
单片机编程过程中经常用到延时函数,最常用的莫过于微秒级延时delay_us()和毫秒级delay_ms()。本文基于STM32F207介绍4种不同方式实现的延时函数。
普通延时




这种延时方式应该是大家在51单片机时候,接触最早的延时函数。这个比较简单,让单片机做一些无关紧要的工作来打发时间,经常用循环来实现,在某些编译器下,代码会被优化,导致精度较低,用于一般的延时,对精度不敏感的应用场景中。

  1. //微秒级的延时
  2. void delay_us(uint32_t delay_us)
  3. {   
  4.   volatile unsigned int num;
  5.   volatile unsigned int t;

  6.   
  7.   for (num = 0; num < delay_us; num++)
  8.   {
  9.     t = 11;
  10.     while (t != 0)
  11.     {
  12.       t--;
  13.     }
  14.   }
  15. }
  16. //毫秒级的延时
  17. void delay_ms(uint16_t delay_ms)
  18. {   
  19.   volatile unsigned int num;
  20.   for (num = 0; num < delay_ms; num++)
  21.   {
  22.     delay_us(1000);
  23.   }
  24. }
复制代码




定时器中断



定时器具有很高的精度,我们可以配置定时器中断,比如配置1ms中断一次,然后间接判断进入中断的次数达到精确延时的目的。这种方式精度可以得到保证,但是系统一直在中断,不利于在其他中断中调用此延时函数,有些高精度的应用场景不适合,比如其他外设正在输出,不允许任何中断打断的情况。

STM32任何定时器都可以实现,下面我们以SysTick 定时器为例介绍:

初始化SysTick 定时器:
  1. /* 配置SysTick为1ms */
  2. RCC_GetClocksFreq(&RCC_Clocks);
  3. SysTick_Config(RCC_Clocks.HCLK_Frequency / 1000);
复制代码


中断服务函数:

  1. void SysTick_Handler(void)
  2. {
  3.   TimingDelay_Decrement();
  4. }
  5. void TimingDelay_Decrement(void)
  6. {
  7.   if (TimingDelay != 0x00)
  8.   {
  9.     TimingDelay--;
  10.   }
  11. }
复制代码


延时函数:



  1. void Delay(__IO uint32_t nTime)
  2. {
  3.   TimingDelay = nTime;
  4.   while(TimingDelay != 0);
  5. }
复制代码


查询定时器



为了解决定时器频繁中断的问题,我们可以使用定时器,但是不使能中断,使用查询的方式去延时,这样既能解决频繁中断问题,又能保证精度。

STM32任何定时器都可以实现,下面我们以SysTick 定时器为例介绍。

STM32的CM3内核的处理器,内部包含了一个SysTick定时器,SysTick是一个24位的倒计数定时器,当计到0时,将从RELOAD寄存器中自动重装载定时初值。只要不把它在SysTick控制及状态寄存器中的使能位清除,就永不停息。

SYSTICK的时钟固定为HCLK时钟的1/8,在这里我们选用内部时钟源120M,所以SYSTICK的时钟为(120/8)M,即SYSTICK定时器以(120/8)M的频率递减。SysTick 主要包含CTRL、LOAD、VAL、CALIB 等4 个寄存器。

▼CTRL:控制和状态寄存器
1.png

▼LOAD:自动重装载除值寄存器
2.png

▼VAL:当前值寄存器
3.png

▼CALIB:校准值寄存器
使用不到,不再介绍


  1. void delay_us(uint32_t nus)
  2. {
  3.   uint32_t temp;
  4.   SysTick->LOAD = RCC_Clocks.HCLK_Frequency/1000000/8*nus;
  5.   SysTick->VAL=0X00;//清空计数器
  6.   SysTick->CTRL=0X01;//使能,减到零是无动作,采用外部时钟源
  7.   do
  8.   {
  9.     temp=SysTick->CTRL;//读取当前倒计数值
  10.   }while((temp&0x01)&&(!(temp&(1<<16))));//等待时间到达
  11.   SysTick->CTRL=0x00; //关闭计数器
  12.   SysTick->VAL =0X00; //清空计数器
  13. }
  14. void delay_ms(uint16_t nms)
  15. {
  16.   uint32_t temp;
  17.   SysTick->LOAD = RCC_Clocks.HCLK_Frequency/1000/8*nms;
  18.   SysTick->VAL=0X00;//清空计数器
  19.   SysTick->CTRL=0X01;//使能,减到零是无动作,采用外部时钟源
  20.   do
  21.   {
  22.     temp=SysTick->CTRL;//读取当前倒计数值
  23.   }while((temp&0x01)&&(!(temp&(1<<16))));//等待时间到达
  24.   SysTick->CTRL=0x00; //关闭计数器
  25.   SysTick->VAL =0X00; //清空计数器
  26. }
复制代码


汇编指令
如果系统硬件资源紧张,或者没有额外的定时器提供,又不想方法1的普通延时,可以使用汇编指令的方式进行延时,不会被编译优化且延时准确。

STM32F207在IAR环境下
  1. /*!
  2. *  @brief   软件延时
  3. *  @param  ulCount:延时时钟数
  4. *  @return none
  5. *  @note   ulCount每增加1,该函数增加3个时钟
  6. */
  7. void SysCtlDelay(unsigned long ulCount)
  8. {
  9.     __asm("    subs    r0, #1\n"
  10.           "    bne.n   SysCtlDelay\n"
  11.           "    bx      lr");
  12. }
复制代码

这3个时钟指的是CPU时钟,也就是系统时钟。120MHZ,也就是说1s有120M的时钟,一个时钟也就是1/120us,也就是周期是1/120us。3个时钟,因为执行了3条指令。

使用这种方式整理ms和us接口,在Keil和IAR环境下都测试通过。

  1. /*120Mhz时钟时,当ulCount为1时,函数耗时3个时钟,延时=3*1/120us=1/40us*/
  2. /*
  3. SystemCoreClock=120000000

  4. us级延时,延时n微秒
  5. SysCtlDelay(n*(SystemCoreClock/3000000));

  6. ms级延时,延时n毫秒
  7. SysCtlDelay(n*(SystemCoreClock/3000));

  8. m级延时,延时n秒
  9. SysCtlDelay(n*(SystemCoreClock/3));
  10. */

  11. #if defined   (__CC_ARM) /*!< ARM Compiler */
  12. __asm void
  13. SysCtlDelay(unsigned long ulCount)
  14. {
  15.     subs    r0, #1;
  16.     bne     SysCtlDelay;
  17.     bx      lr;
  18. }
  19. #elif defined ( __ICCARM__ ) /*!< IAR Compiler */
  20. void
  21. SysCtlDelay(unsigned long ulCount)
  22. {
  23.     __asm("    subs    r0, #1\n"
  24.        "    bne.n   SysCtlDelay\n"
  25.        "    bx      lr");
  26. }

  27. #elif defined (__GNUC__) /*!< GNU Compiler */
  28. void __attribute__((naked))
  29. SysCtlDelay(unsigned long ulCount)
  30. {
  31.     __asm("    subs    r0, #1\n"
  32.        "    bne     SysCtlDelay\n"
  33.        "    bx      lr");
  34. }

  35. #elif defined  (__TASKING__) /*!< TASKING Compiler */                           
  36. /*无*/
  37. #endif /* __CC_ARM */
复制代码







注释

理论上:汇编方式的延时也是不准确的,有可能被其他中断打断,最好使用us和ms级别的延时,采用for循环延时的函数也是如此。采用定时器延时理论上也可能不准确的,定时器延时是准确的,但是可能在判断语句的时候,比如if语句,判断延时是否到了的时候,就在判断的时候,被中断打断执行其他代码,返回时已经过了一小段时间。不过汇编方式和定时器方式,只是理论上不准确,在实际项目中,这两种方式的精度已经足够高了。


3 收藏 6 评论6 发布时间:2020-9-7 10:28

举报

6个回答
乐天乐 回答时间:2020-9-7 13:44:43
非常通用
ag5jz 回答时间:2020-9-8 07:51:50
谢谢分享
开发者 回答时间:2020-9-8 13:29:15
hujjj 回答时间:2020-9-8 14:40:06
分析详细,感谢分享!
_sa 回答时间:2020-9-17 09:34:02
我试了一下汇编延时的那个,使用mdk -o3优化 72Mhz的f103, 发现 SysCtlDelay(24);实际上是2us,应该是6个机器周期吧
小歆-2051663 回答时间:2020-9-17 10:57:12
还可以使用 HAL_Delay 延时指定毫秒

所属标签

STM32团队

意法半导体微控制器和微处理器拥有广泛的产品线,包含低成本的8位单片机和基于ARM® Cortex®-M0、M0+、M3、M4、M33、M7及A7内核并具备丰富外设选择的32位微控制器及微处理器


最新内容

相似技术帖

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
微信公众号二维码 微信公众号
手机版二维码 手机版