Motivace

AD převodník je jednou z nejdůležitějších periferií mikrokontroléru. Tvoří most mezi analogovým a digitálním světem. Využití najde snad kdekoli. Jednoduchým měřením napětí akumulátoru počínaje a záznamem audiosignálu konče. Není proto od věci podrobit ho důkladnější prohlídce.

Úvodem

Vzhledem k důležitosti a širokým aplikačním možnostem AD převodníku bude tento návod poněkud obsáhlejší. Ani ne tak množstvím informací, ale spíše množstvím příkladů. Možnostem a obecné práci s AD převodníkem se bude věnovat úvodní teoretická část. Série příkladů pak bude předvádět různá uplatnění vybraných funkcí AD převodníku. Bude na vás, které příklady si prohlídnete nebo vyzkoušíte.

Jisté základní znalosti obecně o AD převodu by jste již měli mít a pokud nemáte, brouzdejte po webu. Je to problematika natolik rozsáhlá, že se jí nebudu věnovat. Převodník na Atmelu je 10bitový, rozděluje tedy nějáké referenční napětí (o něm později) na 1024 hladin. Vzhledem k povoleným mezím referenčních napětí se rozlišení bude pohybovat v rozsahu 1-4mV. Převodník dle mých zkušeností netrpí žádným větším "šumem", takže v rozumných podmínkách je opakovatelnost měření veliká (v podstatě +-1LSB). Jinak řečeno výsledek měření se náhodně mění jen v rozsahu jednoho kroku. Na veškerou matematiku s AD převodníkem vám bude stačit trojčlenka, kterou předpokládám ovládáte ;)

Napájení AD převodníku

K napájení AD převodníku slouží pin AVCC. Musí na něm být připojeno defakto stejné napětí jakým napájíte Atmel (tedy stejné jako na VCC). Přesněji řečeno nesmí se lišit o víc jak 0.3V. Možná se zeptáte, proč je tedy samostatně vyvedené ? Samotné napájecí napětí Atmelu totiž může být zarušené, digitálním obvodům to většinou nevadí, ale analogové obvody by trpěly. Samostatný AVCC pin vám má umožnit přivést stabilní nezarušené napětí pro AD převodník (například skrze LC filtr).

Reference AD převodníku

Referencí se rozumí napětí vůči němuž převodník vztahuje své výsledky. Převodem získáte zlomek (číslo) z referenčního napětí. Z něj pak hodnotu napětí počítáte. Jinak řečeno jestliže, vám jako výsledek převodu vyjde 256, víte že měřené napětí je 256/1024 (dvě stě padesát šest tisíc dvaceti čtvrtin :D ) z referenčního napětí. Pokud je referenční napětí třeba 4V pak se jedná o 256*4V/1024 = 1V. A to je prosím ta zmiňovaná trojčlenka. Matematicky zapsáno
U = ADC_hodnota*Vref/1024
Kde U je hodnota měřeného napětí, ADC_hodnota je výsledek převodu a Vref je hodnota referenčního napětí.
Pro jistotu uvedu dva příklady
a) Referenční napětí je 1.1V, výsledek převodu je 150, Měřené napětí tedy je U=150*1.1/1024 = 161mV
b) Referenční napětí je 5V, výsledek převodu je 873, měřené napětí tedy je U=873*5/1024 = 4.26V
Referenční napětí tedy limituje měřící rozsah, měřit lze jen napětí nižší jak referenční. Nejvyšší měřitelné napětí je Vref*1023/1024. Převodník totiž měří od nuly a výsledek převodu může být nejvýše 1023. Pokusíte-li se měřit vyšší napětí než je reference, výsledek bude stále 1023. Reference má velký význam, mimo jiné určuje nejmenší napěťové rozlišení převodníku. S referencí 1.1V můžete rozlišit přibližně 1mV, s referencí 5V přibližně 4mV. Obecně se vyplatí měřit vždy s nejnižší možnou referencí, pak je měření nejcitlivější. V Atmelu si totiž referenci můžete vybírat a běžně máte k dispozici tři až čtyři varianty. Volit si ji proto můžete u každé aplikace jinak. Referencí pro převodník může být:

Některé atmely mají interní referenci 1.1V (Attiny24), některé 2.56V (Atmega16) a některé obě (Atmega644). Vnitřní reference jsou výhodné proto, že pracují bez jakéhokoli vnějšího obvodu. Jejich hodnota není nejpřesnější (+-0.1V) a je dobré si je u každého Atmelu změřit a zapsat do programu. Napájecí napětí jako reference poslouží ve specifických případech (měření polohy potenciometru, měření hodnoty napájecího napětí atd.). Externí reference sice vyžaduje vnější obvod (například TL431) ale umožňuje zvolit si libovolnou hodnotu (větší jak 2V) a v případě přesnějších měření je dobrou volbou. Více se o využití těchto čtyř variant dozvíte u jednotlivých příkladů. Reference se vybírá pomocí bitů REFS1 a REFS0 v registru ADMUX. Všechny reference krom externí vyžadují aby byl na AREF připojen filtrační kondenzátor (typicky 10nF-100nF, proti GND). Po zvolení interní reference si její napětí můžete na tomto pinu změřit, nebo ji použít nějakým vnějším obvodem. Pokud vnitřní referenci zapnete (ať už spuštěním AD převodníku nebo komparátoru nebo Brown-out detektoru) musíte počkat nejméně 70us než naběhne. Krom toho musíte počkat než se filtrační kondenzátor na AREF nabije na příslušnou hodnotu (na to musíte čekat i když vnitřní referenci třeba jen změníte). Ta doba může být různá a v datasheetu ji nenajdete. Já si ji změřil. Při přepnutí z interní 2.56V na AVCC trval přechod přibližně 20us. Ale naopak při přepnutí z AVCC na interní 2.5ms. Většinou ale referenci za chodu měnit nebudete.

Clock AD převodníku

Clock ADC převodníku se odvíjí od clocku procesoru a měl by ležet rozsahu 50-200kHz, pokud chceme využívat plných 10 bitů jeho rozlišení. Pokud se obejdeme s nižším rozlišením, můžeme jej taktovat na vyšších frekvencích. K jeho nastavení vám slouží předdělička, která se nastavuje snad u všech Atmelů v registru ADCSRA pomocí bitů ADPS2, ADPS1 a ADPS0. Pokud na měření nespěcháte nebo máte signál s vyšší výstupní impedancí (tzn signál, kterému vadí když jej zatěžujete i malým proudem), taktujte raději nižší frekvence. Pokud naopak spěcháte a potřebujete vyšší vzorkovací frekvenci, smíte podle datasheetu jít až na 1MHz. Testoval jsem však i na 2MHz s dobrým výsledkem (viz příklad I)). Samotný převod trvá 13 cyklů. Výjimkou je úplně první převod po spuštění AD převodníku, tam probíhá jeho inicializace a celkem trvá 25 cyklů. Pokud vás kdy budou zajímat tak si detaily o časování samotného převodu najdete v datasheetu.

Spouštění (triggrování) převodu

Všem atmelům je možné převod spouštět "manuálně", zápisem log.1 do bitu ADSC v registru ADCSRA. Chudší Atmely jako třeba Atmega8 už ani jiné možnosti nemají. Ale u vybavenějších modelů je možné spouštět převod nějakou jinou periferií, jako třeba externím přerušením, komparátorem nebo časovačem. Díky tomu můžete mít přesnou kontrolu nad okamžikem převodu (nebo přesněji řečeno nad okamžikem vzorkování). Kromě toho máte možnost nechat AD převodník běžet v tzv. free-running módu. V něm se neustále vzorkuje a převádí napětí na jednom vybraném kanále a vy tak máte k dispozici vždy tu nejčerstvější hodnotu. Detailně o tom opět v příslušných příkladech. Po dobu převodu je bit ADSC držen v log.1, jakmile převod skončí vynuluje se, takže jej můžete využít při čekání na dokončení převodu. Automatické spouštění se povoluje bitem ADATE v registru ADCSRA. Zdroj spouštěcího signálu se vybírá bity ADTS2,ADTS1 a ADTS0. Ty se nachází v různých registrech. U Atemega16 v registru SFIOR u Atmega644 v registru ADCSRB, takže vám někdy nezbude než kouknout do datasheetu.

Přerušení od AD převondíku

Po dokončení AD převodu může převodník vyvolat přerušení v jehož rutině si pak můžete výsledek vyzvednout a zpracovat. Tato možnost se pro vás stane v podstatě nezbytnou pokud využíváte převodu spouštěného nějakou vnější událostí (ať už pravidelně nebo jednorázově). Přerušení se povoluje bitem ADIE v registru ADCSRA. I když není povoleno tak o příchodu události, která by přerušení spustila informuje vlajka ADIF která se nachází ve stejném registru. Stejně jako se všemi vlajkami se maže zápisem jedničky nebo zavoláním rutiny přerušení.

Multiplexer

Asi všechny Atmely jsou vybaveny analogovým multiplexerem, díky němuž můžete přepínat AD převodníku vstupy. Těch bývá různé množství, u větších Atmelů většinou osm. Označují se ADC0 až ADC7 a jejich rozmístění najdete v datasheetu. Krom toho ale umožňuje multiplexer u většiny Atmelů připojovat k AD převodníku i některé interní vstupy. Jako například reference nebo teplotní čidla. Multiplexer se ovládá bity MUX4MUX0 v registru ADMUX a jejich množství se může podle čipu lišit. V datasheetu je vždy přehledná tabulka popisující všechny možné kanály multiplexeru.

