Frequency Counter With Variable Gate Time

by WilkoL in Circuits > Microcontrollers

5971 Views, 19 Favorites, 0 Comments

Frequency Counter With Variable Gate Time

IMG_5659.JPG

Frequency counter (reciprocal)

Gate time selectable from 0.1 second up to 60 seconds

Frequency range from 0.1 Hz up to 10 MHz (tested)

Input 3.3V and 5V safe

Uses a STM32F103C8 (Blue Pill) and 16x4 LCD

code is available on gitlab:

https://gitlab.com/WilkoL/frequency-counter-with-v...

Supplies

STM32F103C8 (Blue Pill)

16 x 4 LCD screen (HD44780 compatible)

10 MHz OCXO

74HC74

74HC00

3.3V voltage regulator

rotary encoder with button

some resistors, capacitors and diodes

perfboard

project box

Another Frequency Counter?

expensive_frequency_counter.png

Not so long ago I made another reciprocal frequency counter with an ATTINY2313 that works quite well. But it has no option to change the gate time. Its gate time is approximately one second long, except when the input frequency is lower than 1 Hz when it lengthens the gate time to accomodate for a full cycle of the input frequency.

For most applications this is fine, the frequency I measure usually is stable and I just want to know as precisely as possible its value.But recently I started a project that asks for a frequency counter with a gate time of 32 seconds.

BTW, I wish I could afford the counter in the picture :-)

32768 Hz

32768Hz.jpg
32k_crystal_vs_temp.png

That new project is very low power, with a STM32L052K6 microcontroller that uses a 32768 Hz crystal. These crystals are very common in clocks, watches and other places where they are used for time keeping. Crystals are dependable, stable and precise, except for one thing, their frequency varies (a little) versus temperature. To keep the frequency stable at all temperatures there are several options.

One is to put the crytals in a temperature controlled environment, in this frequency counter that is done with an OCXO, an Oven Controlled Xtal Oscillator. But an oven uses a lot of power so that is not an option for my low power project.

A second option is to use a TCXO, a Temperature Compensated Xtal Oscillator. One very popular example of that is the DS3231 from Maxim. It is extremely accurate and low power as well. But this would add an extra integrated circuit to my project and the means more pcb space, cost and power.

But there is a third option. The STM32L052K6 has a Real Time Clock with a special function. It uses an ordinary 32768 Hz crystal so it is clocked with 32768 pulses per second. but when the frequency isn't exactly 32768 Hz, it can add or subtract pulses to compensate.


A Correcting Real-Time-Clock

STM32L052_RTC_colour.jpg

Of course the microcontroller does not "know" when the frequency isn't exactly right, that has to be measured with a frequency counter first. One can then tell the RTC to add or subtract the right number of pulses to come to exact 32768 pulses per second.

Inside the STM32L052 there is also a temperature sensor which we can use to make adjustments of the frequency at different temperatures.

Those adjustment happen over a time period of 32 seconds. So in this 32 second period the RTC will add or subtract pulses to come to a total of 1048576 pulses. The number of extra or less pulses needed can vary and they are spread out of those 32 seconds, and therefore one second can be a little longer or shorter than the next second, still, over the 32 second period they will average out to exactly 1 second.

Reciprocal Frequency Counting

IMG_5652.JPG

So I needed a frequency counter with a gate time of 32 seconds. If I was going to make one to do that, I might as well make one with several gate times. I made it with gates times going from 100 ms in steps of 100ms up to 1 second, then from 1 second up to 50 seconds and a bonus of 60 seconds. Longer gates times are possible, but not without some changes, later more about that.


The frequency counter uses the reciprocal method which means that it counts two signals at the same time, the input signal of course and a second high frequency signal. This second frequency should be as high a possible, the exact value of it isn't very important although its value must be known as precisely as possible.

The trick is to start counting both signals at exactly the same time and also to stop at exacly the same time, both times at the rising edge of the input signal. This is done with a D-flipflop that triggers the start and end of the measuring at the rising edge of the input signal. This way we know that we have measured a whole number (integer) of cycles of the input signal.

At the same time we counted the other high frequency signal, not starting at the rising or falling edge of it, but as it is high frequency, missing one of two pulses does not change the total very much. With those two numbers the frequency of the input signal can be calculated with a very high precision.

For a longer explanation of the reciprocal measuring method take a look at my other counter project, here on instructables. https://www.instructables.com/High-Resolution-Freq...

Schematic

schematic.png

For this counter I used a STM32F103C8 (on a BluePill development board), a D-FlipFlop, a 10 MHz OCXO and a LCD screen with 4 lines of 16 characters. Unfortunately the backlight of the screen is broken, so I can't use it without enough light.

As the OCXO uses a lot of power, esspecially when it is just switched on, I did not use battery power. It is powered with 5V coming in via a USB connector.

A 74HC00 (QUAD NAND) is used at the input and a very small delayline. A rotary encoder is used for setting the wanted gate time.

Input

input.JPG

