STM32 精确延时:SysTick 与硬件定时器全面对比
👁 3 阅读 · 2026-08-17 · STM32 定时器
在STM32嵌入式开发中,精确延时是外设时序控制、通信协议实现和实时性保障的基础。SysTick定时器以其简单易用成为默认选择,而通用定时器(TIM)则提供更高的灵活性和精度。本文将深入对比SysTick与硬件定时器在延时场景下的原理、配置、代码实现及注意事项,帮助开发者根据实际需求选择最优方案。
## 引言
在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库为例)
```c
// 配置SysTick为1ms中断
HAL_SYSTICK_Config(SystemCoreClock / 1000); // 1ms中断
HAL_SYSTICK_EnableInterrupt();
// 中断回调中递减全局变量
```
### 硬件定时器配置(以TIM2为例)
```c
// 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的阻塞延时
```c
// 全局变量,在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. 基于硬件定时器的微秒级延时(查询方式)
```c
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实现高精度微秒延时(无需定时器)
```c
// 启用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足够;但追求极致精度或需要多功能复用时,硬件定时器是更优解。掌握两者的原理与配置,能让你的嵌入式代码更加健壮高效。