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

【经验分享】STM32开发项目:微秒级的精准延时 - 使用system tick/DWT寄存器/NOP命令

[复制链接]
STMCU小助手 发布时间:2022-4-13 11:00
背景
延时函数在STM32单片机开发的项目中有广泛的应用,微秒级延时在一些时间要求严格的场景下(例如软件模拟I2C通讯)是必不可少的。由于FreeRTOS默认采用了system tick作为时间片分配的时基定时器,可能与利用system tick设计的延时函数出现冲突。再加上FreeRTOS提供的延时函数void vTaskDelay( const TickType_t xTicksToDelay )最小的延时时间等于FreeRTOS的tick时间(一般设置为1ms),因此需要重新设计一套不基于system tick的微秒级延时函数。利用CM3/4内核中的数据观察点与跟踪(DWT)寄存器,可以在不占用硬件外设定时器的情况下实现微秒级的精准延时。

源码
通过选择#define USE_SYS_TICK, #define USE_DWT, #define USE_NOP_DELAY其中一个宏定义来决定delay函数的实现方式。需要特别注意的是,由于函数调用是需要时间的,因此使用延时函数时,一般会额外多出一些时间(1微秒左右)。

#define USE_SYS_TICK是利用system tick设计的延时函数,在没有使用实时操作系统的时候,它是一个比较适合实现微秒级延时的通用方法。
#define USE_DWT是利用CM3/4内核DWT寄存器设计的延时函数,它具有不占用system tick与外设定时器,延时精度高的特点。
#define USE_NOP_DELAY是利用了空指令设计的延时函数,它主要实现微秒以下的超短时间延时。
头文件
  1. #ifndef __DELAY_H__
  2. #define __DELAY_H__                           

  3. #include "stm32f10x_conf.h"
  4. #include "stm32f10x.h"

  5. //#define USE_SYS_TICK
  6. #define USE_DWT
  7. //#define USE_NOP_DELAY

  8. void delay_init(void);
  9. void delay_ms(uint16_t ms);
  10. void delay_us(u32 us);

  11. /**
  12. * 定义ns级延时,72M主频下,每一条空指令大约14ns
  13. */
  14. //#define DELAY_6ns()                                        __NOP()
  15. //#define DELAY_12ns()                                DELAY_6ns(); __NOP()
  16. //#define DELAY_18ns()                                DELAY_12ns(); __NOP()
  17. //#define DELAY_24ns()                                DELAY_18ns(); __NOP()
  18. //#define DELAY_30ns()                                DELAY_24ns(); __NOP()
  19. //#define DELAY_60ns()                                DELAY_30ns(); DELAY_30ns()
  20. //#define DELAY_90ns()                                DELAY_60ns(); DELAY_30ns()


  21. #endif

复制代码