Zesilovač a diferenciální měření

Některé Atmely mají AD převodník vybavený možností měřit diferenciálně (tedy rozdíl napětí mezi dvěma kanály). A typicky pak i zesilovač, který umožňuje před měřením tento rozdíl zesílit. Zesílení mají podle typu Atmelu různé volitelné hodnoty od 2x až do 200x. Tato možnost vám v mnoha situacích zjednoduší práci a ušetří vám na desce externí zesilovač. Měření v těchto režimech ale vyžaduje jistou míru opatrnosti (vstupní signál by neměl obsahovat složky vyšší jak 4kHz a pod.). Detaily o použití se dozvíte u příslušných příkladů.

Obecné vlastnosti

Hrubě jsme načrtli základní možnosti AD převodníku a bity, které ho řídí. Přece jen nám ale zůstalo pár ovládacích prvků, které nikam nezapadají. Jednak je to bit ADLAR v registru ADCSRA, ten slouží k zarovnání dat. Výsledek převodu je 10bitový a máte možnost ho zarovnat doprava (na to jste zvyklí) a nebo ho zarovnat doleva. To má hned dva důvody. Jednak pokud chcete pracovat pouze s 8 bitovou hodnotou, můžete si tím usnadnit vyčítání výsledků převodu (i když v Céčku to asi nepoznáte). Za druhé, a to je asi hlavní důvod, umožní vám to lépe převádět výsledek na znaménkový typ. Běžně je převod přirozeně neznaménkový (prostě je hodnota kladná), ale v případě diferenciálního měření už je znaménko potřeba, protože rozdíl napětí na dvou kanálech může být i záporný. Výsledek převodu se nachází v registrech ADCL a ADCH o jejichž korektní vyčtení (nejprve ADCL a hned potom ADCH) se v našem případě postará překladač a v céčku můžeme přistupovat k "registru" ADC. U některých čipů se ještě můžete setkat s možností vypnout vstupní buffery. Co to je a proč ho vypínat se dozvíte v článku o komparátorech.

Co je ve kterých příkladech

První sadu příkladů, na které se naučíme pracovat s referencemi, základní matematiku a přepínat kanály, si předvedeme na Atmega8. Jedná se o příklady A) až E). Je to takový běžný základ se kterým si většina z vás vystačí. Pokud s AD převodníkem začínáte, projděte si je všechny. Všechny tyto příklady by měly být přenositelné na další čipy. V další sadě, tentokrát s Atmega16 si vyzkoušíme možnosti spouštění převodu (příklady H a I). Opět budou povětšinou přenositelné na jiné čipy. Ne však třeba na Atmega8, protože ta vůbec možnosti automatického spouštění převodu nemá. Zacházení s diferenciálními kanály a zesilovači si předvedeme na Atmega644, protože ta je jimi vybavena i v DIP pouzdře (příklady F a G).

Na čem zkoušet ?

Dobrou otázkou je na jakém přípravku zkoušet uvedené příklady ? První vás asi napadne nepájivé (kontaktní) pole, ale to není dobrá volba. Ze zkušenosti můžu říct, že kontakty v nepájivém poli jsou hodně nespolehlivé. Mezi součástkami a polem vznikají přechodové odpory v řádu jednotek až desítek ohmů. Tekoucím proudem na nich vznikají úbytky napětí a běžně dosahují 30-50mV. Tyto jevy zásadně narušují výsledky měření. A to nemluvím o tom, že se úbytky mění jak s polem manipulujete. Proto doporučuji tyto příklady testovat buď na nějakých vývojových deskách, ať už EvB, Arduinech nebo obecně na pájecích prototypových deskách. Podstatné je aby byly klíčové spoje (Ty jimiž teče nějaký proud) spájeny. Že to má smysl pochopíte většinou velice brzy. Představte si, že chcete sestavit jednoduchý teploměr z obvodu LM35 nebo LM335. Jeho výstupní napětí je úměrné teplotě s koeficientem 10mV/°C. Nejistoty v řádu +-30mV by měly za následek nejistoty v teplotě +-3°C (!), přitom čidlo je schopné detekovat změny klidně setin stupně Celsia.

Příklady

A) Atmega8A - jednoduchý převod s VREF=AVCC

Tento příklad demonstruje použití převodníku s referencí AVCC. Na AVCC je napájecí napětí čipu. To běžně nebývá přesně známé a může měnit svoji hodnotu s teplotou nebo odběrem (3.3V může klidně kolísat v rozsahu 3.2-3.4V). Takže typicky jeho hodnotu nebudete moc dobře znát. S takovou referencí pak není možné přesně měřit napětí. Proč tedy něco takového používat jako referenci ? Existují hned dva důvody. Za prvé, ne vždy potřebujete měřit přímo napětí ! Může vás zajímat třeba natočení potenciometru (například v joysticku). V takovém případě připojíte potenciometr mezi VCC a GND a měříte napětí na jezdci potenciometru. V konečném důsledku vás ale nezajímá číselná hodnota napětí na jezdci ale její poměr k VCC, protože ten určuje míru otočení potenciometru. Poměr napětí na jezdci proti VCC je ale přesně to co vám poskytuje převodník s referencí rovnou VCC ! Takže výsledná hodnota v rozsahu 0-1024 vyčtená z převodníku přesně odpovídá natočení potenciometru a jakýkoli přepočet na napětí není potřebný. Krom toho se tato hodnota nemění s kolísajícím napájecím napětím. Jestliže napájecí napětí klesne, klesne i napětí na jezdci potenciometru a jejich poměr zůstane stejný (to je smysl potenciometru). To je asi nejtypičtější využití. Druhý případ kdy využijete toto nastavení reference jsou situace kdy potřebujete přímo měřit napětí v rozsahu 0-VCC a nechcete je dělit odporovým děličem (ať už vás k tomu vede lenost, nedostatek místa na DPS nebo nějaké ušlechtilejší důvody). Vaše měření pak bude trpět výše zmíněnými nedostatky, tedy nepřesně známou referencí a tudíž i nepřesným měřením. Což se naštěstí dá částečně léčit (viz příklad G).

V tomto případě je potřeba připojit na AREF kondenzátor (typicky 100nF) proti GND (viz schéma 1). Po zvolení reference by jste měli na AREF naměřit stejné napětí jako na AVCC. V tomto příkladě (tak jako ve většině ostatních) budeme výsledek převodu posílat UARTem do PC a číst si ho v terminálu. Kdo se s UARTem nesetkal, přečtěte si návod zde. K odesílání spotřebujeme pin TX (PD1) a měřené napětí budeme přivádět na pin ADC1 (PC1), na který si připojte jezdec potenciometru (jeho konce připojte k VCC a GND). Protože přesnou hodnotu AVCC neznáme, budeme výsledek uvádět proporcionálně v procentech (měříme míru natočení potenciometru). Protože je ale převodník 10bitový, tedy jeho rozsah čítá 1024 hodnot a procentních bodů je jen 100, budeme interně měřit polohu potenciometru v promile a procentní výsledek vypíšeme i s jedním desetinným místem. Matematika schovaná za výpočtem promile je pouhá trojčlenka a věřím že ji zvládnete :) Nezapomeňte, že při aritmetice s integery je potřeba dávat pozor na přetečení, proto ve výpočtu přetypovávám hodnotu na 32bit integer (aby se do něj výsledek násobení vešel).


Schéma 1 - použití AVCC jako reference vyžaduje připojení 100nf kondekzátoru na AREF

// A) Atmega8A - jednoduchý převod, REF=AVCC
#include <avr/io.h>
#define F_CPU 8000000
#include <util/delay.h>
#include <stdio.h> // kvůli fci printf_P()
#include <avr/pgmspace.h> // kvůli fci printf_P()

int usart_putchar(char var, FILE *stream);  // odesílání UARTem
void setup_uart(void); // nastavení parametrů UARTu

static FILE mystdout = FDEV_SETUP_STREAM(usart_putchar, NULL, _FDEV_SETUP_WRITE);
uint16_t hodnota, promile; 

int main(void){
	setup_uart(); // nastavení komunikace
	ADMUX = (1<<REFS0) | 0b0001; // reference AVCC, měření na kanálu ADC1
	ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1); // povolit ADC, clock pro ADC F_CPU/64	
	while(1){
		ADCSRA |= (1<<ADSC); // spustit převod
		while(ADCSRA & (1<<ADSC)){} // čekej dokud je ADSC=1 (probíhá převod)
		hodnota=ADC; // vyčti výsledek převodu
		promile = (uint16_t)((uint32_t)hodnota*1000/1023); // trojčlenka 	
		printf_P(PSTR("ADC = %u, %u.%u%%\n\r"),hodnota,promile/10,promile%10);
		_delay_ms(700); // ať máme dost času si výsledek v terminálu přečíst
	}
}

void setup_uart(void){
stdout = &mystdout; // nastavení standardního výstupu na naši funkci usart_putchar()
UBRRL = 51; // baud rate 9600 s clockem 8MHz
UCSRB = (1<<TXEN); // zapnout UART vysílač
UCSRC = (1<<URSEL) | (1<<UCSZ0) | (1<<UCSZ1); // formát zprávy "8N1"	
}

int usart_putchar(char var, FILE *stream) {
while (!(UCSRA & (1<<UDRE))); // čekej než se odešle poslední znak
UDR = var; // odešli další znak
return 0;
}

