一、DWT 简介

DWT(Data Watchpoint and Trace,数据观察点与跟踪单元): 是 Cortex-M 内核自带的高精度硬件定时器,属于内核外设,不属于 STM32 片上外设,所有 Cortex-M3/M4/M7/M0+ 内核均自带 DWT 模块。
DWT 核心作用:
实现系统级高精度微秒/纳秒计时、精准延时、代码运行耗时统计。
相比 HAL_Delay(基于SysTick、精度低、阻塞严重),DWT 依靠内核总线时钟计数,精度极高、不占用定时器资源、无需中断、不阻塞程序。

1、使用 DWT 的核心意义

解决 HAL_Delay 毫秒级精度不足 的问题,支持微秒、亚微秒精准延时。
无需占用定时器、串口、中断等外设资源,纯内核硬件计数。
可精准统计代码执行耗时、函数运行时长、算法耗时。
支持非阻塞延时、高精度时序控制、传感器精准时序驱动。
可用于串口波特率校准、PWM微调、精密通信时序模拟。

2、DWT 与 SysTick 区别

特性 DWT SysTick(HAL_Delay)
精度 极高(内核时钟节拍,ns级) 低(固定1ms)
资源占用 无外设、无中断、纯内核 占用SysTick中断
阻塞性 可实现非阻塞计时 强制阻塞延时
用途 精准延时、耗时统计 普通毫秒延时

二、DWT 模块详解

1、DWT 工作原理

DWT 内部拥有一个32位自增计数器(CYCCNT),计数器时钟等于内核主频 HCLK
内核每运行一个时钟周期,CYCCNT 计数器自动 +1。
因此可以通过:计数值差值 → 换算时间,实现高精度计时与延时。
在这里插入图片描述

2、DWT 核心功能

周期计数功能:CYCCNT 寄存器实时统计内核运行时钟节拍。
高精度微秒延时:实现 1us、10us 精准延时。
代码耗时测量:统计一段代码、函数、算法的执行耗时。
观察点断点(进阶):数据读写触发断点调试。
时序记录:用于调试、性能分析、时序校准。

3、DWT 限制与特性

Cortex-M0/M0+ 部分无DWT计数器,M3/M4/M7 完全支持。
32位计数器,主频72MHz下最大单次计时约59秒,超时会溢出。
复位后默认关闭,需要手动开启 DWT 与 CYCCNT 计数功能。
属于内核资源,全局唯一,所有任务共用。

4、模块结构

DWT 是内核调试子模块,结构极简,核心由三个寄存器组成:
  DWT_CTRL:控制寄存器,开启/关闭 CYCCNT 计数器。
  DWT_CYCCNT:32位周期计数值寄存器(核心计数寄存器)。
  DEMCR:调试主控寄存器,全局使能DWT计数功能。

5、状态标志

计数使能标志:DWT_CTRL.CYCCNTENA 置1表示计数器正常运行。
全局跟踪使能标志:DEMCR.TRCENA 置1表示DWT模块总开关开启。
计数清零状态:CYCCNT=0 计数器归零,开始新一轮计时。

6、其它特性

DWT 计时不受外设时钟、总线分频影响,只跟随内核 HCLK。
进入休眠模式时内核时钟关闭,DWT 计数暂停。
支持随时读取计数值,无中断、无阻塞。

三、DWT 应用代码

1、DWT 完整初始化函数

		// 全局变量:系统主频,用于时间换算
		uint32_t DWT_Freq = 0;
		void DWT_Init(void)
		{
		    // 开启DWT跟踪时钟
		    CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
		    
		    // 获取当前内核时钟频率
		    DWT_Freq = HAL_RCC_GetHCLKFreq();
		    
		    // 计数器清零
		    DWT->CYCCNT = 0;
		    
		    // 使能周期计数
		    DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
		}

2、获取当前计数值

		uint32_t DWT_GetCnt(void)
		{
		    return DWT->CYCCNT;
		}

3、高精度微秒延时函数

		void DWT_DelayUs(uint32_t us)
		{
		    uint32_t start = DWT_GetCnt();
		    // 需要等待的计数值
		    uint32_t wait = us * (DWT_Freq / 1000000);
		    
		    // 溢出兼容写法
		    while((DWT_GetCnt() - start) < wait);
		}

