利用 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 芯片。

#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_usticks 计算可能溢出,若 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 在特定场景下的不足。希望本文能帮助你提升嵌入式开发的效率和灵活性。