Arduino_Core_STM32 icon indicating copy to clipboard operation
Arduino_Core_STM32 copied to clipboard

HardwareTimer: support 32 bit timers

Open GianfrancoIU1JSU opened this issue 2 years ago • 2 comments

Describe the bug The function HardwareTimer::setOverflow() allow to set the overflow value as a uint32_t an then set the value of the STM32 ARR Register to: $overflow - 1$ since the function is meant to recieve as argument the overflow value and not the terminal count. has shown in the function implementation below.

https://github.com/stm32duino/Arduino_Core_STM32/blob/8ff274affbbce42f1d452e74365b66ba03e66c6c/libraries/SrcWrapper/src/HardwareTimer.cpp#L521-L557

this works fine with 16 bit counters and 32 bit counters when not used in the full range. but to set the max value of a 32 bit counter the ARR register value should be set to 0xFFFFFFFF to do so the function argument should be set to 0x100000000 but since the argument is defined as uint32_t the value will not fit and overflow as 0

I want to point out that setting the value to the maximum is an important thing, since not only will increase marginally the maximum counter value but more importantly allows to calculate values of thresholds and time elapsed between input capture event easly because elapsedTicks = capture - oldCapture if all variables are defined as uint32_t and the elapsedTicks guaranteed to be under 0x100000000 will work even if an overflow occurs. and in the same way when generating a square wave updating the ARR value with the next event value can be done as nextVal = lastVal + delta under the same conditions. this methods are common practice on timer usage on any $\mu Controller$

workaround In the actual state the problem can be solved by either

  • compensating for the missing value in the period computation
    if(capture < captureOld) elapsedTicks = capture - oldCapture - 1;
    else elapsedTicks = capture - oldCapture;
    
  • initializing the timer overflow by accessing directly to the STM32 HAL
    timerObj_t _timerObj;
    _timerObj.handle.Instance = TIM2;
    __HAL_TIM_SET_AUTORELOAD(&_timerObj.handle, 0xFFFFFFFF);
    

neither of these solution are efficient nor elegant.

Suggested Fixes to me the easiest way to fix this issue is changing the function definition from

void HardwareTimer::setOverflow(uint32_t overflow, TimerFormat_t format)

to

void HardwareTimer::setOverflow(uint64_t overflow, TimerFormat_t format)

by using this approach the fix should not cause any compatibility issues with existing code

Constatation here you can see the serial output of the STM32 when measuring a 1hz pulse over an 80Mhz timebase both generated by the same oscilltor so they are perfectly coherent. all the readings are perfect except for when the overflow occurs in that case you sistematically get one count more that is due to the missing code.

captureOld capture    difference
4059789198 4139789198 80000000
4139789198 4219789198 80000000
4219789198 4821903    80000001
4821903    84821903   80000000
84821903   164821903  80000000
164821903  244821903  80000000

after applying one of the above workaround the error never appears again

captureOld capture    difference
4106049847 4186049847 80000000
4186049847 4266049847 80000000
4266049847 51082551   80000000
51082551   131082551  80000000
131082551  211082551  80000000
211082551  291082551  80000000

here there is a the code on witch i've found the bug:

#include <Arduino.h>

HardwareTimer *FCT; //FCT Frequency Counter Timer
volatile uint32_t captureOld = 0, capture;
volatile uint32_t period;
volatile bool newMeas = false;

void IC_Callback(){
    capture = FCT->getCaptureCompare(LL_TIM_CHANNEL_CH1);
    Serial.print(captureOld);    //print inside an interrupt, bad practice but accettable for debugging
    Serial.print(" ");
    Serial.print(capture);
    Serial.print(" ");
    period = capture - captureOld;
    Serial.println(period);

    captureOld = capture;
    newMeas = true;
}

void setup()
{
    Serial.begin(115200);

    FCT = new HardwareTimer(TIM2);

    FCT->setMode(LL_TIM_CHANNEL_CH1,TIMER_INPUT_CAPTURE_RISING,PA_0);
    FCT->setPrescaleFactor(1);

    FCT->setOverflow(0xFFFFFFFF);//max value on 32 bit          // invert the comment on these line to passs 
    //timerObj_t _timerObj;                                     //from the bug to the workaround
    //_timerObj.handle.Instance = TIM2;                         //
    //__HAL_TIM_SET_AUTORELOAD(&_timerObj.handle, 0xFFFFFFFF);  //

    FCT->updateRegistersIfNotRunning(_timerObj.handle.Instance);
    FCT->attachInterrupt(LL_TIM_CHANNEL_CH1,IC_Callback);
    FCT->resume();

}


void loop()
{
    if (newMeas){
        //Serial.println(period);
        newMeas = false;
    }
}


// unrelated to the issue
// this was the clock configuration to use a 10Mhz extenal oscillator
// that was important to have the 1pps signal exactly coherent to the clock
// otherwise the problem can be hard to spot
// this may be replicable using a wave generated by another timer
extern "C" void SystemClock_Config(void){...}

Desktop :

  • OS: Windows 10
  • Platformio version: core 6.1.7 on clion
  • STM32 core version: 2.6.0
  • Upload method: Default

Board:

  • Name: Nucleo L476RG
  • Extra hardware used if any: squarewave generator

GianfrancoIU1JSU avatar Jul 15 '23 13:07 GianfrancoIU1JSU

Hi @GianfrancoIU1JSU Thanks for the detailed issue. You can provide a PR then we will review it.

fpistm avatar Jul 15 '23 17:07 fpistm

Hi @GianfrancoIU1JSU As stated in your PR, the implementation to support 32 bits timers required more works. I've changed the title to support 32 bits timers instance. The IS_TIM_32B_COUNTER_INSTANCE could be used to deal with.

Ex of limitation https://github.com/stm32duino/Arduino_Core_STM32/blob/987519a166179e3ada198364f79d388d05f75d3b/libraries/SrcWrapper/src/HardwareTimer.cpp#L33

fpistm avatar Jul 21 '23 15:07 fpistm