用DWT周期计数器做us级精确定时,替代定时器资源紧张时的软件延时

为什么需要DWT

在STM32项目中,硬件定时器(TIM)往往被PWM、输入捕获、编码器接口、串口超时等外设占用。当需要微秒级延时时,常见的for循环软件延时受编译器优化、主频变化影响,精度极差;而占用一个定时器又可能影响其他功能。

Cortex-M内核自带DWT(Data Watchpoint and Trace)单元,其中的CYCCNT寄存器是一个32位自由运行的周期计数器,每个CPU时钟周期加1。利用它可以直接获得CPU周期级的时间基准,实现高精度us延时,且不占用任何外设定时器。

DWT原理与关键寄存器

DWT属于ARM CoreSight调试组件,主要寄存器:

  • DWT_CTRL(地址0xE0001000):bit0为CYCCNTENA,使能周期计数器。
  • DWT_CYCCNT(地址0xE0001004):32位周期计数值,溢出后自动回绕。
  • DEMCR(地址0xE000EDFC):bit24为TRCENA,使能跟踪与调试模块,必须先置位才能访问DWT。

延时原理:记录起始周期值,循环读取当前周期值,当差值达到延时us × 每us周期数时退出。每us周期数 = SystemCoreClock / 1,000,000。

配置步骤

  1. 使能DEMCR的TRCENA位。
  2. 清零DWT_CYCCNT。
  3. 置位DWT_CTRL的CYCCNTENA位。
  4. 编写基于周期差值的延时函数。
  5. 注意计数器溢出处理(32位@72MHz约59.6秒回绕)。

完整代码示例

#include "stm32f10x.h"   // 根据实际芯片替换头文件

#define DWT_CTRL   (*(volatile uint32_t *)0xE0001000)
#define DWT_CYCCNT (*(volatile uint32_t *)0xE0001004)
#define DEMCR      (*(volatile uint32_t *)0xE000EDFC)

static uint32_t cycles_per_us;

void DWT_Delay_Init(void)
{
    DEMCR |= (1UL << 24);          // 使能TRCENA
    DWT_CYCCNT = 0;                // 清零周期计数器
    DWT_CTRL |= (1UL << 0);        // 使能CYCCNT
    cycles_per_us = SystemCoreClock / 1000000UL;
}

void DWT_Delay_us(uint32_t us)
{
    uint32_t start = DWT_CYCCNT;
    uint32_t ticks = us * cycles_per_us;
    // 使用无符号差值,自动处理回绕
    while ((DWT_CYCCNT - start) < ticks) {
        __NOP();
    }
}

调用示例:

int main(void)
{
    SystemInit();
    DWT_Delay_Init();
    while (1) {
        GPIO_SetBits(GPIOA, GPIO_Pin_0);
        DWT_Delay_us(10);          // 精确10us高电平
        GPIO_ResetBits(GPIOA, GPIO_Pin_0);
        DWT_Delay_us(10);
    }
}

精度与实测

  • 在72MHz STM32F103上,cycles_per_us = 72,10us延时理论720周期,实测误差在1~2个周期内(约±0.03us),远优于循环延时。
  • 若需纳秒级,可扩展为DWT_Delay_ns,但受函数调用开销限制,实际分辨率约几十ns。
  • 中断会打断延时循环,导致延时变长;对时间敏感的场景建议关中断或使用硬件定时器。

注意事项

  • 必须先使能DEMCR.TRCENA,否则写DWT_CTRL无效。
  • 部分调试器复位后可能关闭DWT,量产代码中应在初始化时重新配置。
  • 32位计数器在72MHz下约59.6秒回绕,使用无符号差值可安全处理单次延时小于回绕周期的情况。
  • 延时期间若发生中断,实际延时会被拉长;高精度场景需评估中断影响。
  • 不同Cortex-M内核(M0/M0+无DWT CYCCNT)支持情况不同,M3/M4/M7可用。
  • 编译器优化等级不影响DWT延时,因为依赖的是硬件计数器而非指令循环。

总结

DWT周期计数器为嵌入式开发者提供了一种“零外设占用”的高精度延时手段。在定时器资源紧张、又需要us级定时的场景下,它是替代软件循环延时的理想方案。掌握其原理与配置,能显著提升代码的灵活性与时序精度。