stm32duino / STM32Examples

Arduino library to provide several examples for the Arduino core for STM32 MCUs.
140 stars 54 forks source link

multiple instances of timer not working #46

Closed exothink closed 3 years ago

exothink commented 3 years ago

Using the freq/duty cycle example I duplicated all of the code and prefixed all of the var/function names in the copy to create two instances. The only difference besides the prefixed names is the pin names. At first the code froze in 'Setup'. I found certain timer/channel combinations simply froze. After eliminating the offending combinations I found that the results were all zeroes for freq and duty cycle. Then I commented out the second instance and I got the expected results. Then I uncommented that code and commented out the other instance and that one executed correctly!

Finally, I discovered a combination of pins, PB5 and PB6, that allow two instances to operate correctly. I'm using the stm32f103 blue pill boards. The goal is to measure the RPM of two separate shafts using the GP timers two through four. Single pin inputs.

ABOSTM commented 3 years ago

HI @exothink, You didn't specified which version of stm32duino core you are using ... whatever, can you check with the 2.0 release which just come out ? (there is much more capabilities to access pin alternate/remap functions in core 2.0) Warning for this new version, you need to add new json in Arduino IDE https://github.com/stm32duino/BoardManagerFiles/raw/master/package_stmicroelectronics_index.json (see https://github.com/stm32duino/wiki/wiki/Getting-Started)

Can you also specify which

Also can you share your sketch (a minimal one with only the 2 hardware timers), thanks

exothink commented 3 years ago

Hello Alexandre,

Attached is the code I’m using. It works using PB5/PB6. Additional info has been added in the comment section.

Regards,

/* Frequency and dutycycle measurement This example shows how to configure HardwareTimer to measure external signal frequency and dutycycle. The input pin will be connected to 2 channel of the timer, one for rising edge the other for falling edge. Each time a rising edge is detected on the input pin, hardware will save counter value into one of the CaptureCompare register. Each time a falling edge is detected on the input pin, hardware will save counter value into the other CaptureCompare register. External signal (signal generator for example) should be connected to D2.

*/

/* PLATFORM: ST STM32 (11.0.0) > STM32F103CB (20k RAM. 128k Flash) HARDWARE: STM32F103CBT6 72MHz, 20KB RAM, 128KB Flash DEBUG: Current (blackmagic) External (blackmagic, cmsis-dap, jlink, stlink) PACKAGES:

// #define mphPin PA8 // t1c1 old proto brd ---runs // #define rpmPin PB5 // t3c2

// #define mphPin PB6 // TIM4_CH1 --freezes // #define rpmPin PA6 // TIM3_CH2

// #define mphPin PB7 // t4c2 --freezes // #define rpmPin PA6 // t3c1

// #define mphPin PB7 // t4c2 --freezes // #define rpmPin PA0 // t2c1

define mphPin PB4 // t3c1 ---runs not working

define rpmPin PB5 // t2c2 working

// #define mphPin PA6 // t3c1

// #define mphPin PA0 // t2c1 ---runs // #define rpmPin PA6 // t3c1

// #define mphPin PA6 // t3c1 ---runs // #define rpmPin PB5 // t2c2

define mphpin PB5 //PB4 //wheel -- works

define pin PB6 //crank/rpm 538-3800 Hz -- both timers measuring

*/

include

if !defined(STM32_CORE_VERSION) || (STM32_CORE_VERSION < 0x01090000)

error "Due to API change, this sketch is compatible with STM32_CORE_VERSION >= 0x01090000"

endif

//=========================================

define mphpin PB5 //PB4 //wheel

define pin PB6 //crank/rpm 538-3800 Hz

//=========================================

uint32_t channelRising, channelFalling; uint32_t mphchannelRising, mphchannelFalling; volatile uint32_t FrequencyMeasured, DutycycleMeasured, LastPeriodCapture = 0, CurrentCapture, HighStateMeasured; volatile uint32_t mphFrequencyMeasured, mphDutycycleMeasured, mphLastPeriodCapture = 0, mphCurrentCapture, mphHighStateMeasured;