The input is as simple as it gets, a resistor and two diodes to protect the gates of the 74HC00, only one gate would be enough, but I needed just three in the whole schematic. So one was left, and I added it after the one on the input.

Triggering With D-FlipFlop

d_flipflop_delay.JPG

The D-input of the D-FlipFlop for triggering section is set high by the microcontroller. This happens with the required gate time. Then, as soon as there is a rising edge on its clock input, the Q-output is set. That signal is send to the Channel-1 inputs of all 4 timers, the current value of those timers is recorded and processed. The timers never stop.


Because there is a small delay in the D-FlipFlop for the input signal to reach the output, I delayed the input frequency with two NANDs before it is send to the timer that counts the input frequency. That way both the capture-signal and input-signal arrive at the same time.

if (timeout)                                            //defines the approx gate-time
{ timeout--;
        wait_time = ((timeout - 1) / 1000) + 1;                //calc wait time so you know how long to
        if ((timeout % 1000) == 0) show_wait_time = 1;        //wait for the next measurement result
    }
    else
    {
        LL_GPIO_SetOutputPin(GPIOA, LL_GPIO_PIN_11);        //set D-flipflop HIGH
        show_wait_time = 1;
    }

Rotary Encoder

rotary_encoder_analog_filter.JPG

The rotary encoder has some analog filtering to remove most of the bounce of its connectors. More debouncing is done in the interrupt routine of the SYSTICK timer.

pin_b11 <<= 1;
pin_b12 <<= 1; pin_b13 <<= 1;
    if (LL_GPIO_IsInputPinSet(GPIOB, LL_GPIO_PIN_11)) pin_b11++;
    if (LL_GPIO_IsInputPinSet(GPIOB, LL_GPIO_PIN_12)) pin_b12++;
    if (LL_GPIO_IsInputPinSet(GPIOB, LL_GPIO_PIN_13)) pin_b13++;
    pin_b11 &= 0x0F;                                        //only use nibbles, so that the pin only has
    pin_b12 &= 0x0F;                                        //to be high/low for 4 ms instead of 8 ms or
    pin_b13 &= 0x0F;                                        //else we miss pulses from the encoder
    if (pin_b11 == 0x08)                                    //rotation detected
    {
        clear_data = 1;
        timeout = 10;                                        //restart end current measurement after 10 ms
        idle_time = 20;
        if ((pin_b12 == 0x00) && (gate_time < 59)) gate_time++;
        if ((pin_b12 == 0x0F) && (gate_time > 0)) gate_time--;
        if (gate_time > 59) gate_time = 9;                    //just in case... reset to gate_time = 1 second
    }

OCXO

ocxo.JPG

Last but not least, there is the OCXO, the Oven-Controled-Xtal-Oscillator.

It needs 5V and has a fine tuning input to make it oscillate at exactly 10,000.000 Hz. This 10 MHz signal is used as the clock for the STM32F103C8 and multiplied with its PLL to 70 MHz, so everything works at exactly this clock frequency.

On a GPIO of the microcontroller there is a 100 Hz signal derived from this 70 MHz so the OCXO can be tuned to 10 MHz. I did that with another counter that is locked to the GPS 1PPS signal.

CODE: Table and Init

stm32f103c8_pin_use.JPG

The first thing you will notice it that I made a small table for the gate times, for 100ms to 1 second the steps are 100ms, from 1 seconds up to 50 seconds the step size is 1 second and the last step is 10 seconds to 60. I wanted to have short gate times as well as longer but is did not want to use 100ms steps all the way up to 60 seconds.

const uint16_t time_table[60] =
{ 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, //00 - 09 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000, 11000, //10 - 19 12000, 13000, 14000, 15000, 16000, 17000, 18000, 19000, 20000, 21000, //20 - 29 22000, 23000, 24000, 25000, 26000, 27000, 28000, 29000, 30000, 31000, //30 - 39 32000, 33000, 34000, 35000, 36000, 37000, 38000, 39000, 40000, 41000, //40 - 49 42000, 43000, 44000, 45000, 46000, 47000, 48000, 49000, 50000, 60000 //50 - 59 };

Next there are the initializations of the clock, display and the timers. The timers all are 16 bits wide, so they can count from 0 to 65535. This isn't nearly enough when you want a frequency counter.

Most (all?) STM32 microcontrollers can connect timers in several ways. For instance, you can synchonize a timer with another or start, or reset it. Here I connected two timers in such a way that one divides the clock for the next one. Combined they count from 0 to 4,294,967,295 before they start from 0 again.

static void MX_TIM2_Init(void)
{ LL_TIM_InitTypeDef TIM_InitStruct = {0}; LL_GPIO_InitTypeDef GPIO_InitStruct = {0};
    LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM2);
    LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_GPIOA);
    GPIO_InitStruct.Pin = LL_GPIO_PIN_0;                    //CH1
    GPIO_InitStruct.Mode = LL_GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = LL_GPIO_PULL_UP;
    LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    NVIC_SetPriority(TIM2_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),0, 0));
    NVIC_EnableIRQ(TIM2_IRQn);
    TIM_InitStruct.Prescaler = 0;
    TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP;
    TIM_InitStruct.Autoreload = 65535;
    TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1;
    LL_TIM_Init(TIM2, &TIM_InitStruct);
    LL_TIM_DisableARRPreload(TIM2);
    LL_TIM_SetTriggerInput(TIM2, LL_TIM_TS_ITR0);	//clock coming from TIM1
    LL_TIM_SetSlaveMode(TIM2, LL_TIM_CLOCKSOURCE_EXT_MODE1);
    LL_TIM_DisableIT_TRIG(TIM2);
    LL_TIM_DisableDMAReq_TRIG(TIM2);
    LL_TIM_SetTriggerOutput(TIM2, LL_TIM_TRGO_RESET);
    LL_TIM_DisableMasterSlaveMode(TIM2);
    LL_TIM_IC_SetActiveInput(TIM2, LL_TIM_CHANNEL_CH1, LL_TIM_ACTIVEINPUT_DIRECTTI);
    LL_TIM_IC_SetPrescaler(TIM2, LL_TIM_CHANNEL_CH1, LL_TIM_ICPSC_DIV1);
    LL_TIM_IC_SetFilter(TIM2, LL_TIM_CHANNEL_CH1, LL_TIM_IC_FILTER_FDIV1);
    LL_TIM_IC_SetPolarity(TIM2, LL_TIM_CHANNEL_CH1, LL_TIM_IC_POLARITY_RISING);
}

