HardwareTimer + PWM combined example

Post your cool example code here.
Post Reply
AndrewBCN
Posts: 105
Joined: Sun Apr 25, 2021 3:50 pm
Answers: 1
Location: Strasbourg, France

HardwareTimer + PWM combined example

Post by AndrewBCN »

Hello,
The following code combines the HardwareTimer input capture example sketch with the "change PWM frequency" example code snippet in the wiki to demonstrate how the frequency of a signal can be measured precisely by an STM32 development board, in this case a WeAct STM32F411CEU6 "Black Pill" development board. Two timers are used, Timer 2 (input capture) and Timer 4 (PWM generation).
Tested with stm32 Core release 2.0.0.

Code: Select all

/*
  Input capture
  This example shows how to configure HardwareTimer in inputcapture mode to measure external signal frequency.
  Each time a rising edge is detected on the input pin, hardware will save counter value into CaptureCompare register.
  This example has been modified to run on the STM32F411CEU6 Black Pill.
  We generate a 2kHz signal on PB9 - channel 4 of Timer 4, and connect it to PA1 - channel 2 of Timer 2 to measure its
  frequency.
  Measured frequency (2kHz) should be displayed on Serial Monitor.
*/

/*
  Note: Please verify that 'pin' used for PWM has HardwareTimer capability for your board
  This is specially true for F1 serie (BluePill, ...)
*/

#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 measure_frequency_pin  PA1 // Timer 2 Channel 2 from Arduino_Core_STM32/variants/STM32F4xx/F411C(C-E)(U-Y)/PeripheralPins_BLACKPILL_F411CE.c

uint32_t channel;
volatile uint32_t FrequencyMeasured, LastCapture = 0, CurrentCapture, CurCap32;
uint32_t input_freq = 0;
volatile uint32_t rolloverCompareCount = 0;
HardwareTimer *MyTim;

void InputCapture_IT_callback(void)
{
  CurCap32 = 
  CurrentCapture = MyTim->getCaptureCompare(channel);
  /* frequency computation */
  if (CurrentCapture > LastCapture) {
    FrequencyMeasured = input_freq / (CurrentCapture - LastCapture);
  }
  else if (CurrentCapture <= LastCapture) {
    /* 0x10000 is max overflow value */
    FrequencyMeasured = input_freq / (0x10000 + CurrentCapture - LastCapture);
  }
  LastCapture = CurrentCapture;
  rolloverCompareCount = 0;
}

/* In case of timer rollover, frequency is to low to be measured set value 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;
  }

}

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

  // generate a test 2kHz square wave on PB9 PWM pin, using Timer 4 channel 4
  // PB9 is Timer 4 Channel 4 from Arduino_Core_STM32/variants/STM32F4xx/F411C(C-E)(U-Y)/PeripheralPins_BLACKPILL_F411CE.c
  analogWriteFrequency(2000); // default PWM frequency is 1kHz, change it to 2kHz
  analogWrite(PB9, 127); // 127 means 50% duty cycle so a square wave

  // Automatically retrieve TIM instance and channel associated to measure_frequency_pin
  // in this case it should retrieve Timer 2 channel 2 which is associated to PA1
  // This automatic algorithm is used to be compatible with all STM32 series automatically.
  // Here we are just checking that it works.
  TIM_TypeDef *Instance = (TIM_TypeDef *)pinmap_peripheral(digitalPinToPinName(measure_frequency_pin), PinMap_PWM);
  channel = STM_PIN_CHANNEL(pinmap_function(digitalPinToPinName(measure_frequency_pin), PinMap_PWM));

  // 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(channel, TIMER_INPUT_CAPTURE_RISING, measure_frequency_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 the interruption and thus depend on board used
  // Example on Nucleo_L476RG with systemClock at 80MHz the interruption processing is around 4,5 microseconds and thus Max frequency is around 220kHz
  uint32_t PrescalerFactor = 1;
  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(channel, InputCapture_IT_callback);
  MyTim->attachInterrupt(Rollover_IT_callback);
  MyTim->resume();

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


void loop()
{
  /* Print frequency measured on Serial monitor every seconds */
  Serial.print("Timer channel = ");
  Serial.println(channel);
  Serial.print("Timer Clock Frequency = ");
  Serial.println(MyTim->getTimerClkFreq());
  Serial.println((String)"Frequency = " + FrequencyMeasured);
  delay(1000);
}
After running this code I was surprised to find out that the TIM2 clock on the STM32F411CEU6 Black Pill is 96MHz - I thought it was 100MHz, since the HSE is running at 25MHz.

