<< Back

 

Cesky: , English:

Accurate frequency measurement

Two methods are typically used to measure the frequency of a periodic signal: 1) counting pulses over a given period of time (typically 1 second) or 2) measuring the time between two consecutive pulses. The first method is suitable for measuring high frequencies, but is inaccurate for low frequencies. Conversely, the second method is suitable for measuring low frequencies and is inaccurate for high frequencies. The description given here combines both methods and allows accurate measurement of frequencies over the full range. The method was implemented to accurately measure frequencies (up to 7 digits) in the range of 1 Hz to 100 MHz with an ATmega8 processor operating at 16 MHz.

The classical method of frequency measurement consists of counting pulses for a given time interval, typically 1 second. For this, a simple counter that responds to the edge of the pulse is sufficient. If we measure a frequency of 10 MHz for 1 second, we achieve a theoretical accuracy of 7 valid digits (let's not consider the accuracy of the time normal yet). However, this accuracy decreases rapidly with decreasing frequency. When measuring a frequency of 1 Hz, we are only able to obtain an accuracy of 1 valid digit, because the counter counts only 1 pulse in 1 second. We could increase the accuracy by extending the measurement interval, but we cannot extend it indefinitely. And who would want to wait an hour for an accurate result?

If, on the other hand, we measure the period of the pulses, we will use a high frequency reference counter that will count the ticks between the two edges of the signal. If we use a counter running at 10 MHz, we can get an accuracy of 7 valid digits when measuring a frequency of 1 Hz (1 second period). However, as the frequency increases, the accuracy decreases rapidly. When measuring a frequency of 10 MHz, we get an accuracy of only 1 digit because the reference counter counts only 1 pulse between two edges. Theoretically, we could increase the resolution by increasing the frequency of the reference generator, but our options may be quite limited. In addition, this method may have low immunity to interference - the edge may be modulated by noise and this can cause significant accuracy distortion at higher frequencies.

If we combine both methods, we use 2 counters. For a given time (e.g. 1 second) we will count the pulses and at the same time measure the time between the edge of the first and last pulse. We measure the time by writing a time stamp at the first and last edge of t1 and t2. The exact frequency is then calculated by dividing the counted pulses by the measured time t2 - t1.

The method was implemented with an ATmega8 processor with a crystal frequency of 16MHz. The input signal is fed to the processor on 2 inputs. Input T0 is the input to the 8-bit Timer0 counter, which is used to count the pulses. Input ICP is the input to the 16-bit Timer1 counter, which is used to measure the interval between pulses. The input to the timer is routed to the processor both directly and through the 74HC393 predefine. Switching of the input is done automatically, using an analog multiplexer, depending on the measured frequency.

At first glance, the described method sounds simple, but there is one complicating problem - synchronization of counters. We cannot assume that when reading the state of the counters, the state is written simultaneously. If we run into a moment when one counter has updated and the other has not yet, this can mean a large measurement error. The second snag is the range of the counters, which do not have a large enough range to measure the entire interval and need to synchronize the overflow counter with the hardware counter. Let's look at the operation of the counters in more detail.

The basic operation of the counters is simple. The overflow counters increment global variables representing higher counter ranges. The Timer0 counter counts with an increment of 256 because the current counter state is later stored in its lowest byte.

// overflow of frequency counter * 256
u32 FCounter = 0;

// overflow of time counter
u16 TCounter = 0;

// interrupt request of Timer0 overflow (frequency counter)
ISR(TIMER0_OVF_vect)
{
	FCounter += 256;
}

// interrupt request of Timer1 overflow (time counter)
ISR(TIMER1_OVF_vect)
{
	TCounter++;
}

The main work will be done in the operation of the Timer1 timestamp counter, which records the synchronous current state of both counters and treats critical states when the input signal is edge. An interrupt occurs just after the edge arrives, and this ensures that the edge is already recorded in both counters. Of course, the timestamp operator cannot be called continuously, the processor would become overwhelmed at higher signal rates, it is only activated at the beginning and end of the measurement period.

First the service reads the status of Timer1. It reads the last edge time record from ICR1 (timestamp) and the overflow counter of TCounter. At this point, the operator does not know whether the time record in ICR1 occurred before or after the last overflow count in TCounter. It tests the status of the Timer1 overflow flag, and if this flag is set and at the same time the timestamp value is low, it means that the timestamp was recorded just after the Timer1 overflow, but the overflow status was not entered into the TCounter. Therefore, a correction is made and the TCounter is incremented. The two readings are merged into the resulting TCounterRes timestamp value.

// counter result
volatile u32 FCounterRes = 0; // frequency counter result
volatile u32 TCounterRes = 0; // time counter result
volatile Bool CounterResOK = False; // stamp captured OK

// Timer1 input capture event
ISR(TIMER1_CAPT_vect)
{
	// get Timer1 counter
	u16 t1 = ICR1;
	u16 tc = TCounter;

	// time counter overflow
	if (((TIFR & BIT(TOV1)) != 0) && (t1 < 1000))
	{
		tc++;
		TCounter = tc;
		TIFR = BIT(TOV1);
	}

	// get time counter
	TCounterRes = (((u32)tc << 16) | t1);

	// get Timer0 counter
	u8 t0 = TCNT0;
	u32 fc = FCounter;

	// frequency counter overflow
	if (((TIFR & BIT(TOV0)) != 0) && (t0 < 100))
	{
		fc += 256;
		FCounter = fc;
		TIFR = BIT(TOV0);
	}

	// get frequency counter
	FCounterRes = fc | t0;

	// disable capture event interrrupt
	TIMSK &= ~BIT(TICIE1);

	// reset interrupt flags
	TIFR = BIT(ICF1);

	// stamp captured OK
	CounterResOK = True;
}

The pulse reading from the Timer0 counter is serviced further. The status of the Timer0 counter TCNT0 and the FCounter overflow counter is read. If the Timer0 counter overflow flag is set and at the same time the read counter value is small, it means that the counter has overflowed, but the overflow counter has not been updated, so its correction is performed. From the read values, the value of the FCounter frequency counter is constructed.

Let us further note the calculation of the resulting frequency and period. The measurement takes place for about 1 second (with a timeout waiting for an edge of 2 seconds). A third counter, the system time counter Timer2, is used for this, but its function is not essential to the method. Every 1 second, the timestamp status is recorded and the calculation is performed. The counters run continuously and only the current and previous timestamp value is worked with, thus updating the displayed result every second, even at a slow frequency of 1 Hz. From the difference of the current and previous pulse counter, the number of counted pulses per measured period is determined. From the difference of the current and previous time counter, the elapsed time between time stamps is determined.

The calculation is performed in the following function. And because calculations with float arithmetic would be difficult in the ATmega processor, the calculation is done with integer math u64. The measured number of ticks of the pulse counter is first multiplied by the frequency of the processor. This ensures that the measured time is divided by the processor frequency to obtain the real measured time. The result is normalized - shifted to the maximum bit position to ensure maximum accuracy of the result. It is then divided by the measured interval to calculate the resulting frequency. The result is normalized to the range of the number u32. The normalization is performed by multiplying and dividing by 10, while updating the fscale decade counter. This is used later to display the result. Similarly, the period of the signal is calculated, but as an inverse of the frequency.

// multiply by quartz frequency: ff = f * F_CPU
u64 ff = MulQDD(f, F_CPU);
u64 ff0 = ff;

// normalize result UP, to maximize precision
s8 fscale = 0;
while (ff < 900000000000000000ULL) // 63 bits / 10
{
	ff = MulQB(ff, 10);
	fscale--;
}

// divide pulses by time to obtain frequency: ff = ff / t
ff = DivQQ(ff + t/2, t, NULL);

// normalize result DOWN, to fit into DWORD
while (ff > 4000000000UL)
{
	ff = DivQQ(ff + 5, 10, NULL);
	fscale++;
}

// normalize time UP, to maximize precision
s8 tscale = 0;
u64 tt = t;
while (tt < 900000000000000000ULL) // 63 bits / 10
{
	tt = MulQB(tt, 10);
	tscale--;
}

// divide time by pulses to obtain time: tt = tt / ff
tt = DivQQ(tt + ff0/2, ff0, NULL);

// normalize result DOWN, to fit into DWORD
while (tt > 4000000000UL)
{
	tt = DivQQ(tt + 5, 10, NULL);
	tscale++;
}

// result frequency
Freq = (u32)ff;
FreqScale = fscale;

// result time
Tim = (u32)tt;
TimScale = tscale;

Finally, a mention of crystal calibration, which is also applicable to other projects. The manufacturer states the accuracy of the crystal at room temperature is 50 ppm (= 0.005% deviation). This gives us a real accuracy of the measured value of about 5 digits. By calibrating the frequency of the crystal, we can increase the accuracy by an order of magnitude, to 6 digits, and in the short term to 7 digits (but even without maximum accuracy, displaying the reading at 7 digits is useful - when monitoring frequency changes). Accurate calibration of frequency and time is most easily achieved in home conditions, because we have a very accurate reference standard - a precision time clock, available e.g. via the Internet. The ClockTest program can be downloaded from the project website. Its function is that it displays the elapsed time, according to the preset frequency of the processor crystal, and also displays the deviation from a whole minute. Calibration is done by running the program at a precisely known time. After a certain period of time, e.g. after 1 day, read the time on the display at the second exactly known time. The corrected crystal frequency is calculated by the relation F = F_CPU * time_indicated / time_actual. Enter it into the F_CPU parameter in the MAKEFILE of the meter source code, compile the modified program and load it into the processor. From that point on, the processor will calculate the new exact crystal value and measure more accurately.

Link to the complete frequency meter project (with a detailed description of the construction): ../freqmeter/index_en.html

Miroslav Nemecek

<< Back