源文件
  1. #include "delay.h"

  2. #ifdef USE_SYS_TICK

  3. static uint8_t  fac_us = 0;                          //us延时倍乘数
  4. static uint16_t fac_ms = 0;                          //ms延时倍乘数,在ucos下,代表每个节拍的ms数

  5. //初始化延迟函数
  6. //当使用OS的时候,此函数会初始化OS的时钟节拍
  7. //SYSTICK的时钟固定为HCLK时钟的1/8
  8. //SYSCLK:系统时钟
  9. void delay_init()
  10. {
  11.         SysTick_CLKSourceConfig ( SysTick_CLKSource_HCLK_Div8 ); //选择外部时钟  HCLK/8
  12.         fac_us = SystemCoreClock / 8000000;         //为系统时钟的1/8
  13.         fac_ms = ( uint16_t ) fac_us * 1000;             //非OS下,代表每个ms需要的systick时钟数
  14. }

  15. //延时nus
  16. //nus为要延时的us数.
  17. void delay_us ( u32 us )
  18. {
  19.         u32 temp;
  20.         SysTick->LOAD = us * fac_us;               //时间加载
  21.         SysTick->VAL = 0x00;                        //清空计数器
  22.         SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk ;  //开始倒数

  23.         do
  24.         {
  25.                 temp = SysTick->CTRL;
  26.         }
  27.         while ( ( temp & 0x01 ) && ! ( temp & ( 1 << 16 ) ) ); //等待时间到达

  28.         SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;  //关闭计数器
  29.         SysTick->VAL = 0X00;                         //清空计数器
  30. }
  31. //延时nms
  32. //注意nms的范围
  33. //SysTick->LOAD为24位寄存器,所以,最大延时为:
  34. //nms<=0xffffff*8*1000/SYSCLK
  35. //SYSCLK单位为Hz,nms单位为ms
  36. //对72M条件下,nms<=1864
  37. void delay_ms ( uint16_t ms )
  38. {
  39.         u32 temp;
  40.         SysTick->LOAD = ( u32 ) ms * fac_ms;       //时间加载(SysTick->LOAD为24bit)
  41.         SysTick->VAL = 0x00;                        //清空计数器
  42.         SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk ;  //开始倒数

  43.         do
  44.         {
  45.                 temp = SysTick->CTRL;
  46.         }
  47.         while ( ( temp & 0x01 ) && ! ( temp & ( 1 << 16 ) ) ); //等待时间到达

  48.         SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;  //关闭计数器
  49.         SysTick->VAL = 0X00;                        //清空计数器
  50. }

  51. #elif defined USE_DWT

  52. // 0xE000EDFC DEMCR RW Debug Exception and Monitor Control Register.
  53. #define DEMCR           ( *(__IO uint32_t *)0xE000EDFC )
  54. #define TRCENA          ( 0x01 << 24) // DEMCR的DWT使能位

  55. #define  DBGMCU_CR   *(__IO uint32_t *)0xE0042004                        //MCU调试模块控制寄存器,详细内容参考《stm32中文参考手册》调试支持(DBG)章节,747页

  56. // 0xE0001000 DWT_CTRL RW The Debug Watchpoint and Trace (DWT) unit
  57. #define DWT_CTRL        ( *(__IO uint32_t *)0xE0001000 )
  58. #define DWT_CTRL_CYCCNTENA       ( 0x01 << 0 ) // DWT的SYCCNT使能位

  59. // 0xE0001004 DWT_CYCCNT RW Cycle Count register,
  60. #define DWT_CYCCNT      ( *(__IO uint32_t *)0xE0001004) // 显示或设置处理器的周期计数值

  61. //#define DWT_DELAY_mS(mSec)    DWT_DELAY_uS(mSec*1000)

  62. void delay_init()
  63. {
  64.         //使能DWT外设
  65.         DEMCR |= (uint32_t)TRCENA;

  66.         //DWT CYCCNT寄存器计数清0
  67.         DWT_CYCCNT = (uint32_t)0u;

  68.         //使能Cortex-M3 DWT CYCCNT寄存器
  69.         DWT_CTRL |= (uint32_t)DWT_CTRL_CYCCNTENA;
  70. }


  71. // 微秒延时
  72. void delay_us(uint16_t uSec)
  73. {
  74.         if(uSec > 10000) uSec = 10000;

  75.     uint32_t ticks_start, ticks_end, ticks_delay;

  76.     ticks_start = DWT_CYCCNT;
  77.     ticks_delay = ( uSec * ( SystemCoreClock / (1000000) ) ); // 将微秒数换算成滴答数
  78.     ticks_end = ticks_start + ticks_delay;

  79.     // ticks_end没有溢出
  80.     if ( ticks_end >= ticks_start )
  81.     {
  82.         // DWT_CYCCNT在上述计算的这段时间中没有溢出
  83.         if(DWT_CYCCNT > ticks_start)
  84.         {
  85.             while( DWT_CYCCNT < ticks_end );
  86.         }
  87.         // DWT_CYCCNT溢出
  88.         else
  89.         {
  90.             // 已经超时,直接退出
  91.             return;
  92.         }
  93.     }
  94.     else // ticks_end溢出
  95.     {
  96.         // DWT_CYCCNT在上述计算的这段时间中没有溢出
  97.         if(DWT_CYCCNT > ticks_start)
  98.         {
  99.             // 等待DWT_CYCCNT的值溢出
  100.             while( DWT_CYCCNT > ticks_end );
  101.         }
  102.         // 等待溢出后的DWT_CYCCNT到达ticks_end
  103.         while( DWT_CYCCNT < ticks_end );
  104.     }
  105. }

  106. void delay_ms(uint16_t ms)
  107. {
  108.                 for(uint16_t i = 0; i < ms; i++)
  109.     {
  110.         // delay 1 ms
  111.         delay_us(1000);
  112.     }
  113. }

  114. #elif defined USE_NOP_DELAY

  115. void delay_init(void)
  116. {

  117. }

  118. void delay_ms(uint16_t ms)
  119. {

  120. }

  121. void delay_us(u32 us)
  122. {

  123. }

  124. #else

  125. #endif
