FreeRTOS:osDelay与HAL_delay的区别

13

在使用STM32CubeMx创建FreeRTOS应用项目时,有两种方法可以引入延迟,即osDelayHAL_Delay

它们之间有什么区别,哪一个应该优先使用呢?

osDelay 代码:

/*********************** Generic Wait Functions *******************************/
/**
* @brief   Wait for Timeout (Time Delay)
* @param   millisec      time delay value
* @retval  status code that indicates the execution status of the function.
*/
osStatus osDelay (uint32_t millisec)
{
#if INCLUDE_vTaskDelay
  TickType_t ticks = millisec / portTICK_PERIOD_MS;

  vTaskDelay(ticks ? ticks : 1);          /* Minimum delay = 1 tick */

  return osOK;
#else
  (void) millisec;

  return osErrorResource;
#endif
}

HAL_Delay 代码:

/**
* @brief This function provides accurate delay (in milliseconds) based 
*        on variable incremented.
* @note In the default implementation , SysTick timer is the source of time base.
*       It is used to generate interrupts at regular time intervals where uwTick
*       is incremented.
* @note ThiS function is declared as __weak to be overwritten in case of other
*       implementations in user file.
* @param Delay: specifies the delay time length, in milliseconds.
* @retval None
*/
__weak void HAL_Delay(__IO uint32_t Delay)
{
  uint32_t tickstart = 0;
  tickstart = HAL_GetTick();
  while((HAL_GetTick() - tickstart) < Delay)
  {
  }
}

1
很明显,从给定的代码中(甚至不知道任何关于RTOS的信息),第一个依赖于“vTaskDelay”,第二个依赖于轮询。因此,您应该基本上查看“vTaskDelay”的实现。 - barak manos
5个回答

13

HAL_Delay不是FreeRTOS的函数,而_osDelay则是基于FreeRTOS函数构建的函数。它们分别由不同的开发者为不同的目的而创建,二者完全不同。

osDelay是CMSIS库的一部分,内部使用vTaskDelay()引入延迟,其中osDelay的输入参数是延迟时间(毫秒),而_vTaskDelay()的输入参数是要延迟的滴答数。使用此函数,操作系统将被通知有关延迟,并且操作系统将在那段特定的时间内更改任务的状态为blocked

HAL_Delay是我们处理器的硬件抽象层的一部分,基本上使用轮询来引入延迟。使用此函数,操作系统不会收到有关延迟的通知。而且,如果您不使用操作系统,则HAL_Delay是HAL库提供的默认和唯一的阻塞延迟。(acc. @Clifford: ) 这是HAL库的一部分,可以在没有FreeRTOS(或当FreeRTOS未运行时)使用。

要使用FreeRTOS函数引入延迟,可以在调度程序启动后使用vTaskDelay()vTaskDelayUntil()

(acc. @Clifford: )
如果您希望应用程序具有确定性,请始终使用FreeRTOS API函数。
CubeMX是来自多个来源的部分集合。


1
需要注意的是,从版本2开始,CMSIS的osDelay有一个参数ticks而不是millisec,因此将该值逐字传递给vTaskDelay。虽然“1个tick = 1毫秒”的配置是常见默认值,但不一定是这种情况,因此不能在未验证时交换osDelayHAL_Delay调用,以确保节拍率为1000 Hz。 - LWChris

4
看起来 HAL_Delay() 不适用于 RTOS,因为它是一个空循环延迟。如果您从 RTOS 任务调用 HAL_Delay(),则任务将继续运行,直到延迟已过期。较高优先级的任务将能够运行,但在延迟期间,低优先级的任务将被剥夺任何处理时间。这是浪费处理时间、功率,并可能对系统响应性产生不利影响。
另一方面,osDelay() 则使用 RTOS 进行延迟。它告诉 RTOS 在延迟期间没有任何事情要做,因此在该期间不会分配任何处理时间给任务。这样可以节省处理时间,潜在地节省电力,并允许低优先级任务在延迟期间获得处理时间。参见:http://www.freertos.org/FAQWhat.html#WhyUseRTOS

3
有一个最高优先级的任务。如果您使用 HAL_Delay 来阻塞该任务,则可能不会进行上下文切换,因为调度程序不会收到通知,该任务当前仅在 while 循环中轮询计数器,并且实际上并未执行任何有用的操作。优先级较低的任务将不会运行。
另一个函数使用操作系统的 vTaskDelay 函数,我没有查看其源代码,但是可能会通知操作系统当前任务希望被阻塞一段时间,因此任务的状态将变为已阻塞,而调度程序可以在此期间切换到较低优先级的任务。

嗯,这很有道理。这就是为什么有两个,所以你可以根据你的任务要求使用任何一个。 - ARK4579
1
@ARK4579 如果您不使用操作系统,则 HAL_Delay 是 HAL 库提供的默认和唯一可用的阻塞延迟函数。 - Bence Kaulics
2
@ARK4579:不,有两种方法是因为它们是由完全不同的提供者独立开发的,用于完全不同的目的。HAL可以在没有RTOS(或在RTOS运行之前)的情况下使用,并且是一个较低级别的服务-如果您希望应用程序具有确定性调度,请始终优先考虑RTOS API。CubeMX是来自多个来源的部件集合。 - Clifford

0

HAL_Delay在stm32_HAL库中被广泛使用,有时甚至在ISR中调用。除了命名暗示它是硬件抽象层之外,使用HAL_Delay(HAL_GetTick)的定时器需要具有最高的NVIC优先级。(因为它可能在ISR内部被调用且不能被阻塞)从实现角度来看,这是好还是坏,在网络上有一些讨论。然而,这是ST的方式,您可以选择是否使用STM32_HAL。

osDelay在CMSIS层中使用vTaskDelay实现。它使用systick函数作为计时器。FreeRTOS也使用systick进行任务上下文切换。根据FreeRTOS文档,systick的NVIC优先级需要最低。(这样它就不会进入ISR的中间位置)。

哪个函数更受欢迎取决于您正在做什么,一个具有最高优先级,另一个具有最低优先级(根据ST和FreeRTOS的建议)。这就是如果您选择使用FreeRTOS,STM32CubeMX将要求您分配一个硬件定时器作为“tick”的原因,除了systick之外。


0
答案很简单, 如果你的项目是裸机(没有操作系统),你应该(或可以)使用HAL_Delay。
“弱”符号实现使用类似下面的代码。 如果需要,你可以声明自己的函数。
__weak void HAL_Delay(uint32_t Delay)
{
  uint32_t tickstart = HAL_GetTick();
  uint32_t wait = Delay;

  /* Add a period to guaranty minimum wait */
  if (wait < HAL_MAX_DELAY)
  {
    wait += (uint32_t)(uwTickFreq);
  }

  while((HAL_GetTick() - tickstart) < wait)
  {
  }
}

但是,如果您的项目有一个操作系统(比如FreeRTOS或Keil-RTX)或其他任何操作系统,则应该使用osDelay。这是因为正如@ARK4579所解释的那样,如果您使用上述函数定义的hal_delay,则上述函数是一个阻塞调用,这意味着它只是消耗周期。使用osDelay,调用者任务将进入阻塞状态,当滴答完成时,任务将再次处于就绪状态。因此,在这里您不会消耗任何周期。这是一个非阻塞调用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接