renzheshengui 发表于 2025-8-17 18:16

STM32定时器与延时系统完整笔记

一、延时基础概念
1.1 延时精度分级
纳秒级(ns):硬件层面,软件难以实现
微秒级(us):1-999us,需要硬件定时器或精确的软件延时
毫秒级(ms):1-999ms,系统tick或定时器实现
秒级(s):   RTOS延时或RTC实现


1.2 延时实现方法对比
方法        精度        CPU占用        适用场景        优缺点
软件空循环        差(±20%)        100%        极短延时<10us        简单但浪费CPU
SysTick        好(±1%)        100%        裸机ms级延时        占用系统定时器
硬件定时器        最好(<1%)        100%/0%        us到s级延时        精确但占用定时器资源
RTOS延时        取决于tick        0%        ms级以上        不浪费CPU,可多任务
RTC        秒级        0%        长延时        省电,精度一般
二、裸机环境延时实现
2.1 微秒级延时实现
// 方法1:软件延时(精度差,受编译优化影响)
void delay_us_software(uint32_t us) {
    uint32_t ticks = us * (SystemCoreClock / 1000000);
    while(ticks--) {
      __NOP();// 空操作,防止编译器优化
    }
}

// 方法2:SysTick定时器(精度好,但占用系统资源)
void delay_us_systick(uint32_t us) {
    uint32_t ticks = us * (SystemCoreClock / 1000000);
    SysTick->LOAD = ticks - 1;       // 设置重装载值
    SysTick->VAL = 0;               // 清除当前值
    SysTick->CTRL = SysTick_CTRL_ENABLE_Msk;// 启动

    // 等待计数到0
    while(!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk));
    SysTick->CTRL = 0;                // 关闭SysTick
}

// 方法3:硬件定时器(最精确,推荐)
void delay_us_timer(uint32_t us) {
    // 假设TIM2已配置为1MHz (1us per tick)
    TIM2->CNT = 0;      // 清零计数器
    TIM2->ARR = us - 1;   // 设置自动重装载值
    TIM2->CR1 |= TIM_CR1_CEN;// 启动定时器

    // 等待更新事件
    while(!(TIM2->SR & TIM_SR_UIF));
    TIM2->SR &= ~TIM_SR_UIF;    // 清除标志
    TIM2->CR1 &= ~TIM_CR1_CEN;// 停止定时器
}





2.2 性能问题分析
// 阻塞式延时的问题
void blocking_delay_ms(uint32_t ms) {
    for(uint32_t i = 0; i < ms * 8000; i++) {
      __NOP();// CPU利用率100%,但有效工作0%
    }
}

// 性能损失:
// - CPU占用率:100%
// - 有效工作:0%
// - 功耗:最高
// - 响应性:除中断外无法响应其他事件





2.3 非阻塞延时方案
// 使用系统tick计数器
volatile uint32_t sys_tick_ms = 0;

void SysTick_Handler(void) {
    sys_tick_ms++;
}

// 非阻塞延时检查
typedef struct {
    uint32_t start_time;
    uint32_t delay_ms;
} soft_timer_t;

uint8_t is_timeout(soft_timer_t *timer) {
    return (sys_tick_ms - timer->start_time) >= timer->delay_ms;
}

// 使用示例
void main_loop(void) {
    soft_timer_t led_timer = {sys_tick_ms, 500};

    while(1) {
      if(is_timeout(&led_timer)) {
            LED_Toggle();
            led_timer.start_time = sys_tick_ms;
      }

      // 可以执行其他任务
      Process_UART();
      Process_ADC();
    }
}




三、硬件定时器限制
3.1 寄存器位宽限制
// STM32F103定时器类型
定时器类型    位宽    ARR最大值         最大计数
TIM1         16位    65,535         65,535
TIM2         32位    4,294,967,295    约43亿
TIM3,TIM4    16位    65,535         65,535

// 定时器延时计算公式
延时时间 = (PSC + 1) × (ARR + 1) / TIMxCLK

// 其中:
PSC: 预分频器(16位: 0-65535)
ARR: 自动重装载值
TIMxCLK: 定时器输入时钟(最高72MHz)





3.2 最大延时时间计算
// 16位定时器最大延时
// 条件:72MHz系统时钟,最大预分频
最大延时 = 65536 × 65536 / 72MHz = 59.65秒

// 32位定时器最大延时
// 条件:72MHz系统时钟,最大预分频
最大延时 = 65536 × 4294967296 / 72MHz = 45.25天