4、代码耗时统计工具

		// 返回当前系统微秒时间
		uint32_t DWT_GetTimeUs(void)
		{
		    return (DWT_GetCnt() / (DWT_Freq / 1000000));
		}
		
		// 使用示例:统计某段代码耗时
		uint32_t t1 = DWT_GetTimeUs();
		// ==========被测代码段==========
		HAL_Delay(1);
		// ============================
		uint32_t t2 = DWT_GetTimeUs();
		printf("代码耗时:%d us\r\n",t2-t1);

5、主函数调用示例

		#include "main.h"
		#include "rtc.h"
		#include "usart.h"
		#include "gpio.h"
		#include "stdio.h"
		#include "string.h"
		
		/* 重定向printf */
		#ifdef __GNUC__
		#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
		#else
		#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
		#endif
		PUTCHAR_PROTOTYPE
		{
		        HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
		        return ch;
		}
		
		//全局系统主频,用于时间换算
		uint32_t DWT_Freq = 0;
		//DWT初始化
		void DWT_Init(void)
		{
		    // 开启DWT跟踪时钟
		    CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;    
		    // 获取当前内核时钟频率
		    DWT_Freq = HAL_RCC_GetHCLKFreq();
		    // 计数器清零
		    DWT->CYCCNT = 0;    
		    // 使能周期计数
		    DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
		}
		
		//获取DWT当前计数值
		uint32_t DWT_GetCnt(void)
		{
		    return DWT->CYCCNT;
		}
		
		//DWT高精度微秒延时函数
		void DWT_DelayUs(uint32_t us)
		{
		    uint32_t start = DWT_GetCnt();
		    // 需要等待的计数值
		    uint32_t wait = us * (DWT_Freq / 1000000);
		    
		    // 溢出兼容写法
		    while((DWT_GetCnt() - start) < wait);
		}
		
		// 返回当前系统微秒时间
		uint32_t DWT_GetTimeUs(void)
		{
		        return (DWT_GetCnt() / (DWT_Freq / 1000000));
		}
		
		int main(void)
		{
		  HAL_Init();
		  SystemClock_Config();
		  MX_GPIO_Init();
		  MX_RTC_Init();
		  MX_USART1_UART_Init();
		
		        // 初始化DWT
		        DWT_Init();  
		        //查看系统主频
		        printf("HCLK = %d\r\n",SystemCoreClock);
		
		  while (1)
		  {
		        uint32_t t1 = DWT_GetTimeUs();
		        // ==========被测代码段==========
		        
		        DWT_DelayUs(100);  // 精准100us延时
		        
		        // ============================
		        uint32_t t2 = DWT_GetTimeUs();
		        printf("代码耗时:%d us\r\n",t2-t1);
		  }
		}

四、其它要点

1、常见坑点

忘记初始化DWT:直接调用延时会卡死、计时无效。
不兼容M0内核:Cortex-M0 无CYCCNT计数器,无法使用DWT精准延时。
计数器溢出问题:72MHz下约59秒溢出,长时间计时需做溢出判断。
休眠暂停计数:进入Stop/Sleep模式内核停止,DWT暂停计数,唤醒后继续累加。
仿真断点影响计时:在线调试断点会暂停内核,导致计时误差。

2、DWT 与 HAL_Delay 选用场景

普通延时、任务间隔:使用 HAL_Delay。
传感器时序、通信时序、精密驱动:使用 DWT_DelayUs。
算法优化、性能测试:使用 DWT 耗时统计。

3、进阶开发要点

DWT 可以实现非阻塞延时,配合时间戳可实现状态机精准时序。
可用于软件SPI、软件IIC、单总线(DS18B20)精准时序模拟。
可替代定时器计时,节省硬件定时器资源。
多任务系统中,DWT计时不受任务切换影响,计时精度稳定。

4、核心总结

DWT 是 STM32 性价比最高的高精度计时资源,零硬件成本、零外设占用、精度极高,是嵌入式精密时序、代码性能调试、高精度延时的首选方案,是进阶嵌入式开发必须掌握的核心技能。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