dwt的cyccnt可用但需手动使能:先置位demcr.trcena,再置位dwt.ctrl.cyccntena;部分芯片需额外解锁;读取须用volatile asm防止优化,并以实测cpu频率换算微秒。

确认 DWT 和 CYCCNT 是否可用
DWT(Data Watchpoint and Trace)模块的 CYCCNT 寄存器是 Cortex-M3/M4/M7/M33/M55 等内核提供的 32 位自由运行周期计数器,但默认是禁用的,且部分芯片(如某些 STM32L 系列或带安全扩展的 M33)可能在复位后被硬件锁定或由调试器控制。直接读 DWT->CYCCNT 可能返回 0 或触发硬故障。
实操建议:
- 先检查
CoreDebug->DEMCR & CoreDebug_DEMCR_TRCENA_Msk是否为 1,否则写入该位使能跟踪功能(需特权级) - 再检查
DWT->CTRL & DWT_CTRL_CYCCNTENA_Msk,若为 0,需写DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk - 有些芯片(如 NXP LPC55S69)要求先解锁 DWT:写
SYSCON->TRACECTRL = 1或类似寄存器(查对应 RM) - 读取前确保
CYCCNT已清零(DWT->CYCCNT = 0),否则连续测量会溢出叠加
用 inline asm 或 volatile 读取避免编译器优化干扰
单纯用 DWT->CYCCNT 读两次取差值,GCC/Clang 可能因寄存器无副作用而重排、合并或删减访问——尤其函数体很短时,测出来恒为 0 或数值异常。
实操建议:
- 必须用
__asm__ volatile ("" ::: "memory")做编译器屏障,防止指令重排 - 推荐封装成宏,强制每次读取都走真实寄存器访问:
#define DWT_CYCCNT_READ() ({ \ uint32_t _c; \ __asm__ volatile ("ldr %0, [%1]" : "=r"(_c) : "r"(&DWT->CYCCNT) : "memory"); \ _c; \ }) - 不要对被测函数加
inline或__attribute__((noinline))——你要测的是实际调用开销,包括 call/ret 指令和栈帧,不是纯内联展开体
换算周期数为微秒的关键:知道确切 CPU 频率
CYCCNT 计的是 core clock 周期,不是 SysTick 或外设时钟。如果系统主频是 120 MHz,1 个周期 = 1/120 μs ≈ 8.33 ns;但若 PLL 配错、HSI 未校准、或使用了 HSI/MSI 作为系统时钟,实测频率可能和 SYSCLK_FREQ 宏定义不一致。
实操建议:
- 别信头文件里的
SystemCoreClock,用示波器量MCO引脚输出或通过 UART 打印实测频率(例如用 SysTick 中断计 1 秒内中断次数) - 若芯片支持,用
RCC->CFGR & RCC_CFGR_SWS查当前系统时钟源,再查该源的实际频率(比如 HSE=8MHz,PLL_MUL=12 → 96MHz) - 微秒换算公式就是:
(end_cycle - start_cycle) * 1000000ULL / cpu_freq_hz,注意用ULL防止中间结果溢出(32 位周期差 × 1e6 很容易超 int)
处理溢出与最小可观测时间
CYCCNT 是 32 位,最大计到 ~4294967295。在 100 MHz 下约 42.9 秒才溢出,单次函数测量基本不担心;但真正限制精度的是「最小可分辨间隔」——若函数执行
实操建议:
- 对极短函数(如空循环、单条指令),重复执行 N 次再除以 N,N 至少取 100 以上,并检查标准差(可额外记录 10 次结果看离散程度)
- 避免在中断服务程序(ISR)中测——DWT 本身不受中断影响,但 ISR 进入/退出的压栈、上下文切换开销会混入结果
- 关中断(
__disable_irq())只在必要时用:它掩盖了真实场景下的中断抢占延迟,除非你明确要测“无中断干扰下的纯执行路径” - 首次读
CYCCNT后立刻跟一条__asm__ volatile ("nop"),可减少流水线预取对起始点的影响(尤其在 Cortex-M4/M7 上)
最常被忽略的是:DWT 的启用状态在低功耗模式(如 WFI/WFE)下会被关闭,若函数里调用了 HAL_Delay 或进入睡眠,CYCCNT 就停了——这时测的不是函数执行时间,而是“唤醒 + 执行”的混合时间。真要测带延时的逻辑,得换 SysTick 或外部定时器。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











