延时函数新思路-隐匿的定时器DWT

前言

        软件延时不精准?SysTick滴答定时器被占用?定时器资源紧张?还有别的延时方法吗?有请ARM Cortex-M系列特有的隐匿定时器DWT闪亮出场,为阁下解决以上问题!内容利用DWT实现堵塞延时、非堵塞延时、计时功能


目录

前言

凭什么要用DWT

DWT简介

涉及寄存器介绍

代码实现

如何配置DWT

如何实现堵塞延时

如何实现超时判断(非堵塞延时)

如何计时

实战例子


凭什么要用DWT

列举四大延时方法的优缺点:

软件延时

        优点:实现简单;无需消耗硬件资源;

        缺点:延时不精准;只能堵塞延时;

SysTick计数器延时

        优点:延时精准;可实现非阻塞延时;

        缺点:24位计数器延时范围小;

定时器(Timer/SysTick)中断延时

        优点:延时精准;可实现非阻塞延时;

        缺点:占用定时器中断;若实现微秒级则中断频繁,占用过多处理器资源;

DWT延时

        优点:延时精准;可实现非阻塞延时;32位计数器延时范围大;解放其他硬件资源;强行实现简单,看了我这个教程还不简单?

        缺点:仅部分ARM Cortex-M系列才有DWT;实现复杂,看了我这个教程还复杂?

看!DWT延时方式除了缺点就全是优点了。玩笑归玩笑,其实很多大神都在使用这种方法。在嵌入式项目中讲究的就一个“省”字,勤俭持家是种美德,把可以不用的资源“省”出来。更何况有DWT在不用白不用是吧。


DWT简介

        DWT(Data Watchpoint and Trace)是ARM Cortex-M系列处理器独有的数据观测点和跟踪单元。DWT是ARM针对Cortex-M系列处理器设计的一个调试组件,用于监视数据访问、执行时间等。其他ARM处理器系列(如Cortex-A和Cortex-R系列)也有调试组件,但不一定包含DWT。那么哪些内核有DWT呢?

大多数较新的ARM Cortex-M系列处理器都支持DWT,例如:

  • Cortex-M3
  • Cortex-M4
  • Cortex-M7
  • Cortex-M33
  • Cortex-M35P
  • Cortex-M23
  • Cortex-M55

但是,一些较旧或较低成本的处理器则不支持DWT,例如:

  • Cortex-M0
  • Cortex-M0+
  • Cortex-M1

        所以为什么DWT能实现延时啊?因为延时需要计数器对吧?而DWT恰恰就有一个计数器,而且还比SysTick的24位更牛逼的32位计数器。(DWT:真服了你个老六,藏这么深都被你瞅上了!)


涉及寄存器介绍

        那么我们想使用DWT得先了解相关寄存器

寄存器名译名地址本文用途
DWT_CTRLDWT控制寄存器0xE0001000配置DWT
DWT_CYCCNTDWT计数器0xE0001004计时
DEMCR调试异常和监控控制寄存器0xE000EDFC使能DWT

        了解相关寄存器要使用的位

位名所在寄存器所在寄存器位本文用途
TRCENADEMCR24使能DWT
CYCCNTENADWT_CTRL0使能DWT计数器计数

        相关信息可通过查阅《Cortex-M3 技术参考手册》获取,下面是对应的截图。相关手册资源白嫖

82e59eb27e0e483ea97a598ccd66380c.jpeg
DWT_CTRL寄存器地址与作用

c5839b86c3db4dd994d0903717ea604a.jpeg
DWT_CYCCNT寄存器地址与作用

4911f8d32b57454b8d46025164fb8d2c.jpeg
DEMCR寄存器地址与作用

9f389fa4aefd43cea4ec41625c9f4308.jpeg
CYCCNTENA的作用


代码实现

如何配置DWT

        在配置过程中需要注意一点,DWT延时所用寄存器可能会因不同的处理器而有所不同。因此,如果要使用DWT延时,建议先查阅相关处理器的技术参考手册或用户手册,以了解该处理器上DWT的详细寄存器信息。

        定心丸:我暂时啃过Cortex-M3 /Cortex-M4 技术参考手册,寄存器地址是一致的。下面即将出现的代码也在项目中所用到的CM3/CM4内核单片机上应用着。

DWT配置顺序

  1. 使能DWT;
  2. DWT计数器清零;
  3. 使能DWT计数器;
/*当内核变化时,地址和位可能会发生变化,请按对应技术参考手册修改(目前已阅CM3与CM4是一致如下的)*/
#define DWT_CTRL			(*(volatile unsigned int *)0xE0001000)	/**< DWT控制寄存器 */
#define DWT_CYCCNT			(*(volatile unsigned int *)0xE0001004)	/**< DWT32位向上计数器 */
#define DEMCR				(*(volatile unsigned int *)0xE000EDFC)	/**< 调试异常和监控控制寄存器 */
#define DEMCR_TRCENA		(1<<24)									/**< TRCENA位置1使能DWT */
#define DWT_CTRL_CYCCNTENA	(1<<0)									/**< CYCCNTENA位置1使能计数器 */