uint32_t input_freq = 0; uint32_t mphinput_freq = 0; volatile uint32_t rolloverCompareCount = 0; volatile uint32_t mphrolloverCompareCount = 0; HardwareTimer MyTim; HardwareTimer mphMyTim;

/** @brief Input capture interrupt callback : Compute frequency and dutycycle of input signal

/ void TIMINPUT_Capture_Rising_IT_callback(void) { CurrentCapture = MyTim->getCaptureCompare(channelRising); / frequency computation / if (CurrentCapture > LastPeriodCapture) { FrequencyMeasured = input_freq / (CurrentCapture - LastPeriodCapture); DutycycleMeasured = (HighStateMeasured 100) / (CurrentCapture - LastPeriodCapture); } else if (CurrentCapture <= LastPeriodCapture) { / 0x1000 is max overflow value / FrequencyMeasured = input_freq / (0x10000 + CurrentCapture - LastPeriodCapture); DutycycleMeasured = (HighStateMeasured * 100) / (0x10000 + CurrentCapture - LastPeriodCapture); }

LastPeriodCapture = CurrentCapture; rolloverCompareCount = 0; }

void mphTIMINPUT_Capture_Rising_IT_callback(void) { mphCurrentCapture = mphMyTim->getCaptureCompare(channelRising); / frequency computation / if (mphCurrentCapture > mphLastPeriodCapture) { mphFrequencyMeasured = mphinput_freq / (mphCurrentCapture - mphLastPeriodCapture); mphDutycycleMeasured = (mphHighStateMeasured 100) / (mphCurrentCapture - mphLastPeriodCapture); } else if (mphCurrentCapture <= mphLastPeriodCapture) { / 0x1000 is max overflow value / mphFrequencyMeasured = mphinput_freq / (0x10000 + mphCurrentCapture - mphLastPeriodCapture); mphDutycycleMeasured = (mphHighStateMeasured 100) / (0x10000 + mphCurrentCapture - mphLastPeriodCapture); }

mphLastPeriodCapture = mphCurrentCapture; mphrolloverCompareCount = 0; }

/ In case of timer rollover, frequency is to low to be measured set values to 0 To reduce minimum frequency, it is possible to increase prescaler. But this is at a cost of precision. / void Rollover_IT_callback(void) { rolloverCompareCount++;

if (rolloverCompareCount > 1) { FrequencyMeasured = 0; DutycycleMeasured = 0; } }

void mphRollover_IT_callback(void) { mphrolloverCompareCount++;

if (mphrolloverCompareCount > 1) { mphFrequencyMeasured = 0; mphDutycycleMeasured = 0; } }

/** @brief Input capture interrupt callback : Compute frequency and dutycycle of input signal

/ void TIMINPUT_Capture_Falling_IT_callback(void) { / prepare DutyCycle computation */ CurrentCapture = MyTim->getCaptureCompare(channelFalling);

if (CurrentCapture > LastPeriodCapture) { HighStateMeasured = CurrentCapture - LastPeriodCapture; } else if (CurrentCapture <= LastPeriodCapture) { / 0x1000 is max overflow value / HighStateMeasured = 0x10000 + CurrentCapture - LastPeriodCapture; } }

