HAL_Delay原理+优化方案

1. HAL_Delay() 的实现原理

实现机制

HAL_Delay() 是 STM32 HAL 库提供的阻塞式延时函数,其核心实现依赖 SysTick 定时器,具体流程如下:

// HAL 库关键代码片段
__weak void HAL_Delay(uint32_t Delay) {
  uint32_t tickstart = HAL_GetTick();
  while ((HAL_GetTick() - tickstart) < Delay) {
    // 空循环等待
  }
}
​
// 系统滴答计时器(SysTick)的中断服务函数
void SysTick_Handler(void) {
  HAL_IncTick(); // 全局变量 uwTick 自增
}
​
// 获取当前系统时间戳
__weak uint32_t HAL_GetTick(void) {
  return uwTick;
}
关键点解析
  • SysTick 配置:默认使用 1ms 中断周期(通过 HAL_SYSTICK_Config() 设置)

  • 阻塞式设计:通过 while 循环持续检查时间差,占用 CPU 资源

  • 弱函数特性__weak 修饰符允许用户重写实现

2. HAL_Delay() 的缺陷

(1) 实时性杀手
// 在中断服务函数中使用 HAL_Delay() 会导致灾难性后果
void USART_IRQHandler(void) {
  HAL_Delay(100); // 将阻塞所有低优先级中断
}
(2) 精度局限
  • 受 SysTick 中断响应延迟影响

  • 最小延时单位 = 1ms(默认配置)

  • 无法实现微秒级精确延时

(3) 资源浪费
  • while 循环期间,CPU 无法执行其他任务

  • 多任务系统中可能引发优先级反转问题

3. 替代方案与实现方法

方案一:非阻塞式延时(推荐)
用结构体实现
// 定义非阻塞延时结构体(检查过程不会阻塞程序)
typedef struct {
  uint32_t start_time;//记录当前时间
  uint32_t delay_ms;//延时总时长
} NonBlockingDelay;
​
// 初始化延时
void Delay_Start(NonBlockingDelay* d, uint32_t ms) {
  d->start_time = HAL_GetTick();
  d->delay_ms = ms;
}
​
// 检查是否超时
uint8_t Delay_IsTimeout(NonBlockingDelay* d) {
  return (HAL_GetTick() - d->start_time) >= d->delay_ms;
}
​
​
// 使用示例
NonBlockingDelay led_delay;
Delay_Start(&led_delay, 500);
​
if (Delay_IsTimeout(&led_delay)) {
  // 执行周期任务
  Delay_Start(&led_delay, 500); // 重置计时
}
简化版
// 非阻塞延时简化版,仅处理一个延时
//结构体繁琐,可以用 静态变量+函数 简化 节省内存资源(适合单一延时场景)
uint8_t isDelayDone(uint32_t ms) {
    static uint32_t start = 0; //静态变量
    if (HAL_GetTick() - start >= ms) {
        start = HAL_GetTick(); // 自动重置 
        return 1;
    }
    return 0;
}
 
// 使用示例 
if (isDelayDone(500)) {
    // 每500ms执行一次 
}

方案二:硬件定时器精确延时
// 使用 TIM2 实现微秒级延时(预分频配置为 84MHz/84 = 1MHz)
void Delay_us(uint16_t us) {
  __HAL_TIM_SET_COUNTER(&htim2, 0);
  HAL_TIM_Base_Start(&htim2);
  while (__HAL_TIM_GET_COUNTER(&htim2) < us);
  HAL_TIM_Base_Stop(&htim2);
}
​
// 实现原理:
// TIM2 时钟源 = APB1 Timer clocks (默认 84MHz)
// 预分频值 Prescaler = 83 → 计数器频率 = 1MHz (1us/计数)
方案三:RTOS 任务调度
// FreeRTOS 的精确延时(需要使能 vTaskDelayUntil)
void TaskFunction(void const * argument) {
  TickType_t xLastWakeTime = xTaskGetTickCount();
  
  while(1) {
    // 每 500ms 精确执行(不受任务执行时间影响)
    vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(500));
    
    // 执行周期性任务
  }
}
方案四:低功耗模式结合
// 使用 STOP 模式实现超低功耗延时
void Enter_StopMode(uint32_t ms) {
  RTC->CR |= RTC_CR_WUTE; // 使能 RTC 唤醒
  
  // 配置 RTC 唤醒间隔
  HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, ms, RTC_WAKEUPCLOCK_CK_SPRE_16BITS);
  
  HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
  SystemClock_Config(); // 唤醒后需重新配置时钟
}

三、与其他延时方案的区别

方案原理阻塞?适用场景
HAL_Delay()依赖SysTick中断,死循环等待直到时间到阻塞简单任务,无需多线程
SysTick寄存器轮询直接读取SysTick寄存器计算时间差,无需中断非阻塞需要高精度或避免中断的场景
状态机定时检查在状态机中结合时间条件切换状态,依赖非阻塞计时非阻塞复杂逻辑(如按键消抖、协议解析)
本文的非阻塞结构体封装开始时间和时长,通过时间差检查超时非阻塞多任务并行、周期性操作

四、关键差异详解

  1. HAL_Delay()

    • CPU空转:调用后卡在原地,无法处理其他逻辑。

    • 中断依赖:若中断被禁用,计时会失效。

    • 简单粗暴:适合初始化、单任务等简单场景。

  2. SysTick寄存器轮询

    • 更高精度:直接读寄存器,避免中断延迟。

    • 需处理溢出:需自行处理32位计数器的溢出问题

    • (如用 (current - start) < 0x7FFFFFFF)。

  3. 状态机定时检查

    • 逻辑解耦:将时间条件嵌入状态迁移,适合复杂流程。

    • 资源复用:多个状态可共享计时逻辑,节省内存。

  4. 非阻塞结构体

    • 模块化:结构体封装多个独立计时器,方便管理多任务。

    • 即插即用:通过 Delay_StartDelay_IsTimeout 快速实现周期任务。

五、实战场景对比

1. 控制LED闪烁
  • 阻塞写法(HAL_Delay)

    while(1) {
        HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
        HAL_Delay(500); // CPU卡在此处,无法做其他事 
    }
  • 非阻塞写法(结构体)

    NonBlockingDelay led_delay;
    Delay_Start(&led_delay, 500);
    ​
    while(1) {
        if (Delay_IsTimeout(&led_delay)) {
            HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
            Delay_Start(&led_delay, 500); // 重置计时 
        }
        // 此处可插入其他任务(如按键扫描、通信)
    }
2. 状态机中的超时处理
typedef enum { STATE_IDLE, STATE_WAIT } State;
State current_state = STATE_IDLE;
NonBlockingDelay timeout;
 
void state_machine() {
    switch(current_state) {
        case STATE_IDLE:
            if (触发条件) {
                Delay_Start(&timeout, 1000);
                current_state = STATE_WAIT;
            }
            break;
        case STATE_WAIT:
            if (事件达成) {
                current_state = STATE_IDLE;
            } else if (Delay_IsTimeout(&timeout)) {
                // 超时处理 
                current_state = STATE_IDLE;
            }
            break;
    }
}

六、如何选择?

  • 简单任务:用 HAL_Delay 快速实现。

  • 多任务/复杂逻辑:非阻塞结构体或状态机。

  • 高精度需求:SysTick寄存器轮询。

  • 资源受限:状态机+静态变量节省内存。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值