static unsigned short		m_mcuCLK = 0;							/**< 保存MCU主频 注意单位为(MHz) */



/**
* @brief		DWT延时初始化
* @param[in]	clk				MCU主频(单位为MHz)
* @return		null
* @par			注意事项:
* - 参数clk单位为MHz;
* @par			示例:
* @code
*
* DWTDelay_Init(72);	//当主频为72MHz时的DWT延时初始化
*
* @endcode
*/
void DWTDelay_Init(unsigned short clk)
{
#if DWT_ENABLE
	DEMCR		|= DEMCR_TRCENA;			/* DEMCR的TRCENA位置1使能DWT */
	DWT_CYCCNT	= 0U;						/* 计数器清零 */
	DWT_CTRL	|= DWT_CTRL_CYCCNTENA;		/* DWT_CTRL的CYCCNTENA位置1使能计数器 */
	m_mcuCLK	= clk;						/* 更新MCU主频单位为(MHz) */
#endif
}

如何实现堵塞延时

        实现微秒延时

/**
* @brief		DWT微秒延时
* @param[in]	us				延时微秒数
* @return		null
* @par			注意事项:
* - 参数us < ((2^32)/m_mcuCLK)
* @par			示例:
* @code
*
* DWT_DelayUs(1);	//延时1微秒
*
* @endcode
*/
void DWT_DelayUs(unsigned int us)
{
#if DWT_ENABLE
	unsigned int tempStartVal;		/* 初始值 */
	unsigned int tempDelayVal;		/* 延时值 */
	unsigned int tempNowVal;		/* 当前值 */
	unsigned int tempIntervalVal;	/* 间隔值 */
		
	tempStartVal	= DWT_CYCCNT;							/* 保存初始值 */
	tempDelayVal	= m_mcuCLK *us;							/* 计算延时值 */
	tempIntervalVal	= 0;									/* 初始化间隔值 */
	
	while(tempIntervalVal < tempDelayVal)
	{
		tempNowVal = DWT_CYCCNT;							/* 获取当前计数值 */
		if(tempNowVal > tempStartVal)						/* 正常往上计数 */
		{
			tempIntervalVal = tempNowVal -tempStartVal;
		}
		else												/* 已溢出unsigned int归零过 */
		{
			tempIntervalVal = 0xFFFFFFFF -tempStartVal +tempNowVal;
		}
	}
#endif
}

        这里有一个错误方法,再次叮嘱:这个是有BUG的!至于BUG是什么呢?有兴趣的同学可以仔细分析,想知道答案的同学可以下载源码,源码里面有对BUG进行分析(提示:需要结合底层进行分析)。

void DWT_DelayUs(unsigned int us)
{
	unsigned int startVal;			/* 初始值 */
	unsigned int endVal;			/* 最终值 */
	unsigned int addVal;			/* 增加值 */
	
	startVal	= DWT_CYCCNT;						/* 保存当前计数器的值 */
	addVal		= m_mcuCLK *us;						/* 计算增加值 */
	endVal		= startVal +addVal;     
	
	if(endVal < startVal)							/* 判断是否溢出unsigned int范围 */
	{
		while(DWT_CYCCNT > endVal);					/* 溢出则等待自加溢出归零 */
	}
	
	while(DWT_CYCCNT < endVal);						/* 等待到达最终值 */	//BUG处
}

        实现毫秒延时,在微秒的基础*1000即可。

/**
* @brief		DWT毫秒延时
* @param[in]	ms				延时毫秒数
* @return		null
* @par			注意事项:
* - 参数ms < ((2^32)/m_mcuCLK/1000)
* @par			示例:
* @code
*
* DWT_DelayMs(100);		//延时100毫秒
*
* @endcode
*/
void DWT_DelayMs(unsigned int ms)
{
#if DWT_ENABLE
	DWT_DelayUs(ms *1000);
#endif
}

如何实现超时判断(非堵塞延时)

        首先先调用DWT_GetNowTimeCount()函数获取保留当前时刻(DWT计数值即可)。

/**
* @brief		获取DWT当前计数值
* @param		void
* @return		DWT当前计数值
* @par			功能介绍:
* - 可搭配DWT_CompareNowTime()函数实现超时判断;
* - 可搭配DWT_Timer()函数实现计时;
* @par			示例:
* @code
*
* unsigned int tempVal = DWT_GetNowTimeCount();	//更新获取DWT当前计数值
*
* @endcode
*/
unsigned int DWT_GetNowTimeCount(void)
{
#if DWT_ENABLE
	return DWT_CYCCNT;
#endif
}

        然后结合状态机等方式,在合适的代码位置如下例子调用DWT_CompareNowTime()函数进行判断超时即可完成非堵塞延时。