B) Atmega8A - měření s externí referenci (TL431)

Tento příklad slouží jako ukázka použití externího referenčího napětí. Tato, na externí součástky náročnější, varianta slouží typicky k přesnějším měřením. Má hned dvě hlavní výhody. Díky tomu, že si referenční napětí volíte sami, volíte si i rozsah měření. Jestliže o měřeném signálu víte že se bude měnit v rozsahu 0-2V můžete použít externí referenci o hodnotě 2V a měřený signál se tak bude pohybovat v celém měřícím rozsahu převodníku. Získáte tím nejvyšší možné rozlišení. Naopak pokud bude signál v rozsahu například 0-4V, nic vám nebrání připojit si externí referenci o hodnotě 4V čímž opět přizpůsobíte rozshah převodníku měřenému signálu. K čemu to je ? Představte si že měříte signál v rozsahu 0-1V převodníkem s referencí 0-5V. Výsledek převodu může v takovém případě nabývat hodnot pouze v rozsahu 0-205. Měřený signál tedy rozdělíte pouze na 205 úrovní a měření bude hrubé. Referenci se proto snažte volit co nejmenší (ale tak aby ji měřený signál nepřekročil). Druhá výhoda externí reference je v přesnosti. Interní reference mají dost nepřesnou hodnotu a je potřeba ji ručně měřit a korigovat v programu. Jako externí referenci můžete použít jeden z mnoha integrovaných obvodů (TL431, LM336, LM4040,MCP1541, REF191 atd.), jejichž výstupní napětí je jasně definované a předem známé. My jako externí referenci použijeme obvod TL431. Jeho výstupní napětí jde pomocí děliče upravovat. Na schématu 2 vidíte způsob zapojení. Rezistory R3 a R4 slouží k hrubému nastavení referenčního napětí někde okolo 4V, trimr R2 pak slouží k jemnému doladění hodnoty. Já ji naladil přesně na 4.096V (je to pěkně dělitelné 1024). Až ji budete ladit, připojte voltmetr mezi AREF a GND a otáčejte trimrem R2 tak dlouho než dosáhnete vámi požadované hodnoty. Rozsah přeladění by měl být přibližně 3.95-4.5V. Měřený signál přivádějte na ADC1.


schéma 2 - Připojení externí reference k Atmelu. R1,R2,R3,R4 a VR1 tvoří obvod reference. Jehož výstup je přiveden na AREF

Program je celkem přímočarý, externí referenci nastavujete nulováním bitů REFS0 a REFS1. Já je explicitně nenuluji, protože po restartu Atmelu jsou vynulované. V multiplexeru (ADMUX) zvolíme kanál ADC1. Nastavením bitu ADEN spustíme převodník, clock převodníku volíme v povoleném rozsahu, z 8MHz děličkou 64 dostáváme 125kHz. Převod spouštíme nastavením bitu ADSC a čekáme dokud je tento bit nastaven. Jakmile je jeho hodnota log.0 víme že převod skončil a můžeme vyzvednout výsledek z "registru" ADC. Pak už zbývá jen trojčlenka a přepočet na napětí. Hodnotu referenčního napětí můžete specifikovat v makru VREF. Protože počítáme s celými čísly, která ale reprezentují čísla desetinná, musíme si zvolit pevně pozici desetinné čárky. Jinak řečeno celou aritmetiku jsem se rozhodl provádět v jednotkách mV. Volba je přirozeně na vás. Nikdo vám nebrání vyjadřovat napětí v 10mV, ale přišli by jste tak o část informace, protože rozlišení převodníku při referenci 4.096V je 4mV. Výpočty s celými čísly je potřeba řešit tak aby se "ztrátové" operace prováděly buď co nejméně nebo alespoň co "nejpozději". To je přirozeně téma na delší povídání, tak jen lehce nastíním o jaký problém jde. Sledujte příklad

Zvláště pokud je výpočet komplexnější tak se nevyplatí rozdělovat ho na mezivýsledky. Lepší je s tužkou na papíře spočítat obecný vztah a pak ho uspořádat tak aby se eliminovaly "zaokrouhlovací" chyby. Vyjma toho je příklad nezáludný. K odeslání výsledků do PC je využit USART s jehož použitím se můžete seznámit zde.

// B) Atmega8A - měření s externí referenci 4.096V 

#include <avr/io.h>
#define F_CPU 8000000
#include <util/delay.h>
#include <stdio.h> // kvůli fci printf_P()
#include <avr/pgmspace.h> // kvůli fci printf_P()

#define VREF 4096 // napětí externí reference v mV

int usart_putchar(char var, FILE *stream);  // odesílání UARTem
void setup_uart(void); // inicializace UART rozhraní

static FILE mystdout = FDEV_SETUP_STREAM(usart_putchar, NULL, _FDEV_SETUP_WRITE);
uint16_t  val,u; 

int main(void){
	setup_uart(); // nastavení komunikace
	ADMUX = 1; // externí reference na AREF (REFS1=0,REFS0=0), kanál ADC1
	ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1); // povolit ADC, clock pro ADC F_CPU/64
	
	while(1){		
		ADCSRA |= (1<<ADSC); // spustit převod
		while(ADCSRA & (1<<ADSC)){} // čekej dokud je ADSC=1 (probíhá převod)
		val = ADC; // výsledek měření na ADC1
		u = (uint16_t)((uint32_t)val*VREF/1023); // přepočíst na napětí
		printf_P(PSTR("val = %u, u= %u mV\n\r"),val,u);  // odeslat do PC
		_delay_ms(700);
	}
}

void setup_uart(void){
	stdout = &mystdout; // nastavení standardního výstupu na naši funkci usart_putchar()
	UBRRL = 51; // baud rate 9600 s clockem 8MHz
	UCSRB = (1<<TXEN); // zapnout UART vysílač
	UCSRC = (1<<URSEL) | (1<<UCSZ0) | (1<<UCSZ1); // formát zprávy "8N1"	
}

int usart_putchar(char var, FILE *stream) {
	while (!(UCSRA & (1<<UDRE))); 
	UDR = var; 
	return 0;
}

C) Atmega8A - měření s interní referencí 2.56V a s průměrováním

Na tomto příkladě si předvedeme jak používat interní referenci Atmelu. Jestliže se nechcete obtěžovat s připojováním externí reference, případně na ni nemáte na DPS místo, můžete využít referenci interní, kterou je vybaven nejspíš každý Atmel. Její použití vás do jisté míry limituje. V prvé řadě vám omezuje rozsah měření, protože jak jsem již nastínil v úvodu, měřené napětí musí být nižší jak referenční. Mnohdy vás to může donutit snižovat měřená napětí odporovým děličem (který se špatně kalibruje) a obecně tak snižovat přesnost měření. Krom toho je interní reference dosti nepřesně určená. Typicky ji datasheet uvádí s rozptylem +-0.1V. Naštěstí její hodnota příliš nekolísá s teplotou ani s provozním napětím čipu, takže když ji jednou změříte, už se s ní dále dá zacházet jako s relativně přesnou referencí. Problém je v tom, že se liší kus od kusu, takže ji musíte měřit u každého konkrétního Atmelu. I přes to je ale její použití široce rozšířené (ne vždy potřebujete měřit přesnou hodnotu napětí). Ošetření pinu AREF je stejné jako v příkladě A), tedy kondenzátorem 10-100nF mezi AREF a GND. Jakmile referenci zapnete můžete si její hodnotu na pinu AREF změřit. Výsledek měření je pak vhodné využít v programu pro výpočet napětí. V mém případě měla interní reference hodnotu 2.53V.

Aby program nebyl tak nudný, ukážeme si v něm mimo jiné i ukázku toho jak drobně zvýšit přesnost měření průměrováním. Průměrování má smysl tam kde je měřená hodnota zarušená (obsahuje náhodný šum). Rozhodně to není samospásný prostředek ke zvýšení přesnosti. Klidný a nezašumněný signál běžně průměrováním nezlepšíte (i když by se vám na první pohled mohlo zdát že ano). Teď ale přejděme ke zdrojovému kódu. Nastavením bitů REFS0 a REFS1 vybereme interní referenci jako referenci pro převodník. Po nastartování AD převodníku (bitem ADEN) se spustí i interní reference a potřebuje 70us na svoje ustálení. Clock pro převodník volíme pomocí bitů ADPSn na 125kHz. Celý proces měření i s průměrováním řeší funkce zmer. Prvním argumentem je kanál na němž chceme měřit. U Atmega8 máme k dispozici defakto 8 vnějších kanálů (ADC0 až ADC7). V našem příkladě volíme kanál ADC1. Druhým argumentem je počet vzorků z nichž má funkce průměrovat. Typicky stačí 8 nebo 16 vzorků. My volíme 32. Maximální množství vzorků je pro tuto funkci 64. To je dáno rozsahem uint16_t. Pokud bychom zvolili pro proměnnou tmp větší formát, mohli bychom průměrovat větší množstvím vzroků, ale nemělo by to asi valný význam. Funkce nejprve ošetří vstupy, pak zapisem do registru ADMUX zvolí kanál. Číslo kanálu je také pro jistotu ošetřeno (maskou 0x0f) aby uživatel zadáním nevhodných hodnot nezpůsobil zbytečný problém. Dále se ve forcyklu do proměnné tmp přičítají výsledky převodu. Po načtení požadovaného množství vzorků se spočte aritmetický průměr a funkce výsledek vrátí. Ten je v hlavní smyčce zpracován a odeslán do PC pomocí USART (více o USARTu zde).

