Arduino millis()在STM32中的应用

11

我正在尝试将一些Arduino库移植到stm32。在Arduino中,millis()函数返回从启动开始以毫秒为单位计算的时间。在stm32中是否有相应的函数?我正在使用stm32f0微控制器。


1
为什么不看一下 millis() 的实现(https://github.com/arduino/Arduino/blob/master/hardware/arduino/avr/cores/arduino/wiring.c),并尝试做类似的事情呢? - Ouss4
3
尝试阅读HAL驱动程序手册,它将帮助您节省时间。 - imbearr
在启动时设置一个计时器并读取它。 - old_timer
@dwelch,OP使用HAL“驱动程序”,并使用具有1毫秒时间对齐的定时器始终在其中实现。 - imbearr
设置定时器而不使用HAL会更容易,但每个人都有自己的选择... OP可以自行决定。 - old_timer
8个回答

18
您可以使用HAL_GetTick()函数:此函数获取当前SysTick计数器值(在SysTick中断中递增),用于处理超时的外设驱动程序。

17

SysTick是ARM核心提供的外设,旨在实现定时器功能。根据您的需求进行适应:

首先,对其进行初始化。

// Initialise SysTick to tick at 1ms by initialising it with SystemCoreClock (Hz)/1000

volatile uint32_t counter = 0;
SysTick_Config(SystemCoreClock / 1000);

提供一个中断处理程序。你的编译器可能需要用附加属性装饰中断处理程序。

SysTick_Handler(void) {
  counter++;
}

这是你的 millis() 函数,非常简单:

uint32_t millis() {
  return counter;
}

需要注意的一些限制条件。

  1. SysTick是一个24位计数器,当溢出时会重新计数。在比较值或实现延迟方法时请注意这一点。

  2. SysTick来源于处理器核心时钟。如果您更改了核心时钟(例如将其放慢以节省电源),则必须手动调整SysTick频率。


5
生成您1毫秒滴答的硬件计数器可能是24位,但几乎无关紧要。您的代码用于计算它们的变量是一个32位无符号整数,因此它将基于此而溢出,而不是基于更小的值。 - Chris Stratton
1
如果您正在使用由CubeMX生成的默认SysTick_Handler实现,则可以使用HAL_GetTick()。 - Guillaume Legrain

3
我建议使用定时器来实现。这样,您也可以获得1微秒的步长,只需控制您的时间步长大小即可。无论如何,大多数STM32 MCU都具有8个或更多的定时器,因此在大多数情况下,您可以自由选择一个。
我将展示非常简单的基本想法如何做到这一点。
只需创建计时器:
uint32_t time = 0;

void enable_timer(void){
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM16, ENABLE);
    TIM_TimeBaseInitTypeDef timerInitStructure;

    /* if MCU frequency 48 MHz, prescaler of value 48 will make 1us counter steps
    timerInitStructure.TIM_Prescaler = 48;

    timerInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    /*how often you'll get irq*/
    timerInitStructure.TIM_Period = 0xFFFF; // will get irq each 65535us on TIMx->CNT overflow
    timerInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInit(TIM16, &timerInitStructure);
    TIM_Cmd(TIM16, ENABLE);
    NVIC_InitTypeDef NVIC_InitStruct;
    NVIC_InitStruct.NVIC_IRQChannel = TIM16_IRQn;
    /*for more accuracy irq priority should be higher, but now its default*/
    NVIC_InitStruct.NVIC_IRQChannelPriority = 0;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    TIM_ClearITPendingBit(TIM16,TIM_IT_Update);
    NVIC_Init(&NVIC_InitStruct);
    TIM_ITConfig(TIM16,TIM_IT_Update,ENABLE);
}

在IRQ中,你需要控制计时器计数器的溢出并更新全局时间。

void TIM16_IRQHandler(void){
if (TIM_GetITStatus(TIM16, TIM_IT_Update) != RESET){
    TIM_ClearITPendingBit(TIM16, TIM_IT_Update);
    time +=65535;
}

实时将是:

}

uint32_t real_time_us = time + (uint32_t)TIM16->CNT;

但是如果你可以使用32位定时器,甚至可以不需要IRQ就完成它,只需简单地将time = TIMx->CNT。是的,它取决于定时器配置和你的需求,同时你也需要知道uint32_t时间变量也可能会溢出,但这只是一个细节,可以轻松管理。


我认为实时时间,real_time_us = time + (uint32_t)TIM16->CNT,不能这样使用。如果该语句在中间被定时器中断打断,那么time和CNT值都会发生改变。 - eos1d3

2
我建议您设置自己的定时器。
要创建定时器,您必须在CubeMX或手动配置预分频器和周期。
时钟配置 因为我的时钟是72MHz,这意味着我需要一个预分频器为 72MHz / 1MHz - 1 = 71。 因此,我的周期将是 1MHz / 1kHz - 1 = 999,因为我希望中断每1毫秒触发一次。
预分频计算 以下是在CubeMX中的设置方式。
定时器配置 然后您需要在定时器回调函数中添加计数器递增。
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  /* USER CODE BEGIN Callback 0 */
  if (htim->Instance == TIM10) {
    counter++;
  }
  /* USER CODE END Callback 0 */
  if (htim->Instance == TIM1) {
    HAL_IncTick();
  }
  /* USER CODE BEGIN Callback 1 */

  /* USER CODE END Callback 1 */
}

1

HAL库使用定时器(您可以在STM32CubeMX中选择)生成Tick计数器。每个Tick需要1ms。您可以按照以下方式使用它来测量经过的时间:

   uint32_t startTime = HAL_GetTick();
                .
                .
                .
   uint32_t endTime = HAL_GetTick();
   uint32_t elapsedTime = endTime-startTime; // in ms 

1
您需要先初始化SysTick,使用SystemCoreClock(Hz)/ 1000进行初始化,以1毫秒为间隔进行滴答。

0
在某些情况下,您可能无法使用SysTick来实现此目的。例如,在使用已经实现SysTick的FreeRTOS时。这种情况下,在一个任务中运行您的代码,然后就可以使用vTaskDelay了。根据您想要做什么,软件定时器也可以发挥作用:http://www.freertos.org/FreeRTOS-Software-Timer-API-Functions.html 如果您不能使用SysTick,并且没有FreeRTOS或类似的东西,那么您可以设置具有中断的计时器,并将代码放置在适当的回调函数中。

0

嗯,我认为我需要更多的信息才能给出一个合适的答案,但我会尽力。

如果您正在进行高分辨率测量(需要精确时间),那么您需要为准确性提供外部晶体,因为MCU的内部时钟不太准确。考虑到这一点,您应该这样做:

  1. 配置一个TIM以便每1毫秒执行中断
  2. 启用TIM中断
  3. 在TIM中断中,增加一个计数器(这将是您的时间),该计数器被定义为该模块的全局变量。

void interrupt TIMX_interrupt(void){ counter++; // 这将记录时间计数(毫秒)。 }

  1. 创建一个名为millis()的函数,返回计数器的值,如下所示:

uint32_t millis(void){ return counter; }

通过这种方式,您可以在此MCU中使用相同的函数。


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