HardwareTimer + direct register access = 10MHz frequencymeter

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 + direct register access = 10MHz frequencymeter

Post by AndrewBCN »

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:

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();
  
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

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);
}
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).
razvitm
Posts: 38
Joined: Sat Apr 11, 2020 12:35 pm

Re: HardwareTimer + direct register access = 10MHz frequencymeter

Post by razvitm »

Does not compile on latest core:
src\main.cpp: In function 'void pinModeAF(int, uint32_t)':
src\main.cpp:48:7: error: 'LL_GPIO_SetAFPin_0_7' was not declared in this scope; did you mean 'LL_GPIO_SetPinPull'?
48 | LL_GPIO_SetAFPin_0_7( get_GPIO_Port(STM_PORT(pn)), STM_LL_GPIO_PIN(pn), Alternate);
| ^~~~~~~~~~~~~~~~~~~~
| LL_GPIO_SetPinPull
src\main.cpp:50:7: error: 'LL_GPIO_SetAFPin_8_15' was not declared in this scope; did you mean 'LL_GPIO_SetPinPull'?
50 | LL_GPIO_SetAFPin_8_15(get_GPIO_Port(STM_PORT(pn)), STM_LL_GPIO_PIN(pn), Alternate);
| ^~~~~~~~~~~~~~~~~~~~~
| LL_GPIO_SetPinPull
User avatar
fpiSTM
Posts: 1723
Joined: Wed Dec 11, 2019 7:11 pm
Answers: 91
Location: Le Mans
Contact:

Re: HardwareTimer + direct register access = 10MHz frequencymeter

Post by fpiSTM »

That is normal as this code has been tested for STM32F4.
I saw in other post you used an STM32F1xx and it does not manage the AF in the same manner than all other STM32 series that's why LL_GPIO_SetAFPin_8_15 and LL_GPIO_SetAFPin_0_7 are not defined.
So you have to update the code to use the correct API.

Here you can see how:
https://github.com/stm32duino/Arduino_C ... .h#L68-L85

with pin_SetF1AFPin here:
https://github.com/stm32duino/Arduino_C ... 2F1.h#L196
Post Reply

Return to “Code snippets”