// C) Atmega8A - měření s interní referencí 2.56V a s průměrováním

#include <avr/io.h>
#define F_CPU 8000000
#include <util/delay.h>
#include <stdio.h> // kvůli fci printf_P()
#include <avr/pgmspace.h> // kvůli fci printf_P()

#define VREF 2530 // změřená hodnota interní reference v mV

int usart_putchar(char var, FILE *stream);  // odesílání UARTem
void setup_uart(void); // inicializace USART
uint16_t zmer(char kanal, char vzorku); // měří a průměruje

static FILE mystdout = FDEV_SETUP_STREAM(usart_putchar, NULL, _FDEV_SETUP_WRITE);
uint16_t  val,u;

int main(void){
	setup_uart(); // nastavení komunikace
	ADMUX = (1<<REFS0) | (1<<REFS1); // vybírá referenci interní 2.56V
	ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1); // povolit ADC, clock pro ADC F_CPU/64
	_delay_us(70); // počkej než se nastartuje reference
	
	while(1){		
		val=zmer(1,32); // změř napětí na kanálu 1 a průměruj ze 32 měření
		u = (uint16_t)((uint32_t)val*VREF/1023); // vypočti hodnotu napětí
		printf_P(PSTR("val = %u, u= %u mV\n\r"),val,u);  // odešli ji do PC
		_delay_ms(700);
	}
}

// kanal - vybírá kanál ADC (0 až 7) 
// vzorku - počet měření z nichž se bude průměrovat (max 64)
uint16_t zmer(char kanal, char vzorku){
char i;
uint16_t tmp=0;

if(vzorku>64){vzorku=64;} // ošetření vstupních dat
ADMUX = (1<<REFS0) | (1<<REFS1) | (kanal & 0x0f); // vybírá kanál, reference interní 2.56V
for(i=0;i<vzorku;i++){	
		ADCSRA |= (1<<ADSC); // spustit převod
		while(ADCSRA & (1<<ADSC)){} // čekej dokud je ADSC=1 (probíhá převod)
		tmp = tmp + ADC; // sčítá výsledky převodu
		}
return (tmp/vzorku);	// vypočítá průměr a vrátí ho	
}

void setup_uart(void){
	stdout = &mystdout; // nastavení standardního výstupu na naši funkci usart_putchar()
	UBRRL = 51; // baud rate 9600 s clockem 8MHz
	UCSRB = (1<<TXEN); // zapnout UART vysílač
	UCSRC = (1<<URSEL) | (1<<UCSZ0) | (1<<UCSZ1); // formát zprávy "8N1"	
}

int usart_putchar(char var, FILE *stream) {
	while (!(UCSRA & (1<<UDRE))); 
	UDR = var; 
	return 0;
}

D) Atmega8A - měření více kanálů

Tento příklad je celkem jednoduchý a předvádí měření na více kanálech (konkrétně na 5ti - ADC0 až ADC4) s referencí AVCC. Můžete ho využít v aplikacích kde obsluha zadává vstupní parametry pomocí potenciometrů (např ovladač k RC modelu). Jako referenci používá AVCC, která se hodí právě k výše zmíněnému účelu. Pro pin AREF platí totéž co v příkaladě A), tedy že je potřeba jej filrovat kondenzátorem 10-100nF proti GND. Clock převodníku opět volíme do povolených mezi (konkrétně na 125kHz). Celý převod pak obstarává funkce zmer. Jejím argumentem je ukazatel na pole, kam mám všech 5 změřených hodnot uložit. Z těla funkce je patrné že přepínání kanálů je přímočaré, stačí zvolit vhodnou kombinaci v ADMUX a spustit převod (nastavením ADSC). Počkat na dokončení převodu (dokud je ADSC v jedničce) a výsledek z registru ADC uložit.

// D) Atmega8A - měření více kanálů  

#include <avr/io.h>
#define F_CPU 8000000
#include <util/delay.h>
#include <stdio.h> // kvůli fci printf_P()
#include <avr/pgmspace.h> // kvůli fci printf_P()

int usart_putchar(char var, FILE *stream);  // odesílání UARTem
void setup_uart(void); // inicializace UART rozhraní
void zmer(uint16_t pole[]); // změří 5 hodnot a vrátí je v poli

static FILE mystdout = FDEV_SETUP_STREAM(usart_putchar, NULL, _FDEV_SETUP_WRITE);
uint16_t  val[5]; // pole pro výsledky měření

int main(void){
	setup_uart(); // nastavení komunikace
	ADMUX = (1<<REFS0); // reference AVCC
	ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1); // povolit ADC, clock pro ADC F_CPU/64
	
	while(1){		
		zmer(val); // změř všech 5 kanálů a ulož je do pole val
		printf_P(PSTR("%u, %u, %u, %u, %u\n\r"),val[0],val[1],val[2],val[3],val[4]);
		_delay_ms(700);
	}
}

void zmer(uint16_t pole[]){
char i;
for(i=0;i<5;i++){
	ADMUX = (1<<REFS0) | i; // nastavení měřeného kanálu 
	ADCSRA |= (1<<ADSC); // spustit převod
	while(ADCSRA & (1<<ADSC)){} // čekej dokud je ADSC=1 (probíhá převod)
	pole[i] = ADC; // ulož výsledek převodu
	}	
}

void setup_uart(void){
	stdout = &mystdout; // nastavení standardního výstupu na naši funkci usart_putchar()
	UBRRL = 51; // baud rate 9600 s clockem 8MHz
	UCSRB = (1<<TXEN); // zapnout UART vysílač
	UCSRC = (1<<URSEL) | (1<<UCSZ0) | (1<<UCSZ1); // formát zprávy "8N1"	
}

int usart_putchar(char var, FILE *stream) {
	while (!(UCSRA & (1<<UDRE))); 
	UDR = var; 
	return 0;
}

E) Atmega8A - měření hodnoty AVCC s využitím interní reference

Tento příklad ačkoli má trochu nesrozumitelný název skýtá celkem velký potenciál. Řekněme že vás napadla otázka. Může si čip sám sobě změřit napájecí napětí (třeba když je Atmel připojený přímo na akumulátor a chce zjistit stav jeho nabití) ? Vzhledem k tomu, že měřené napětí musí být vždy rovno nebo menší referenčnímu a to musí být menší nebo rovno napájecímu, mohlo by se na první pohled zdát že ne. Naštěstí to samozřejmně jde. První možností je připravit si dělič z napájecího napětí a měřit tedy jen jeho zlomek (třeba polovinu). Takový má dostatečně nízkou hodnotu aby jste mohli využít interní nebo externí referenci a změřit ho. Aby jste však mohli hodnotu napájecího napětí spočítat musíte znát přesně parametry děliče. Někteří z vás se ale budou chtít použití dalších dvou vnějších součástek vyhnout. A pro ně je tu druhá možnost :)

Podíváte-li se do datasheetu uvidíte že krom osmi vstupních kanálů (ADC0 až ADC7) může multiplexer připojit na vstup AD převodníku ještě GND a Bandgap referenci (VBG). Pro bandgap referenci platí to co pro interní reference převodníku, tedy že je celkem stabilní, ale její hodnota není předem přesně známa. Představte si že bychom její hodnotu ale přesně znali (a zanedlouho si ukážeme jak ji zjistit). V takovém případě můžeme změřit převodníkem známou hodnotu (VBG má v tomto případě přibližně 1.3V). Když tedy zvolíme převodníku jako referenci neznámé AVCC (napájecí napětí čipu) a změříme tuto interní referenci, můžeme pomocí trojčlenky dopočítat hodnotu AVCC ! Stačí z nám známého vztahu:
U = ADC_hodnota * Vref / 1024
vyjádřit AREF
Vref = U*1024/ADC_hodnota
A protože měřené napětí U známe (je to 1.23V reference) a výsledek převodu (ADC_hodnota) přirozeně také, jsme schopni stanovit hodnotu napájecího napětí (Vref) ! Kromě toho také můžeme dělat i jiná kouzla. Jistě si vzpomenete,že není možné přímo měřit napětí větší než referenční. Takže použitím interní reference 2.56V se vzdáváme možnosti měřit signály o větším napětí než oněch 2.56V. Kdežto použijeme-li AVCC jako referenční napětí tak můžeme měřit s větším rozsahem (až do napájecího napětí, takže typicky 3.3V nebo 5V). A díky tomu, že jsme schopni hodnotu napájecího napětí (které se může dosti měnit) stanovit, můžeme teď měřit i relativně přesně. Abychom však nejásali zbytečně moc, poznamenejme že tento postup dost redukuje přesnost. Jestliže je napájecí napětí 5V a Bandgap reference 1.23V stanovujeme hodnotu napájecího napětí s chybou přibližně 5/1.23=4.1 LSB z AVCC, tedy asi 4.1*4.8mV = +-20mV. Takže to celkově zase taková sláva není.

