搜索
bottom↓
回复: 32

共享一下使用DWT模块实现精确延时

  [复制链接]

出40入42汤圆

发表于 2016-12-16 21:12:04 | 显示全部楼层 |阅读模式
好久好久没发技术贴,于是想水一贴。

搜了论坛,好像提及DWT模块的帖子不多,最近刚好用到,所以来吹吹水。

参考的转载原文:http://blog.csdn.net/linux_liulu/article/details/44998581
//-----------------------------------------------------------------------------------------------//
正文(其实是转载网上其他电工的博客):

        DWT,全称是The Debug Watchpoint and Trace (DWT) unit,用于系统调试及跟踪,详细的介绍可以参考
        ARM官方文档:ARMv7-M Architecture Reference Manual。本文将使它来实现一个系统的延时功能。
        (所以,所有ARMv7-M架构,即Cortex-M系列的单片机,无论M0/M3/M4/M7都适用。另,只在M3和M4上使用过。)

  1. 寄存器简单介绍
        要实现延时的功能,总共涉及到三个寄存器:DEMCR 、DWT_CTRL、DWT_CYCCNT,分别用于开启DWT功能、开启CYCCNT及获得系
        统时钟计数值。

        DEMCR
        其官方手册说明如下,这里我们只需要关注其第24位TRCENA。

       

        该寄存器的TRCENA位置位,使能DWT功能。

        DWT_CTRL寄存器
        其包含很多功能,这里我们只开启其循环计数功能。

       

        CYCCNT寄存器
        该寄存器地址见上图,其描述如下:

       

        当DWT的CYCCNTENA位置位后,该寄存器的值与系统周期计数值保持同步,我们可以用它的值来实现一个延时的功能。
        (注意,当CYCCNT计数器溢满后,会复位为0x0,重新开始计数,不停循环)

  2. 延时程序编写
        //.c文件

  1. #include "DWT_Delay.h"

  2. /*
  3. *  添加注释
  4. * (1)DWT寄存器是ARMv7架构的芯片内部的一个通用模块
  5. *      所有的ARMv7架构的芯片都有该功能,所以此处定义为common功能
  6. * (2)DWT模块全称为"The Debug Watchpoint and Trace (DWT) unit",
  7. *      用于系统调试及跟踪,详细可以参考ARM公司的官方手册
  8. * (3)在调试模式下,该功能自动会使能,但正常模式下则不会使能,
  9. *      所以首先要使能该功能模块
  10. * (4)该模块的CYCCNT计数器是需要用到的功能,该计数器从模块使能后,
  11. *      会随着CPU的每次跳动,自加1次,溢满后复位重来
  12. */


  13. //0xE000EDFC DEMCR RW Debug Exception and Monitor Control Register.  
  14. //使能DWT模块的功能位
  15. #define DEMCR           ( *(unsigned int *)0xE000EDFC )  
  16. #define TRCENA          ( 0x01 << 24) // DEMCR的DWT使能位  
  17.   
  18. //0xE0001000 DWT_CTRL RW The Debug Watchpoint and Trace (DWT) unit  
  19. //使能CYCCNT计数器开始计数
  20. #define DWT_CTRL        ( *(unsigned int *)0xE0001000 )  
  21. #define CYCCNTENA       ( 0x01 << 0 ) // DWT的SYCCNT使能位

  22. //0xE0001004 DWT_CYCCNT RW Cycle Count register,   
  23. //CYCCNT计数器的内部值(32位无符号)
  24. #define DWT_CYCCNT      ( *(unsigned int *)0xE0001004) //显示或设置处理器的周期计数值  
  25.   
  26. //系统CPU的频率 Hz
  27. static unsigned int m_ulSysClk = 0;  
  28. //计时器的定义
  29. typedef struct _Time_Counter_Struct_
  30. {
  31.     unsigned int        BeginTime;  //开始时刻
  32.     unsigned int        EndTime;    //结束时刻
  33.     unsigned int        DeltaTime;  //计时器记录的差值
  34.     unsigned int        MaxVal;     //计时器记录的最大差值
  35.     unsigned int        MinVal;     //计时器记录的最小差值
  36. }TIME_COUNTER_STRUCT;
  37. TIME_COUNTER_STRUCT     m_stTimeCounter[DWT_TIME_COUNTER_NUM] =
  38. {
  39.   {0,0,0,0,1000000000},{0,0,0,0,1000000000},{0,0,0,0,1000000000},{0,0,0,0,1000000000},
  40.   {0,0,0,0,1000000000},{0,0,0,0,1000000000},{0,0,0,0,1000000000},{0,0,0,0,1000000000},
  41.   {0,0,0,0,1000000000},{0,0,0,0,1000000000}
  42. };


  43. /*
  44. 功      能:    初始化DWT模块功能
  45. 参      数:    ulsysclk -- 当前CPU的时钟频率 单位:Hz
  46. 返  回  值:    无
  47. 注意 事 项:   
  48. 所在文件名:   
  49. */
  50. void DWT_INIT(unsigned int ulsysclk)  
  51. {  
  52.     DEMCR       |= TRCENA;  
  53.     DWT_CTRL    |= CYCCNTENA;  

  54.     m_ulSysClk   = ulsysclk; //保存当前系统的时钟周期,eg. 72,000,000(72MHz).   
  55. }  
  56.   
  57. /*
  58. 功      能:    精确的微妙级延时
  59. 参      数:    nCount -- 需要延时的时间 单位:微妙(us)
  60. 返  回  值:    无
  61. 注意 事 项:   
  62. 所在文件名:   
  63. */  
  64. void DWT_DELAY_US(unsigned int ulTime)  
  65. {  
  66.     unsigned int ultickstart, ultick_end, ultickdelay;  
  67.    
  68.     //记录当前的CYCCNT计数值
  69.     ultickstart = DWT_CYCCNT;  
  70.    
  71.     //计算延时条件
  72.     if(!m_ulSysClk )  
  73.         DWT_INIT(DEFAULT_CPU_SYSCLK_HZ);  
  74.    
  75.     //将微秒数换算成滴答数  
  76.     ultickdelay = (ulTime*(m_ulSysClk/(1000*1000)));           
  77.       
  78.     //目标计数值
  79.     ultick_end = ultickstart + ultickdelay;  
  80.       
  81.     if(ultick_end > ultickstart )  
  82.     {//正常不溢出情况下
  83.         while(DWT_CYCCNT < ultick_end);  
  84.     }  
  85.     else  
  86.     {//计数溢出,复位翻转
  87.         while(DWT_CYCCNT >= ultick_end);   
  88.         while(DWT_CYCCNT < ultick_end);  
  89.     }  
  90. }

  91. /*
  92. 功      能:    计时器记录时间的动作
  93. 参      数:    iNo -- 计时器的编号 范围从0到(DWT_TIME_COUNTER_NUM-1)
  94.                 flag -- 计时器的动作  DWT_TIME_COUNTER_BEGIN:计时开始
  95.                                       DWT_TIME_COUNTER_END  :计时结束
  96. 返  回  值:    无
  97. 注意 事 项:   
  98. 所在文件名:   
  99. */
  100. void DWT_TIMECOUNTER(int iNo,int flag)
  101. {
  102.     //范围不能超限
  103.     if(iNo >= DWT_TIME_COUNTER_NUM)
  104.         return ;
  105.    
  106.     //需要初始化DWT模块
  107.     if (!m_ulSysClk)  
  108.         DWT_INIT(DEFAULT_CPU_SYSCLK_HZ);
  109.    
  110.     if(flag == DWT_TIME_COUNTER_BEGIN)
  111.     {   //计时器开始,记录该时刻计数
  112.         m_stTimeCounter[iNo].BeginTime = DWT_CYCCNT;
  113.     }
  114.     else if(flag == DWT_TIME_COUNTER_END)
  115.     {   //计时器结束,记录该时刻计数,并计算差值
  116.         m_stTimeCounter[iNo].EndTime = DWT_CYCCNT;
  117.         
  118.         m_stTimeCounter[iNo].DeltaTime = m_stTimeCounter[iNo].EndTime - m_stTimeCounter[iNo].BeginTime;
  119.         
  120.         if(m_stTimeCounter[iNo].DeltaTime > m_stTimeCounter[iNo].MaxVal)
  121.             m_stTimeCounter[iNo].MaxVal = m_stTimeCounter[iNo].DeltaTime;
  122.         
  123.         if(m_stTimeCounter[iNo].DeltaTime < m_stTimeCounter[iNo].MinVal)
  124.             m_stTimeCounter[iNo].MinVal = m_stTimeCounter[iNo].DeltaTime;
  125.     }
  126. }
