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]