cancel
Showing results for 
Search instead for 
Did you mean: 

Is this a legit reason to use a global variable?

magene
Senior II

I understand the general concept of why global variables are not a great idea but I have a specific use that may make sense and I'd like to see if anyone has a better approach.

I time stamp a lot of events in my applications and also calculate time intervals to make sure activities complete in a defined amount of time. So, I need a quick way to get a timestamp that is accurate to something less than 0.1 seconds. Here's what I'm doing now in my main.cpp:

//sysTickCounter is supposed to be a quick way to get time differences for timeout purposes
//TODO need to check to see if it's accurate
 
uint64_t sysTickCounter = 0;
extern "C" void SysTick_Handler(void)
{
	HAL_IncTick();
	sysTickCounter++;
}

and I can declare sysTickCounter as an extern wherever I need it like this:

extern uint64_t sysTickCounter;

This seems to work so far but I'd be very interested if someone has a better way.

Thanks

1 ACCEPTED SOLUTION

Accepted Solutions

As it's incremented in an interrupt others should see it as volatile

Unfortunately the 64-bit read might not be atomic, you might want to have a singular read routine in a subroutine, where you read it twice, returning when the values are unchanged.

For high resolution one might consider using a 32-bit TIM, or DWT CYCCNT, the later clocking at core speed.

If you're within the roll-over time the 32-bit unsigned computation (current - last) will work.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..

View solution in original post

8 REPLIES 8

As it's incremented in an interrupt others should see it as volatile

Unfortunately the 64-bit read might not be atomic, you might want to have a singular read routine in a subroutine, where you read it twice, returning when the values are unchanged.

For high resolution one might consider using a 32-bit TIM, or DWT CYCCNT, the later clocking at core speed.

If you're within the roll-over time the 32-bit unsigned computation (current - last) will work.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
S.Ma
Principal

If you only use the global var without concatenating with hw systick counter (which is downcounting), if the main only reads a 32 bit or 1 cycle read value from global, volatile may become optional, while your code may become less portable. Cyccnt is missing on cortex M0+ if I recall well. If you need below msec resolution, you can use new timers with overflow flag on bit 31 of 16 bit timer counters, or read the value twice at more than one timer tick and check the value didn t corrupt by overflow.

magene
Senior II

Thanks for the quick response. Since no one yelled at me for using a global I'm going to assume using sysTickCounter this way doesn't violate any immutable law of nature.

Normal microcontroller programmers do use globals.

Programming rules as perpetuated by influential types are mostly based on personal beliefs and anecdotal evidence, rather than solid data-based research ((C) Derek Jones), see e.g. the MISRA misery.

There's nothing fundamentally wrong with global accessibility of any variable; the problem lies in entangled software (and that, contrary to popular management-driven belief, *is* fundamental property of any nontrivial software). As long as the global variable has a clean meaning and there's a clean policy how to access it, it's perfectly OK.

JW

PS. @Community member​ , couldn't be made this atomic by writing the ISR in asm, using double load/store; and similarly providing a function (possibly static inline) or macro, again using double load, to read sysTickCounter in the application?

> Since no one yelled at me for using a global

You can get away with the global this time 😉 Worser things are happening to the world and the laws.

But read with attention what @Community member​ wrote.

In your first post you said "I time stamp a lot of events in my applications and also calculate time intervals to make sure activities complete in a defined amount of time."

These two usages are different.

For timestamps with resolution 0.1s (or make it 0.05s), 32-bit gives ~ 2500 days. Is this enough? If yes, a free running 32-bit timer is ideal.

Also note that there's a RTC timer with calendar for fancy timestamps. Especially useful to keep time when your program crashes and restarts.

--pa

@Pavel A.​ I'm "taking advantage" of the existing SysTick_Handler which has a 0.001 second resolution according to my tests. That's more resolution than I need but it is already part of the infrastructure so I didn't have to do much to use it. So a uint32_t would only have about 50 days of duration. I could make that work with a little effort but I just opted for the uint64_t. Now that @Community member​ pointed out there may be some issues with unit64_t and you've got me thinking about what I'm really trying to do, maybe using SysTick isn't the right approach. I'll look into a free running timer when I get a chance.

Thanks

Pavel A.
Evangelist III

Free running 32-bit HAL timer on STM32H7: something like this:

//~~~~~~~~~~~~~~~~~ TICKLESS HAL TIMER using TIM2 ~~~~~~~~~~~~~~
 
static TIM_HandleTypeDef htim2;
 
extern uint32_t uwTickPrio; /* in hal.c */
extern HAL_TickFreqTypeDef uwTickFreq;
 
#define TIMER2_INPUT_CLK_hz   (200000000UL)  // TIM2 on APB1 @ 200Mhz = 2*HAL_RCC_GetPCLK1Freq()
 
 
static void MX_TIM2_Init(void)
{
    __HAL_RCC_TIM2_CLK_ENABLE();
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
 
  htim2.Instance = TIM2;
  htim2.Init.Prescaler =  TIMER2_INPUT_CLK_hz/1000 - 1;
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim2.Init.Period = 0xFFFFFFFFU;
  htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
     Error_Handler();
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
      Error_Handler();
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
      Error_Handler();
}
 
static inline uint32_t _get_hrtime32()
{
    return htim2.Instance->CNT;
}
 
uint32_t HAL_GetTick(void)
{
    return _get_hrtime32();
}
 
 
void HAL_Delay(uint32_t Delay)
{
  uint32_t tickstart = _get_hrtime32();
  uint32_t wait = Delay;
  if (wait == 0)
  {
      wait = 1;
  }
 
  while ((_get_hrtime32() - tickstart) < wait)
  {
      __NOP();
  }
}
 
 
HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
    // NOTE! this can be called twice, because of ST HAL quirks! 
    HAL_NVIC_DisableIRQ(TIM2_IRQn); // interrupt not used
    uwTickPrio = TickPriority;    
    uwTickFreq = HAL_TICK_FREQ_1KHZ; // we simulate 1KHz tick
    MX_TIM2_Init();
    HAL_TIM_Base_Start_IT(&htim2);
    __HAL_TIM_CLEAR_IT(&htim2, TIM_IT_UPDATE); 
    return HAL_OK;
}
 
void HAL_SuspendTick(void)
{
    __BKPT(0); // not implemented
}
 
void HAL_ResumeTick(void)
{
    __BKPT(0); // not implemented
}
 
// HAL time tick interrupt handler
// *** Overrides HAL provided HAL_IncTick()
void HAL_IncTick(void)
{
  __BKPT(0); // must not be called
}
 
 

magene
Senior II

Very nice. Thank you.