复制代码


        //.h文件

  1. #ifndef _DWT_DELAY_H_
  2. #define _DWT_DELAY_H_


  3. //默认的系统CPU频率 100MHz
  4. #define DEFAULT_CPU_SYSCLK_HZ               (100000000)

  5. //定义计时器个数
  6. #define DWT_TIME_COUNTER_NUM                (10)   

  7. //计时器开始标志
  8. #define DWT_TIME_COUNTER_BEGIN              (0)     

  9. //计时器结束标志
  10. #define DWT_TIME_COUNTER_END                (1)     



  11. #ifdef __cplusplus
  12. extern "C"{
  13. #endif

  14.   /*
  15.   功      能:    初始化DWT模块功能
  16.   参      数:    ulsysclk -- 当前CPU的时钟频率 单位:Hz
  17.   返  回  值:    无
  18.   注意 事 项:   
  19.   所在文件名:   
  20.   */
  21.   void DWT_INIT(unsigned int ulsysclk);
  22.    
  23.   /*
  24.   功      能:    精确的微妙级延时
  25.   参      数:    nCount -- 需要延时的时间 单位:微妙(us)
  26.   返  回  值:    无
  27.   注意 事 项:   
  28.   所在文件名:   
  29.   */  
  30.   void DWT_DELAY_US(unsigned int ulTime);

  31.   /*
  32.   功      能:    计时器记录时间的动作
  33.   参      数:    iNo -- 计时器的编号 范围从0到(DWT_TIME_COUNTER_NUM-1)
  34.                   flag -- 计时器的动作  DWT_TIME_COUNTER_BEGIN:计时开始
  35.                                         DWT_TIME_COUNTER_END  :计时结束
  36.   返  回  值:    无
  37.   注意 事 项:   
  38.   所在文件名:   
  39.   */
  40.   void DWT_TIMECOUNTER(int iNo,int flag);

  41. #ifdef __cplusplus
  42. }
  43. #endif


  44. #endif
