<< Zpět

 

  Česky: , English:

Přesné měření frekvence

K měření frekvence periodického signálu se zpravidla používají dvě metody: 1) počítání pulsů za danou dobu (typicky 1 sekunda) nebo 2) měření doby mezi dvěma následujícími pulsy. První metoda je vhodná k měření vysokých kmitočtů, pro nízké kmitočty je nepřesná. Druhá metoda je vhodná naopak k měření nízkých frekvencí a je nepřesná pro vysoké kmitočty. Zde uvedený popis obě metody kombinuje a umožňuje přesné měření frekvencí v plném rozsahu. Metoda byla realizována k přesnému měření frekvencí (až na 7 číslic) v rozsahu 1 Hz až 100 MHz s procesorem ATmega8, pracujícím na frekvenci 16 MHz.

Klasická metoda měření frekvence spočívá v počítání pulsů za daný časový interval, typicky za 1 sekundu. K tomu stačí jednoduchý čítač reagující na hranu pulsu. Měříme-li frekvenci 10 MHz po dobu 1 sekundy, dosáhneme teoretické přesnosti 7 platných číslic (zatím neuvažujme přesnost časového normálu). Ovšem tato přesnost s klesající frekvencí rychle klesá. Při měření frekvence 1 Hz jsme schopni získat přesnost jen 1 platnou číslici, protože za dobu 1 sekundy čítač napočítá pouze 1 impuls. Přesnost bychom mohli zvýšit prodloužením měřicího intervalu, ale ten nemůžeme prodlužovat do nekonečna. A také komu by se na přesný výsledek chtělo čekat hodinu.

Budeme-li naopak měřit periodu pulsů, použijeme referenční čítač o vysoké frekvenci, který bude počítat tiky mezi dvěma hranami signálu. Použijeme-li čítač běžící na frekvenci 10 MHz, můžeme získat při měření frekvence 1 Hz (perioda 1 sekunda) přesnost 7 platných číslic. Ovšem s rostoucí frekvencí přesnost rychle klesá. Při měření frekvence 10 MHz získáme přesnost jen 1 číslici, protože referenční čítač napočítá mezi dvěma hranami pouze 1 impuls. Teoreticky bychom mohli zvýšit rozlišení zvýšením frekvence referenčního generátoru, ale v tom mohou být naše možnosti už dost omezené. Navíc tato metoda může mít nízkou odolnost proti rušení - hrana může být modulována šumem a to může u vyšších frekvencí způsobit značné zkreslení přesnosti.

Zkombinujeme-li obě metody, použijeme 2 čítače. Po danou dobu (např. 1 sekunda) budeme počítat pulsy a současně změříme dobu mezi hranou prvního a posledního impulsu. Měření času realizujeme zápisem časového razítka při první a poslední hraně t1 a t2. Přesnou frekvenci pak vypočteme vydělením napočtených pulsů naměřeným časem t2 - t1.

Metoda byla realizována s procesorem ATmega8 s frekvencí krystalu 16MHz. Vstupní signál je do procesoru přiveden na 2 vstupy. Vstup T0 je vstup do 8-bitového čítače Timer0, který slouží k čítání pulsů. Vstup ICP je vstup do 16-bitového čítače Timer1, kterou slouží k měření intervalu mezi pulsy. Vstup do měřiče je veden do procesoru jednak přímo, a jednak přes předděličku 74HC393. Přepínání vstupu se děje automaticky, pomocí analogového multiplexeru, v závislosti na naměřené frekvenci.

Na první pohled zní popisovaná metoda jednoduše, ovšem je zde jedno znesnadňující úskalí - synchronizace čítačů. Nemůžeme předpokládat, že při čtení stavu čítačů proběhl záznam stavu současně. Strefíme-li se do okamžiku, kdy se jeden čítač updatoval a druhý ještě ne, může to znamenat velkou chybu měření. Druhým zádrhelem je rozsah čítačů, které nemají dostatečně velký rozsah k měření celého intervalu a je potřeba synchronizovat čítač přetečení s hardwarovým čítačem. Podívejme se na obsluhu čítačů podrobněji.

Základ obsluhy čítačů je jednoduchý. Čítače při přetečení inkrementující globální proměnné, představující vyšší rozsahy čítačů. Čítač Timer0 čítá s inkrementací 256, protože se později do jeho nejnižšího bajtu uloží aktuální stav čítače.

// 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++;
}

Hlavní práce proběhne v obsluze časového razítka čítače Timer1, který při hraně vstupního signálu zaznamená synchronní aktuální stav obou čítačů a ošetří kritické stavy. Přerušení nastane těsně po příchodu hrany, a to zajistí, že hrana je v obou čítačích již zaznamenána. Samozřejmě obsluhu časového razítka nelze volat neustále, procesor by se při vyšší frekvenci signálu zahltil, je aktivována pouze na začátku a na konci měřicí periody.

