用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。
配置步骤
- 使能
DEMCR的TRCENA位。 - 清零
DWT_CYCCNT。 - 置位
DWT_CTRL的CYCCNTENA位。 - 编写基于周期差值的延时函数。
- 注意计数器溢出处理(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级定时的场景下,它是替代软件循环延时的理想方案。掌握其原理与配置,能显著提升代码的灵活性与时序精度。
暂无评论,来抢沙发~