复制代码


        至此,简单、好用、精准又不占用CPU其它外设资源的延时函数就实现了。
        (代码是重新整理过的,原文到这里结束)

  3.应用
        有时候我们需要在程序中实现精确的延时,但我看到很多电工都是用这种形式:

  1. void SimpleDelay(unsigned long ulCycle)
  2. {
  3.         for (;ulCycle>0;ulCycle--)
  4.         {}
  5. }
复制代码


        这明显不现实,效果也奇差啊,怎么办?

        系统定时中断!但如果我要时间短一些,微妙级别的,恐怕力有不逮。
        也有人说,可以算。但未免麻烦。

        定时器Timer!也是一种办法,但如果已经有其他用途了呢?资源紧张呢?
       
        本文提供的方法即不占用资源,精度也很高,因为是CPU频率级别的,精度应该是在ns (纳秒)间。
        所以完美符合电工的精确延时需求。

        除了精确延时外,上面的代码也提供了一个计时器,用来干嘛的呢?
        比如说,我想测试某一个函数的耗时有多少,可以用DWT直接记录下来,调试时可以直接查看,或打印出来。
        这样就可以测试功能模块的效率性能啦。

        好吧,好久不发帖。就说这么多,先去喝口水。。。

        不知道发在【ARM】版块合不合适?不合适请管理员告诉我啊,表封我ID。。


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2017-7-7 14:02:15 | 显示全部楼层
这么好的贴怎么是0回复呢