/**
* @brief		判断超时
* @param[in]	oldCount		起始时刻的DWT计数值
* @param[in]	delayMs			毫秒超时
* @return
* - 0:			已超时
* - 1:			未超时
* @par			功能介绍:
* - 搭配DWT_GetNowTimeCount()函数实现超时判断;
* @par			示例:
* @code
*
* unsigned int tempVal = DWT_GetNowTimeCount();	//更新获取DWT当前计数值
* ...
* if(!DWT_CompareNowTime(tempVal, 100))			//超时100ms
* {
*	...
* }
*
* @endcode
*/
unsigned char DWT_CompareNowTime(unsigned int oldCount, unsigned short delayMs)
{
#if DWT_ENABLE
	unsigned int tempNowVal;												/* 当前值 */
	unsigned int tempOvertimeVal;											/* 超时值 */
	
	tempNowVal		= DWT_CYCCNT;											/* 获取当前值 */
	tempOvertimeVal	= m_mcuCLK *delayMs *1000;								/* 计算超时值 */
	
	if(tempNowVal > oldCount)
	{
		if((tempNowVal -oldCount) > tempOvertimeVal)return 0;				/* 已超时 */
	}
	else
	{
		if((0xFFFFFFFF -oldCount +tempNowVal) > tempOvertimeVal)return 0;	/* 已超时 */
	}
	return 1;																/* 未超时 */
#endif
}

如何计时

        结合上面的DWT_GetNowTimeCount()函数,先保留计时起始时刻,然后在计时位置如下例子调用DWT_Timer()函数,返回得到耗时时间。

/**
* @brief		(毫秒)计时
* @param[in]	oldCount			起始时刻的DWT计数值
* @return		返回浮点型毫秒计时时间
* @par			功能介绍:
* - 搭配DWT_GetNowTimeCount()函数实现计时;
* @par			示例:
* @code
*
* unsigned int tempVal = DWT_GetNowTimeCount();	//更新获取DWT当前计数值
* ...
* float tempTime = DWT_Timer(tempVal);			//计时
*
* @endcode
*/
float DWT_Timer(unsigned int oldCount)
{
#if DWT_ENABLE

	unsigned int tempNowVal;											/* 当前值 */
	float tempFloat;													/* 计时值 */
	
	tempNowVal = DWT_CYCCNT;											/* 获取当前值 */
	
	if(tempNowVal > oldCount)											/* 计数没溢出 */
	{
		tempFloat = (float)(tempNowVal -oldCount)/(float)(m_mcuCLK *1000);
	}
	else																/* 计数溢出 */
	{
		tempFloat = (float)(0xFFFFFFFF -oldCount +tempNowVal)/(float)(m_mcuCLK *1000);
	}
	
	return tempFloat;													/* 返回浮点型毫秒计时时间 */
#endif
}

实战例子

#include "stm32f10x.h"
#include "DWT_Delay.h"



#define	LED_GPIO	GPIOB									/**< LED引脚端口号 */
#define	LED_PIN		GPIO_Pin_0								/**< LED引脚编号 */



/*LED引脚配置*/
void LED_GPIO_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	/*打开时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);			

	/*配置GPIO*/
	GPIO_InitStructure.GPIO_Mode	= GPIO_Mode_Out_PP;		/* 模式-推挽输出 */
	GPIO_InitStructure.GPIO_Speed	= GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin		= LED_PIN;
	GPIO_Init(LED_GPIO, &GPIO_InitStructure);
	
	/*初始化电平*/
	GPIO_SetBits(LED_GPIO, LED_PIN);						/* 默认输出高电平关闭LED */
}



/*设置LED开关*/
void Set_LEDState(unsigned char state)
{
	GPIO_WriteBit(LED_GPIO, LED_PIN, (BitAction)state);
}



int main(void)
{
	DWTDelay_Init(72);		//主频72MHz
	LED_GPIO_Init();		//LED引脚配置
	
	while(1)
	{
		/*闪烁灯*/
		Set_LEDState(1);
		DWT_DelayMs(500);
		Set_LEDState(0);
		DWT_DelayMs(500);
	}
}

用DWT延时实现闪烁灯演示视频


        文章DWT延时源码,内附详细注释、注意事项与代码说明文档,详细请看README.txt文件。

        分享先到这里,希望能给大家带来启发与帮助。如果对内容存在疑问或想法,欢迎在评论区留言,我会积极回复大家的问题。在我的“经验分享”专栏中,还有很多实用的经验,欢迎一起探讨、一起学习。

  • 15
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小星星星球

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值