利用 ARM Cortex-M 内核 DWT 计数器实现微秒级精确延时,替代 SysTick 的实战指南
👁 2 阅读 · 2026-08-27 · 嵌入式
在嵌入式开发中,SysTick 定时器常用于系统节拍和延时,但在某些场景下(如 RTOS 占用、高频中断),SysTick 可能不可用或精度不足。ARM Cortex-M 内核自带的 Data Watchpoint and Trace (DWT) 单元提供了一个免费的 32 位周期计数器,可实现微秒级精确延时,且不占用外设定时器。本文将深入讲解 DWT 的工作原理、配置步骤,并给出完整的可移植代码示例,助你轻松掌握这一高效技巧。
# 利用 DWT 计数器实现微秒级精确延时
在嵌入式开发中,精确延时是基础需求。SysTick 是最常用的延时工具,但在 RTOS 环境下,SysTick 被内核占用;或者当需要更高精度(如微秒级)时,SysTick 的配置可能不够灵活。此时,ARM Cortex-M 内核自带的 DWT 单元提供了一个优雅的解决方案。
## 1. DWT 计数器原理
DWT (Data Watchpoint and Trace) 是 Cortex-M3/M4/M7 内核中的一个调试组件,其中包含一个 32 位的自由运行计数器(CYCCNT)。该计数器每个内核时钟周期递增一次,因此可以用于精确测量时间。
关键寄存器:
- `DWT_CTRL` (0xE0001000): 控制寄存器,bit0 使能 CYCCNT,bit1 使能 CYCCNT 中断(通常不用)。
- `DWT_CYCCNT` (0xE0001004): 当前计数值,32 位,溢出后回绕。
- `CoreDebug_DEMCR` (0xE000EDFC): 调试控制寄存器,bit24 (TRCENA) 必须置 1,否则 DWT 无法访问。
原理:通过读取 CYCCNT 的差值,乘以内核时钟周期,即可得到经过的时间。由于是硬件计数器,精度可达 1 个时钟周期,通常为纳秒级。
## 2. 为什么替代 SysTick?
- **SysTick 被 RTOS 占用**:FreeRTOS 等系统使用 SysTick 作为时基,直接调用会导致冲突。
- **精度限制**:SysTick 通常配置为 1ms 中断,若用于微秒延时需频繁重载,开销大。
- **灵活性**:DWT 不依赖外设,只需内核支持,且代码可移植到所有 Cortex-M3+ 芯片。
## 3. 配置步骤
1. **使能 TRCENA**:设置 `CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk`。
2. **使能 CYCCNT**:设置 `DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk`。
3. **清零计数器**(可选):`DWT->CYCCNT = 0`。
4. **读取计数值**:`uint32_t count = DWT->CYCCNT`。
注意:在 Cortex-M0/M0+ 上,DWT 可能不可用(部分芯片有),需检查芯片手册。
## 4. 完整代码示例
以下代码基于 STM32F4 (Cortex-M4),可直接移植到其他 Cortex-M3/M4/M7 芯片。
```c
#include "stm32f4xx.h"
// 初始化 DWT 计数器
void DWT_Init(void) {
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; // 使能 DWT 访问
DWT->CYCCNT = 0; // 清零计数器
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; // 使能 CYCCNT
}
// 获取当前计数值
static inline uint32_t DWT_GetCount(void) {
return DWT->CYCCNT;
}
// 微秒延时函数
void delay_us(uint32_t us) {
uint32_t start = DWT_GetCount();
uint32_t ticks = us * (SystemCoreClock / 1000000); // 计算所需时钟周期数
while ((DWT_GetCount() - start) < ticks); // 等待差值达到目标
}
// 毫秒延时(基于微秒延时)
void delay_ms(uint32_t ms) {
for (uint32_t i = 0; i < ms; i++) {
delay_us(1000);
}
}
// 示例:主函数中初始化并使用
int main(void) {
SystemInit();
DWT_Init();
while (1) {
GPIO_ToggleBits(GPIOA, GPIO_Pin_5); // 翻转 LED
delay_us(500); // 精确延时 500us
}
}
```
**代码说明**:
- `SystemCoreClock` 是系统时钟频率,需正确配置(如 168MHz)。
- 使用无符号减法处理溢出,保证正确性。
- `delay_us` 中 `ticks` 计算可能溢出,若 us 过大,可分段延时。
## 5. 注意事项
- **确保 DWT 可用**:Cortex-M0/M0+ 部分芯片无 DWT,需查勘手册。
- **时钟频率**:`SystemCoreClock` 必须与实际一致,否则延时不准。
- **中断影响**:DWT 计数器不受中断影响,但若中断服务函数耗时过长,延时可能被拉长(因为 CPU 被抢占)。
- **溢出处理**:32 位计数器在 168MHz 下约 25.6 秒回绕一次,使用差值计算可避免问题。
- **功耗**:DWT 在调试模式下可能增加功耗,量产代码可考虑关闭。
## 6. 扩展应用
- **性能分析**:测量函数执行时间,只需记录前后计数值。
- **脉冲生成**:结合 GPIO 翻转,生成精确 PWM 或时序信号。
- **与 RTOS 共存**:在 FreeRTOS 中,DWT 延时不会干扰调度器。
## 7. 总结
DWT 计数器是 Cortex-M 内核的隐藏宝藏,利用它可实现微秒级精确延时,且不占用外设资源。通过简单的寄存器配置和差值计算,即可替代 SysTick 在特定场景下的不足。希望本文能帮助你提升嵌入式开发的效率和灵活性。