出0入0汤圆

发表于 2017-7-7 14:30:16 | 显示全部楼层
M0用不了,M0不是ARMV7架构

出40入42汤圆

 楼主| 发表于 2017-7-7 14:33:04 | 显示全部楼层
笑笑我笑了 发表于 2017-7-7 14:30
M0用不了,M0不是ARMV7架构

这个后来我查了资料,M0是Armv6的,但帖子编辑不了就没改了

出40入42汤圆

 楼主| 发表于 2017-7-7 14:38:10 | 显示全部楼层
plb83 发表于 2017-7-7 14:02
这么好的贴怎么是0回复呢

多谢捧场哈

出0入0汤圆

发表于 2017-7-9 14:08:03 来自手机 | 显示全部楼层
好方法,试试看再说

出0入0汤圆

发表于 2017-10-15 10:35:00 | 显示全部楼层
请问使用的时候,只是初始化一下,就可以拿来使用了吗?

void DWT_TIMECOUNTER(int iNo,int flag)

这个函数的作用是干什么啊?

谢谢!

出40入42汤圆

 楼主| 发表于 2017-10-16 11:16:16 | 显示全部楼层
阿豪博士 发表于 2017-10-15 10:35
请问使用的时候,只是初始化一下,就可以拿来使用了吗?

void DWT_TIMECOUNTER(int iNo,int flag)

对的,初始化之后DWT模块就使能了,就可以进行延时计数

DWT_TIMERCOUNTER(int iNo,int flag)
这个函数是用来进行代码运行时间计算的,记录开始和结束的时刻,算时间

出0入296汤圆

发表于 2017-10-17 21:04:01 | 显示全部楼层
Cortex-M有SysTick,也是可以配制成更CPU同频率,效果一样。

出0入0汤圆

发表于 2017-10-18 10:41:08 | 显示全部楼层
....问个事,用JTAG调试时候,是否有影响。。。

出0入0汤圆

发表于 2017-10-18 10:45:18 | 显示全部楼层
我用DWT主要还是看函数运行时间.

出40入42汤圆

 楼主| 发表于 2017-10-18 12:44:48 来自手机 | 显示全部楼层
dy22511825 发表于 2017-10-18 10:41
....问个事,用JTAG调试时候,是否有影响。。。

貌似不会,断点停,时钟就停了

出40入42汤圆

 楼主| 发表于 2017-10-18 12:46:03 来自手机 | 显示全部楼层
Gorgon_Meducer 发表于 2017-10-17 21:04
Cortex-M有SysTick,也是可以配制成更CPU同频率,效果一样。

多谢大师的补充。

出0入0汤圆

发表于 2017-10-18 22:23:56 | 显示全部楼层
这种是阻塞的吧?
请问,好像在延时过程中,会停止其他运行那??

出0入0汤圆

发表于 2017-10-19 08:48:51 | 显示全部楼层
顶一下,这个模块我用来测量周期

出0入0汤圆

发表于 2018-9-18 23:57:12 | 显示全部楼层
你好,我在 stm32f7x 的单片机使用使用貌似不行

第一次使用 jlink 下载程序貌似可以,但是重新开关机后,重新启动系统就不行了,这是怎么回事啊 ??

出40入42汤圆

 楼主| 发表于 2018-9-19 08:36:11 | 显示全部楼层
hpdell 发表于 2018-9-18 23:57
你好,我在 stm32f7x 的单片机使用使用貌似不行

第一次使用 jlink 下载程序貌似可以,但是重新开关机后, ...

用JLink调试的时候可用,断开了就不行,应该是DWT模块没有初始化,
在JLink调试模式下DWT模块会自动开启,你可以看看M7的相关寄存器手册,看初始化部分是否需要改动

出0入0汤圆