Nyní bychom si měli říct jak stanovit pokud možno přesně hodnotu interní bandgap reference. Připojíme čip ideálně na stabilní napájecí napětí, a změříme si jeho hodnotu multimetrem. Dejme tomu, že mě vyšla 5.01V. Nahrajeme do čipu jednoduchý program, který v ADMUX vybere na vstup AD převodníku kanál 14 (tedy interní bandgap referenci), jako referenci převodníku zvolíme AVCC (REFS0=1) a necháme si výsledek převodu vypsat do PC. Dejme tomu, že mě vyšel výsledek převodu 258. Protože přesně známe referenční napětí převodníku (5.01V) mohu spočítat přesně hodnotu bandgap reference (tak jako u všech předchozích převodů)
U = ADC_hodnota*Vref/1024.
U = 258 * 5.01 / 1024 = 1.26V
To je tedy přesné napětí bandgap reference. Jak ho jednou znáte můžete počítat s tím, že už se příliš měnit nebude (viz graf "Bandgap Voltage vs. VCC" v datasheetu).

Ještě malý komentář si zaslouží postup jak pomocí bandgap reference měřit obecně jakákoli napětí na vstupech ADC0 až ADC7. Někdo z vás možná namítne, proč o tom psát, je to přece jasné. Nejprve změříme hodnotu AVCC s pomocí známé VBG a spočteme ji pomocí vztahu Vref = VBG*1024/ADC_hodnota. Pak už jen změříme příslušný kanál (např. ADC1) a ze známého vztahu U = ADC_hodnota*Vref/1024 dopočítáme napětí U (Vref je v tomto případě AVCC). Takhle ale děláte přesně to co matematika s celými čísly nemá ráda. Rozdělujete výpočet na dva a v obou z nich dělíte. Vhodnější je sloučit výpočet do jednoho vztahu a vyvarovat se tak jednoho dělení. Výsledný vztah pro napětí na vnějším kanálu (ADC1) pak bude vypadat následovně:
U = VBG*ADC_kanálu/ADC_BGref
kde VBG je stanovené napětí bandgap reference (v mV), ADC_kanálu je výsledek převodu kanálu ADC1 a ADC_BGref je výsledek převodu interní bandgap reference. Přesně tento postup provádíme v příkladu. Nejprve inicializujeme převodník, zvolíme jako referenci AVCC, počkáme 70us než se spolehlivě nastartuje interní bandgap reference. Pak zvolíme v multiplexeru jako vstup kanál 14 (interní bandgap referenci) a výsledek převodu uložíme do proměnné ref. Poté přepneme multiplexer na kanál ADC1 a výsledek jeho měření uložíme do val. Nakonec nás čeká trocha matematiky, kterou jsme už ale probrali a v proměnných avcc a u nám zůstane napájecí napětí čipu a napětí na kanále ADC1. Opět výpočty provádím v mV. Je potřeba si uvědomit, že napájecí napětí se může měnit s vnějšími okolnostmi (teplota, odběr okolních obvodů atd.), je proto dobré proces měření hodnoty AVCC nebo přesněji řečeno proměnné ref provádět před měřením vnějšího kanálu (např ADC1) a doufat že se během tohoto procesu napájecí napětí nijak výrazně nezmění. Chápu že příklad je poněkud komplexnější a možná vás děsí, ale nikdo vás nenutí tento postup používat ;)

// E) Atmega8A - měření AVCC pomocí známé interní reference
#include <avr/io.h>
#define F_CPU 8000000
#include <util/delay.h>
#include <stdio.h> // kvůli fci printf_P()
#include <avr/pgmspace.h> // kvůli fci printf_P()

#define VBG 1260 // experimentálně stanovená hodnota bandgap reference v mV 

int usart_putchar(char var, FILE *stream);  // odesílání UARTem
void setup_uart(void);

static FILE mystdout = FDEV_SETUP_STREAM(usart_putchar, NULL, _FDEV_SETUP_WRITE);
uint16_t ref, val, u, avcc;

int main(void){
	setup_uart(); // nastavení komunikace
	ADMUX = (1<<REFS0) | 0b1110; // reference AVCC, měření na kanálu 14 (interní reference 1.20V)
	ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1); // povolit ADC, clock pro ADC F_CPU/64
	_delay_us(70); // počkej než se nastartuje bangap reference
	
	while(1){
		ADMUX &=~(0b1111); // mažeme nastavení multiplexeru, neovliňujeme nastavení reference
		ADMUX |= 0b1110; // nastavujeme k měření kanál 14 (interní reference ~1.20V) 
		ADCSRA |= (1<<ADSC); // spustit převod
		while(ADCSRA & (1<<ADSC)){} // čekej dokud je ADSC=1 (probíhá převod)
		ref=ADC; // výsledek měření bandgap reference
		ADMUX &=~(0b1111); // mažeme nastavení multiplexeru, neovliňujeme nastavení reference
		ADMUX |= 1; // nastavujeme k měření kanál ADC1 
		ADCSRA |= (1<<ADSC); // spustit převod
		while(ADCSRA & (1<<ADSC)){} // čekej dokud je ADSC=1 (probíhá převod)
		val=ADC; // výsledek měření na ADC1
		avcc = (uint16_t)(((uint32_t)1024*VBG)/(uint32_t)ref); // AVCC[mV] = 1024*BG[mV]/ref		
		u = (uint16_t)((VBG*(uint32_t)val)/1024); // U = BG*val/ref	
		printf_P(PSTR("AVCC = %u mV, U = %u mV\n\r"),avcc,u); // vypsat na PC
		_delay_ms(700);
	}
}

void setup_uart(void){
	stdout = &mystdout; // nastavení standardního výstupu na naši funkci usart_putchar()
	UBRRL = 51; // baud rate 9600 s clockem 8MHz
	UCSRB = (1<<TXEN); // zapnout UART vysílač
	UCSRC = (1<<URSEL) | (1<<UCSZ0) | (1<<UCSZ1); // formát zprávy "8N1"	
}

int usart_putchar(char var, FILE *stream) {
	while (!(UCSRA & (1<<UDRE))); 
	UDR = var; 
	return 0;
}

F) Atmega644P - Jednoduché diferenciální měření

AD převodník některých atmelů je vybaven možností měřit diferenciálně. To znamená, že umí přímo převádět rozdíl napětí mezi dvěma kanály. Na první pohled se vám to může zdát zbytečné, vždyť přece není problém změřit obě napětí samostatně a výsledky měření od sebe odečíst. Tento prvoplánový (a hloupý) postup má ale svoje úskalí. U signálů s vyšším napětím vás nutí používat větší referenci i přes to, že rozdíl signálů, který chcete měřit je malý. Tím vás okrádá o rozlišovací schopnost. Vysvětlím na příkladě. Máte dvojici signálů, dejme tomu U1=2.9V a U2=3.1V. Aby jste každý z nich mohli měřit, potřebujete referenční napětí větší jak 3.1V. Dejme tomu, že zvolíte Uref=4.096V. Při něm je rozlišovací schopnost převodu 4mV. Po změření a odečtení zjistíte, že rozdíl je 200mV+-4mV. Pokud ale využijete diferenciálního převodu, můžete použít referenci například 2.56V s rozlišením přibližně 2mV. A máte tak šanci dosáhnout výsledku s lepší přesností. Další důležitou výhodou diferenciálního měření je fakt, že nedochází k žádnému časovému rozposunutí mezi vzorky z obou kanálů (tak jako ve výše uvedené hloupé metodě) - vzorkování probíhá v jeden relativně krátký a dobře definovaný okamžik.Tím největším bonusem diferenciálního režimu je ale zesilovač, umožňující měřit 10x, 20x nebo 200x zesílený difereniální signál. O těchto možnostech se dozvíte až v dalších příkladech. My zůstaneme u diferenciálního měření bez zesilovače. Pro každý Atmel je potřeba prohédnout datasheet a zjistit zda je vůbec možné provádět diferenciální převod a mezi kterými kanály. Já jsem pro tento příklad použil Atmega644, protože z těch Atmelů co znám je jediný, který má tak vybavený AD převodník i v DIP pouzdře. A očekávám, že někteří ze čtenářů mohou patřit ke starší generaci, která se SMD technologii bude vyhýbat. Stejné funkce v DIP pozdře by měl ale mít i Atmega164P a 324P a v SMD provedení pak i takový evergreen jako je Atmega16/32.

V rámci příkladu si ukážeme diferenciální měření mezi kanály ADC0 a ADC1. Využijeme interní referenci 2.56V, kterou je dobré si zkalibrovat - viz příklad C). Na oba kanály si přiveďte napětí například pomocí potenciometrů a pokud chcete ověřit funkci, připojte si i voltmetr. Program je nekomplikovaný a přímočarý. A komentář si zaslouží jen několik kroků. Všimněte si, že u Atmega644 je vhodné vypnout vstupní buffery na pinech na kterých měříme (PA0 a PA1). Pro snažší úpravu výstupních dat si zapínám bitem ADLAR zarovnání dat doleva. Výsledek diferenciálního měření může být kladný i záporný a je tedy znamenkové číslo. Zarovnáním vlevo si zajistíme, možnost pracovat s výsledkem jako s int16_t. Po přečtení hodnoty z "registru" ADC použiji rotaci doprava. A tady je potřeba spozornět ! Při rotaci vpravo by jste mohli očekávat, že zleva se číslo bude plnit nulami. Ale takto rotace vpravo na typu int16_t v překladači implementována není. Namísto toho aby šlo o logický posun se provádí aritmetický posun (tedy dělení mocninou dvou). Konvenční rotace, která přidává zleva nuly by znaménkový typ znehodnotila. Doplněním nuly zleva do záporného čísla ať už bylo jakékoli by vzniklo okamžitě kladné (viz reprezentace čísla ve dvojkovém doplňku). Díky vhodné interpretaci rotace pro int16_t získáme z převodu správnou hdonotu v typu int16_t bez jakékoli složité aritmetiky. Pak už nás čeká jen kousek matematiky, kde platí tak jako vždy trojčlenka. Jen s tím rozdílem, že celých 1024 kroků se dělí na kladný a záporný interval a každý z nich je tak porkyt pouze 512 kroky. Ještě malé upozornění závěrem. Jestli budete příklad zkoušet, dejte si pozor na to, že čip je v tomto příkladu taktován 16MHz. Tato konfigurace typicky vyžaduje krystal. Vzhledem k tomu, že Atmega644 nepatří k nejlevnějším čipům, používám ji většinou jen na "náročnější" aplikace a tam většinou vyžaduji vyšší takt. Takže pokud ji máte k dispozici a pracujete bez krystalu, musíte si program upravit pro 8MHz. Což spočívá jen ve dvou krocích. Snižte hodnotu v UBRR na 53 a prescaler pro AD převodník nastavte na dělení 64.