Nejdříve obsluha načítá stav měřiče času Timer1. Načte záznam času poslední hrany z ICR1 (časové razítko) a čítač přetečení čítače TCounter. V tomto okamžiku obsluha neví, zda záznam času v ICR1 nastal před započtením posledního přetečení v TCounter, nebo po něm. Testuje stav příznaku přetečení Timer1 a pokud je tento příznak nastaven a současně je hodnota časového razítka nízká, znamená to, že razítko bylo zaznamenáno těsně po přetečení čítače Timer1, ale stav přetečení nebyl zanesen do čítače TCounter. Je proto provedena oprava a čítač TCounter je inkrementován. Oba údaje jsou sloučeny do výsledné hodnoty časového razítka TCounterRes.

// 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;
}

Dále je obslouženo čtení pulsů z čítače Timer0. Je načten stav čítače Timer0 TCNT0 a čítač přetečení FCounter. Je-li nastaven příznak přetečení čítače Timer0 a současně je načtená hodnota čítače malá, znamená to, že čítač přetekl, ale čítač přetečení nebyl updatován, proto je provedena jeho korekce. Z načtených hodnot je sestavena hodnota čítače frekvence FCounter.

Povšimněme si dále výpočtu výsledné frekvence a periody. Měření probíhá po dobu asi 1 sekundy (s timeoutem čekání na hranu 2 sekundy). K tomu se využívá třetí čítač, čítač systémového času Timer2, ale jeho funkce není pro metodu podstatná. Každou 1 sekundu se zaznamená stav časového razítka a provede se výpočet. Čítače běží nepřetržitě stále dál a pracuje se pouze s aktuální a předešlou hodnotou časového razítka, čímž je možné každou sekundu aktualizovat zobrazený výsledek, a to i u pomalé frekvence 1 Hz. Z rozdílu aktuálního a předešlého čítače pulsů se zjistí počet napočtených pulsů za měřenou periodu. Z rozdílu aktuálního a předešlého čítače času se zjistí uběhlá doba mezi časovými razítky.

Výpočet se provede v následující funkci. A protože v procesoru ATmega by byly obtížné výpočty s float aritmetikou, je výpočet proveden s celočíselnou matematikou u64. Naměřený počet tiků čítače pulsů se nejdříve vynásobí frekvencí procesoru. Tím je zajištěno vydělení naměřeného času frekvencí procesoru, pro obdržení reálného naměřeného času. Výsledek se normalizuje - posune na maximální bitovou pozici, aby se zajistila maximální přesnost výsledku. Poté se vydělí naměřeným intervalem, čímž se vypočte výsledná frekvence. Výsledek se normalizuje do rozsahu čísla u32. Normalizace se provádí násobením a dělením číslem 10, přitom se updatuje čítač dekadického řádu fscale. To se využije později k zobrazení výsledku. Podobně se vypočte perioda signálu, ovšem jako převrácená hodnota frekvence.

// 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;

Na závěr zmínka o kalibraci krystalu, která je využitelná i pro jiné projekty. Výrobce uvádí přesnost krystalu při pokojové teplotě 50 ppm (= odchylka 0,005%). To nám dává reálnou přesnost naměřeného údaje asi 5 číslic. Kalibrací frekvence krystalu můžeme přesnost zvýšit o řád, na 6 číslic, krátkodobě až na 7 číslic (ale i bez maximální přesnosti je zobrazení údaje na 7 číslic užitečné - při sledování změn frekvence). Přesná kalibrace frekvence a času je v domácích podmínkách nejsnáze dosažitelná, protože máme k dispozici velmi přesný referenční normál - hodiny přesného času, dostupné např. přes Internet. Na stránkách projektu je ke stažení program ClockTest. Jeho funkce spočívá v tom, že zobrazuje uběhlý čas, podle přednastavené frekvence krystalu procesoru, a zobrazuje i odchylku od celé minuty. Kalibraci provedeme tak, že program spustíme v přesně známý čas. Po určité době, např. po 1 dni, odečteme v druhý přesně známý čas údaj času na displeji. Opravenou frekvenci krystalu vypočteme vztahem F = F_CPU * čas_indikovaný / čas_skutečný. Zaneseme ji do parametru F_CPU v MAKEFILE zdrojového kódu měřiče, upravený program přeložíme a nahrajeme do procesoru. Od té chvíle bude procesor počítat s novou přesnou hodnotou krystalu a bude měřit přesněji.

Odkaz na celý projekt měřiče frekvence (s podrobným popisem konstrukce): ../freqmeter/index.html

Download této www stránky: FreqMeterInfo.zip

Miroslav Němeček

<< Zpět