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

STM32高精度延时实验

[复制链接]
STMCU小助手 发布时间:2021-1-20 15:00
STM32高精度延时实验


3.1.jpg
1 前言
在STM32编程过程中经常用到延时函数,最常用的莫过于微秒级延时和毫秒级延时。那么本文针对STM32的延时进行分析和实验。关于STM32的时钟系统,参考笔者博文。
2 裸机延时
2.1普通延时
  1. //粗延时函数,微秒
  2. void delay_us(u16 time)
  3. {   
  4.    u16 i=0;

  5.    while(time--)
  6.    {
  7.       i=10; //自己定义
  8.       while(i--) ;   
  9.    }
  10. }

  11. //毫秒级的延时
  12. void delay_ms(u16 time)
  13. {   
  14.    u16 i=0;

  15.    while(time--)
  16.    {
  17.       i=12000; //自己定义
  18.       while(i--) ;   
  19.    }
  20. }
复制代码

这个比较简单,让单片机做一些无关紧要的工作来打发时间,经常用循环来实现,不过要做的比较精准还是要下一番功夫。下面的代码是在网上搜到的,经测试延时比较精准。
2.2SysTick 定时器延时
CM3 内核的处理器,内部包含了一个SysTick 定时器,SysTick 是一个24 位的倒计数定时器,当计到0 时,将从RELOAD 寄存器中自动重装载定时初值。只要不把它在SysTick 控制及状态寄存器中的使能位清除,就永不停息。系统定时器一般用于操作系统,用于产生时基,维持操作系统的心跳。SysTick 在STM32的参考手册里面介绍的很简单,其详细介绍,请参阅《Cortex-M3 权威指南》。
1.中断方式
如下,定义延时时间time_delay,SysTick_Config()定义中断时间段,在中断中递减time_delay,从而实现延时。
  1. volatile unsigned longtime_delay; // 延时时间,注意定义为全局变量

  2. //延时n_ms
  3. void delay_ms(volatile unsignedlong nms)
  4. {
  5.   //SYSTICK分频--1ms的系统时钟中断
  6. if (SysTick_Config(SystemFrequency/1000))
  7. {
  8.        while (1);
  9. }

  10. time_delay=nms;//读取定时时间

  11. while(time_delay);

  12. SysTick->CTRL=0x00; //关闭计数器
  13. SysTick->VAL =0X00; //清空计数器
  14. }

  15. //延时nus
  16. void delay_us(volatile unsignedlong nus)
  17. {
  18. //SYSTICK分频--1us的系统时钟中断
  19. if (SysTick_Config(SystemFrequency/1000000))
  20. {
  21.        while (1);
  22. }

  23. time_delay=nus;//读取定时时间
  24. while(time_delay);
  25. SysTick->CTRL=0x00; //关闭计数器
  26. SysTick->VAL =0X00; //清空计数器
  27. }

  28. //在中断中将time_delay递减。实现延时
  29. void SysTick_Handler(void)
  30. {
  31. if(time_delay)
  32.         time_delay--;
  33. }
复制代码

还有一种标准写法:
  1. static __IO u32 TimingDelay;
  2. #define delay_ms(x) delay_us(1000*x)       //单位ms
  3. /**
  4. * @brief   启动系统滴答定时器 SysTick
  5. * @param   无
  6. * @retval  无
  7. */
  8. void sysTick_init(void)
  9. {
  10.        /*SystemFrequency / 1000    1ms中断一次
  11.         * SystemFrequency / 100000 10us中断一次
  12.         * SystemFrequency / 1000000 1us中断一次
  13.         */
  14.        if(SysTick_Config(SystemCoreClock / 1000000))       //ST3.5.0库版本
  15.        {
  16.               /*Capture error */
  17.               while(1);
  18.        }
  19.        //关闭滴答定时器
  20.        SysTick->CTRL&= ~ SysTick_CTRL_ENABLE_Msk;
  21. }

  22. /**
  23. * @brief    us延时程序,1us为一个单位
  24. * @param    nTime: Delay_us( 1 ) 则实现的延时为 1 * 1us = 1us
  25. * @retval   无
  26. */
  27. void delay_us(__IO u32 nTime)
  28. {
  29.        TimingDelay= nTime;      
  30.        //使能滴答定时器
  31.        SysTick->CTRL|=  SysTick_CTRL_ENABLE_Msk;

  32.        while(TimingDelay!= 0);
  33. }

  34. /**
  35. * @brief      获取节拍程序
  36. * @param      无
  37. * @retval     无
  38. * @attention  在 SysTick 中断函数 SysTick_Handler()调用
  39. */
  40. void TimingDelay_Decrement(void)
  41. {
  42.        if(TimingDelay != 0x00)
  43.        {
  44.               TimingDelay--;
  45.        }
  46. }
  47. /**

  48. * @brief  This function handlesSysTick Handler.
  49. * @param  None
  50. * @retval None
  51. */
  52. void SysTick_Handler(void)
  53. {
  54.        TimingDelay_Decrement();   
  55. }
