落叶知秋 发表于 2016-12-16 21:12:04

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

好久好久没发技术贴,于是想水一贴。

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

参考的转载原文: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文件

#include "DWT_Delay.h"

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


//0xE000EDFC DEMCR RW Debug Exception and Monitor Control Register.
//使能DWT模块的功能位
#define DEMCR         ( *(unsigned int *)0xE000EDFC )
#define TRCENA          ( 0x01 << 24) // DEMCR的DWT使能位

//0xE0001000 DWT_CTRL RW The Debug Watchpoint and Trace (DWT) unit
//使能CYCCNT计数器开始计数
#define DWT_CTRL      ( *(unsigned int *)0xE0001000 )
#define CYCCNTENA       ( 0x01 << 0 ) // DWT的SYCCNT使能位

//0xE0001004 DWT_CYCCNT RW Cycle Count register,   
//CYCCNT计数器的内部值(32位无符号)
#define DWT_CYCCNT      ( *(unsigned int *)0xE0001004) //显示或设置处理器的周期计数值

//系统CPU的频率 Hz
static unsigned int m_ulSysClk = 0;
//计时器的定义
typedef struct _Time_Counter_Struct_
{
    unsigned int      BeginTime;//开始时刻
    unsigned int      EndTime;    //结束时刻
    unsigned int      DeltaTime;//计时器记录的差值
    unsigned int      MaxVal;   //计时器记录的最大差值
    unsigned int      MinVal;   //计时器记录的最小差值
}TIME_COUNTER_STRUCT;
TIME_COUNTER_STRUCT   m_stTimeCounter =
{
{0,0,0,0,1000000000},{0,0,0,0,1000000000},{0,0,0,0,1000000000},{0,0,0,0,1000000000},
{0,0,0,0,1000000000},{0,0,0,0,1000000000},{0,0,0,0,1000000000},{0,0,0,0,1000000000},
{0,0,0,0,1000000000},{0,0,0,0,1000000000}
};


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

    m_ulSysClk   = ulsysclk; //保存当前系统的时钟周期,eg. 72,000,000(72MHz).   
}

/*
功      能:    精确的微妙级延时
参      数:    nCount -- 需要延时的时间 单位:微妙(us)
返回值:    无
注意 事 项:   
所在文件名:   
*/
void DWT_DELAY_US(unsigned int ulTime)
{
    unsigned int ultickstart, ultick_end, ultickdelay;
   
    //记录当前的CYCCNT计数值
    ultickstart = DWT_CYCCNT;
   
    //计算延时条件
    if(!m_ulSysClk )
      DWT_INIT(DEFAULT_CPU_SYSCLK_HZ);
   
    //将微秒数换算成滴答数
    ultickdelay = (ulTime*(m_ulSysClk/(1000*1000)));         
      
    //目标计数值
    ultick_end = ultickstart + ultickdelay;
      
    if(ultick_end > ultickstart )
    {//正常不溢出情况下
      while(DWT_CYCCNT < ultick_end);
    }
    else
    {//计数溢出,复位翻转
      while(DWT_CYCCNT >= ultick_end);   
      while(DWT_CYCCNT < ultick_end);
    }
}

/*
功      能:    计时器记录时间的动作
参      数:    iNo -- 计时器的编号 范围从0到(DWT_TIME_COUNTER_NUM-1)
                flag -- 计时器的动作DWT_TIME_COUNTER_BEGIN:计时开始
                                    DWT_TIME_COUNTER_END:计时结束
返回值:    无
注意 事 项:   
所在文件名:   
*/
void DWT_TIMECOUNTER(int iNo,int flag)
{
    //范围不能超限
    if(iNo >= DWT_TIME_COUNTER_NUM)
      return ;
   
    //需要初始化DWT模块
    if (!m_ulSysClk)
      DWT_INIT(DEFAULT_CPU_SYSCLK_HZ);
   
    if(flag == DWT_TIME_COUNTER_BEGIN)
    {   //计时器开始,记录该时刻计数
      m_stTimeCounter.BeginTime = DWT_CYCCNT;
    }
    else if(flag == DWT_TIME_COUNTER_END)
    {   //计时器结束,记录该时刻计数,并计算差值
      m_stTimeCounter.EndTime = DWT_CYCCNT;
      
      m_stTimeCounter.DeltaTime = m_stTimeCounter.EndTime - m_stTimeCounter.BeginTime;
      
      if(m_stTimeCounter.DeltaTime > m_stTimeCounter.MaxVal)
            m_stTimeCounter.MaxVal = m_stTimeCounter.DeltaTime;
      
      if(m_stTimeCounter.DeltaTime < m_stTimeCounter.MinVal)
            m_stTimeCounter.MinVal = m_stTimeCounter.DeltaTime;
    }
}


        //.h文件

#ifndef _DWT_DELAY_H_
#define _DWT_DELAY_H_


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

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

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

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



#ifdef __cplusplus
extern "C"{
#endif

/*
功      能:    初始化DWT模块功能
参      数:    ulsysclk -- 当前CPU的时钟频率 单位:Hz
返回值:    无
注意 事 项:   
所在文件名:   
*/
void DWT_INIT(unsigned int ulsysclk);
   
/*
功      能:    精确的微妙级延时
参      数:    nCount -- 需要延时的时间 单位:微妙(us)
返回值:    无
注意 事 项:   
所在文件名:   
*/
void DWT_DELAY_US(unsigned int ulTime);

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

#ifdef __cplusplus
}
#endif


