当前位置:首页 > EDA > 电子设计自动化
[导读]裸机项目里SysTick常被RTOS占用,或某些小容量MCU根本不打算用内核滴答。毫秒级延时不一定非靠它:Cortex-M可用DWT周期计数器做纯硬件计时,通用MCU可用普通定时器轮询,51类可用Timer0/1做1ms时基。核心原则是“用硬件计数器读时间,不在循环里空转估时间”。


裸机项目里SysTick常被RTOS占用,或某些小容量MCU根本不打算用内核滴答。毫秒级延时不一定非靠它:Cortex-M可用DWT周期计数器做纯硬件计时,通用MCU可用普通定时器轮询,51类可用Timer0/1做1ms时基。核心原则是“用硬件计数器读时间,不在循环里空转估时间”。

一、Cortex-M用DWT,不占SysTick

DWT是内核调试组件,CYCCNT是32位自由运行周期计数器,使能后每个CPU时钟加1,溢出回零 。M3/M4/M7大多可用,M0/M0+通常没有,需改定时器方案。

#define DEMCR   (*(volatile uint32_t *)0xE000EDFC)

#define DWTCR   (*(volatile uint32_t *)0xE0001000)

#define CYCCNT  (*(volatile uint32_t *)0xE0001004)

#define TRCENA  0x01000000u

#define CNTENA  0x00000001u


static uint32_t cpu_hz = 72000000u;


void dwt_delay_init(uint32_t hz){

   cpu_hz = hz;

   DEMCR |= TRCENA;

   DWTCR |= CNTENA;

   CYCCNT = 0;

}


void dwt_delay_ms(uint32_t ms){

   uint32_t total = (uint64_t)ms * cpu_hz / 1000u;

   uint32_t start = CYCCNT;

   while ((uint32_t)(CYCCNT - start) < total);

}


void dwt_delay_us(uint32_t us){

   uint32_t total = (uint64_t)us * cpu_hz / 1000000u;

   uint32_t start = CYCCNT;

   while ((uint32_t)(CYCCNT - start) < total);

}

CYCCNT按处理器时钟运行,不受RTOS调度影响;但调试暂停时不计数,长时间延时要注意32位溢出。72MHz下1ms约72000个周期,1μs约72个周期,精度远高于软件for循环 。

二、普通定时器轮询,适合无DWT芯片

没有DWT时,用任一通用TIM,配1ms计数、不进中断,主循环读CNT判断:

// 假设TIM2时钟72MHz,分频7199得10kHz,自动重装999得1ms

void tim_delay_init(void){

   RCC->APB1ENR |= (1u<<0);     // 使能TIM2

   TIM2->PSC = 7199;

   TIM2->ARR = 999;

   TIM2->CNT = 0;

   TIM2->CR1 = (1u<<0);         // 启动,向上计数

}


void tim_delay_ms(uint32_t ms){

   for(uint32_t i=0;i<ms;i++){

       TIM2->CNT = 0;

       while((TIM2->SR & (1u<<0))==0);   // 等更新标志

       TIM2->SR &= ~(1u<<0);

   }

}

轮询方式不依赖SysTick,也不被定时器中断频率绑死;若系统已有其他定时器,可只借CNT做时间差,避免反复重装。

三、51/小核用Timer做1ms时基

12MHz机器周期1μs,Timer0模式1定时1ms初值65536-1000=64536=0xFC18:

static volatile uint32_t g_ms;


void timer0_init(void){

   TMOD &= 0xF0; TMOD |= 0x01;   // Timer0模式1

   TH0 = 0xFC; TL0 = 0x18;

   ET0 = 1; EA = 1; TR0 = 1;

}


void timer0_isr(void) interrupt 1{

   TH0 = 0xFC; TL0 = 0x18;

   g_ms++;

}


void sw_delay_ms(uint32_t ms){

   uint32_t t = g_ms;

   while((g_ms - t) < ms);

}

中断只维护ms计数,业务用时间差判断,既不占SysTick,也避免软件空循环被编译器优化掉 。

四、软件循环只做粗延时

无定时器可用时再用循环,但必须按主频校准、变量加volatile,否则-O2/-Os可能把空循环删掉:

static volatile uint32_t loops_per_ms = 8000u;  // 示波器校准

void soft_delay_ms(uint32_t ms){

   volatile uint32_t n = ms * loops_per_ms;

   while(n--);

}

上板用GPIO翻转+示波器测实际宽度,反调loops_per_ms;这种方案只适合LED闪烁、上电稳定等粗时序,不适合通信严格时序。

五、避坑要点

软件延时会被中断拉长,要求高精度别用空循环,改DWT或硬件定时器轮询 。

DWT在M0/M0+不一定存在,移植前查TRM;没有就走TIM或RTC。

32位周期计数器长时间延时会溢出,用无符号相减判断区间可自动回绕。

低功耗场景别用忙等延时,改RTC闹钟、低功耗定时器唤醒,睡醒再继续。

多实例外设不要把同一个TIM既做PWM又做延时基准,分开或只用CNT差分。

编译器优化开-Os/-O2时,空循环、NOP延时都要volatile或内联汇编,最好用硬件计数器替代。

按“Cortex-M优先DWT、通用MCU用TIM轮询、8/51用Timer中断计ms”的分层思路,不用SysTick也能把毫秒延时做成可测算、可移植、不受任务调度拖偏的稳定时基。

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
关闭