发表于 2018-9-19 09:45:14 | 显示全部楼层
本帖最后由 hpdell 于 2018-9-19 10:27 编辑
落叶知秋 发表于 2018-9-19 08:36
用JLink调试的时候可用,断开了就不行,应该是DWT模块没有初始化,
在JLink调试模式下DWT模块会自动开启 ...


好的,我找找看

是不是跟如下的有关啦 ?





貌似找到答案了,如下:

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出40入42汤圆

 楼主| 发表于 2018-9-19 10:05:07 | 显示全部楼层
hpdell 发表于 2018-9-19 09:45
好的,我找找看

是不是跟如下的有关啦 ?

看着应该是的,@E0000E80寄存器的位3,DWTENA

出0入0汤圆

发表于 2018-9-19 10:48:22 | 显示全部楼层
落叶知秋 发表于 2018-9-19 10:05
看着应该是的,@E0000E80寄存器的位3,DWTENA

你好,我资料里面貌似没有找到 关于

#define DWT_CTRL        ( *(volatile unsigned int *)0xE0001000 )
#define DWT_CYCCNT      ( *(volatile unsigned int *)0xE0001004)

上述2个寄存器的地址的介绍 貌似没有找到 ?

出40入42汤圆

 楼主| 发表于 2018-9-19 13:08:20 | 显示全部楼层
hpdell 发表于 2018-9-19 10:48
你好,我资料里面貌似没有找到 关于

#define DWT_CTRL        ( *(volatile unsigned int *)0xE0001000  ...

楼主位的代码是Cortex-M3的权威手册里面的寄存器地址,你用的M7,就直接查M7的手册吧
你上面的截图里的ITM相关寄存器应该就是了,替换一下地址和掩码位应该就可以用了

出0入0汤圆

发表于 2018-9-19 19:24:59 | 显示全部楼层
本帖最后由 hpdell 于 2018-9-19 20:55 编辑
落叶知秋 发表于 2018-9-19 13:08
楼主位的代码是Cortex-M3的权威手册里面的寄存器地址,你用的M7,就直接查M7的手册吧
你上面的截图里的IT ...


貌似还是不行
我现在直接使用库函数也还是不行

void bsp_InitDWT(void)
{
       
        CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
       
//        ITM->TCR |= (uint32_t)(ITM_TCR_SYNCENA_Msk | ITM_TCR_DWTENA_Msk);
        ITM->TCR |= (uint32_t)(ITM_TCR_SYNCENA_Msk);
       
        /*DWT 必须配置为触发 ITM:必须将 DWT->CTRL 控制寄存器的位 CYCCNTENA(位 0)置1;
          此外,还必须将 ITM 跟踪控制寄存器的位 2 (SYNCENA) 置 1。
                注: 如果未将 SYNENA 位置 1, DWT 产生给 TPIU 的同步触发,将仅发送 TPIU 同步数据包,
                不发送 ITM 同步数据包
        */
       
        DWT->CYCCNT = (uint32_t)0;     //显示或设置处理器的周期计数值
        DWT->CTRL   = DWT_CTRL_CYCCNTENA_Msk;

        /*
        当DWT->CTRL的CYCCNTENA位置位后,该寄存器的值与系统周期计数值保持同步,我们可以用它的值来实现一个延时的功能。
        (注意,当CYCCNT计数器溢满后,会复位为0x0,重新开始计数,不停循环)
*/
}

仿真查看 itm tcr 寄存器的值始终为 0 如下:



我把使用jlink 仿真时,把 dwt 寄存器的值打印出来,且此时的 dwt 延时是能够正常运行的
DEMCR 0x1000000
ITM->TCR 0x0
DWT->CYCCNT 0xBB194
DWT->CTRL 0x40000001

之后程序重新启动系统的值如下:
DEMCR 0x1000000
ITM->TCR 0x0
DWT->CYCCNT 0x0
DWT->CTRL 0x40000000     //这个的bit0位没有写入,不知道该位在什么情况下才能够写入啊 ???????????

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出40入42汤圆

 楼主| 发表于 2018-9-20 08:48:20 | 显示全部楼层