#endif


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

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

void SimpleDelay(unsigned long ulCycle)
{
        for (;ulCycle>0;ulCycle--)
        {}
}


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

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

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

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

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

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


plb83 发表于 2017-7-7 14:02:15

这么好的贴怎么是0回复呢

笑笑我笑了 发表于 2017-7-7 14:30:16

M0用不了,M0不是ARMV7架构

落叶知秋 发表于 2017-7-7 14:33:04

笑笑我笑了 发表于 2017-7-7 14:30
M0用不了,M0不是ARMV7架构

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

落叶知秋 发表于 2017-7-7 14:38:10

plb83 发表于 2017-7-7 14:02
这么好的贴怎么是0回复呢

多谢捧场哈 {:lol:}

upli 发表于 2017-7-9 14:08:03

好方法,试试看再说

阿豪博士 发表于 2017-10-15 10:35:00

请问使用的时候,只是初始化一下,就可以拿来使用了吗?

void DWT_TIMECOUNTER(int iNo,int flag)

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

谢谢!

落叶知秋 发表于 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)
这个函数是用来进行代码运行时间计算的,记录开始和结束的时刻,算时间

Gorgon_Meducer 发表于 2017-10-17 21:04:01

Cortex-M有SysTick,也是可以配制成更CPU同频率,效果一样。

dy22511825 发表于 2017-10-18 10:41:08

....问个事,用JTAG调试时候,是否有影响。。。

cheungman 发表于 2017-10-18 10:45:18

我用DWT主要还是看函数运行时间.

落叶知秋 发表于 2017-10-18 12:44:48

dy22511825 发表于 2017-10-18 10:41
....问个事,用JTAG调试时候,是否有影响。。。

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

落叶知秋 发表于 2017-10-18 12:46:03

Gorgon_Meducer 发表于 2017-10-17 21:04
Cortex-M有SysTick,也是可以配制成更CPU同频率,效果一样。

多谢大师的补充。

阿豪博士 发表于 2017-10-18 22:23:56

这种是阻塞的吧?
请问,好像在延时过程中,会停止其他运行那??

liurangzhou 发表于 2017-10-19 08:48:51

顶一下,这个模块我用来测量周期

hpdell 发表于 2018-9-18 23:57:12

你好,我在 stm32f7x 的单片机使用使用貌似不行

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

落叶知秋 发表于 2018-9-19 08:36:11

hpdell 发表于 2018-9-18 23:57
你好,我在 stm32f7x 的单片机使用使用貌似不行

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

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

hpdell 发表于 2018-9-19 09:45:14

本帖最后由 hpdell 于 2018-9-19 10:27 编辑

落叶知秋 发表于 2018-9-19 08:36
用JLink调试的时候可用,断开了就不行,应该是DWT模块没有初始化,
在JLink调试模式下DWT模块会自动开启 ...

好的,我找找看

是不是跟如下的有关啦 ?





貌似找到答案了,如下:

落叶知秋 发表于 2018-9-19 10:05:07

hpdell 发表于 2018-9-19 09:45
好的,我找找看

是不是跟如下的有关啦 ?

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

hpdell 发表于 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个寄存器的地址的介绍 貌似没有找到 ?

落叶知秋 发表于 2018-9-19 13:08:20

hpdell 发表于 2018-9-19 10:48
你好,我资料里面貌似没有找到 关于

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

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

hpdell 发表于 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位没有写入,不知道该位在什么情况下才能够写入啊 ???????????

落叶知秋 发表于 2018-9-20 08:48:20

本帖最后由 落叶知秋 于 2018-9-20 09:24 编辑

hpdell 发表于 2018-9-19 19:24
貌似还是不行
我现在直接使用库函数也还是不行



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

编辑原因:添加文字

hpdell 发表于 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 延时,效果杠杠的

落叶知秋 发表于 2018-9-20 10:52:11

hpdell 发表于 2018-9-20 09:37
已经搞定了,貌似在 f7 上需要 如下

//address of the register


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

hpdell 发表于 2018-9-20 12:23:13

落叶知秋 发表于 2018-9-20 10:52
原来还要解锁,还以为只有ITM才需要。。不过,恭喜功能可用了

itm-tcr 寄存器可以不用管他,{:lol:}

Pjm2008 发表于 2018-9-25 14:59:46

这个延时方法思路可以借鉴,顶一把

gloryglory 发表于 2021-7-10 00:27:43

精确延时的好办,谢了,准备试下

fengyunyu 发表于 2021-7-10 08:04:14

不错,研究的深入

eddia2012 发表于 2021-7-18 22:12:05

楼主好思路,可以在代码中试下,谢谢啦

yiyinzhudan 发表于 2021-9-16 13:56:56

多谢楼主无私分享

akin 发表于 2021-9-16 14:48:01

while(—t);都不知道有多好用,没有额外开销,只需校准一下每秒钟的步数就可以了,然后把步数定个宏

yiyinzhudan 发表于 2021-12-31 08:11:06


楼主好思路,可以在代码中试下,谢谢啦
页: [1]
查看完整版本: 共享一下使用DWT模块实现精确延时