// F) Atmega644P - Jednoduché diferenciální měření

#include <avr/io.h>
#define F_CPU 16000000
#include <util/delay.h>
#include <stdio.h> // kvůli fci printf_P()
#include <avr/pgmspace.h> // kvůli fci printf_P()

#define VREF 2598 // experimentálně stanovená hodnota vnitřní reference ("2.56") v mV (měření na AREF)
int usart_putchar(char var, FILE *stream);  // odesílání UARTem
void setup_uart(void);

static FILE mystdout = FDEV_SETUP_STREAM(usart_putchar, NULL, _FDEV_SETUP_WRITE);
int16_t hodnota,u;

int main(void){
	setup_uart(); // ke komunikaci použijeme UART0
	ADMUX = (1<<REFS1) | (1<<REFS0) | (1<<ADLAR) | 0b10000; // reference interních 2.56V, diferenciální měření ADC0-ADC1
	ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0); // ADC_clock = F_CPU/128;
	DIDR0 = (1<<ADC0D) | (1<<ADC1D); // vypníná digital input buffer na PA0,PA1
	_delay_us(70); // čas na stabilizaci interní reference

    while (1) {
		_delay_ms(700);
		ADCSRA |= (1<<ADSC); // spustit převod
		while(ADCSRA & (1<<ADSC)){} // čekej dokud je ADSC=1 (probíhá převod)
		hodnota=ADC; // vyčti výsledek převodu (zarovnaný vlevo)
		hodnota=hodnota>>6; // pozor, posun pro signed int ! interpretace záleží na překladači !
		u = (int16_t)((int32_t)hodnota*VREF/512); // přepočíst na napětí
		printf_P(PSTR("ADC = %i, U = %i mV\n\r"),hodnota, u); // poslat do PC
    }
}

void setup_uart(void){
	stdout = &mystdout; // nastavení standardního výstupu na naši funkci usart_putchar()
	UBRR0L = 103; // baudrate 9600 s clockem 16MHz
	UCSR0B = (1<<TXEN0); // zapnout UART vysílač
	UCSR0C =  (1<<UCSZ00) | (1<<UCSZ01); // formát zprávy "8N1"
}

int usart_putchar(char var, FILE *stream) {
	while (!(UCSR0A & (1<<UDRE0)));
	UDR0 = var;
	return 0;
}

o world!'



Obr.F1 - Zapojení pro příklad F1. Použití interní reference vyžaduje kondenzátor na AREF.

G) Atmega644P - Diferenciální měření se zesilovačem

V předchozím příkladě jsme si ukázali jak zacházet s AD převodníkem v diferenciálním režimu. A také jsme zmínili, že asi tím nejzajímavějším je možnost využít interního zesilovače a rozdílový signál si zesílit. V tomhle příkladě si to vyzkoušíme. Situací kdy se vám možnost zesílení signálu hodí je mnoho. My si předvedeme jednu. Představte si, že chcete měřit elektrický proud. Můžete mu do cesty vřadit rezistor o dobře známé hodnotě a měřit na něm úbytek napětí (tak to taky dělají ampérmetry v multimetrech). Přirozeně ale nechcete měřením obvod nijak výrazně narušovat. Proto musíte volit hodnotu rezistoru nízkou. Jenže na malém odporu jsou i malá napětí. A ty si přímo říkají o to aby je člověk zesílil. My k tomu využijeme zesilovač, který je součástí AD převodníku. Sestavíme si obvod tak jak je na schématu G1. Proud budeme snímat na odporu o hodnotě 1 Ohm. S touto hodnotou se nám bude proud snadno přepočítávat (1mV odpovídá 1mA). V naší úloze budou proudy dosahovat maximálně hodnoty 100mA, zvolíme tedy převod se ziskem 10x. Nejvyšší měřené napětí 100mV bude mít po zesílení hodnotu 1V a na jeho měření bude vhodné zvolit referenci 1.1V. Tady pozor, tato volba je v rozporu s datasheetem, který uvádí, že pro diferenciální měření musí být reference nejméně 2.56V. I přes to však příklad dopadl úspěšně - čímž jsem byl mile překvapen. Co je však ještě milejší je fakt, že v tomto zapojení měřil převodník i záporné proudy (!). Tady ale pozor, během pokusů nesmíte nikdy překročit absolutní limit, který specifikuje že na žádném pinu Atmelu nesmí být záporné napětí větší než 0.5V. Znamená to tedy, že proud tekoucí opačným směrem (z GND "nahoru") nemsí překročit hodnotu 500mA. Samozřejmně tak jak je zapojení nakresleno nepřipadá tato situace v úvahu, pokud ale namísto RLOAD připojíte baterii, může proud téci oběma směry (nabíjení/vybíjení). Díky tomu že AD převodník pracuje i se zápornými hodnotami, můžete měřit proud nabíjení i vybíjení baterie. Protože to ale je v rozporu s provozními parametry ADC, tak vám raději shchema nenakreslím aby jste nepochytili špatné návyky :D Přesnost měření není přirozeně nijak valná, v rozsahu 0-100mA dostahovala odchylka v nejhorším případě 2mA. Kvalitním externím zesilovačem dosáhnete lepší přesnosti. Nicméně ne vždy ji budete vyžadovat. Dalším limitem, který je dobré mít na zřeteli je omezení maximální frekvence pro vstup AD převodníku, když je zapnutý zesilovač. Ten totiž nezkresluje jen signály do 4kHz. Vyší frekvence může zkreslovat. Krom toho je Pro naše defakto stejnosměrné měření nás to ale nemusí trápit.


Schéma G1 - Ukázka měření proudu pomocí diferenciálního ADC s vnitřním zesilovačem

Pokud budete volit hodnotu snímacího odporu jinou (pro větší proudy volíte nižší odpor) nebo jiný zisk vnitřního zesilovače (třeba 200x) ulehčím vám práci a zapíšu obecnou rovnici pro výpočet napětí z výsledku měření.
I[mA]=ADC*VREF[mV]/(512*ZISK*R)
Kde R je hodnota snímacího rezistoru, ZISK je zesílení zesilovačů (1x,10x,200x), Vref je referenční napětí a ADC je výsledek převodu. Při reprezentován desetinných čísel pomocí celočíselných typů je potřeba vhodně volit pozici desetinné čárky (viz Fixed poin arithmetic). Špatnou volbou se můžete připravit o část přesnosti, nebo si zbytečně komplikovat výpočty velkými číselnými typy. Já část přesnosi obětoval a zvolil jsem si jako základní jednotku mA. Když se náhodou v matematice ztratíte a chcete zjisit jak dobře jste si zvolili pozici desetinné čárky, stačí sledovat jak se mění výsledek když se o jedničku změní výsledek převodu. Můj výpočet přiřadí hodnotám ADC v rozsahu 301 až 304 vždy stejný výsledek 66mA. Mohl bych tedy výslednou hodnotu vyjadřovat přesněji. Podobně o tom vypovídá i logika, že maximální měřitelný rozsah přibližně 2x110mA (kladné a záporné) dělím pouze na 2x110 dílů. Ačkoli mám k dispozici AD převodník s 1024 kroky. Mohl bych tedy počítat 1024/220 = 4 a půl krát přesněji. Protože je však odchylka celého měření typicky 1mA, nemá smysl počítat výsledek přesněji.

Nezdržujme se dále teorií a okomentujme si samotný příklad. Všimněte si že celé měření se stavá ze dvou kroků. V prvém kroku nuluji offset. Jestli jste si prohlíželi tabulku všech možností vstupního multiplexeru tak vás jistě udivila možnost měřit rozdíle napětí mezi ADC0 a ADC0, případně mezi ADC2 a ADC2. Tyto varianty slouží právě k nulování offsetu. Pokud by interní zesilovač vykazoval nějaký offset (přidával by k výsledku stále stejné napětí), můžete ho touto cestou změřit a následně od výsledku odečíst. A to přesně v příkladu dělám. Zapsáním hodnoty 0b01000 do MUX4..0 volím ADC0-ADC0 se ziskem 10x a měřím offset. Po změně multiplexeru musím počkat než se výstup zesilovače ustálí (u 4Khz je to přibližně 1/f=250us). Teprve poté mohu převádět a výsledek převodu upravit a uložit do proměnné offset. V druhé části měřícího cyklu pak volím diferenciální kanál ADC1-ADC0, opět čekám na ustálení výstupu zesilovače a od výsledku měření odečtu offset. Pak provedu již dříve diskutovanou aritmetiku a výsledek pošlu do PC. Stejně jak ov předchozím příkladě si dejte pozor že příklad je připraven pro čip s kmitočtem 16MHz.