复制代码

2.非中断方式
SysTick的时钟以 HCLK(AHB 时钟)或 HCLK/8作为运行时钟,在这里我们选用内部时钟源72M,固定为HCLK 时钟的1/8,所以SYSTICK的时钟为9M,即SYSTICK定时器以9M的频率递减。SysTick主要包含CTRL、LOAD、VAL、CALIB 等4 个寄存器。
CTRL: SysTick控制和状态寄存器
LOAD: SysTick重装载值寄存器
VAL:    SysTick当前值寄存器
CALIB:SysTick校准值寄存器
对这几个寄存器的操作被封装到core_cm3.h中:
3.2.png
           
STM32中的Systick 部分内容属于NVIC控制部分,一共有4个寄存器,名称和地址分别是:
STK_CTRL,     0xE000E010 --  控制寄存器      
表1SysTick控制及状态寄存器
3.3.png
第0位:ENABLE,Systick 使能位  (0:关闭Systick功能;1:开启Systick功能)      
第1位:TICKINT,Systick 中断使能位(0:关闭Systick中断;1:开启Systick中断)      
第2位:CLKSOURCE,Systick时钟源选择(0:使用HCLK/8 作为Systick时钟;1:使用HCLK作为Systick时钟)   
第16位:COUNTFLAG,Systick计数比较标志,如果在上次读取本寄存器后,SysTick 已经数到了0,则该位为1。如果读取该位,该位将自动清零
STK_LOAD,    0xE000E014 --  重载寄存器
表2SysTick重装载数值寄存器
3.4.png
Systick是一个递减的定时器,当定时器递减至0时,重载寄存器中的值就会被重装载,继续开始递减。STK_LOAD  重载寄存器是个24位的寄存器最大计数0xFFFFFF。
STK_VAL,     0xE000E018 --  当前值寄存器   
表3SysTick当前数值寄存器
3.5.png
也是个24位的寄存器,读取时返回当前倒计数的值,写它则使之清零,同时还会清除在SysTick 控制及状态寄存器中的COUNTFLAG 标志。
STK_CALRB,  0xE000E01C --  校准值寄存器
表4SysTick校准数值寄存器
3.6.png
校准值寄存器提供了这样一个解决方案:它使系统即使在不同的CM3产品上运行,也能产生恒定的SysTick中断频率。最简单的作法就是:直接把TENMS的值写入重装载寄存器,这样一来,只要没突破系统极限,就能做到每10ms来一次 SysTick异常。如果需要其它的SysTick异常周期,则可以根据TENMS的值加以比例计算。只不过,在少数情况下, CM3芯片可能无法准确地提供TENMS的值(如, CM3的校准输入信号被拉低),所以为保险起见,最好在使用TENMS前检查器件的参考手册。
SysTick定时器除了能服务于操作系统之外,还能用于其它目的:如作为一个闹铃,用于测量时间等。要注意的是,当处理器在调试期间被喊停( halt)时,则SysTick定时器亦将暂停运作。
程序如下,相当于查询法。
  1. static u8  fac_us=0;                                            //us延时倍乘数                 
  2. static u16 fac_ms=0;                                            //ms延时倍乘数
  3. //SYSTICK的时钟固定为HCLK时钟的1/8
  4. //SYSCLK:系统时钟
  5. /**
  6. * @brief  初始化延迟函数
  7. * @param  None
  8. * @retval None
  9. */
  10. void sysTick_init()
  11. {
  12.        //SysTick->CTRL&=0xfffffffb;//bit2清空,选择外部时钟  HCLK/8
  13.        SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);    //选择外部时钟  HCLK/8
  14.        fac_us=SystemCoreClock/8000000;                           //为系统时钟的1/8
  15.        fac_ms=(u16)fac_us*1000;                                 //非OS下,代表每个ms需要的systick时钟数  
  16. }                                                         
  17.       
  18. /**
  19. * @brief  延时nus
  20. * @param  nus为要延时的us数.
  21. * @retval None
  22. */
  23. void delay_us(u32 nus)
  24. {            
  25.        u32temp;               
  26.        SysTick->LOAD=nus*fac_us;                              //时间加载              
  27.        SysTick->VAL=0x00;                                     //清空计数器
  28.        SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk;    //开始倒数   

  29.        do
  30.        {
  31.               temp=SysTick->CTRL;
  32.        }while((temp&0x01)&&!(temp&(1<<16)));         //等待时间到达  
  33.        SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;//关闭计数器
  34.        SysTick->VAL=0X00;                                       //清空计数器   
  35. }

  36. //SysTick->LOAD为24位寄存器,所以,最大延时为:
  37. //nms<=0xffffff*8*1000/SYSCLK
  38. //SYSCLK单位为Hz,nms单位为ms
  39. //对72M条件下,nms<=1864
  40. /**
  41. * @brief  延时nms
  42. * @param  nms为要延时的nms数.
  43. * @retval None
  44. */
  45. void delay_ms(u16 nms)
  46. {                          
  47.        u32temp;            
  48.        SysTick->LOAD=(u32)nms*fac_ms;                    //时间加载(SysTick->LOAD为24bit)
  49.        SysTick->VAL=0x00;                                             //清空计数器
  50.        SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk;    //开始倒数

  51.        do
  52.        {
  53.               temp=SysTick->CTRL;
  54.        }while((temp&0x01)&&!(temp&(1<<16)));         //等待时间到达  

  55.        SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;//关闭计数器
  56.        SysTick->VAL=0X00;                                     //清空计数器              
  57. }