// 实际配置示例
void config_timer_for_1ms(void) {
    // 72MHz / 72 = 1MHz (1us per tick)
    TIM3->PSC = 72 - 1;   
    // 1000us = 1ms
    TIM3->ARR = 1000 - 1;   
}






3.3 超长延时解决方案
// 方案1:自动调整预分频器
void adaptive_delay_us(uint32_t us) {
    uint32_t prescaler, arr;

    if(us <= 65535) {
      prescaler = 72;      // 1us分辨率
      arr = us;
    } else if(us <= 655350) {
      prescaler = 720;   // 10us分辨率
      arr = us / 10;
    } else if(us <= 6553500) {
      prescaler = 7200;    // 100us分辨率
      arr = us / 100;
    } else {
      return;// 超出范围
    }

    TIM3->PSC = prescaler - 1;
    TIM3->ARR = arr - 1;
    TIM3->CNT = 0;
    TIM3->CR1 |= TIM_CR1_CEN;
    while(!(TIM3->SR & TIM_SR_UIF));
    TIM3->SR &= ~TIM_SR_UIF;
}

// 方案2:分段延时
void long_delay_ms(uint32_t ms) {
    const uint32_t MAX_DELAY = 60000;// 60秒

    while(ms > 0) {
      uint32_t current = (ms > MAX_DELAY) ? MAX_DELAY : ms;
      delay_ms_timer(current);
      ms -= current;
    }
}

// 方案3:使用RTC(适合超长延时)
void rtc_delay_hours(uint32_t hours) {
    uint32_t target = RTC_GetCounter() + (hours * 3600);
    while(RTC_GetCounter() < target) {
      PWR_EnterSTOPMode();// 进入低功耗
    }
}







四、RTOS环境下的延时
4.1 RTOS延时机制原理
// RTOS延时不是空转,而是任务切换
void vTaskDelay(TickType_t xTicksToDelay) {
    // 1. 将当前任务移出就绪列表
    // 2. 加入延时列表
    // 3. 触发任务调度,CPU切换到其他任务
    // 4. 延时到期后,任务重新就绪
}

// 对比:
// 裸机delay: CPU利用率0%,阻塞
// RTOS delay: CPU利用率90%+,非阻塞




4.2 RTOS中的us级延时
// RTOS不适合us级延时的原因:
// 1. 系统tick通常是1ms
// 2. 任务切换开销约100-200个时钟周期(1.4-2.8us @72MHz)

// 解决方案:混合延时
void rtos_delay_us(uint32_t us) {
    if(us < 100) {
      // 短延时直接阻塞
      taskENTER_CRITICAL();
      delay_us_timer(us);
      taskEXIT_CRITICAL();
    } else if(us < 1000) {
      // 中等延时用硬件定时器
      delay_us_timer(us);
    } else {
      // 长延时用RTOS
      vTaskDelay(pdMS_TO_TICKS(us / 1000));
    }
}




五、多任务环境的线程安全问题
5.1 全局变量竞争问题
//
错误示例:全局变量被多任务共享
volatile uint32_t delay_counter;// 危险!

void Task1() { delay_counter = 100; }// 任务1设置
void Task2() { delay_counter = 200; }// 任务2覆盖
// 结果:混乱

//
正确方案1:互斥锁保护
SemaphoreHandle_t timer_mutex;

void safe_delay(uint32_t ms) {
    xSemaphoreTake(timer_mutex, portMAX_DELAY);
    // 使用定时器
    xSemaphoreGive(timer_mutex);
}

// 正确方案2:任务本地存储
typedef struct {
    uint32_t delay_count;
    TaskHandle_t task;
} task_delay_t;

task_delay_t delays;// 每个任务独立




5.2 多任务延时管理方案
// 完整的多任务延时管理器
typedef struct {
    TaskHandle_t task;
    uint32_t remaining_ms;
    uint8_t active;
} delay_entry_t;

#define MAX_DELAYS 10
delay_entry_t delay_list;
SemaphoreHandle_t list_mutex;

// 定时器中断(每1ms)
void TIM3_IRQHandler(void) {
    if(TIM3->SR & TIM_SR_UIF) {
      TIM3->SR &= ~TIM_SR_UIF;

      BaseType_t xHigherPriorityTaskWoken = pdFALSE;

      for(int i = 0; i < MAX_DELAYS; i++) {
            if(delay_list.active) {
                if(--delay_list.remaining_ms == 0) {
                  // 通知任务
                  vTaskNotifyGiveFromISR(delay_list.task,
                                          &xHigherPriorityTaskWoken);
                  delay_list.active = 0;
                }
            }
      }

      portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
    }
}

