HAL_GetTick() return 是滴答还是毫秒? (以及如何以微秒为单位进行测量)
Does HAL_GetTick() return ticks or milliseconds? (and how to measure in microseconds)
我刚开始使用 HAL 函数。函数 HAL_GetTick()
的 The description 表示它 "provides a tick value in millisecond".
我不明白这个函数 returns 是滴答还是毫秒。当然,要将滴答转换为毫秒,我需要知道一毫秒有多少滴答,这是 CPU 具体的。
那么 HAL_GetTick()
到底是什么 return?
编辑:
我真正的问题是知道如何以微秒为单位测量时间。所以我想从 HAL_GetTick()
获取刻度并将它们转换为微秒。这在评论中得到解决,至少在一个答案中得到解决,所以我在这里也提到了这一点,并且我编辑了标题。
两者兼而有之。大多数情况下,增加 HAL 滴答计数器的函数会挂接到 SysTick 中断,该中断被配置为每 1 毫秒滴答一次。因此 HAL_GetTick()
将 return 自配置 SysTick 中断以来的毫秒数(基本上是自程序启动以来)。这也可以表示为 "the number of times the SysTick interrupt has 'ticked'".
HAL_GetTick()
should return 自启动以来经过的毫秒数,因为许多 HAL 函数都依赖于它。你如何实现它取决于你。默认情况下,HAL_Init()
查询系统时钟速度,并将 SysTick 频率设置为该频率的 1/1000:
__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
/*Configure the SysTick to have interrupt in 1ms time basis*/
HAL_SYSTICK_Config(SystemCoreClock /1000);
/*Configure the SysTick IRQ priority */
HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority ,0);
/* Return function status */
return HAL_OK;
}
然后默认的 SysTick 中断处理程序调用 HAL_IncTick()
以每毫秒递增一次内部计数器,并且 HAL_GetTick()
return 是该计数器的值。
所有这些函数都定义为weak
,所以你可以覆盖它们,只要你的版本HAL_GetTick()
returns 以毫秒为单位的经过时间,它会好的。你可以例如将 HAL_InitTick()
替换为让 SysTick 运行 在 10 kHz,但是您应该确保 HAL_IncTick()
仅在每 10 个中断时被调用。在 216 MHz STM32F7 控制器(或刚刚发布的 400MHz STM32H743)上,您实际上可以降低到 1 MHz Systick,但是您应该 非常 小心 return 尽快尽可能从处理程序。除非您在处理程序中执行硬件计数器无法执行的操作,否则这仍然是对宝贵处理器周期的可怕浪费。
或者您可以完全不配置 SysTick(用空函数覆盖 HAL_InitTick()
),但设置一个 32 位硬件定时器,它有足够的预分频器来计算每一微秒,然后让 HAL_GetTick()
return 定时器计数器。
回到你真正的问题,以微秒为单位测量时间,有更好的方法。
如果你有可用的 32 位定时器,那么你可以将相应 APB 时钟的 MHz 值放入预分频器,启动它,并且有你的微秒时钟,而不是从你的应用程序中占用处理时间全部。此代码应在 STM32L151/152/162STM32F4:
上启用它(未测试)
__HAL_RCC_TIM5_CLK_ENABLE();
TIM5->PSC = HAL_RCC_GetPCLK1Freq()/1000000 - 1;
TIM5->CR1 = TIM_CR1_EN;
然后通过阅读TIM5->CNT
随时获取它的值。
查看您的参考手册,哪些硬件计时器具有 32 位计数器,以及它从哪里获得时钟。它在整个 STM32 系列中变化很大,但应该出现在 F4 上。
如果您不能使用 32 位定时器,那么可以使用核心循环计数器。只需使用
启用一次
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CYCCNT = 0;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
然后从DWT->CYCCNT
读取值。请注意,由于它 return 是经过的处理器周期,它会在几秒钟内溢出。
编辑:
我刚刚注意到您使用的是 STM32L0。所以,忘掉 32 位定时器和 200+ MHz 内核吧。使用DWT->CYCCNT
,或者非常仔细地考虑你想要测量的间隔有多长,以及精度如何,然后使用一个 16 位定时器。您可以 post 它作为一个单独的问题,更详细地描述您的硬件的外观以及它应该做什么。可能有一种方法可以通过您想要计时的事件直接触发计数器 start/stop。。
y我需要一个精度为 1uS 的时间戳,并且如上所述使用 TIM5 有效,但需要进行一些调整。这是我的想法。
/* Initialization */
__HAL_RCC_TIM5_CLK_ENABLE();
TIM5->PSC = HAL_RCC_GetPCLK1Freq() / 500000;
TIM5->CR1 = TIM_CR1_CEN;
TIM5->CNT = -10;
/* Reading the time */
uint32_t microseconds = TIM5->CNT << 1;
我没有完全探究为什么我必须做我所做的事情。但我很快意识到两件事。 (1) 预分频缩放不起作用,尽管它看起来是正确的。这是我试图让它工作的几件事之一(基本上是半个时钟,并将结果除以 2)。 (2) 时钟已经运行,一开始给出了奇怪的结果。我尝试了几个不成功的方法来停止、重新编程和重新启动它,将计数设置为 -10 是一种粗略但有效的方法,只是让它完成当前周期,然后根据需要快速开始工作。当然有更好的方法来实现这一点。但总的来说,这是一种以非常低的开销获得准确事件时间戳的简单方法。
虽然问题已经回答了,但我认为看看HAL 如何使用HAL_GetTick()
来计算毫秒数会有所帮助。这个可以看HAL的函数HAL_Delay(uint32_t Delay)
.
从 stm32l0xx_hal.c
实施 HAL_Delay(uint32_t Delay)
:
/**
* @brief This function provides minimum 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(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++;
}
while((HAL_GetTick() - tickstart) < wait)
{
}
}
查看我的调试器时,我可以看到我有可用的 uwTick
全局变量,它似乎与针对我自己定义的全局变量调用 HAL_GetTick()
的结果相同。
根据文档:
void HAL_IncTick (void )
This function is called to increment a global variable "uwTick" used
as application time base.
Note:
In the default implementation, this variable is incremented each 1ms
in Systick ISR.
This function is declared as __weak to be
overwritten in case of other implementations in user file.
我遇到了同样的问题,但后来在 PlatformIO 中找到了一个库函数,returns 微秒。
uint32_t getCurrentMicros(void)
{
/* Ensure COUNTFLAG is reset by reading SysTick control and status register */
LL_SYSTICK_IsActiveCounterFlag();
uint32_t m = HAL_GetTick();
const uint32_t tms = SysTick->LOAD + 1;
__IO uint32_t u = tms - SysTick->VAL;
if (LL_SYSTICK_IsActiveCounterFlag()) {
m = HAL_GetTick();
u = tms - SysTick->VAL;
}
return (m * 1000 + (u * 1000) / tms);
}
位于~/.platformio/packages/framework-arduinoststm32/libraries/SrcWrapper/src/stm32/clock.c
不过STM32CubeIde好像没有,所以就从PlatformIO上复制过来了。我还必须复制 LL_SYSTICK_IsActiveCounterFlag() 函数:
static inline uint32_t LL_SYSTICK_IsActiveCounterFlag(void)
{
return ((SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) == (SysTick_CTRL_COUNTFLAG_Msk));
}
我刚开始使用 HAL 函数。函数 HAL_GetTick()
的 The description 表示它 "provides a tick value in millisecond".
我不明白这个函数 returns 是滴答还是毫秒。当然,要将滴答转换为毫秒,我需要知道一毫秒有多少滴答,这是 CPU 具体的。
那么 HAL_GetTick()
到底是什么 return?
编辑:
我真正的问题是知道如何以微秒为单位测量时间。所以我想从 HAL_GetTick()
获取刻度并将它们转换为微秒。这在评论中得到解决,至少在一个答案中得到解决,所以我在这里也提到了这一点,并且我编辑了标题。
两者兼而有之。大多数情况下,增加 HAL 滴答计数器的函数会挂接到 SysTick 中断,该中断被配置为每 1 毫秒滴答一次。因此 HAL_GetTick()
将 return 自配置 SysTick 中断以来的毫秒数(基本上是自程序启动以来)。这也可以表示为 "the number of times the SysTick interrupt has 'ticked'".
HAL_GetTick()
should return 自启动以来经过的毫秒数,因为许多 HAL 函数都依赖于它。你如何实现它取决于你。默认情况下,HAL_Init()
查询系统时钟速度,并将 SysTick 频率设置为该频率的 1/1000:
__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
/*Configure the SysTick to have interrupt in 1ms time basis*/
HAL_SYSTICK_Config(SystemCoreClock /1000);
/*Configure the SysTick IRQ priority */
HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority ,0);
/* Return function status */
return HAL_OK;
}
然后默认的 SysTick 中断处理程序调用 HAL_IncTick()
以每毫秒递增一次内部计数器,并且 HAL_GetTick()
return 是该计数器的值。
所有这些函数都定义为weak
,所以你可以覆盖它们,只要你的版本HAL_GetTick()
returns 以毫秒为单位的经过时间,它会好的。你可以例如将 HAL_InitTick()
替换为让 SysTick 运行 在 10 kHz,但是您应该确保 HAL_IncTick()
仅在每 10 个中断时被调用。在 216 MHz STM32F7 控制器(或刚刚发布的 400MHz STM32H743)上,您实际上可以降低到 1 MHz Systick,但是您应该 非常 小心 return 尽快尽可能从处理程序。除非您在处理程序中执行硬件计数器无法执行的操作,否则这仍然是对宝贵处理器周期的可怕浪费。
或者您可以完全不配置 SysTick(用空函数覆盖 HAL_InitTick()
),但设置一个 32 位硬件定时器,它有足够的预分频器来计算每一微秒,然后让 HAL_GetTick()
return 定时器计数器。
回到你真正的问题,以微秒为单位测量时间,有更好的方法。
如果你有可用的 32 位定时器,那么你可以将相应 APB 时钟的 MHz 值放入预分频器,启动它,并且有你的微秒时钟,而不是从你的应用程序中占用处理时间全部。此代码应在 STM32L151/152/162STM32F4:
__HAL_RCC_TIM5_CLK_ENABLE();
TIM5->PSC = HAL_RCC_GetPCLK1Freq()/1000000 - 1;
TIM5->CR1 = TIM_CR1_EN;
然后通过阅读TIM5->CNT
随时获取它的值。
查看您的参考手册,哪些硬件计时器具有 32 位计数器,以及它从哪里获得时钟。它在整个 STM32 系列中变化很大,但应该出现在 F4 上。
如果您不能使用 32 位定时器,那么可以使用核心循环计数器。只需使用
启用一次CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CYCCNT = 0;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
然后从DWT->CYCCNT
读取值。请注意,由于它 return 是经过的处理器周期,它会在几秒钟内溢出。
编辑:
我刚刚注意到您使用的是 STM32L0。所以,忘掉 32 位定时器和 200+ MHz 内核吧。使用DWT->CYCCNT
,或者非常仔细地考虑你想要测量的间隔有多长,以及精度如何,然后使用一个 16 位定时器。您可以 post 它作为一个单独的问题,更详细地描述您的硬件的外观以及它应该做什么。可能有一种方法可以通过您想要计时的事件直接触发计数器 start/stop。。
y我需要一个精度为 1uS 的时间戳,并且如上所述使用 TIM5 有效,但需要进行一些调整。这是我的想法。
/* Initialization */
__HAL_RCC_TIM5_CLK_ENABLE();
TIM5->PSC = HAL_RCC_GetPCLK1Freq() / 500000;
TIM5->CR1 = TIM_CR1_CEN;
TIM5->CNT = -10;
/* Reading the time */
uint32_t microseconds = TIM5->CNT << 1;
我没有完全探究为什么我必须做我所做的事情。但我很快意识到两件事。 (1) 预分频缩放不起作用,尽管它看起来是正确的。这是我试图让它工作的几件事之一(基本上是半个时钟,并将结果除以 2)。 (2) 时钟已经运行,一开始给出了奇怪的结果。我尝试了几个不成功的方法来停止、重新编程和重新启动它,将计数设置为 -10 是一种粗略但有效的方法,只是让它完成当前周期,然后根据需要快速开始工作。当然有更好的方法来实现这一点。但总的来说,这是一种以非常低的开销获得准确事件时间戳的简单方法。
虽然问题已经回答了,但我认为看看HAL 如何使用HAL_GetTick()
来计算毫秒数会有所帮助。这个可以看HAL的函数HAL_Delay(uint32_t Delay)
.
从 stm32l0xx_hal.c
实施 HAL_Delay(uint32_t Delay)
:
/**
* @brief This function provides minimum 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(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++;
}
while((HAL_GetTick() - tickstart) < wait)
{
}
}
查看我的调试器时,我可以看到我有可用的 uwTick
全局变量,它似乎与针对我自己定义的全局变量调用 HAL_GetTick()
的结果相同。
根据文档:
void HAL_IncTick (void )
This function is called to increment a global variable "uwTick" used as application time base.
Note:
In the default implementation, this variable is incremented each 1ms in Systick ISR.
This function is declared as __weak to be overwritten in case of other implementations in user file.
我遇到了同样的问题,但后来在 PlatformIO 中找到了一个库函数,returns 微秒。
uint32_t getCurrentMicros(void)
{
/* Ensure COUNTFLAG is reset by reading SysTick control and status register */
LL_SYSTICK_IsActiveCounterFlag();
uint32_t m = HAL_GetTick();
const uint32_t tms = SysTick->LOAD + 1;
__IO uint32_t u = tms - SysTick->VAL;
if (LL_SYSTICK_IsActiveCounterFlag()) {
m = HAL_GetTick();
u = tms - SysTick->VAL;
}
return (m * 1000 + (u * 1000) / tms);
}
位于~/.platformio/packages/framework-arduinoststm32/libraries/SrcWrapper/src/stm32/clock.c
不过STM32CubeIde好像没有,所以就从PlatformIO上复制过来了。我还必须复制 LL_SYSTICK_IsActiveCounterFlag() 函数:
static inline uint32_t LL_SYSTICK_IsActiveCounterFlag(void)
{
return ((SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) == (SysTick_CTRL_COUNTFLAG_Msk));
}