As I said, the internal clock of the microcontroller is 70 MHz, this means that in 61 seconds such a "combined timer" would overflow. So there you have the reason that the gate time isn't higher than 60 seconds. Also, the STM32F103C8 can work with a clock of 72Mhz, but that would mean that the timers overflow after 59 seconds.

It is possible to go (much) higher if you are prepared to use the overflow (called UPDATE) interrupt of the timers. That interrupt could then be used to increment yet another counter (in software) and added to the values of the timers. I didn't think it would be necessary here.

CODE: Main and Interrupts

activity.JPG

The main program is a loop that checks if there are signals coming from interrupts.

- When a new measurment is ready it will calculate the new frequency and an average of it.

- If the button of the rotary encoder was pressed it will reset the average frequency.

- When you turn it it will get the new gate time from the table and start using that.

- And last, every second it will show a timer decrementing the time you have to wait for the next measurement. That last option helps to see that there still is something happening when you have selected long gate times.


From the interrupt routines the TIM1_CC_IRQHandler is the most interesting.

The triggering signal coming from the D-FlipFlop is connected to all four timers, so you might assume that all four produce an interrupt. But that isn't necessary, as they all happen at the same time, and there will not be another coming for at least 100ms, it is enough to trigger just one interrupt. I chose TIM1 as the felt most logical, but it actually doesn't matter which one you take.

In the interrupt all four channel-captures are assembled into value for frequency and reference, subtracted from these are the previous values to calculate the actual counted frequency and reference. As they all are unsigned integers there are no negative numbers and as long as the total count did not go higher than 4,294,967,295 it is fine to subtract a high value from a low one.

void TIM1_CC_IRQHandler(void)
{ static uint32_t prev_raw_freq; //all CH1 channels of TIM1, TIM2, TIM3 and TIM4 are static uint32_t prev_reference; //triggered at the same time, the values are combined uint32_t new_raw_freq; //to the raw_freq and reference uint32_t new_reference;
    if (LL_TIM_IsActiveFlag_CC1(TIM1))
    {
        LL_TIM_ClearFlag_CC1(TIM1);
        new_raw_freq = (LL_TIM_IC_GetCaptureCH1(TIM2) << 16) | LL_TIM_IC_GetCaptureCH1(TIM1);
        new_reference = (LL_TIM_IC_GetCaptureCH1(TIM4) << 16) | LL_TIM_IC_GetCaptureCH1(TIM3);
        raw_freq = new_raw_freq - prev_raw_freq;
        reference = new_reference - prev_reference;
        prev_raw_freq = new_raw_freq;
        prev_reference = new_reference;
        measurement_ready = 1;
    }
}

Building It

IMG_5643.JPG
IMG_5646.JPG

I build it as usual on perfboard and in my favorite plastic project box. There isn't much to tell about it, some parts are placed under the LCD and I had to drill two huge holes in the board because the connectors were longer than I expected.

One thing I should mention, as you can see I removed the 8 MHz crystal from the Blue Pill because it is clocked by the 10 MHz OCXO. You can see the 100 ohm resistor that sits between the OCXO, running at 5V and the 3.3V microcontroller. What you do not see it the two diodes on the underside that clamp the singla to 3.3V and ground (see schematic).

Also worth mentioning is that I do not trust the 3.3V regulators on Blue Pills, so I remove them and also the capacitors that belong to them. That leaves the 5V pin of the Blue Pill unused, which in this case I could use as the clock input.
Because I removed the 3.3V regulator from the Blue Pill I have to put one on the perfboard myself, that is the small TO92 (transistor like) device. I did not put it in the schematic.