前言
软件延时不精准?SysTick滴答定时器被占用?定时器资源紧张?还有别的延时方法吗?有请ARM Cortex-M系列特有的隐匿定时器DWT闪亮出场,为阁下解决以上问题!内容利用DWT实现堵塞延时、非堵塞延时、计时功能
目录
前言
凭什么要用DWT
DWT简介
涉及寄存器介绍
代码实现
如何配置DWT
如何实现堵塞延时
如何实现超时判断(非堵塞延时)
如何计时
实战例子
列举四大延时方法的优缺点:
软件延时
优点:实现简单;无需消耗硬件资源;
缺点:延时不精准;只能堵塞延时;
SysTick计数器延时
优点:延时精准;可实现非阻塞延时;
缺点:24位计数器延时范围小;
定时器(Timer/SysTick)中断延时
优点:延时精准;可实现非阻塞延时;
缺点:占用定时器中断;若实现微秒级则中断频繁,占用过多处理器资源;
DWT延时
优点:延时精准;可实现非阻塞延时;32位计数器延时范围大;解放其他硬件资源;强行实现简单,看了我这个教程还不简单?
缺点:仅部分ARM Cortex-M系列才有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,例如:
但是,一些较旧或较低成本的处理器则不支持DWT,例如:
所以为什么DWT能实现延时啊?因为延时需要计数器对吧?而DWT恰恰就有一个计数器,而且还比SysTick的24位更牛逼的32位计数器。(DWT:真服了你个老六,藏这么深都被你瞅上了!)
那么我们想使用DWT得先了解相关寄存器
寄存器名 | 译名 | 地址 | 本文用途 |
---|---|---|---|
DWT_CTRL | DWT控制寄存器 | 0xE0001000 | 配置DWT |
DWT_CYCCNT | DWT计数器 | 0xE0001004 | 计时 |
DEMCR | 调试异常和监控控制寄存器 | 0xE000EDFC | 使能DWT |
了解相关寄存器要使用的位
位名 | 所在寄存器 | 所在寄存器位 | 本文用途 |
---|---|---|---|
TRCENA | DEMCR | 24 | 使能DWT |
CYCCNTENA | DWT_CTRL | 0 | 使能DWT计数器计数 |
相关信息可通过查阅《Cortex-M3 技术参考手册》获取,下面是对应的截图。相关手册资源白嫖
在配置过程中需要注意一点,DWT延时所用寄存器可能会因不同的处理器而有所不同。因此,如果要使用DWT延时,建议先查阅相关处理器的技术参考手册或用户手册,以了解该处理器上DWT的详细寄存器信息。
定心丸:我暂时啃过Cortex-M3 /Cortex-M4 技术参考手册,寄存器地址是一致的。下面即将出现的代码也在项目中所用到的CM3/CM4内核单片机上应用着。
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文件。
分享先到这里,希望能给大家带来启发与帮助。如果对内容存在疑问或想法,欢迎在评论区留言,我会积极回复大家的问题。在我的“经验分享”专栏中,还有很多实用的经验,欢迎一起探讨、一起学习。