11

I am trying to port some Arduino library to stm32. In Arduino, millis() returns the number of milliseconds since boot. Is there an equivalent function in stm32? I am using stm32f0 MCU.

  • 1
    Why don't you take a look at the `millis()` implementation (https://github.com/arduino/Arduino/blob/master/hardware/arduino/avr/cores/arduino/wiring.c) and try to do something similair. – Ouss4 May 22 '16 at 18:04
  • 3
    Try to read [HAL driver manual](http://www.st.com/content/ccc/resource/technical/document/user_manual/2f/77/25/0f/5c/38/48/80/DM00122015.pdf/files/DM00122015.pdf/jcr:content/translations/en.DM00122015.pdf). It will help you to save your time. – imbearr May 23 '16 at 10:01
  • setup a timer on boot and read it. – old_timer May 23 '16 at 13:23
  • @dwelch, OP use HAL "driver" and such timer with time aligment to 1ms always implemented in it. – imbearr May 23 '16 at 14:51
  • significantly easier to just setup the timer and not use the hal, but to each his/her own...the OP has this choice. – old_timer May 23 '16 at 15:02

8 Answers8

18

You could use HAL_GetTick(): this function gets current SysTick counter value (incremented in SysTick interrupt) used by peripherals drivers to handle timeouts.

nbout
  • 1,159
  • 8
  • 12
17

SysTick is an ARM core peripheral provided for this purpose. Adapt this to your needs:

Firstly, initialise it

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

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

Provide an interrupt handler. Your compiler may need interrupt handlers to be decorated with additional attributes.

SysTick_Handler(void) {
  counter++;
}

Here's your millis() function, couldn't be simpler:

uint32_t millis() {
  return counter;
}

Some caveats to be aware of.

  1. SysTick is a 24 bit counter. It will wrap on overflow. Be aware of that when you're comparing values or implementing a delay method.

  2. SysTick is derived from the processor core clock. If you mess with the core clock, for example slowing it down to save power then the SysTick frequency must be manually adjusted as well.

Andy Brown
  • 11,766
  • 2
  • 42
  • 61
  • 5
    The hardware counter that generates your 1ms ticks might be 24 bits, but that scarcely matters. The variable your code is using to count them is a 32-bit unsigned integer, so it will overflow based on that, not something smaller. – Chris Stratton Jun 04 '16 at 08:17
  • 1
    if you are using the default SysTick_Handler implementation generated by CubeMX, you can use HAL_GetTick() . – Guillaume Legrain Sep 10 '18 at 20:23
3

I would suggest to do it with timer. In this way you also can get 1us step, just control your time step size. Anyway most of STM32 MCU's has 8 or more timers, so in most cases you are free to take one. I'll show very simple basic idea how to do it.

Just create timer:

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);
}

at IRQ you have to control overflows of timer counter and update your global time

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

}

And real time will be :

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

But if you are free to use 32 bit timer you can even do it without IRQ, just simply time= TIMx->CNT. Yea it depends on timer configuration and your needs, also you have to know that uint32_t time variable also can overflow, but that's a details, it can be managed easily.

David.O
  • 104
  • 5
  • I think the real time, real_time_us = time + (uint32_t)TIM16->CNT, cannot be used like that. If the statement is interrupted in the middle by the timer interrupt, the both time and CNT values are changed. – eos1d3 Jul 01 '22 at 16:52
2

I would suggest setting up your own timer.

To make a timer you must configure a prescalar and period in CubeMX or manually.
Clock Configuration

Because my clock is 72MHz this means I want a prescalar of 72MHz / 1MHz - 1 = 71 Consequently my period will be 1MHz / 1kHz - 1 = 999 because I want the interrupt to trigger once every 1 millisecond.
Prescalar Calculation

Here is how that looks in CubeMX.
Timer Configuration

Then you want to add a counter increment in the timer callback function.

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 */
}
studentbrad
  • 113
  • 7
1

You need to initialise SysTick first, to tick at 1ms by initialising it with SystemCoreClock (Hz)/1000.

Danijel
  • 11
  • 1
1

HAL library use a Timer(you can select it in the STM32CubeMX) to generate a Tick counter. every Tick takes 1ms. you can use it as follow to measure elapsed time:

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

In some situations you may not use SysTick for this purpose. For example when using FreeRTOS, which already implements SysTick. In this case, run your code in a task, and you can use vTaskDelay. Depending on what you would like to do, a software timer can also make the trick: http://www.freertos.org/FreeRTOS-Software-Timer-API-Functions.html

In case you cannot use SysTick, and you don't have FreeRTOS or similar, you can setup a Timer with an interrupt and place your code in the appropriate callback.

Tom
  • 834
  • 1
  • 14
  • 24
0

Well, I think I need more information for a proper answer, but I will give you my best shot.

A consideration you need to have for your application, if you are making a high resolution measurements (need exact time), you will need to provide an external crystal for the accuracy since the internal clocks of the MCU are not quite accurate.

Having that in mind, what you shall do is the following:

  1. Configure a TIM so it can execute an interruption every 1ms
  2. Enable the TIM interrupt,
  3. In the TIM interrupt, increase a counter ( this will be your time) that is defined as a global variable for this module.

void interrupt TIMX_interrupt(void){ counter++; // this will have the time count in MS. }

  1. Create a Function named millis() that return the counter something like:

uint32_t millis(void){ return counter; }

In this way you could use the same function in this MCU.

adrianzx
  • 21
  • 2