一、为什么需要非阻塞微秒延时?
在嵌入式开发中,HAL_Delay() 等阻塞延时会导致CPU空转,浪费算力且影响实时响应。非阻塞延时通过记录时间戳,在主循环中轮询判断,实现多任务并行。微秒级精度对传感器时序、通信协议(如单总线、I2C)至关重要。
Cortex-M0+内核精简,通常不包含DWT(Data Watchpoint and Trace)周期计数器,但许多厂商(如STM32G0、STM32L0)在调试组件中保留了DWT。通过使能调试跟踪,可访问DWT->CYCCNT,获得CPU周期计数,实现高精度计时。
二、DWT周期计数器原理
DWT是ARM CoreSight调试组件的一部分,其CYCCNT寄存器在每个处理器时钟周期递增。访问需满足:
- 使能调试跟踪(CoreDebug->DEMCR的TRCENA位)
- 使能DWT计数器(DWT->CTRL的CYCCNTENA位)
- 复位并启动CYCCNT
在Cortex-M0+上,DWT寄存器基地址为0xE0001000,需确保芯片支持。部分低端型号可能裁剪DWT,使用前请查阅参考手册。
三、配置步骤
- 使能调试跟踪:
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; - 复位周期计数器:
DWT->CYCCNT = 0; - 使能CYCCNT:
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; - 读取当前周期数:
uint32_t cyc = DWT->CYCCNT;
注意:CYCCNT为32位,在72MHz下约59.6秒溢出,需处理回绕。
四、微秒级非阻塞延时实现
假设系统时钟为 SystemCoreClock,则每微秒周期数为 SystemCoreClock / 1000000。定义延时结构体记录起始周期和延时周期数。
#include "stm32g0xx.h" // 根据实际芯片调整
typedef struct {
uint32_t start_cyc;
uint32_t delay_cyc;
} dwt_delay_t;
void dwt_init(void) {
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CYCCNT = 0;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
}
void dwt_delay_start(dwt_delay_t *d, uint32_t us) {
d->start_cyc = DWT->CYCCNT;
d->delay_cyc = us * (SystemCoreClock / 1000000U);
}
// 返回1表示延时未到,0表示已到
int dwt_delay_check(dwt_delay_t *d) {
uint32_t elapsed = DWT->CYCCNT - d->start_cyc; // 自动处理回绕
return (elapsed < d->delay_cyc) ? 1 : 0;
}
使用示例:
dwt_delay_t delay1;
dwt_init();
dwt_delay_start(&delay1, 100); // 延时100us
while (dwt_delay_check(&delay1)) {
// 执行其他任务
}
// 延时结束
五、时钟偏差校准
DWT基于CPU时钟,若系统时钟源(如HSI)存在偏差,会导致延时误差。可利用SysTick(通常由HCLK驱动)或外部晶振进行校准。
校准思路:使用高精度参考(如GPS秒脉冲或已知频率信号)测量固定时间内的DWT周期数,计算实际每微秒周期数。
简化方法:用SysTick产生1ms中断,在中断中读取DWT差值,多次平均得到实际周期/毫秒,进而计算校准系数。
volatile uint32_t dwt_per_ms = 0;
volatile uint32_t last_cyc = 0;
void SysTick_Handler(void) {
uint32_t now = DWT->CYCCNT;
dwt_per_ms = now - last_cyc;
last_cyc = now;
}
void calibrate_dwt(void) {
// 配置SysTick为1ms中断
SysTick_Config(SystemCoreClock / 1000U);
// 等待多次测量取平均
uint32_t sum = 0;
for (int i = 0; i < 10; i++) {
while (dwt_per_ms == 0);
sum += dwt_per_ms;
dwt_per_ms = 0;
}
uint32_t avg = sum / 10;
// 实际每微秒周期数 = avg / 1000
// 可更新全局变量用于延时计算
}
校准后,将 SystemCoreClock / 1000000U 替换为 avg / 1000 即可。
六、注意事项
- 芯片支持性:并非所有Cortex-M0+都实现DWT,使用前确认参考手册。
- 调试器影响:连接调试器时DWT正常工作,脱机运行需确保TRCENA已使能。
- 溢出处理:使用无符号减法自动处理32位回绕,但延时不能超过溢出周期。
- 中断干扰:DWT计数不受中断影响,但长时间延时中若发生频繁中断,可能影响精度(因中断服务程序占用CPU周期,但CYCCNT仍递增,实际延时不变)。
- 低功耗模式:睡眠时CYCCNT可能停止,需根据应用调整。
- 校准时机:建议在系统稳定后校准,避免时钟切换影响。
七、总结
利用DWT周期计数器,可在Cortex-M0+上实现高精度非阻塞微秒延时,结合SysTick校准可消除时钟偏差。该方法资源占用少、精度高,适用于时序敏感的嵌入式场景。代码已封装为可复用模块,读者可根据实际芯片调整寄存器定义。
暂无评论,来抢沙发~