欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 维修 > 【STM32】通过 DWT 实现毫秒级延时

【STM32】通过 DWT 实现毫秒级延时

2025/9/24 20:53:34 来源:https://blog.csdn.net/Teminator_/article/details/143446544  浏览:    关键词:【STM32】通过 DWT 实现毫秒级延时

目录

  • 零、前言
  • 一、DWT
    • 1、DEMCR
    • 2、DWT_CTRL
    • 3、DWT_CYCCNT
  • 二、实现代码
  • 三、测试


零、前言

在 FreeRTOS 中,SysTick 被用于作为调度器的一部分进行任务调度,那么如果我需要使用软件模拟通信,例如软件 I2C,需要使用 delay,就无法使用 SysTick 实现的 delay。

因此,这里提供一种基于 DWT 实现的 delay。

一、DWT

在实现我们的代码之前,如果你没有了解过 DWT,那就先来看一下:

这里只介绍待会儿会用到的延时相关的内容



在 Cortex-M 内核内核中里面有一个外设叫 DWT(Data Watchpoint and Trace),是用于系统调试及跟踪。

它有一个 32 位的寄存器叫 CYCCNT,它是一个向上的计数器,记录的是内核时钟运行的个数,内核时钟跳动一次,该计数器就加 1。

它的精度非常高,取决于内核的频率是多少,如果是 F103 系列,内核时钟是 72M,那精度就是 1 / 72 M = 14 n s 1/72M = 14ns 1/72M=14ns,而程序的运行时间都是微秒级别的,所以 14ns 的精度是远远够的。最长能记录的时间为: 60 s = 2 32 / 72000000 60s = 2^{32}/72000000 60s=232/72000000 (假设内核频率为72M,内核跳一次的时间大概为 1 / 72 M = 14 n s 1/72M=14ns 1/72M=14ns),而如果是 H7 这种 400M 主频的芯片,那它的计时精度高达 2.5ns( 1 / 400000000 = 2.5 1/400000000 = 2.5 1/400000000=2.5)。

CYCCNT 溢出之后,会清 0 重新开始向上计数。

要实现延时的功能,总共涉及到三个寄存器:DEMCRDWT_CTRLDWT_CYCCNT,分别用于开启 DWT 功能、开启 CYCCNT 及获得系统时钟计数值。下面就来看一下这几个寄存器吧。

1、DEMCR

参照权威指南:

配置的时候,将 TRCENA 设置为 1 就行了。

2、DWT_CTRL


CYCCNTENA 使能位置 1 即可。

3、DWT_CYCCNT


使用 DWT_CYCCNT 寄存器之前,先清 0。

综上所述,要使用 DWT 的 CYCCNT 配置步骤如下:

  1. 使能 DWT 外设,这个由内核调试寄存器 DEMCR 的位 24 TRCENA 控制,写 1 使能
  2. 使能 CYCCNT 寄存器之前,先清 0。
  3. 使能 CYCCNT 寄存器,这个由 DWT 控制寄存器的 CYCCNTENA 位控制,也就是 DWT 控制寄存器的位 0 控制,写 1 使能

二、实现代码

#define  DWT_CYCCNT  *(volatile unsigned int *)0xE0001004
#define  DWT_CR      *(volatile unsigned int *)0xE0001000
#define  DEM_CR      *(volatile unsigned int *)0xE000EDFC#define  DEM_CR_TRCENA               (1 << 24)
#define  DWT_CR_CYCCNTENA            (1 <<  0)/******************************************************************************* @brief  初始化 DWT* @return none
******************************************************************************/
void bsp_dwt_init(void)
{DEM_CR         |= (unsigned int)DEM_CR_TRCENA;   /* Enable Cortex-M4's DWT CYCCNT reg.  */DWT_CYCCNT      = (unsigned int)0u;DWT_CR         |= (unsigned int)DWT_CR_CYCCNTENA;
}/******************************************************************************* @brief      * @param[in]  _delay_time    :    延时时间  * @return     none
******************************************************************************/
void bsp_dwt_delay(uint32_t _delay_time)
{uint32_t cnt, delay_cnt;uint32_t start;cnt = 0;delay_cnt = _delay_time;  /* 需要的节拍数 */ 		      start = DWT_CYCCNT;       /* 刚进入时的计数器值 */while(cnt < delay_cnt){cnt = DWT_CYCCNT - start; /* 求减过程中,如果发生第一次32位计数器重新计数,依然可以正确计算 */	}
}/******************************************************************************* @brief      这里的延时采用CPU的内部计数实现,32位计数器*             OSSchedLock(&err);*			   bsp_DelayUS(5);*			   OSSchedUnlock(&err); 根据实际情况看看是否需要加调度锁或选择关中断* @param[in]  _delay_time    :    延迟长度,单位1 us* @return     none* @note       1. 主频168MHz的情况下,32位计数器计满是2^32/168000000 = 25.565秒*                建议使用本函数做延迟的话,延迟在1秒以下。  *             2. 实际通过逻辑分析仪测试,微妙延迟函数比实际设置实际多运行0.25us左右的时间。*             3. 测试硬件:STM32F407VET6
******************************************************************************/
void bsp_delay_us(uint32_t _delay_time)
{uint32_t cnt, delay_cnt;uint32_t start;start = DWT_CYCCNT;                                     /* 刚进入时的计数器值 */cnt = 0;delay_cnt = _delay_time * (SystemCoreClock / 1000000);	 /* 需要的节拍数 */ 		      while(cnt < delay_cnt){cnt = DWT_CYCCNT - start; /* 求减过程中,如果发生第一次32位计数器重新计数,依然可以正确计算 */	}
}/******************************************************************************* @brief      为了让底层驱动在带RTOS和裸机情况下有更好的兼容性*             专门制作一个阻塞式的延迟函数,在底层驱动中ms毫秒延迟主要用于初始化,并不会影响实时性。 * @param[in]  _delay_time    :    延迟长度,单位1 ms* @return     none
******************************************************************************/
void bsp_delay_ms(uint32_t _delay_time)
{bsp_delay_us(1000 * _delay_time);
}

三、测试

下面通过一个简单的 demo 测试一下 us 级的延时函数,通过翻转 PC0 的电平状态,再通过逻辑分析仪查看延时效果:

int main(void)
{/******* 系统及外设初始化函数*****/bsp_dwt_init();/* 使用PC0测试时间 */{GPIO_InitTypeDef GPIO_InitStructure;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;	/* 输出类型为推挽 */GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;	/* 内部上拉电阻使能 */GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;	/* 复用模式 */GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOC, &GPIO_InitStructure);		}while (1){GPIOC->BSRR = (uint32_t)GPIO_Pin_0;bsp_delay_us(1);GPIOC->BSRR = (uint32_t)GPIO_Pin_0 << 16;bsp_delay_us(1);}return 0;
}

bsp_delay_us(10)

由于本人使用的逻辑分析仪精度较低,只能看个大概数据,大致要比实际设置的时间多运行 0.25 us

bsp_delay_us(1)

注意事项

在烧录运行程序的时候,由于下载器的问题,早期用的 D 版 JLINK,不能正常复位 DWT。所以 DWT 时钟计数器容易出现不运行的情况,而调试状态或者重新上电都不存在问题,使用的时候要注意。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词