## 引言 在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足够;但追求极致精度或需要多功能复用时,硬件定时器是更优解。掌握两者的原理与配置,能让你的嵌入式代码更加健壮高效。