void mphTIMINPUT_Capture_Falling_IT_callback(void) { / prepare DutyCycle computation / mphCurrentCapture = mphMyTim->getCaptureCompare(channelFalling);

if (mphCurrentCapture > mphLastPeriodCapture) { mphHighStateMeasured = mphCurrentCapture - mphLastPeriodCapture; } else if (CurrentCapture <= LastPeriodCapture) { / 0x1000 is max overflow value / mphHighStateMeasured = 0x10000 + CurrentCapture - LastPeriodCapture; } } void setup() { Serial.begin(921600);

// Automatically retrieve TIM instance and channelRising associated to pin // This is used to be compatible with all STM32 series automatically. TIM_TypeDef Instance = (TIM_TypeDef )pinmap_peripheral(digitalPinToPinName(pin), PinMap_PWM); channelRising = STM_PIN_CHANNEL(pinmap_function(digitalPinToPinName(pin), PinMap_PWM));

// channelRisings come by pair for TIMER_INPUT_FREQ_DUTY_MEASUREMENT mode: // channelRising1 is associated to channelFalling and channelRising3 is associated with channelRising4 switch (channelRising) { case 1: channelFalling = 2; break; case 2: channelFalling = 1; break; case 3: channelFalling = 4; break; case 4: channelFalling = 3; break; }

// Instantiate HardwareTimer object. Thanks to 'new' instantiation, HardwareTimer is not destructed when setup() function is finished. MyTim = new HardwareTimer(Instance);

// Configure rising edge detection to measure frequency MyTim->setMode(channelRising, TIMER_INPUT_FREQ_DUTY_MEASUREMENT, pin);

// With a PrescalerFactor = 1, the minimum frequency value to measure is : TIM counter clock / CCR MAX // = (SystemCoreClock) / 65535 // Example on Nucleo_L476RG with systemClock at 80MHz, the minimum frequency is around 1,2 khz // To reduce minimum frequency, it is possible to increase prescaler. But this is at a cost of precision. // The maximum frequency depends on processing of both interruptions and thus depend on board used // Example on Nucleo_L476RG with systemClock at 80MHz the interruptions processing is around 10 microseconds and thus Max frequency is around 100kHz uint32_t PrescalerFactor = 128; // good for 900-7200rpm MyTim->setPrescaleFactor(PrescalerFactor); MyTim->setOverflow(0x10000); // Max Period value to have the largest possible time to detect rising edge and avoid timer rollover MyTim->attachInterrupt(channelRising, TIMINPUT_Capture_Rising_IT_callback); MyTim->attachInterrupt(channelFalling, TIMINPUT_Capture_Falling_IT_callback); MyTim->attachInterrupt(Rollover_IT_callback);

MyTim->resume();

// Compute this scale factor only once input_freq = MyTim->getTimerClkFreq() / MyTim->getPrescaleFactor();

//=======================================================================================

// Automatically retrieve TIM instance and channelRising associated to pin // This is used to be compatible with all STM32 series automatically. TIM_TypeDef mphInstance = (TIM_TypeDef )pinmap_peripheral(digitalPinToPinName(mphpin), PinMap_PWM); mphchannelRising = STM_PIN_CHANNEL(pinmap_function(digitalPinToPinName(mphpin), PinMap_PWM));

// channelRisings come by pair for TIMER_INPUT_FREQ_DUTY_MEASUREMENT mode: // channelRising1 is associated to channelFalling and channelRising3 is associated with channelRising4 switch (mphchannelRising) { case 1: mphchannelFalling = 2; break; case 2: mphchannelFalling = 1; break; case 3: mphchannelFalling = 4; break; case 4: mphchannelFalling = 3; break; }

// Instantiate HardwareTimer object. Thanks to 'new' instantiation, HardwareTimer is not destructed when setup() function is finished. mphMyTim = new HardwareTimer(mphInstance);

// Configure rising edge detection to measure frequency mphMyTim->setMode(mphchannelRising, TIMER_INPUT_FREQ_DUTY_MEASUREMENT, mphpin);

// With a PrescalerFactor = 1, the minimum frequency value to measure is : TIM counter clock / CCR MAX // = (SystemCoreClock) / 65535 // Example on Nucleo_L476RG with systemClock at 80MHz, the minimum frequency is around 1,2 khz // To reduce minimum frequency, it is possible to increase prescaler. But this is at a cost of precision. // The maximum frequency depends on processing of both interruptions and thus depend on board used // Example on Nucleo_L476RG with systemClock at 80MHz the interruptions processing is around 10 microseconds and thus Max frequency is around 100kHz uint32_t mphPrescalerFactor = 256; // 225 good for 1 mph mphMyTim->setPrescaleFactor(mphPrescalerFactor); mphMyTim->setOverflow(0x10000); // Max Period value to have the largest possible time to detect rising edge and avoid timer rollover mphMyTim->attachInterrupt(mphchannelRising, mphTIMINPUT_Capture_Rising_IT_callback); mphMyTim->attachInterrupt(mphchannelFalling, mphTIMINPUT_Capture_Falling_IT_callback); mphMyTim->attachInterrupt(mphRollover_IT_callback);

mphMyTim->resume();

// Compute this scale factor only once mphinput_freq = mphMyTim->getTimerClkFreq() / mphMyTim->getPrescaleFactor(); Serial.println("042221"); }

void loop() { / Print frequency and dutycycle measured on Serial monitor every seconds / Serial.printf("r \t%d \t%d\n", FrequencyMeasured 900 / 30, mphFrequencyMeasured 64 / 183);
// Serial.println(" % = " + DutycycleMeasured);

// Serial.print((String)"mphFreq: " + mphFrequencyMeasured); // Serial.println((String)" % = " + mphDutycycleMeasured); delay(100); }

ABOSTM commented 3 years ago

Hi @exothink , I tested you sketch and here are my findings: 1) in mphTIMINPUT_Capture_Rising_IT_callback() there is use of channelRising, but it should bemphchannelRising The same for mphTIMINPUT_Capture_Falling_IT_callback() with channelFalling

2) I didn't check all combinations you provided, but I tested on my bluePill F103C8T6:

    #define mphpin  PB6
    #define pin     PA6

