HardwareTimer + PWM combined example
Posted: Sun Apr 25, 2021 4:11 pm
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.
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.
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);
}
UPDATE: I have posted an extended (improved?) version of this example below.