HAL_DWT
文章目录
一、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 性价比最高的高精度计时资源,零硬件成本、零外设占用、精度极高,是嵌入式精密时序、代码性能调试、高精度延时的首选方案,是进阶嵌入式开发必须掌握的核心技能。
更多推荐



所有评论(0)