I did not reproduce you frozen behavior, but I suspect the problem comes from definition of PinMap_PWM[] in PeripheralPins.c :

          ...
      #ifndef ARDUINO_BLUEPILL_F103C6
        {PB_6,  TIM4,   STM_PIN_DATA_EXT(STM_MODE_AF_PP, GPIO_PULLUP, AFIO_NONE, 1, 0)}, // TIM4_CH1
        {PB_7,  TIM4,   STM_PIN_DATA_EXT(STM_MODE_AF_PP, GPIO_PULLUP, AFIO_NONE, 2, 0)}, // TIM4_CH2
        {PB_8,  TIM4,   STM_PIN_DATA_EXT(STM_MODE_AF_PP, GPIO_PULLUP, AFIO_NONE, 3, 0)}, // TIM4_CH3
        {PB_9,  TIM4,   STM_PIN_DATA_EXT(STM_MODE_AF_PP, GPIO_PULLUP, AFIO_NONE, 4, 0)}, // TIM4_CH4
      #endif

in case ARDUINO_BLUEPILL_F103C6 is defined the pins above cannot be used for timer because timer4 is not supported. But it would mean that you selected BluePill F103C6 (or generic F103C6). Can you please double check your board selection ?

3) There is a typo in your comments:

// #define mphPin PB4 // t3c1  ---runs  not working
// #define rpmPin PB5 // t2c2  working

In fact PB5 is TIM3CH2. And this combination is not possible because it is the same timer TIM3 and because channel2 is already mphchannelFalling, so it cannot also be channelRising

4) with combination:

// #define mphPin PA0 // t2c1  ---runs
// #define rpmPin PA6 // t3c1

It is working perfectly on my side, I tested with both 500 Hz and 3000Hz input frequency generator. Make sure you have GND connected from your input frequencies system to the bluepill. Verify your input signal (oscillo or Logical analyser), or better use a PWM signal generator (another board could do the job by changing frequency and duty cycle of example https://github.com/stm32duino/STM32Examples/blob/master/examples/Peripherals/HardwareTimer/All-in-one_setPWM/All-in-one_setPWM.ino)

fpistm commented 3 years ago

Hi @exothink any feedback based on @ABOSTM one ? Thank you in advance.

fpistm commented 3 years ago

Closed as no feedback