共享一下使用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。。
这么好的贴怎么是0回复呢 M0用不了,M0不是ARMV7架构 笑笑我笑了 发表于 2017-7-7 14:30
M0用不了,M0不是ARMV7架构
这个后来我查了资料,M0是Armv6的,但帖子编辑不了就没改了 plb83 发表于 2017-7-7 14:02
这么好的贴怎么是0回复呢
多谢捧场哈 {:lol:} 好方法,试试看再说 请问使用的时候,只是初始化一下,就可以拿来使用了吗?
void DWT_TIMECOUNTER(int iNo,int flag)
这个函数的作用是干什么啊?
谢谢! 阿豪博士 发表于 2017-10-15 10:35
请问使用的时候,只是初始化一下,就可以拿来使用了吗?
void DWT_TIMECOUNTER(int iNo,int flag)
对的,初始化之后DWT模块就使能了,就可以进行延时计数
DWT_TIMERCOUNTER(int iNo,int flag)
这个函数是用来进行代码运行时间计算的,记录开始和结束的时刻,算时间 Cortex-M有SysTick,也是可以配制成更CPU同频率,效果一样。 ....问个事,用JTAG调试时候,是否有影响。。。 我用DWT主要还是看函数运行时间. dy22511825 发表于 2017-10-18 10:41
....问个事,用JTAG调试时候,是否有影响。。。
貌似不会,断点停,时钟就停了 Gorgon_Meducer 发表于 2017-10-17 21:04
Cortex-M有SysTick,也是可以配制成更CPU同频率,效果一样。
多谢大师的补充。 这种是阻塞的吧?
请问,好像在延时过程中,会停止其他运行那?? 顶一下,这个模块我用来测量周期 你好,我在 stm32f7x 的单片机使用使用貌似不行
第一次使用 jlink 下载程序貌似可以,但是重新开关机后,重新启动系统就不行了,这是怎么回事啊 ?? hpdell 发表于 2018-9-18 23:57
你好,我在 stm32f7x 的单片机使用使用貌似不行
第一次使用 jlink 下载程序貌似可以,但是重新开关机后, ...
用JLink调试的时候可用,断开了就不行,应该是DWT模块没有初始化,
在JLink调试模式下DWT模块会自动开启,你可以看看M7的相关寄存器手册,看初始化部分是否需要改动 本帖最后由 hpdell 于 2018-9-19 10:27 编辑
落叶知秋 发表于 2018-9-19 08:36
用JLink调试的时候可用,断开了就不行,应该是DWT模块没有初始化,
在JLink调试模式下DWT模块会自动开启 ...
好的,我找找看
是不是跟如下的有关啦 ?
貌似找到答案了,如下:
hpdell 发表于 2018-9-19 09:45
好的,我找找看
是不是跟如下的有关啦 ?
看着应该是的,@E0000E80寄存器的位3,DWTENA 落叶知秋 发表于 2018-9-19 10:05
看着应该是的,@E0000E80寄存器的位3,DWTENA
你好,我资料里面貌似没有找到 关于
#define DWT_CTRL ( *(volatile unsigned int *)0xE0001000 )
#define DWT_CYCCNT ( *(volatile unsigned int *)0xE0001004)
上述2个寄存器的地址的介绍 貌似没有找到 ?
hpdell 发表于 2018-9-19 10:48
你好,我资料里面貌似没有找到 关于
#define DWT_CTRL ( *(volatile unsigned int *)0xE0001000...
楼主位的代码是Cortex-M3的权威手册里面的寄存器地址,你用的M7,就直接查M7的手册吧
你上面的截图里的ITM相关寄存器应该就是了,替换一下地址和掩码位应该就可以用了 本帖最后由 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 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: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 延时,效果杠杠的 hpdell 发表于 2018-9-20 09:37
已经搞定了,貌似在 f7 上需要 如下
//address of the register
原来还要解锁,还以为只有ITM才需要。。不过,恭喜功能可用了{:lol:} 落叶知秋 发表于 2018-9-20 10:52
原来还要解锁,还以为只有ITM才需要。。不过,恭喜功能可用了
itm-tcr 寄存器可以不用管他,{:lol:} 这个延时方法思路可以借鉴,顶一把 精确延时的好办,谢了,准备试下 不错,研究的深入 楼主好思路,可以在代码中试下,谢谢啦 多谢楼主无私分享 while(—t);都不知道有多好用,没有额外开销,只需校准一下每秒钟的步数就可以了,然后把步数定个宏
楼主好思路,可以在代码中试下,谢谢啦
页:
[1]