引言
在嵌入式开发中,精确延时是基础需求。STM32F4 系列默认使用 SysTick 作为系统节拍,若同时用于微秒级延时,不仅会干扰系统调度,还可能因中断优先级问题导致延时不准。DWT(Data Watchpoint and Trace)单元是 Cortex-M4 内核自带的调试组件,其中的 CYCCNT 计数器以内核时钟频率递增,可轻松实现微秒级延时,且与 SysTick 完全独立,互不干扰。
DWT 原理简介
DWT 是 Cortex-M 内核的调试与跟踪单元,包含多个计数器,其中 CYCCNT 是一个 32 位自由运行计数器,每个内核时钟周期加 1。当内核时钟为 168MHz(STM32F4 最高主频)时,CYCCNT 每秒计数 168,000,000 次,即每微秒约 168 次。通过读取 CYCCNT 的差值,即可精确计算耗时。
关键寄存器:
-
DWT_CTRL(地址 0xE0001000):控制 DWT 功能,其中 bit0(CYCCNTENA)使能 CYCCNT 计数。 -
DWT_CYCCNT(地址 0xE0001004):当前计数值。 -
CoreDebug_DEMCR(地址 0xE000EDFC):bit24(TRCENA)必须置 1,以允许访问 DWT。
配置步骤
-
使能 DWT 访问:设置
CoreDebug_DEMCR的 TRCENA 位。 -
复位 CYCCNT:将
DWT_CYCCNT清零。 -
使能 CYCCNT:设置
DWT_CTRL的 CYCCNTENA 位。 - 编写延时函数:读取当前计数值,计算目标差值,循环等待。
完整代码示例
以下代码适用于 STM32F4 系列,主频 168MHz。若主频不同,请调整 SYSTEM_CLOCK 宏。
#include "stm32f4xx.h"
#define SYSTEM_CLOCK 168000000UL // 根据实际主频修改
// 初始化 DWT 延时功能
void DWT_Delay_Init(void)
{
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; // 使能 DWT 访问
DWT->CYCCNT = 0; // 清零计数器
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; // 使能 CYCCNT
}
// 微秒级延时,参数 us 为微秒数
void DWT_Delay_us(uint32_t us)
{
uint32_t start = DWT->CYCCNT;
uint32_t ticks = us * (SYSTEM_CLOCK / 1000000UL); // 计算所需时钟周期数
while ((DWT->CYCCNT - start) < ticks); // 等待达到目标
}
// 毫秒级延时,基于微秒实现
void DWT_Delay_ms(uint32_t ms)
{
for (uint32_t i = 0; i < ms; i++)
{
DWT_Delay_us(1000);
}
}
// 示例:主函数中初始化并测试
int main(void)
{
DWT_Delay_Init();
while (1)
{
GPIOA->ODR ^= (1 << 5); // 翻转 PA5
DWT_Delay_us(500); // 延时 500us
}
}
注意事项
-
主频一致性:
SYSTEM_CLOCK必须与实际内核时钟一致,否则延时误差较大。可通过SystemCoreClock变量获取实际主频,但需注意该变量在启动文件中初始化。 -
计数器溢出:CYCCNT 是 32 位,在 168MHz 下约 25.6 秒溢出一次。对于微秒级延时,差值计算(
DWT->CYCCNT - start)利用无符号整数回绕特性,可正确处理溢出,但延时时间不能超过溢出周期。 - 中断影响:DWT 延时是忙等待,期间不会响应中断(除非中断嵌套),若需在延时中处理中断,请考虑其他方案。
- 调试器干扰:使用调试器时,DWT 可能被调试工具占用,建议在发布代码前确认 DWT 未被其他功能使用。
- 低功耗模式:在低功耗模式下,内核时钟可能停止,DWT 计数也会暂停,因此不适用于低功耗唤醒后的精确延时。
总结
DWT 延时方法利用内核调试单元,避免了 SysTick 冲突,实现简单且精度高,特别适合需要精确时序控制的场景,如传感器读取、通信协议时序等。只需几行代码即可集成,是嵌入式开发者的实用技巧。希望本文能帮助你解决延时难题,提升开发效率。