UPDATE: I have posted an extended (improved?) version of this example below.
Last edited by AndrewBCN on Mon Apr 26, 2021 8:17 pm, edited 1 time in total.
User avatar
fpiSTM
Posts: 1738
Joined: Wed Dec 11, 2019 7:11 pm
Answers: 91
Location: Le Mans
Contact:

Re: HardwareTimer + PWM combined example

Post by fpiSTM »

AFAIK it is configured at 96 MHz to have USB clock at 48MHz.
AndrewBCN
Posts: 105
Joined: Sun Apr 25, 2021 3:50 pm
Answers: 1
Location: Strasbourg, France

Re: HardwareTimer + PWM combined example

Post by AndrewBCN »

fpiSTM wrote: Sun Apr 25, 2021 4:25 pm AFAIK it is configured at 96 MHz to have USB clock at 48MHz.
Ah yes, that makes sense. Thank you for the explanation.
AndrewBCN
Posts: 105
Joined: Sun Apr 25, 2021 3:50 pm
Answers: 1
Location: Strasbourg, France

HardwareTimer + PWM combined example, extended version

Post by AndrewBCN »

Here is an extended version of the HardwareTimer + PWM combined example above.

This version combines use of the HardwareTimer library with direct register access to make full use of the 32-bit timer capability of Timer 2 in the STM32F411CEU6. This allows to measure frequencies down to 1 Hz (the original example had a lower frequency limit of around 1.8kHz).

One can also watch the actual counter value increasing by 96 million every second, and wrapping around when it reaches 2^32.

Code: Select all

/*
  Input capture
  This example shows how to configure HardwareTimer in inputcapture mode to measure external signal frequency.
  Each time a rising edge is detected on the input pin, hardware will save counter value into CaptureCompare register.
  This example has been modified to run on the STM32F411CEU6 Black Pill.
  We generate a 2kHz signal on PB9 - channel 4 of Timer 4, and connect it to PA1 - channel 2 of Timer 2 to measure its
  relative period. We then calculate the input signal frequency based on the relative period and the timer clock frequency.
  Calculated frequency (2kHz) should be displayed on Serial Monitor.
  Note that Timer 2 on the STM32F411CEU6 is a 32 bit counter timer, so we can measure much lower frequencies than with a
  16 bit counter timer.
  This example also reads the Capture Compare Register for channel 2 (CCR2) of Timer 2 by direct access and
  sets a much larger value for max overflow, allowing the measurement of much lower frequencies than the original example.
*/

/*
  Note: Please verify that 'pin' used for PWM has HardwareTimer capability for your board
  This is specially true for F1 serie (BluePill, ...)
*/

#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 measure_frequency_pin  PA1 // Timer 2 Channel 2 from Arduino_Core_STM32/variants/STM32F4xx/F411C(C-E)(U-Y)/PeripheralPins_BLACKPILL_F411CE.c

uint32_t channel;
volatile uint32_t FrequencyMeasured, LastCapture = 0, CurrentCapture;
uint32_t input_freq = 0;
const uint32_t maxOverflow = 0xffffffff; // 32-bit value
volatile uint32_t rolloverCompareCount = 0;
HardwareTimer *MyTim;

void InputCapture_IT_callback(void)
{
  CurrentCapture = TIM2->CCR2; // direct access to Capture Compare Register for channel 2, Timer 2.
  /* frequency computation */
  if (CurrentCapture > LastCapture) {
    FrequencyMeasured = input_freq / (CurrentCapture - LastCapture);
  }
  else if (CurrentCapture <= LastCapture) {
    /* max overflow value was reached */
    FrequencyMeasured = input_freq / (maxOverflow + CurrentCapture - LastCapture);
  }
  LastCapture = CurrentCapture;
  rolloverCompareCount = 0;
}