//D) Atmega644P - Diferenciální ADC se zesilovačem - měření proudu

#include <avr/io.h>
#define F_CPU 16000000
#include <util/delay.h>
#include <stdio.h> // kvůli fci printf_P()
#include <avr/pgmspace.h> // kvůli fci printf_P()

#define VREF 1126 // měřením na AREF stanovená hodnota vnitřní reference ("1.1V") v mV
#define ZISK 10 // zisk zesilovačů u ADC (10x)

int usart_putchar(char var, FILE *stream);  // odesílání UARTem
void setup_uart(void);

static FILE mystdout = FDEV_SETUP_STREAM(usart_putchar, NULL, _FDEV_SETUP_WRITE);
int16_t hodnota,i,offset;

int main(void){
	setup_uart(); // nastavení komunikace
	DIDR0 = (1<<ADC0D) | (1<<ADC1D);
	ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0); // ADCclock=F_CPU/128
	
    while (1) {	
		//měření vnitřní nesymetrie (offsetu)
		// interní reference 1.1V, výsledek zarovnat doleva (signed int !)
		ADMUX = (1<<REFS1) | (1<<ADLAR) | 0b01000; 		// diferenciální vstup ADC0-ADC0 (10x) 
		_delay_us(250); // zesilovače u ADC pracují max na 4kHz (počkat na ustálení jejich výstupu)
		ADCSRA |= (1<<ADSC); // spustit převod
		while(ADCSRA & (1<<ADSC)){} // čekej dokud je ADSC=1 (probíhá převod)
		offset=ADC; // vyčti výsledek převodu (zarovnaný vlevo)
		offset=offset>>6; // pozor, posun pro signed int ! interpretace záleží na překladači !
		
		// měření vnějšího napětí (proudu skrze rezistor R)
		ADMUX = (1<<REFS1) | (1<<ADLAR) | 0b01001; 		// diferenciální vstup ADC1-ADC0 (10x)
		_delay_us(250); // zesilovače u ADC pracují max na 4kHz (počkat na ustálení jejich výstupu)
		ADCSRA |= (1<<ADSC); // spustit převod
		while(ADCSRA & (1<<ADSC)){} // čekej dokud je ADSC=1 (probíhá převod)
		hodnota=ADC; // vyčti výsledek převodu (zarovnaný vlevo)
		hodnota=hodnota>>6; // pozor, posun pro signed int ! interpretace záleží na překladači !
		hodnota=hodnota-offset; // korekce		
		i = (int16_t)((int32_t)hodnota*VREF/(512*ZISK)); // přepočíst na proud v mA
		printf_P(PSTR("ADC = %i, U = %i mA\n\r"),hodnota, i);
		_delay_ms(700); 
    }
}

void setup_uart(void){
	stdout = &mystdout; // nastavení standardního výstupu na naši funkci usart_putchar()
	UBRR0L = 103; // baudrate 9600 s clockem 16MHz
	UCSR0B = (1<<TXEN0); // zapnout UART vysílač
	UCSR0C =  (1<<UCSZ00) | (1<<UCSZ01); // formát zprávy "8N1"
}

int usart_putchar(char var, FILE *stream) {
	while (!(UCSR0A & (1<<UDRE0)));
	UDR0 = var;
	return 0;
}

H) Atmega16 - Externím přerušením spouštěný ADC převod

V tomto příkladu si předvedeme jak lze přesně ovládat okamžik zahájení AD převodu. V modelovém příkladě přivádíme na pin ADC2 měřený signál (z obrazků je patrné, že jde o sadu pulzů z biologického vzorku). Na pin PD2 nějaký vnější obvod přivádí pulzy, jejichž nástupnou hranou označuje okamžiky kdy je potřeba měřit napětí. Atmel dokáže v rámci relativně krátkého času (hrubě pod 1us) zareagovat a zahájit převod. Bohužel samotné "samplování" AD převodníkem trvá 1.5 jeho strojového cyklu, tedy typicky 15us (při 100kHz clocku pro převodník). Pro rozumně pomalé signály (jako v našem příkladě) je ale okamžik převodu docela dobře definován. Popravdě tuto funkci jsem ještě nikdy nevyužil, takže vám asi neprozradím, jaká může najít uplatnění. Nicméně je dobré když budete alespoň vědět, že máte něco takového k dispozici. Člověk totiž nikdy neví co jednoho dne budete potřebovat.

AD převodník může být trigrován hned několika signály.Může to být výstup analogového komparátoru, Externí přerušení (INT0). Dále pak signály od časovačů a to buď okamžik přetečení (Overflow), Compare událost a případně u Čítače/Časovače 1 i Capture událost. Zvláště užitečné bude trigrování časovačem, protože mimo jiné umožňuje dobře definovat vzorkovací frekvenci kontinuálního AD převodu. Protože ale nemám jistotu že se s časovači kamarádíte, zvolil jsem pro ukázku raději externí přerušení (INT0). Protože spouštěcí signál převodu může přijít naprosto kdykoli, je na místě využít ke zpracování dat přerušení od AD převodníku. Jinak bychom museli neustále sledovat vlajku ADIF, což je nemalá komplikace pro program.

Nastavení není nijak zvlášť složité. V registru ADMUX, vybereme referenci (interní 2.56V) a kanál (ADC2). Dále nastavíme clock pro převodník. Protože čip běží na 16MHz, musím použít děličku 128 abych frekvenci clocku převodníku dostal do vhodných mezí (50-200kHz). Nastavením bitu ADIE povolím přerušení od dokončení převodu a bitem ADATE povolím spozštění vnitřním signálem. Jeho zdroj si vybírám v registru SFIOR pomocí bitů ADTS2ATDS0. Tady udělám malou vsuvku jazyka "C". Když potřebuji upravit část nějakého registru o němž nemám informace, a nechci zasahovat do jeho zbylé části, musím to udělat ve dvou krocích. Nejprve všechny bity, které měním smažu a pak do těch do kterých potřebuji zapíšu jedničky. Jiný postup v "C", který by zapsal libovolné hodnoty na libovolná místa a neovlivnil by osatní bity neznám. Přirozeně mohl bych vycházet z toho, že těsně po startu je regisr SFIOR v předem známém stavu a celou operaci udělat jedním zápisem, ale tento přístup je obecnější (správně bych ho měl aplikovat v podstatě na všechny přístupy do registrů). Tím je AD převodník připraven. Teď zbývá nastavím externí přerušení. O tom si můžete přečíst v předchozích dílech (zde a zde). V MCUCR aktivuji detekci nástupné hrany na kanálu INT0 (PD2). Samotné externí přerušení (v registru GICR) ale nepovoluji. Nepotřebuji vyvolávat rutinu přerušení od INT0. Před globálním povolením všech přerušení pro jistotu smažu vlajku ADIF, pro případ že signál z jakéhokoli důvodu spustil AD převod během nastavování. A ještě smažu vlajku INTF0 v registru GIFR. To je nutnost, neboť právě nastavení této vljaky z log.0 do log.1 spustí AD převod. Kdyby vlajka zůstala z jakéhokoli důvodu nastavená, nikdy by se převod nespustil a program by na to čekal marně. Jakmile přijde spouštěcí signál (nástupná hrana) na INT0 (PD2), dojde k nastavení vlajky INTF0, čímž se spustí AD převod. Po jeho dokončení se zavolá rutina přerušení a program v ní nejprve vyčte výsledek převodu, poté smaže vlajku INTF0 (aby tím umožnil dalšímu spuštění) a nakonec dá pomocí proměnné prevod_dokoncen hlavní smyčce informaci o tom že má k dispozici nová data. Nezapomínejte že globální proměnná, která se nachází v hlavní smyčce programu a v rutině přerušení musí být deklarováná jako volatile. Jinak si překladač bude myslet, že v rámci hlavní smyčy nemůže změnit hodnotu a celou smyčku v rámci optimalizace programu vypustí. V hlavní smyčce už pak jen pomocí Printf odešleme výpočítanou hodnotu do PC.

// H) Atmega16 - Externím přerušením spouštěný ADC převod

#include <avr/io.h>
#include <avr/interrupt.h>
#define F_CPU 16000000
#include <stdio.h> // kvůli fci printf_P()
#include <avr/pgmspace.h> // kvůli fci printf_P()

#define VREF 2560 // měřením na AREF stanovená hodnota vnitřní reference ("2.56V") v mV

int usart_putchar(char var, FILE *stream);  // odesílání UARTem
void setup_uart(void);

static FILE mystdout = FDEV_SETUP_STREAM(usart_putchar, NULL, _FDEV_SETUP_WRITE);
uint16_t hodnota,u;
volatile char prevod_dokoncen=0;

ISR(ADC_vect){	
	hodnota=ADC;   // dokončen převod ADC, vyzvednout data
	GIFR |= (1<<INTF0); // mažeme vlajku od INT0 aby nám mohla příště spustit další převod
	prevod_dokoncen=1;
}

