引言

在STM32开发中,延时函数无处不在:从LED闪烁到传感器时序控制,从I2C/SPI通信到RTOS心跳。SysTick作为Cortex-M内核自带的24位倒计时定时器,因其配置简单被广泛使用;而通用定时器(TIM)凭借多通道、输入捕获、PWM输出等特性,在复杂场景下更具优势。本文将从原理到实战,全面剖析两者的差异。

一、原理剖析:SysTick vs 硬件定时器

1. SysTick定时器

SysTick是ARM Cortex-M内核集成的24位递减计数器,时钟源通常为HCLK或HCLK/8。其核心寄存器包括:

  • CTRL:控制使能、时钟源选择、中断使能
  • LOAD:重装载值(24位,最大16,777,215)
  • VAL:当前计数值

工作原理:计数器从LOAD值开始递减,减至0时置位COUNTFLAG,若使能中断则触发异常,并自动重载LOAD值。

2. 硬件定时器(以TIM2为例)

STM32的通用定时器是16/32位(如TIM2为32位)可编程预分频器驱动的计数器,支持向上、向下、中心对齐模式。关键寄存器:

  • PSC:预分频器(16位),实际分频系数为PSC+1
  • ARR:自动重装载寄存器(决定计数周期)
  • CNT:当前计数值
  • SR/DIER:状态与中断使能

定时器时钟源为APB1/APB2总线时钟,经倍频器后得到定时器时钟(通常为72MHz或84MHz)。

二、配置步骤对比

SysTick配置(以HAL库为例)

// 配置SysTick为1ms中断
HAL_SYSTICK_Config(SystemCoreClock / 1000);  // 1ms中断
HAL_SYSTICK_EnableInterrupt();
// 中断回调中递减全局变量

硬件定时器配置(以TIM2为例)

// 1. 使能时钟
__HAL_RCC_TIM2_CLK_ENABLE();

// 2. 初始化定时器
TIM_HandleTypeDef htim2;
htim2.Instance = TIM2;
htim2.Init.Prescaler = 72 - 1;          // 72MHz/72 = 1MHz
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 1000 - 1;           // 1MHz/1000 = 1kHz
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_Base_Init(&htim2);

// 3. 启动定时器(中断模式)
HAL_TIM_Base_Start_IT(&htim2);

三、代码实现:精确延时函数

1. 基于SysTick的阻塞延时

// 全局变量,在SysTick中断中递减
volatile uint32_t uwTick;

void SysTick_Handler(void) {
    uwTick++;
}

void delay_ms_systick(uint32_t ms) {
    uint32_t start = uwTick;
    while ((uwTick - start) < ms);
}

void delay_us_systick(uint32_t us) {
    // 使用DWT或循环实现微秒级延时(见下文)
}

2. 基于硬件定时器的微秒级延时(查询方式)

void TIM2_Delay_us(uint32_t us) {
    // 重新配置定时器为单次模式
    __HAL_TIM_SET_COUNTER(&htim2, 0);
    __HAL_TIM_SET_AUTORELOAD(&htim2, us - 1);
    HAL_TIM_Base_Start(&htim2);
    while (__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_UPDATE) == RESET);
    __HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_UPDATE);
    HAL_TIM_Base_Stop(&htim2);
}

3. 使用DWT实现高精度微秒延时(无需定时器)

// 启用DWT周期计数器
void DWT_Init(void) {
    CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
    DWT->CYCCNT = 0;
    DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
}

void delay_us_dwt(uint32_t us) {
    uint32_t start = DWT->CYCCNT;
    uint32_t ticks = us * (SystemCoreClock / 1000000);
    while ((DWT->CYCCNT - start) < ticks);
}

四、对比分析:选型指南

| 特性 | SysTick | 硬件定时器 | |------|---------|------------| | 位数 | 24位 | 16/32位 | | 精度 | 中(受中断影响) | 高(硬件计数) | | 占用资源 | 内核中断 | 外设+中断 | | 多通道 | 无 | 有(可同时延时+PWM等) | | 功耗 | 低 | 中(需时钟使能) | | 易用性 | 极高 | 较高 |

选型建议:

  • 简单延时、RTOS心跳:首选SysTick
  • 微秒级高精度延时:使用DWT或TIM
  • 需要同时进行PWM输出、输入捕获:必须使用TIM
  • 低功耗场景:SysTick可随内核停止,TIM需额外管理

五、注意事项

  1. 中断优先级:SysTick中断优先级通常设为最低,若主程序中有长临界区,会导致延时不准。TIM中断可设置更高优先级。
  2. 时钟源选择:SysTick的时钟源若为HCLK/8,则延时计算需除以8;TIM的时钟需确认APB1/APB2预分频系数(通常为1或2)。
  3. 溢出处理:24位SysTick在72MHz下最大延时约233ms,超过需循环;32位TIM2在72MHz下最大延时约59.6秒,但需注意计数溢出。
  4. 阻塞与非阻塞:阻塞延时浪费CPU,建议在RTOS中使用信号量或事件标志。
  5. DWT延时:DWT周期计数器在调试模式下可能受影响,需确认是否禁用。

结语

SysTick与硬件定时器各有千秋,选择时需权衡精度、资源占用和功能需求。对于大多数应用,SysTick足够;但追求极致精度或需要多功能复用时,硬件定时器是更优解。掌握两者的原理与配置,能让你的嵌入式代码更加健壮高效。