/* In case of timer rollover, frequency is to low to be measured set value 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;
  }

}

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

  // generate a test 2kHz square wave on PB9 PWM pin, using Timer 4 channel 4
  // PB9 is Timer 4 Channel 4 from Arduino_Core_STM32/variants/STM32F4xx/F411C(C-E)(U-Y)/PeripheralPins_BLACKPILL_F411CE.c
  analogWriteFrequency(2000); // default PWM frequency is 1kHz, change it to 2kHz
  analogWrite(PB9, 127); // 127 means 50% duty cycle so a square wave

  // Automatically retrieve TIM instance and channel associated to measure_frequency_pin
  // in this case it should retrieve Timer 2 channel 2 which is associated to PA1
  // This automatic algorithm is used to be compatible with all STM32 series automatically.
  // Here we are just checking that it works.
  TIM_TypeDef *Instance = (TIM_TypeDef *)pinmap_peripheral(digitalPinToPinName(measure_frequency_pin), PinMap_PWM);
  channel = STM_PIN_CHANNEL(pinmap_function(digitalPinToPinName(measure_frequency_pin), PinMap_PWM));

  // 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(channel, TIMER_INPUT_CAPTURE_RISING, measure_frequency_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.
  // In this version since we are using a 32 bit counter timer, we can measure down to 1Hz.
  // The maximum frequency depends on processing of the interruption and thus depend on board used
  // Example on Nucleo_L476RG with systemClock at 80MHz the interruption processing is around 4,5 microseconds and thus Max frequency is around 220kHz
  uint32_t PrescalerFactor = 1;
  MyTim->setPrescaleFactor(PrescalerFactor);
  MyTim->setOverflow(50000); // Max Period value to have the largest possible time to detect rising edge and avoid timer rollover
  TIM2->ARR = maxOverflow; // basically we extend the count to 32 bits, allowing the measurement of much lower frequencies.
  MyTim->attachInterrupt(channel, InputCapture_IT_callback);
  MyTim->attachInterrupt(Rollover_IT_callback);
  MyTim->resume();

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


void loop()
{
  /* Print frequency measured on Serial monitor every seconds */
  Serial.print("CCR2, Timer 2 = ");
  Serial.println(CurrentCapture); 
  Serial.print("Timer channel = ");
  Serial.println(channel);
  Serial.print("Timer Clock Frequency = ");
  Serial.println(MyTim->getTimerClkFreq());
  Serial.println((String)"Frequency = " + FrequencyMeasured);
  delay(1000);
}
Iva
Posts: 1
Joined: Tue Dec 28, 2021 10:49 pm

Re: HardwareTimer + PWM combined example

Post by Iva »

The last code does not work.
wanderlg
Posts: 4
Joined: Thu Jun 09, 2022 6:05 pm

Re: HardwareTimer + PWM combined example

Post by wanderlg »

Guys, I'm new here.
Is there a way to measure more than 3MHz using the STM32F4?
In this example the person can measure 3.2MHz using ATMEGA328
https://forum.arduino.cc/t/counting-fal ... /21?page=2

Code: Select all

//TCNT1 16 bit max value = 65,534
//20ms sample period gives frequency counter to a bit over 3.2 mhz

unsigned int dwell = 20000; // dwell in microseconds for counter
unsigned long final_counts;
unsigned long start_time;
unsigned long measured_time;

void setup()
{
  Serial.begin(115200);
  TCCR1A = 0; //initialize Timer1
  TCCR1B = 0;
  TCNT1 = 0;

  pinMode( 5, INPUT_PULLUP); //external source pin for timer1
}

void loop()
{
  start_time = micros();
  TCNT1 = 0;//initialize counter
  // External clock source on Timer1, pin (D5). Clock on rising edge.
  // Setting bits starts timer
  TCCR1B =  bit (CS10) | bit (CS11) | bit (CS12); //external clock source pin D5 rising edge

  while (micros() - start_time < dwell) {} // do nothing but wait and count during dwell time

  TCCR1B = 0; //stop counter
  final_counts = TCNT1; //frequency limited by unsigned int TCNT1 without rollover counts

  measured_time = micros() - start_time;

  Serial.print(measured_time); // report resulting counts
  Serial.print("\t\t");
  Serial.println(50 * final_counts); //20ms sample in Hz
}
Is it easy to convert this code to STM32F401?

By the way, the two example codes of this post are working perfectly and reading up to 330KHz using the STM32F401

Thanks.
ag123
Posts: 1655
Joined: Thu Dec 19, 2019 5:30 am
Answers: 24

Re: HardwareTimer + PWM combined example

Post by ag123 »

welcome,
take a look at hardware timer
https://github.com/stm32duino/wiki/wiki ... er-library

e.g. try this example
https://github.com/stm32duino/STM32Exam ... apture.ino

I've not really tested input capture on timer, but i think it should work.
dannyf
Posts: 447
Joined: Sat Jul 04, 2020 7:46 pm

Re: HardwareTimer + PWM combined example

Post by dannyf »

Is it easy to convert this code to STM32F401?
the spirit of the code can be ported. the code itself cannot.

