# 引言 在 STM32F4 系列(基于 Cortex-M4 内核)开发中,精确延时常见于传感器读取、通信协议时序(如 DHT11、DS18B20)或 PWM 波形模拟。SysTick 定时器虽然简单,但若在中断服务函数(ISR)中调用延时,或系统时钟频率较高时,其基于中断的计数方式会导致精度下降。DWT 单元提供了一种基于 CPU 周期计数的非侵入式延时方案,精度可达 1 个时钟周期,且不依赖中断,非常适合微秒级延时。 # DWT 原理 DWT 是 Cortex-M3/M4 内核自带的调试组件,其中 CYCCNT 寄存器是一个 32 位自由运行计数器,每个 CPU 时钟周期递增一次。当计数器溢出(从 0xFFFFFFFF 回绕到 0)时,可触发溢出中断,但通常我们只需读取其值计算时间差。 关键寄存器: - `DWT_CTRL`(地址 0xE0001000):控制 CYCCNT 使能。 - `DWT_CYCCNT`(地址 0xE0001004):当前周期计数。 - `CoreDebug_DEMCR`(地址 0xE000EDFC):需要设置 TRCENA 位以允许访问 DWT。 ## 延时原理 延时函数通过读取起始 CYCCNT 值,然后循环等待当前值减去起始值达到所需周期数。由于是纯轮询,不触发中断,因此避免了中断嵌套导致的延时抖动。 # 配置步骤 1. **使能 DWT 访问**:设置 `CoreDebug->DEMCR` 的位 24(TRCENA)。 2. **复位并启动 CYCCNT**:将 `DWT->CYCCNT` 清零,然后设置 `DWT->CTRL` 的位 0(CYCCNTENA)为 1。 3. **编写延时函数**:根据系统时钟频率(如 168MHz)计算周期数,并处理计数器回绕。 # 代码实现 以下代码适用于 STM32F4 系列,系统时钟为 168MHz(可通过 `SystemCoreClock` 获取)。 ```c #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 } // 微秒延时函数,us 范围 1~1000000 void DWT_Delay_us(uint32_t us) { uint32_t start = DWT->CYCCNT; uint32_t ticks = us * (SystemCoreClock / 1000000); // 计算所需周期数 while ((DWT->CYCCNT - start) < ticks); // 等待,注意无符号减法处理回绕 } // 毫秒延时(基于微秒延时) void DWT_Delay_ms(uint32_t ms) { for (uint32_t i = 0; i < ms; i++) { DWT_Delay_us(1000); } } ``` **使用示例**: ```c int main(void) { DWT_Init(); while (1) { GPIO_ToggleBits(GPIOA, GPIO_Pin_5); // 翻转 LED DWT_Delay_us(500); // 精确延时 500us } } ``` # 中断嵌套风险与规避 ## 风险分析 - **中断抢占**:若在低优先级中断中调用 DWT_Delay_us,高优先级中断可能抢占执行,导致延时时间被拉长(因为轮询循环被暂停)。 - **计数器溢出**:CYCCNT 是 32 位,在 168MHz 下约 25.6 秒回绕一次。若延时期间发生回绕,无符号减法仍能正确计算差值,但若中断导致延时超过回绕周期,则结果错误。 - **临界区问题**:若在延时过程中被中断打断,且中断内也调用延时,可能造成嵌套,增加不确定性。 ## 规避策略 1. **避免在 ISR 中调用延时**:尽量将延时操作移至主循环或任务中。 2. **使用临界区保护**:若必须在 ISR 中延时,可暂时屏蔽高优先级中断(但需谨慎,避免影响实时性)。 3. **处理回绕**:使用无符号减法(如 `(uint32_t)(DWT->CYCCNT - start)`)自动处理回绕,但需确保延时时间小于回绕周期。 4. **校准时钟**:若系统时钟不是整数 MHz,需用浮点或定点计算周期数,避免累积误差。 **改进版延时函数(支持任意时钟频率)**: ```c void DWT_Delay_us_accurate(uint32_t us) { uint32_t start = DWT->CYCCNT; uint64_t ticks = (uint64_t)us * SystemCoreClock / 1000000; // 使用 64 位避免溢出 while ((uint32_t)(DWT->CYCCNT - start) < (uint32_t)ticks); } ``` # 注意事项 - **初始化时机**:DWT_Init() 应在系统时钟配置完成后调用,且只需调用一次。 - **调试影响**:若使用调试器(如 ST-Link)暂停程序,CYCCNT 会停止计数,导致延时失效,因此发布版本应禁用调试。 - **功耗**:DWT 计数器在睡眠模式下可能停止,不适合低功耗场景。 - **替代方案**:对于长时间延时(>1ms),建议使用 SysTick 或定时器,以降低 CPU 占用。 # 总结 利用 DWT 的 CYCCNT 实现微秒延时,精度高、代码简洁,且不依赖中断,有效规避了中断嵌套风险。通过合理设计,可确保在复杂中断环境下仍能获得稳定时序。开发者应根据实际需求选择延时方案,并注意初始化与边界条件,以构建可靠的嵌入式系统。