用DWT周期计数器做us级精确定时,替代定时器资源的实战配置

1. 为什么需要DWT做us级定时?

在嵌入式项目中,硬件定时器(TIM)是稀缺资源。当多个外设需要PWM、输入捕获、编码器接口时,定时器往往捉襟见肘。而软件延时(如for循环)精度差、受编译器优化影响大。

Cortex-M内核自带DWT(Data Watchpoint and Trace) 单元,其中的CYCCNT寄存器是一个32位自由运行的周期计数器,每个CPU时钟周期加1。利用它可实现纳秒级分辨率的延时,且不占用任何外设定时器。

2. DWT周期计数器原理

  • CYCCNT在DWT使能后从0开始递增,溢出后自动回绕(32位)。
  • 计数频率等于CPU主频(如STM32F103为72MHz,STM32F4为168MHz)。
  • 通过读取前后两次CYCCNT的差值,即可换算出经过的时间。
  • 关键寄存器:
    • DWT->CTRL:使能CYCCNT(bit0)。
    • DWT->CYCCNT:当前周期计数值。
    • CoreDebug->DEMCR:使能DWT跟踪(bit24 TRCENA)。

3. 配置步骤

  1. 使能DWT跟踪:设置CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk。
  2. 清零并启动CYCCNT:DWT->CYCCNT = 0; DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;。
  3. 获取CPU频率:通常使用SystemCoreClock变量(需在系统初始化后更新)。
  4. 实现微秒延时函数:计算目标周期数 = 延时us × (SystemCoreClock / 1000000)。
  5. 处理溢出:使用无符号减法自动处理回绕。

4. 完整代码示例

#include "stm32f4xx.h"  // 根据实际芯片修改

// 初始化DWT周期计数器
void DWT_Init(void)
{
    // 使能DWT跟踪
    CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
    // 清零周期计数器
    DWT->CYCCNT = 0;
    // 使能周期计数器
    DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
}

// 微秒级延时(阻塞式)
void DWT_DelayUs(uint32_t us)
{
    uint32_t start = DWT->CYCCNT;
    uint32_t ticks = us * (SystemCoreClock / 1000000U);
    // 无符号减法自动处理溢出
    while ((DWT->CYCCNT - start) < ticks) {
        __NOP();  // 防止编译器优化掉空循环
    }
}

// 毫秒级延时(基于us延时)
void DWT_DelayMs(uint32_t ms)
{
    while (ms--) {
        DWT_DelayUs(1000);
    }
}

// 获取当前周期计数(用于非阻塞超时判断)
uint32_t DWT_GetCycles(void)
{
    return DWT->CYCCNT;
}

// 判断是否超时(单位:us)
// 返回值:1-超时,0-未超时
uint8_t DWT_IsTimeout(uint32_t startCycles, uint32_t timeoutUs)
{
    uint32_t ticks = timeoutUs * (SystemCoreClock / 1000000U);
    return ((DWT->CYCCNT - startCycles) >= ticks) ? 1 : 0;
}

使用示例:

int main(void)
{
    HAL_Init();
    SystemClock_Config();  // 配置系统时钟,更新SystemCoreClock
    DWT_Init();

    while (1) {
        DWT_DelayUs(500);  // 精确500us延时
        // 非阻塞超时检测示例
        uint32_t start = DWT_GetCycles();
        while (!DWT_IsTimeout(start, 1000)) {
            // 等待1ms超时,期间可执行其他任务
        }
    }
}

5. 注意事项

  • SystemCoreClock必须准确:若使用HAL库,在SystemClock_Config()后调用SystemCoreClockUpdate()确保变量值正确。
  • 调试器影响:某些调试器会暂停CYCCNT计数,在线调试时延时可能变长,脱机运行正常。
  • 中断干扰:DWT_DelayUs是阻塞延时,若被高优先级中断打断,实际延时会略长。对时间敏感的场景建议关闭中断或使用非阻塞方式。
  • 溢出处理:32位计数器在72MHz下约59.6秒溢出一次,无符号减法可正确处理单次溢出,但超长延时(>59秒)需额外处理。
  • 低功耗模式:进入Sleep/Stop模式后CYCCNT可能停止,唤醒后需重新校准或避免依赖。
  • 移植性:DWT是Cortex-M内核通用外设,但不同厂商的头文件定义可能略有差异,需包含正确的CMSIS头文件。

6. 总结

DWT周期计数器为嵌入式开发者提供了一种零硬件成本、高精度、不占用定时器的延时方案。通过本文的配置与代码,你可以轻松实现us级定时,并将宝贵的TIM资源留给PWM、编码器等关键功能。在电机控制、传感器时序、通信协议等对时间精度要求较高的场景中,DWT延时是值得掌握的实战技巧。