复制代码

特别说明

使用DWT内核的延时函数中,涉及了两次数据溢出处理,第一次数据溢出处理是指计算结束时间(ticks_end = ticks_start + ticks_delay)发生溢出,如下图所示:

这里有个限制条件就是延时长度(ticks_delay)不能超过uint32_t的最大值,但考虑到这是一个极大的数值,几乎不可能发生,因此可以简化处理限制微秒延时函数的输入参数小于10k,以提高延时精度。对于这个溢出判断就比较明确了:

  1. if ( ticks_end >= ticks_start )
  2. {
  3.         // 计算未溢出
  4. }
  5. else
  6. {
  7.         // 计算溢出
  8. }
复制代码

第二个溢出相对比较复杂,处理不当会导致严重问题,尤其是使用了RTOS的系统中有很可能遇到。其核心原因是从标记初始化时间戳ticks_start = DWT_CYCCNT到准备比较时间戳例如while( DWT_CYCCNT < ticks_end );的这段时间的耗时是不确定的。即使没有使用RTOS,如下代码(使用绿色底色标注)在执行时也可能被硬中断暂停,从而导致DWT_CYCCNT时间戳可能已经越过了ticks_end ,或者也发生了溢出重置。

  1. ticks_start = DWT_CYCCNT;
  2. ticks_delay = ( uSec * ( SystemCoreClock / (1000000) ) ); // 将微秒数换算成滴答数
  3. ticks_end = ticks_start + ticks_delay;

  4. if ( ticks_end >= ticks_start )
  5. {
  6.         // DWT_CYCCNT在上述计算的这段时间中没有溢出
  7.         if(DWT_CYCCNT > ticks_start)
  8.         {

  9.         }
  10.         else
  11.         {
  12.         
  13.         }
  14. }
  15. else
  16. {
  17.         // DWT_CYCCNT在上述计算的这段时间中没有溢出
  18.         if(DWT_CYCCNT > ticks_start)
  19.         {

  20.         }
  21.         else
  22.         {
  23.         
  24.         }
  25. }
复制代码

因此需要根据延时等待操作时DWT_CYCCNT所处位置再进行一次判断:

7d3cd7bd163a4f0a812be51734efb715.png

ticks_end未溢出时(ticks_end1):
DWT_CYCCNT未溢出(DWT_CYCCNT2 & DWT_CYCCNT3)时:while( DWT_CYCCNT < ticks_end );
DWT_CYCCNT溢出(DWT_CYCCNT4)时:直接返回。
ticks_end溢出时(ticks_end2):
DWT_CYCCNT未溢出(DWT_CYCCNT2)时:while( DWT_CYCCNT > ticks_end ); while( DWT_CYCCNT < ticks_end );
DWT_CYCCNT溢出(DWT_CYCCNT4 & DWT_CYCCNT5)时:while( DWT_CYCCNT < ticks_end );

UR7)6S95828HELIL(I$Y$TG.png

使用指南
在程序开始的地方初始化延时函数:delay_init();
在需要延时的地方调用延时函数:delay_us(10);
需要特别说明的是,在FreeRTOS的任务中调用本文介绍的例如void delay_us(uint32_t us)等延时函数,任务会阻塞等待延时结束。如果是调用FreeRTOS提供的延时函数void vTaskDelay( const TickType_t xTicksToDelay ),系统会执行低优先级任务直到延时结束。



收藏 评论0 发布时间:2022-4-13 11:00

举报

0个回答

所属标签

相似分享

官网相关资源

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