The key in any counting is to route the pulse train to a timer as an external input. To do that:
1. you better use an asynchronous counter - some (low-end) PICs have that. The STM32s don't.
2. the best way to count varies bepending on how fast the input pulse train is. For slow inputs, the best approach is to use the input train to gate an internal counter; for fast inputs, the best approach is to count the number of external pulses within a known period of time.

the AVR code isn't that great but what it does is to use Timer 1 to count an external pulse train, and wait for a period of time and read the timer count. the code can be improved.

Many timers on the STM32 (but not all) can count external pulses so porting it over isn't difficult at all. I would say that it is fairly easy to count to 1/2 or at least 1/4 of the clock frequency -> see the datasheet.

for extremely high speed counting (100MHz + or GHz), use an external counter.
wanderlg
Posts: 4
Joined: Thu Jun 09, 2022 6:05 pm

Re: HardwareTimer + PWM combined example

Post by wanderlg »

example: AndrewBCN memberlist.php?mode=viewprofile&u=1395

viewtopic.php?f=41&t=1030&p=6899&hilit= ... Mode#p6899
It was possible to count in PA15 frequencies between 1Hz to just over 40MHz using an external clock of 1Hz in PB10. Strangely, when joining PB10+PA15, we don't need the external clock of 1Hz and we can measure normally, but very unstable.

I don't have much knowledge and I don't know if it's possible to improve accuracy and instability without having to use an external 1Hz clock.

Code: Select all

volatile uint32_t Freq = 0, Freq_Raw = 0, Freq_Temp = 0;


void pinModeAF(int ulPin, uint32_t Alternate)
{
  int pn = digitalPinToPinName(ulPin);

  if (STM_PIN(pn) < 8) {
    LL_GPIO_SetAFPin_0_7( get_GPIO_Port(STM_PORT(pn)), STM_LL_GPIO_PIN(pn), Alternate);
  } else {
    LL_GPIO_SetAFPin_8_15(get_GPIO_Port(STM_PORT(pn)), STM_LL_GPIO_PIN(pn), Alternate);
  }

  LL_GPIO_SetPinMode(get_GPIO_Port(STM_PORT(pn)), STM_LL_GPIO_PIN(pn), LL_GPIO_MODE_ALTERNATE);
}


void Timer_ISR_1Hz(void)
{
    Freq_Raw = TIM2->CCR3;
    unsigned long t;
    if (Freq_Raw != Freq_Temp)
    {
      Freq = Freq_Raw - Freq_Temp;
      Freq_Temp = Freq_Raw;
      t = millis();
    }
    if (Freq_Raw == TIM2->CCR3 && millis() - t > 1000) Freq = 0;
}


void setup()
{
  Serial.begin(115200);
  // setup and start Timer 2 which measures OCXO frequency

  pinMode(PA15, INPUT_PULLUP);  // TIM2 channel 1 - ETR from OCXO
  pinModeAF(PA15, GPIO_AF1_TIM2); // setup PA15 as TIM2 channel 1 / ETR
  pinMode(PB10, INPUT_PULLUP); // external 1hz clock

  HardwareTimer *tim1Hz = new HardwareTimer(TIM9);
  tim1Hz->setOverflow((1000000), MICROSEC_FORMAT);
  tim1Hz->attachInterrupt(Timer_ISR_1Hz);
  tim1Hz->resume();


  HardwareTimer *FreqMeasTim = new HardwareTimer(TIM2);

  // Configure rising edge detection to measure frequency
  FreqMeasTim->setMode(3, TIMER_INPUT_CAPTURE_RISING, PB10);

  // Configure 32-bit auto-reload register (ARR) with maximum possible value
  TIM2->ARR = 0xffffffff; // count to 2^32, then wraparound (approximately every 429 seconds)

  // select external clock source mode 2 by writing ECE=1 in the TIM2_SMCR register
  TIM2->SMCR |= TIM_SMCR_ECE; // 0x4000

  // start the timer
  FreqMeasTim->resume();

}


void loop()
{

  Serial.println(Freq);
  delay(1000);

}
ag123
Posts: 1655
Joined: Thu Dec 19, 2019 5:30 am
Answers: 24

Re: HardwareTimer + PWM combined example

Post by ag123 »

you'd need to look in the specs sheet and ref manual for your chip. normally the APB runs at something like 40 mhz or 80 mhz (max).
This could either mean that is about max 'countable' clocks or it may in fact be 1/2 that or lower, e.g. like 20 Mhz. Check in the manuals.
Then the gpio ports has max speeds too (normally i think it refers to output), nor sure about input.
Post Reply

Return to “Code snippets”