int main(void){
	setup_uart();
	MCUCR = (1<<ISC01) | (1<<ISC00); // chytáme nástupnou hranu na INT0
	ADMUX = (1<<REFS1) | (1<<REFS0) | 0b00010; // reference interní 2.56V, měříme na ADC2
	// ADC povolit, Auto Trigger povolit, Přerušení od ADC povolit, ADCclock=F_CPU/128
	ADCSRA = (1<<ADEN) | (1<<ADATE) | (1<<ADIE) | (1<<ADPS2) | (1<<ADPS1)  | (1<<ADPS0); 
	SFIOR &= 0x1f; // mažeme nastavení ADTS2 až ADTS1 (nevím co tam bylo minule)
	SFIOR |= (1<<ADTS1); // nastavujeme trigger pro ADC (Externím přerušením INT0)
	GIFR |= (1<<INTF0); // mažeme vlajku přerušení INT0 (pro jistotu, kdyby tam "visela" z minula)
	ADCSRA |= (1<ADIF); // mažeme vlajku ADC (není nutné)
	sei();  // globální povolení přerušení

  while (1){
	 if(prevod_dokoncen){
		  prevod_dokoncen=0;
		  u = (uint16_t)((uint32_t)hodnota*VREF/1023);
		  printf_P(PSTR("U=%umV\n\r"),u);
		}
  }
}

void setup_uart(void){
	stdout = &mystdout; // nastavení standardního výstupu na naši funkci usart_putchar()
	UBRRL = 103; // baudrate 9600 s clockem 16MHz
	UCSRB = (1<<TXEN); // zapnout UART vysílač
	UCSRC =  (1<<URSEL)| (1<<UCSZ0) | (1<<UCSZ1); // formát zprávy "8N1"
}

int usart_putchar(char var, FILE *stream) {
	while (!(UCSRA & (1<<UDRE)));
	UDR = var;
	return 0;
}

Na následujícím obrázku (F1) pak můžete vidět výsledek. Žlutý průběh znázorňuje měřený signál, modrý průběh je spuštěcí signál. V krátkém časovém intervalu po příchodu nástupné hrany na spouštěcím signálu proběhne převod. Výsledek převodu je pak pomocí USARTu odeslán do PC (viz růžová linka). Orientační odečtení úrovně vstupního signálu pomocí kurzorů ukazuje přibližně 700mV. Z odeslaných dat můžeme vyčíst, že výsledek převodu byl 705mV. Což je vzhledem k nejistotě s jakou odečítáme hodnotu pomocí kurzorů docela dobrá shoda.


Obrázek F1 - Externím signálem spouštěný AD převod. Žlutý průběh je měřený signál, modrý průběh je spouštěcí signál.

I) Atmega16 - Free running ADC s přerušením

V příkladě H), jsme si předvedli signálem spouštěný převod. Záměrně jsme opominuli jeden signál, kterým jde převod spustit. A to je ukončení předchozího převodu. Díky tomu, lze spustit AD převodník v režimu "free running", v němž probíhá převod stále dokola. Po skončení jednoho převodu je zahájen další. Taková funkce najde uplatnění v několika případech. V situacích kdy potřebujete neustále monitorovat jeden kanál a chcete mít záruku, že kdykoli se program rozhodne bude mít k dispozici co nejčestvější data. V takovém případě spustíte převodník ve free-running módu a kdykoli se vám zamane přečtete obsah "registru" ADC a v něm bude výsledek posledního převodu. Data tak nebudou nikdy starší než dobu jednoho převodu. Další možností kdy se vám tento režim může hodit je monitorování časového průběhu nějákého signálu. Prostě když potřebujete "osciloskop". K takovým účelům typicky využijete časovačem spouštěného převodu, ale jestliže potřebujete měřit co nejrychleji, může být free-running mód vhodnější. Přitom informaci o okamžicích záznamu neztrácíte, protože víte jak dlouho jeden převod trvá. My si využití tohoto módu ukážeme na primitivním příkladu. Opět budeme mít neznámý vstupní signál, jehož časový průběh chceme proměřit. Aby to ale nebyla úplná nuda, zkusíme z AD převodníku vyždímat co nejvíc. Dle datasheetu víme, že bychom měli AD převodník taktovat frekvencí 50-200kHz. Což by při 13 cyklech na jeden převod odpovídalo periodě 65ms. To bychom náš signál, který trvá přibližně 100us, neměli nejmenší šanci zachytit. Musíme tedy AD převodník "přetaktovat". Což samo o sobě není nic nepřípustného. V datasheetu se dočtete, že pokud oželíte přesnost, smíte taktovat převodník až na 1MHz. My ale nebudeme troškařit a pustíme mu 2MHz (vzhledem ke clocku čipu - 16MHz volíme děličku pro AD převodník 8). Jden převod by tak měl trvat přibližně 6.5us.

Měřený signál přivedeme na ADC1. Na pinu PC0 budeme signalizovat okmažiky ukončení převodu (to je jen pro názornost). AD převodník konfigurujeme podobně jako v předchozím příkladu. Tedy pomocí ADIE povolujeme přerušní, bitem ADATE vybíráme spouštěný převod. Nastavíme interní 2.56V referenci a zvolíme k převodu kanál ADC1. Zdroj spouštění specifikujeme v registru SFIOR kde všechny tři bity ATDSx vynulujeme. První převod je nutné sputit "ručně", tedy nastavením bitu ADSC. Pak se už AD převodník rozběhne sám a pravidelně volá rutinu přerušení. V ní program výsledek převodu uloží do pole hodnota. Celý záznam má 32 vzorků (jejich počet specifikuje definice SAMPLES). Po změření všech vzorků převod zastavíme vynulováním bitu ADCSRA. Hlavní smyčka pak celý blok dat zpracuje a odešle do PC. Tím celý program končí. Resetováním Atmelu ho spustíte znovu. Přirozeně by se celý proces dal pojmout nějak komplexněji, ale to už je dáno konkrétní aplikací. Takže je na vás zda a jak příklad včleníte do vašeho projektu.

// I) Atmega16A - Free running ADC s přerušením

#include <avr/io.h>
#define F_CPU 16000000
#include <util/delay.h>
#include <stdio.h> // kvůli fci printf_P()
#include <avr/pgmspace.h> // kvůli fci printf_P()
#include <avr/interrupt.h> 

#define SAMPLES 32 // počet vzorků v měření

int usart_putchar(char var, FILE *stream);  // odesílání UARTem
void setup_uart(void);

static FILE mystdout = FDEV_SETUP_STREAM(usart_putchar, NULL, _FDEV_SETUP_WRITE);
volatile uint16_t hodnota[SAMPLES];	// pole změřených hodnot
volatile char i=0; // počítadlo vzorků

int main(void){
	DDRC |= (1<<DDC0); // PC0 k indikaci okamžiku převodu
	setup_uart(); // nastavení komunikace
	ADMUX = (1<<REFS1) | (1<<REFS0) | 0b00001;  // měříme na ADC1, s interní referencí 2.56V
  // povolit převodník, povolit přerušení, povolit "trigger",ADC clock F_CPU/8 
	ADCSRA = (1<<ADEN) | (1<<ADATE) | (1<<ADIE) | (1<<ADPS1) | (1<<ADPS0); 
	_delay_us(75); // čas na stabilizaci interní reference
	SFIOR &= 0x1f; // [ADTS2..ATDS0]=0 (trigger source: Free running)	
	sei(); // globální povolení přerušení 
	ADCSRA |=(1<<ADSC); // spuštění prvního převodu (od teď běží ADC samo)	
	while(1){
		if(i==SAMPLES){ // až je převod dokončen
			for(i=0;i<SAMPLES;i++){ // odešli všechny vzorky do PC
				printf_P(PSTR("%u\n\r"),hodnota[i]);
			}
		i=0;
		}
	}
}

ISR(ADC_vect){
PORTC=0xff; // indikace kdy došlo k převodu
hodnota[i]=ADC; // uložíme výsledek převodu
i++; // příště ukládáme další vzorek
if(i>=SAMPLES){ADCSRA=0;} // pokud jsou změřený všechny vzorky, vypni ADC
PORTC=0; // konec indikace
}

void setup_uart(void){
	stdout = &mystdout; // nastavení standardního výstupu na naši funkci usart_putchar()
	UBRRL = 103; // baud rate 9600 s clockem 8MHz
	UCSRB = (1<<TXEN); // zapnout UART vysílač
	UCSRC = (1<<URSEL) | (1<<UCSZ0) | (1<<UCSZ1); // formát zprávy "8N1"	
}

int usart_putchar(char var, FILE *stream) {
	while (!(UCSRA & (1<<UDRE))); 
	UDR = var; 
	return 0;
}

Na obrázku I1, je patrný průběh převodu. Žlutou barvou je znázorněn měřený signál, modrou barvou jsou pak zvýrazněny okamžiky převodu. Vidíte, že perioda převodu je přibližně 6.4us, což v rámci tolerance odečtu oscilosckopem odpovídá předpokládáné hodnotě. Celkem by mělo proběhnou 32 převodů. Na obrázku I2 pak vidíte jednoduchou rekonstrukci signálu ze změřených dat. Všimněte si že do PC posíláme surová data, bez přepočtu na napětí. Při tomto druhu měření je to typické, zaokrouhlováním při přepočtu bychom ztratili část informace. A protože jsou data určena k pozdějšímu zpracování, neprovádíme na nich žádné ztrátové operace. Orientačním výpočtem můžeme vidět že vrchol křivky dosahuje přibližně 1.5V (což odpovídá záznamu z osciloskopu).


Obrázek I1 - Žlutý průběh - měřený signál, Modrý průběh - značky okamžiků převodu

Obrázek I2 - Jednoduchá rekonstrukce změřeného průběhu

Home
| V1.01/
| By Michal Dudka (m.dudka@seznam.cz) /