同一个 100 μs 周期,为什么 DWT 能测出 13600 和 19200 两个数?
因为 CYCCNT 按 CPU 时钟计数,不是固定频率:13600 周期在 136 MHz、19200 在 192 MHz、24000 在 240 MHz 下都等于 100 μs。引用周期数时必须同时给出当时主频,否则纳秒换算会差 1.4–2 倍。
DWT 的周期计数器每来一个 CPU 时钟加一,所以它记的是「多少个 CPU 周期」,不是「多少纳秒」。同一个 100 μs 周期,在 136 MHz 主频下是 13,600 个周期,在 192 MHz 下是 19,200 个,在 240 MHz 下是 24,000 个——三个数都对,前提是换算时各用各的主频。换算公式只有一条:纳秒 = 周期数 ÷ 主频 × 10⁹。
现象
同一套控制固件在不同阶段的记录里,出现了三个都被标注为「100 μs」的周期数:
| 记录中的周期数 | 标注的主频 | 换算结果 |
|---|---|---|
| 13,600 | 136 MHz(定时器时钟) | 100.00 μs |
| 19,200 | 未标注 | 192 MHz 下为 100.00 μs |
| 23,978–24,022 | 标注为 480 MHz | 480 MHz 下只有 49.95–50.05 μs |
前两行都能对上 100 μs,第三行对不上——差了一倍。这不是测量错误,而是标注错位:24,000 周期对应的主频是 240 MHz,不是 480 MHz。
同类错位还出现在抖动数据里:一份记录写「18 个周期 = 93.8 ns」,同时标注主频 136 MHz。18 ÷ 136 MHz = 132.4 ns,18 ÷ 192 MHz = 93.75 ns——93.8 ns 这个数只在 192 MHz 下成立。
原因
DWT(Data Watchpoint and Trace)的 CYCCNT 由处理器时钟驱动,它计数的是内核执行的时钟周期,与定时器时钟、总线时钟都不是同一路信号。因此:
- 同一段代码在 136 MHz 和 192 MHz 下运行,CYCCNT 的增量必然不同;
- 用周期数反推时间时,必须除以当时真实的内核主频;
- 一旦主频被改过(PLL 重配、低功耗降频),历史周期数就只能在原主频下解释。
这也是为什么「100 μs 对应 13,600 周期」和「100 μs 对应 24,000 周期」可以同时正确:前者按 136 MHz 算,后者按 240 MHz 算。
另外两个容易忽略的点:
32 位计数器会回绕。 240 MHz 下 CYCCNT 每 2³² ÷ 240×10⁶ ≈ 17.9 秒回绕一次。做差时用无符号减法(now - start)即可自动处理单次回绕,但跨两次回绕就无法恢复。
睡眠与 halt 会停表。 内核进入深度睡眠或调试器 halt 时,CYCCNT 可能停止递增;此时「周期数」不再对应真实时间,任何基于它的抖动统计都会失真。
怎么做
三步:使能、取时间戳、按主频换算。
/* Cortex-M7:使能 DWT 周期计数器 */
#define DEMCR (*(volatile uint32_t *)0xE000EDFCUL)
#define DWT_CTRL (*(volatile uint32_t *)0xE0001000UL)
#define DWT_CYCCNT (*(volatile uint32_t *)0xE0001004UL)
DEMCR |= (1U << 24); /* TRCENA = 1,解锁跟踪单元 */
DWT_CYCCNT = 0; /* 清零,避免初值影响第一次做差 */
DWT_CTRL |= (1U << 0); /* CYCCNTENA = 1,开始计数 */
/* 测量:两次读数相减,无符号减法可容纳一次 32 位回绕 */
uint32_t t0 = DWT_CYCCNT;
run_isr_once();
uint32_t cycles = DWT_CYCCNT - t0;
/* 换算:必须用采样当时的 CPU 主频 */
double ns = (double)cycles * 1e9 / (double)cpu_hz;
配套三条纪律,能消除绝大多数误读:
- 保存主频快照。 每次测量时把
cpu_hz一起存下来,不要事后凭印象填。 - 上报时成对给出。 记录写成「13,600 cycles @ 136 MHz」,而不是只写「13,600」。
- 优先用时间戳对做差。 若只关心周期稳定性,直接比较相邻两次中断入口的 CYCCNT 差值,再乘上「1 ÷ 主频」;不要先换成绝对时间再相减,多一步换算就多一个出错点。
验证
先看一个直接的对照:改前把 24,000 周期按 480 MHz 换算,得到 50 μs(错,周期被低估一半)→ 改后按 240 MHz 换算,得到 100 μs(与实测周期一致)。 同一批样本、同一个周期数,只改了除号后面的主频。

四个数字放一起做自洽检查,错误会立刻暴露:
| 周期数 | 按 136 MHz | 按 192 MHz | 按 240 MHz | 按 480 MHz |
|---|---|---|---|---|
| 13,600 | 100.00 μs | 70.83 μs | 56.67 μs | 28.33 μs |
| 19,200 | 141.18 μs | 100.00 μs | 80.00 μs | 40.00 μs |
| 24,000 | 176.47 μs | 125.00 μs | 100.00 μs | 50.00 μs |
规则很直观:每个周期数只在它自己的那一列等于 100 μs。 所以看到「周期数 + 一个主频」,先做这一列对应,再判断数字是否合理;如果对不上,先怀疑主频标错了,而不是先怀疑测量。
同一方法也解释了那条 93.8 ns 的记录:18 个周期 ÷ 192 MHz = 93.75 ns,与 93.8 ns 吻合;换算成 136 MHz 则是 132 ns,与记录不符。用「ns 值反推主频」是查这类矛盾的快捷手段——反推出来的主频如果和标称值差一截,多半就是标称值错了。
边界
- 不同内核、不同厂家的 DWT 参考时钟可能不是内核主频(有的实现挂在调试时钟域),以对应参考手册为准;本文结论基于 Cortex-M7。
- 睡眠、halt、调试断点期间 CYCCNT 可能停走,这段时间的统计必须丢弃,不能当作「执行很快」。
- 32 位计数器在 240 MHz 下约 17.9 秒回绕一次;跨回绕的区间无法用单次相减恢复,需要外部计时器配合。
- 周期数只反映「执行了多少个时钟」,不反映缓存缺失、总线等待等隐性开销的成因;要定位成因仍需逻辑分析仪或性能计数器。
- 若主频在测量期间被动态调整(变频、低功耗切换),同一批样本不能混在一起统计。
最后验证于 2026-10-29 · Cortex-M7 / STM32H723ZG · DWT 控制寄存器地址取自 ARM 架构参考