复制代码


前文所述的两种方式各有利弊,第一种方式采用库函数,编写简单,由于中断的存在,不利于在其他中断中调用此延时函数,还需要考虑中断嵌套和中断优先级的问题。第二种方式直接操作寄存器,看起来比较繁琐,其实也不难,同时克服了中断方式实现的缺点。

3 RTOS延时

在RTOS中,我们时常需要高精度的定时,一般RTOS都有延时函数,但是不够精确,我们还是用SysTick 定时器,采用寄存器方式进行延时,代码如下:
  1. /*Includes*********************************************************************/

  2. #include"./SysTick/stm32f103_SysTick.h"

  3. #define SYSTEM_SUPPORT_OS             1            //定义系统文件夹是否支持UCOS
  4. //如果使用rt-thread,则包括下面的头文件即可.
  5. #if SYSTEM_SUPPORT_OS
  6. #include "rtthread.h"                     //支持OS时,使用
  7. #endif

  8. //********************************************************************************
  9. static uint32_t fac_us=0;                                            //us延时倍乘数

  10. #if SYSTEM_SUPPORT_OS      
  11.     static uint16_t fac_ms=0;                             //ms延时倍乘数,在os下,代表每个节拍的ms数
  12. #endif

  13. #if SYSTEM_SUPPORT_OS                                          //如果SYSTEM_SUPPORT_OS定义了,说明要支持OS了(不限于rt-thread).
  14. //当delay_us/delay_ms需要支持OS的时候需要三个与OS相关的宏定义和函数来支持
  15. //首先是3个宏定义:
  16. //delay_osrunning:用于表示OS当前是否正在运行,以决定是否可以使用相关函数
  17. //delay_ostickspersec:用于表示OS设定的时钟节拍,delay_init将根据这个参数来初始哈systick
  18. //delay_osintnesting:用于表示OS中断嵌套级别,因为中断里面不可以调度,delay_ms使用该参数来决定如何运行
  19. //然后是3个函数:
  20. //delay_osschedlock:用于锁定OS任务调度,禁止调度
  21. //delay_osschedunlock:用于解锁OS任务调度,重新开启调度
  22. //delay_ostimedly:用于OS延时,可以引起任务调度.
  23. //本例程仅作RT-Thread的支持,其他OS,请自行参考着移植
  24. //支持RT-Thread

  25. extern volatile rt_uint8_trt_interrupt_nest;
  26. //在board.c文件的rt_hw_board_init()里面将其置为1

  27. uint8_t OSRunning=0;

  28. #ifdef    RT_THREAD_PRIORITY_MAX                                        //RT_THREAD_PRIORITY_MAX定义了,说明要支持RT-Thread   
  29. #define delay_osrunning         OSRunning                           //OS是否运行标记,0,不运行;1,在运行
  30. #define delay_ostickspersec   RT_TICK_PER_SECOND     //OS时钟节拍,即每秒调度次数
  31. #define delay_osintnesting    rt_interrupt_nest             //中断嵌套级别,即中断嵌套次数
  32. #endif

  33. //us级延时时,关闭任务调度(防止打断us级延迟)
  34. void delay_osschedlock(void)
  35. {
  36. #ifdef RT_THREAD_PRIORITY_MAX
  37.         rt_enter_critical();
  38. #endif  
  39. }

  40. //us级延时时,恢复任务调度
  41. void delay_osschedunlock(void)
  42. {     
  43. #ifdef RT_THREAD_PRIORITY_MAX
  44.          rt_exit_critical();
  45. #endif  
  46. }
  47. //调用OS自带的延时函数延时
  48. //ticks:延时的节拍数
  49. void delay_ostimedly(uint32_tticks)
  50. {
  51. #ifdef RT_THREAD_PRIORITY_MAX
  52.          rt_thread_delay(ticks);
  53. #endif   
  54. }
  55. #endif
  56.    
  57. //初始化延迟函数
  58. //当使用ucos的时候,此函数会初始化ucos的时钟节拍
  59. //SYSTICK的时钟固定为AHB时钟
  60. //SYSCLK:系统时钟频率
  61. void delay_init()
  62. {
  63. #if SYSTEM_SUPPORT_OS                                         //如果需要支持OS.
  64.        uint32_treload;
  65. #endif
  66. SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);      //选择外部时钟  HCLK/8
  67.        fac_us=SystemCoreClock/8000000;                           //为系统时钟的1/8
  68. #if SYSTEM_SUPPORT_OS                                                   //如果需要支持OS.
  69.        reload=SystemCoreClock/8000000;                           //每秒钟的计数次数单位为K              
  70.        reload*=1000000/delay_ostickspersec;      //根据delay_ostickspersec设定溢出时间

  71.                                                                                                //reload为24位寄存器,最大值:16777216,在180M下,约合0.745s左右     

  72.        fac_ms=1000/delay_ostickspersec;              //代表OS可以延时的最少单位      
  73.        SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;//开启SYSTICK中断
  74.        SysTick->LOAD=reload;                                     //每1/OS_TICKS_PER_SEC秒中断一次
  75.        SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk;//开启SYSTICK
  76. #endif
  77. }                                                         

  78. #if SYSTEM_SUPPORT_OS                                         //如果需要支持OS.
  79. //延时nus
  80. //nus:要延时的us数.   
  81. //nus:0~190887435(最大值即2^32/fac_us@fac_us=22.5)                                                                 
  82. void delay_us(uint32_t nus)
  83. {            
  84.        uint32_tticks;
  85.        uint32_ttold,tnow,tcnt=0;
  86.        uint32_treload=SysTick->LOAD;                         //LOAD的值                  
  87.        ticks=nus*fac_us;                                       //需要的节拍数
  88.        delay_osschedlock();                             //阻止OS调度,防止打断us延时
  89.        told=SysTick->VAL;                                //刚进入时的计数器值

  90.        while(1)
  91.        {
  92.               tnow=SysTick->VAL;  
  93.               if(tnow!=told)
  94.               {         
  95.                      if(tnow<told)
  96.                          tcnt+=told-tnow;//这里注意一下SYSTICK是一个递减的计数器就可以了.
  97.                      else
  98.                          tcnt+=reload-tnow+told;   
  99.                      told=tnow;
  100.                      if(tcnt>=ticks)
  101.                         break;                //时间超过/等于要延迟的时间,则退出.
  102.               }
  103.        };
  104.        delay_osschedunlock();                                //恢复OS调度                                                                           
  105. }

  106. //延时nms
  107. //nms:要延时的ms数
  108. //nms:0~65535
  109. void delay_ms(uint16_t nms)
  110. {     
  111.        if(delay_osrunning&&delay_osintnesting==0)//如果OS已经在跑了,并且不是在中断里面(中断里面不能任务调度)        
  112.        {              
  113.               if(nms>=fac_ms)                                    //延时的时间大于OS的最少时间周期
  114.               {
  115.                   delay_ostimedly(nms/fac_ms);      //OS延时
  116.               }
  117.               nms%=fac_ms;                                       //OS已经无法提供这么小的延时了,采用普通方式延时   
  118.        }
  119.        delay_us((uint32_t)(nms*1000));                       //普通方式延时
  120. }

  121. #else  //不用ucos时
  122. //延时nus
  123. //nus为要延时的us数.
  124. //nus:0~190887435(最大值即2^32/fac_us@fac_us=22.5)   
  125. void delay_us(uint32_t nus)
  126. {            
  127.        uint32_tticks;
  128.        uint32_ttold,tnow,tcnt=0;
  129.        uint32_treload=SysTick->LOAD;                         //LOAD的值                  
  130.        ticks=nus*fac_us;                                       //需要的节拍数
  131.        told=SysTick->VAL;                                //刚进入时的计数器值
  132.        while(1)
  133.        {
  134.               tnow=SysTick->VAL;  
  135.               if(tnow!=told)
  136.               {         
  137.                      if(tnow<told)
  138.                         tcnt+=told-tnow;//这里注意一下SYSTICK是一个递减的计数器就可以了.
  139.                      else
  140.                         tcnt+=reload-tnow+told;   
  141.                      told=tnow;
  142.                      if(tcnt>=ticks)
  143.                         break;                //时间超过/等于要延迟的时间,则退出.
  144.               }
  145.        };
  146. }
  147. //延时nms
  148. //nms:要延时的ms数
  149. void delay_ms(uint16_t nms)
  150. {
  151.        uint32_ti;
  152.        for(i=0;i<nms;i++)
  153.        {     
  154.               delay_us(1000);
  155.        }
  156. }
  157. #endif
复制代码

以上代码适配RT-Thread实时系统,针对系统嵌入式系统需要进行修改,以上代码包含了裸机的延时函数。值得注意的是,初始化函数在board.c中调用的。
3.7.png
【ps】针对RT-Thread官方是有高精度延时方案的,大家也可参考:
文章出处:  嵌入式实验楼


1 收藏 1 评论6 发布时间:2021-1-20 15:00

举报

6个回答
boclandc 回答时间:2021-1-20 15:23:24
收集归纳比较好!
zhongwn 回答时间:2021-1-20 15:39:19
学习了,mark一下
radio2radio 回答时间:2021-1-20 18:57:48
这叫什么高精度呀,只能说是普通石英晶体振荡器的精度。

要能够保证连续运行一个月,时间累计误差不超过几秒钟的,才对得起高精度一词。
goyhuan 回答时间:2021-1-20 19:40:03
good job
kylixyao 回答时间:2021-1-23 20:23:52
高精度还是用定时器比较合适
胤幻1988 回答时间:2021-1-25 10:40:20
这不就是正点原子的代码么,大哥~~

所属标签

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