本帖最后由 落叶知秋 于 2018-9-20 09:24 编辑
hpdell 发表于 2018-9-19 19:24
貌似还是不行
我现在直接使用库函数也还是不行

  1. DWT->CTRL 0x40000000     //这个的bit0位没有写入,
复制代码

看样子就是这个DWT控制寄存器的计数功能为没有使能了,看到代码里是直接将掩码位赋值给DWT->CTRL,
改为“位与”操作试一下吧,再来就是检查一下掩码位的值是否正确了。
还有,我重新看了下M3的手册,ITM模块功能是不会影响DWT模块的,反倒是DWT会影响ITM功能,
所以,有关ITM模块的寄存器读写可以忽略,直接用楼主位的代码应该就可以了的。但我不知道你那里为什么不行,
还是打印一下DWT相关寄存器的值出来看一下吧

编辑原因:添加文字

出0入0汤圆

发表于 2018-9-20 09:37:01 | 显示全部楼层
本帖最后由 hpdell 于 2018-9-20 09:38 编辑
落叶知秋 发表于 2018-9-20 08:48
看样子就是这个DWT控制寄存器的计数功能为没有使能了,看到代码里是直接将掩码位赋值给DWT->CTRL,
改为 ...


已经搞定了,貌似在 f7 上需要 如下

//address of the register
volatile unsigned int *DWT_CYCCNT   = (volatile unsigned int *)0xE0001004;     

//address of the register
volatile unsigned int *DWT_CONTROL  = (volatile unsigned int *)0xE0001000;     

//address of the register
volatile unsigned int *DWT_LAR      = (volatile unsigned int *)0xE0001FB0;   //正确的寄存器地址  

//address of the register
volatile unsigned int *SCB_DEMCR    = (volatile unsigned int *)0xE000EDFC;



void bsp_dwtInit(void)
{
        *SCB_DEMCR |= 0x01000000;
        *DWT_LAR = 0xC5ACCE55; // unlock (CM7)  这个解锁很关键,否则 写入 *DWT_CONTROL |= 1 ;  这个无效
        *DWT_CYCCNT = 0; // reset the counter
        *DWT_CONTROL |= 1 ; // enable the counter
}

我现在使用这个来进行模拟 i2c 延时,效果杠杠的

出40入42汤圆

 楼主| 发表于 2018-9-20 10:52:11 | 显示全部楼层
hpdell 发表于 2018-9-20 09:37
已经搞定了,貌似在 f7 上需要 如下

//address of the register

原来还要解锁,还以为只有ITM才需要。。不过,恭喜功能可用了

出0入0汤圆

发表于 2018-9-20 12:23:13 | 显示全部楼层
落叶知秋 发表于 2018-9-20 10:52
原来还要解锁,还以为只有ITM才需要。。不过,恭喜功能可用了

itm-tcr 寄存器可以不用管他,

出0入14汤圆

发表于 2018-9-25 14:59:46 | 显示全部楼层
这个延时方法思路可以借鉴,顶一把

出0入0汤圆

发表于 2021-7-10 00:27:43 | 显示全部楼层
精确延时的好办,谢了,准备试下

出100入101汤圆

发表于 2021-7-10 08:04:14 来自手机 | 显示全部楼层
不错,研究的深入

出0入0汤圆

发表于 2021-7-18 22:12:05 来自手机 | 显示全部楼层
楼主好思路,可以在代码中试下,谢谢啦

出0入0汤圆

发表于 2021-9-16 13:56:56 | 显示全部楼层
多谢楼主无私分享

出0入0汤圆

发表于 2021-9-16 14:48:01 来自手机 | 显示全部楼层
while(—t);都不知道有多好用,没有额外开销,只需校准一下每秒钟的步数就可以了,然后把步数定个宏

出0入0汤圆

发表于 2021-12-31 08:11:06 | 显示全部楼层

楼主好思路,可以在代码中试下,谢谢啦
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子论坛 ( 公安交互式论坛备案:44190002001997 粤ICP备09047143号 )

GMT+8, 2022-7-6 02:45

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表