Skip to content

HardwareTimer: support 32 bit timers #2071

Open
@GianfrancoIU1JSU

Description

@GianfrancoIU1JSU

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: o v e r f l o w 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.

void HardwareTimer::setOverflow(uint32_t overflow, TimerFormat_t format)
{
uint32_t ARR_RegisterValue;
uint32_t PeriodTicks;
uint32_t Prescalerfactor;
uint32_t period_cyc;
// Remark: Hardware register correspond to period count-1. Example ARR register value 9 means period of 10 timer cycle
switch (format) {
case MICROSEC_FORMAT:
period_cyc = overflow * (getTimerClkFreq() / 1000000);
Prescalerfactor = (period_cyc / 0x10000) + 1;
LL_TIM_SetPrescaler(_timerObj.handle.Instance, Prescalerfactor - 1);
PeriodTicks = period_cyc / Prescalerfactor;
break;
case HERTZ_FORMAT:
period_cyc = getTimerClkFreq() / overflow;
Prescalerfactor = (period_cyc / 0x10000) + 1;
LL_TIM_SetPrescaler(_timerObj.handle.Instance, Prescalerfactor - 1);
PeriodTicks = period_cyc / Prescalerfactor;
break;
case TICK_FORMAT:
default :
PeriodTicks = overflow;
break;
}
if (PeriodTicks > 0) {
// The register specifies the maximum value, so the period is really one tick longer
ARR_RegisterValue = PeriodTicks - 1;
} else {
// But do not underflow in case a zero period was given somehow.
ARR_RegisterValue = 0;
}
__HAL_TIM_SET_AUTORELOAD(&_timerObj.handle, ARR_RegisterValue);
updateRegistersIfNotRunning(_timerObj.handle.Instance);
}

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 μ C o n t r o l l e r

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

Activity

fpistm

fpistm commented on Jul 15, 2023

@fpistm
Member

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

added 3 commits that reference this issue on Jul 16, 2023
4c5ac62
8099328

add fix stm32duino#2071 modification also to getter

be7e127
added this to the 2.7.0 milestone on Jul 17, 2023
changed the title [-]Setting 32 bit timers overflow value to maximum is not possible[/-] [+]HardwareTimer: support 32 bit timers[/+] on Jul 21, 2023
added
enhancementNew feature or request
and removed
bug 🐛Something isn't working
on Jul 21, 2023
fpistm

fpistm commented on Jul 21, 2023

@fpistm
Member

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

#define MAX_RELOAD ((1 << 16) - 1) // Currently even 32b timers are used as 16b to have generic behavior

removed this from the 2.7.0 milestone on Nov 15, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Participants

      @fpistm@GianfrancoIU1JSU

      Issue actions

        HardwareTimer: support 32 bit timers · Issue #2071 · stm32duino/Arduino_Core_STM32