// 任务安全的延时函数
void multi_task_delay(uint32_t ms) {
    TaskHandle_t self = xTaskGetCurrentTaskHandle();
    int slot = -1;

    // 分配延时槽
    xSemaphoreTake(list_mutex, portMAX_DELAY);
    for(int i = 0; i < MAX_DELAYS; i++) {
      if(!delay_list.active) {
            delay_list.task = self;
            delay_list.remaining_ms = ms;
            delay_list.active = 1;
            slot = i;
            break;
      }
    }
    xSemaphoreGive(list_mutex);

    if(slot >= 0) {
      // 等待通知
      ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
    } else {
      // 回退到vTaskDelay
      vTaskDelay(pdMS_TO_TICKS(ms));
    }
}




六、最佳实践总结
6.1 延时方法选择决策树
if (需要延时) {
    if (延时 < 10us) {
      使用NOP循环或硬件定时器;
    } else if (延时 < 100us) {
      if (在RTOS中) {
            考虑是否真的需要如此精确的延时;
            如必须:使用临界区+硬件定时器;
      } else {
            使用硬件定时器;
      }
    } else if (延时 < 1ms) {
      if (在RTOS中) {
            评估使用vTaskDelay(1)是否足够;
      } else {
            使用定时器中断;
      }
    } else if (延时 < 1s) {
      if (在RTOS中) {
            使用vTaskDelay();
      } else {
            使用定时器中断+计数器;
      }
    } else {
      使用RTC或软件定时器;
    }
}





6.2 性能优化建议
// 1. 避免阻塞延时

while(delay--);

vTaskDelay() 或 定时器中断

// 2. 合理使用临界区
短延时(<100us): 可以使用临界区
长延时(>100us): 避免关中断太久

// 3. 资源分配策略
优先级高的任务: 分配独立定时器
普通任务: 使用共享定时器或软件定时器
后台任务: 使用vTaskDelay

// 4. 功耗优化
短延时: 忙等待可接受
长延时: 使用WFI或低功耗模式
超长延时: 使用RTC+深度睡眠



6.3 常见应用场景示例
// 场景1:I2C/SPI时序(us级)
void i2c_delay(void) {
    delay_us_timer(5);// 硬件定时器保证精度
}

// 场景2:LED闪烁(ms级)
void led_task(void *param) {
    while(1) {
      LED_Toggle();
      vTaskDelay(500);// RTOS延时
    }
}

// 场景3:传感器采样(秒级)
void sensor_task(void *param) {
    TickType_t xLastWake = xTaskGetTickCount();
    while(1) {
      read_sensor();
      vTaskDelayUntil(&xLastWake, pdMS_TO_TICKS(1000));
    }
}

// 场景4:低功耗定时唤醒(分钟/小时级)
void low_power_delay(uint32_t minutes) {
    RTC_SetAlarm(minutes * 60);
    PWR_EnterSTANDBYMode();// 深度睡眠
}




6.4 调试技巧
// 1. 测量实际延时时间
GPIO_SetHigh();
delay_us_timer(100);
GPIO_SetLow();
// 用示波器测量脉冲宽度

// 2. 检测任务延时冲突
void check_delay_collision(void) {
    static TaskHandle_t last_user = NULL;
    TaskHandle_t current = xTaskGetCurrentTaskHandle();

    if(last_user != NULL && last_user != current) {
      // 检测到不同任务使用
      printf("Warning: Timer conflict!\n");
    }
    last_user = current;
}

// 3. 监控延时精度
uint32_t start = DWT->CYCCNT;
delay_us_timer(1000);
uint32_t cycles = DWT->CYCCNT - start;
float actual_us = cycles / (SystemCoreClock / 1000000.0);




七、核心要点回顾
延时方法选择:根据精度需求、是否在RTOS中、延时长度选择合适方法
性能考虑:裸机delay浪费CPU,RTOS delay释放CPU
硬件限制:16位定时器最大约60秒,32位约45天
线程安全:多任务环境必须考虑资源竞争和互斥
最佳实践:优先使用RTOS机制,避免全局变量,合理分配资源
————————————————
版权声明:本文为CSDN博主「m0_55576290」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/m0_55576290/article/details/150146843

页: [1]
查看完整版本: STM32定时器与延时系统完整笔记