HardwareTimer + direct register access = 10MHz frequencymeter
Posted: Wed Apr 28, 2021 8:09 am
Hello,
I am working on an STM32 GPSDO and I needed to use Timer 2 on an STM32F411CEU6 Black Pill in External Clock Mode 2 (ETR). This mode is not supported by the HardwareTimer library but is quite easy to configure with direct register access.
In this application, Timer 2 is used to precisely measure the frequency of a 10MHz Oven Controlled Crystal Oscillator (OCXO), which is input to the TIM2 CH1/ETR pin (PA15). A 1PPS pulse from the GPS is input to TIM2 CH3 (PB10). Consequently TIM2->CCR3 captures a count every second which is incremented by the OCXO frequency.
This algorithm requires neither interrupts nor DMA.
Timer 2 is setup with the following code:
Note that to configure PA15 as CH1/ETR, I am using a function written by Joep (jsuijs) which he posted in his description of a quadrature encoder algorithm.
viewtopic.php?f=62&t=828
TIM2->CCR3 (32-bit long for Timer 2 in the STM32F411CEU6) is read in the main loop.
This hardware/software setup can be used to read any frequency up to approx. 24MHz (1/4 of the Timer 2 clock on the STM32F411CEU6 Black Pill).
I am working on an STM32 GPSDO and I needed to use Timer 2 on an STM32F411CEU6 Black Pill in External Clock Mode 2 (ETR). This mode is not supported by the HardwareTimer library but is quite easy to configure with direct register access.
In this application, Timer 2 is used to precisely measure the frequency of a 10MHz Oven Controlled Crystal Oscillator (OCXO), which is input to the TIM2 CH1/ETR pin (PA15). A 1PPS pulse from the GPS is input to TIM2 CH3 (PB10). Consequently TIM2->CCR3 captures a count every second which is incremented by the OCXO frequency.
This algorithm requires neither interrupts nor DMA.
Timer 2 is setup with the following code:
Code: Select all
// setup and start Timer 2 which measures OCXO frequency
// setup pins used
pinMode(PA15, INPUT_PULLUP); // TIM2 channel 1 - ETR from OCXO
pinModeAF(PA15, GPIO_AF1_TIM2); // setup PA15 as TIM2 channel 1 / ETR
// setup Timer 2 in input capture mode, active input channel 3
// to latch counter value on rising edge. We use the HardwareTimer library for this.
TIM_TypeDef *Instance2 = (TIM_TypeDef *)pinmap_peripheral(digitalPinToPinName(PB10), PinMap_PWM);
channel = STM_PIN_CHANNEL(pinmap_function(digitalPinToPinName(PB10), PinMap_PWM));
// Instantiate HardwareTimer object. Thanks to 'new' instantiation, HardwareTimer is not destructed when setup() function is finished.
MyTim = new HardwareTimer(Instance2);
// Configure rising edge detection to measure frequency
MyTim->setMode(channel, TIMER_INPUT_CAPTURE_RISING, PB10);
uint32_t PrescalerFactor = 1; // possibly redundant
MyTim->setPrescaleFactor(PrescalerFactor); // possibly redundant
MyTim->setOverflow(50000); // possibly redundant
TIM2->ARR = 0xffffffff; // basically we extend the count to 32 bits, allowing the measurement of much higher frequencies.
//MyTim->attachInterrupt(channel, InputCapture_IT_callback); // no interrupts needed
//MyTim->attachInterrupt(Rollover_IT_callback); // no interrupts needed
// select external clock source mode 2 by writing ECE=1 in the TIM2_SMCR register
TIM2->SMCR |= TIM_SMCR_ECE; // 0x4000
// And now we can start the timer
MyTim->resume();
viewtopic.php?f=62&t=828
Code: Select all
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);
}
This hardware/software setup can be used to read any frequency up to approx. 24MHz (1/4 of the Timer 2 clock on the STM32F411CEU6 Black Pill).