Analogový komparátor na Attiny24

Analogový komparátor je součástí všech Atmelů a může zastat slušnou práci. Zvu vás na prohlídku jeho možností. Vypořádáme se s problémy a v závěru nabídnu několik praktických příkladů.

Obsah

Předkrm

Co je analogový komparátor ?

Co je a k čemu slouží komparátor by jste měli už vědět. Pokud nemáte ani představu bylo by pro vás asi vhodnější četbu na chvíli přerušit a vydat se do vln internetu najít odpovědi. Hodně ve stručnosti můžeme říct, že je to součástka porovnávající dvě napětí na svých vstupech. Její výstup nabývá dvou logických hodnot, podle toho které ze vstupních napětí je větší. Pokud už budete pátrat po informacích, věnujte alespoň okrajově trochu pozornosti vstupnímu offsetu a hysterezi komparátorů. Druhý pojem se v článku určitě objeví.

Předeslal bych, že komparátor se se svými možnostmi částečně překrývá s AD převodníkem. Takže existuje široká paleta úloh, která jde řešit buď komparátorem nebo AD převodníkem. Předveďme si to na triviálním snímání intenzity okolního osvětlení. Podívejte se na schéma č.1. Spojíte-li do serie rezistor (R1) a fotorezistor (PH1), získáte dělič napětí s proměnným dělícím poměrem. Napětí v uzlu mezi rezistory (tedy napětí na PA1) bude měnit svoji hodnotu v závislosti na okolním osvětlení. Jestliže chcete hlídat zda osvětlení dosáhlo jisté úrovně budete porovnávat napětí z děliče s nějakou jinou hodnotou. V našem případě bude ta hodnota nastavena trimrem R2 a přivedena na pin PA2. Poznamenejme, že slušné by bylo alespoň referenční hodnotu na PA2 filtrovat kondenzátorem (a PA1 by to také neuškodilo). V této situaci můžete zvolit dva přístupy. Buď nastavíte AD převodník, budete střídavě měřit hodnotu napětí na PA2 (ADC2) a PA1 (ADC1) a pak je číselně porovnávat a nebo využijete komparátor. Přístup s převodníkem je náročnější, ale flexibilnější. Můžete si do měření zabudovat libovolnou hysterezi. Jednoduše řečeno můžete naprogramovat, že noc nastává když klesne intenzita osvětlení (PA1) pod dejme tomu 40% hodnoty na PA2 a den když intenzita překročí 90% hodnoty na PA2. Komparátor vám umožní pouze rozeznat zda je inenzita větší či ne. Ale jeho použití je podstatně jednodušší jak použití převodníku. Tímto příkladem jsem chtěl pouze ilustrovat kde se funkce obou periferií překrývají a kde je nutné předem zvážit zda nám funkce komparátoru bude stačit. Podobně můžete hlídat i teplotu. Například termistorem, kde bude situace defakto stejná jako na schématu č.1 jen namísto fotorezistoru budete mít termistor. Případně můžete použít nějakého integrovaného obvodu (LM35 atp.). Existují ale i vtipnější aplikace. Můžete třeba před vypnutím ukládat uživatelské nastavení. Komparátorem detekujete pokles napájecího napětí a jakmile k němu dojde, rychle uložíte do vnitřní EEPROM nebo nějaké vnější paměti data. Přirozeně to musíte udělat chytře a zajistit atmelu dostatek času aby to provedl. Může vám sloužit na diskriminaci analogového signálu (třeba až si budete měřit srdeční puls). Takto by se dalo pokračovat ještě dlouho. Aplikací je tedy nespočet.

Schéma č.1 - detekce intenzity osvětlení

Co nám Attiny24 nabízí ?

Výbava Attiny24 v tomto směru není nijak závratná, například ve srovnání s Attiny441, ale bude postačovat. Je v podstatě stejná jako na čipech řady Atmega. Na pinu PA1 je AIN0 - pozitivní vstup komparátoru a na pinu PA2 je AIN1 - negativní vstup komparátoru. Komparátor může jako pozitivní vstup používat buď pin PA1(AIN0) a nebo vnitřní referenci. Ta podle datasheetu nabývá hodnoty mezi 1V a 1.2V (typicky 1.1V). Její provoz vás stojí typicky 15uA a použít ji můžete typicky 40us po startu. Na Figure 21-44. v datasheetu můžete vidět jak její hodnoty závisí na napájecím napětí čipu. Na Figure 21-45. pak jak závisí na teplotě. Negativní vstup komparátoru může být buď PA2(AIN1) nebo výstup vnitřního analogového multiplexeru. Jednoduše řečeno můžete si negativní vstup komparátoru spojit s libovolným vstupem ADC0 až ADC7 (PA0 až PA7). Podíváte-li se na Figure 21-50. v datasheetu uvidíte, že komparátor má jistý offset (napěťovou nesymetrii). Je celkem překvapivé, že při vstupním napětí pod cca 0,4V je offset kladný (cca 2mV) kdežto při vyšším napětí je záporný (cca 5mV). Což vás většinou asi nebude zajímat, ale člověk nikdy neví co vymyslíte ;). Výstup komparátoru je pouze interní, nemůžete ho tedy vyvést přímo ven z čipu (na rozdíl od Attiny441). Má svoje přerušení a můžete si vybrat jednu ze tří událostí kdy vás přeruší. Na sestupnou hranu, tedy když výstup komparátoru přechází z log.1 do log.0, na nástupnou hranu nebo na jakoukoli změnu. Krom toho může výstup z komparátoru spouštět AD převod (pokud máte vhodně nastavený převodník) nebo provést Input Capture událost v čítači. Jinak řečeno zaznamenat okamžitě hodnotu čítače do ICR registru. Což jsou funkce, které oceníte obecně u analýzy signálů. Provoz analogového komparátoru vás bude stát typicky mezi 60-80uA. Při nižším napětí ale může přesáhnout 140uA. Viz Figure 21-17. v datasheetu. Zpoždění komparátoru by se mělo pohybovat mezi 75 - 750ns podle provozního napětí a vstupního signálu + čas potřebný na synchronizaci s clockem čipu (1-2 takty). Více o tom v Table 20-10. v datasheetu. To je ve stručnosti všechno.

Jak se ovládá ?

K ovládání samotného komparátoru slouží jeden registr. K ovládání analogového multiplexeru pak další dva. ACSR – Analog Comparator Control and Status Register jak již název napovídá nastavuje komparátor. Zapsáním log.1 do bitu ACD můžete komparátor vypnout (když spoříte energii), bit ACBG slouží k přepnutí kladného vstupu komparátoru mezi AIN0 (log.0) a vnitřní referencí (log.1). Bit ACO je výstup komparátoru a slouží jen ke čtení. ACI je pak vlajka přerušení, indikuje vám tedy zda došlo k události, kterou si vybíráte pomocí bitů ACIS0 a ACIS1. Zůstává v log.1 do té doby než ji zápisem log.1 smažete nebo než program vstoupí do rutiny přerušení. Pokud přerušení nemáte povolené, pořád pro vás nese informaci o tom, že vybraná událost nastala. Události máte k dispozici tři. Změnu hodnoty výstupu komparátoru (vyberete natavením ACIS1=0 a ACIS0=0), sestupnou hranu na výstupu komparátoru (ACIS1=1, ACIS0=0) a nebo nástupnou hranu (ACIS1=1, ACIS0=1). Bit ACIE pak slouží k povolení (log.1) nebo zakázání (log.0) přerušení od komparátoru. Bitem ACIC povolujete aby výstup komparátoru mohl sloužit k vyvolání Input Capture události v čítači 1.

ACSR – Analog Comparator Control and Status Register
76543210
ACDACBGACOACIACIEACICACIS1ACIS0

Pokud vám vstupy AIN0 a AIN1 nestačí, budete si muset nastavit multiplexer. U toho ale musíte mít vypnutý AD převodník. Pokud máte AD převodník zapnutý, patří multiplexer jemu. Multiplexer spustíte bitem ACME (log.1) v registru ADCSRB. Zapnutí / vypnutí AD převodníku provádíte bitem ADEN v registru ADCSRA.

ADCSRB – ADC Control and Status Register B
76543210
BINACME-ADLAR-ATDS2ADTS1ADTS0

Multiplexer řídíte registrem ADMUX pomocí bitů MUX0MUX5. Zbylé dva bity slouží k nastavení reference pro AD převodník, což vás nemusí zajímat, protože ho určitě máte vypnutý ;) Vzhledem k tomu, že u AD převodníku je i zesilovač (se zesílením 20x) je škoda, že není možné jeho výstup též připojit na komparátor. V tabulce č.1 jsou kombinace bitů MUX0 až MUX5 pro výběr výstupu multiplexeru (ve vašem případ vstupu komparátoru).

ADMUX – ADC Multiplexer Selection Register
76543210
REFS1REFS0MUX5MUX4MUX3MUX2MUX1MUX0

tabulka č.1 - analogový multiplexer
Výstup z multiplexeruMUX[5..0]
ADC0 (PA0)0b000000
ADC1 (PA1)0b000001
ADC2 (PA2)0b000010
ADC3 (PA3)0b000011
ADC4 (PA4)0b000100
ADC5 (PA5)0b000101
ADC6 (PA6)0b000110
ADC7 (PA7)0b000111
0V (AGND)0b100000
1.1V (Reference)0b100001
ADC8 (interní teplota)0b100010
rezervovánovše ostatní

Poznámka o vstupním bufferu

Vstupní buffer je obvod, který se stará o snímání logické hodnoty na vstupu. Datasheet v sekci 7.4.6 uvádí, že na všech analogových pinech by jste měli vždy udržovat vstupní buffer vypnutý, protože při napětí okolo Vcc/2 by mohl do pinu téct nějaký proud. Což jednak může ovlivnit samotné měření, zvláště v případech kdy je výstupní impedance měřeného signálu vysoká (nelze ho zatěžovat). Krom toho by vás takový proud mohl rozčilovat ještě v režimech spánku (kdy chcete odběr minimalizovat). Připadalo mi to zajímavé, tak jsem se pokusil proud změřit. Asi jsem měl smůlu, v celém rozsahu napětí mi do vstupu neteklo nic co bych byl schopen na 200uA rozsahu ampérmetru změřit. Datasheet také uvádí, že "Leakage Current" by měl typicky být pod 50nA. Možná by nám to mohlo být ukradené, ale za ten jeden řádek kódu nic nedáme a budeme tedy na analogových pinech vypínat vstupní buffer. Na to máme u atitny24 registr DIDR0. Je přirozeně společný pro AD převodník i pro komparátor, což jsou jediné dvě periferie, které pracují s analogovými hodnotami. Ovládání je prosté. Zápisem log.1 do příslušného bitu vypnete vstupní buffer na příslušném pinu (ADC0 až ADC7).



DIDR0 – Digital Input Disable Register 0
76543210
ADC7DADC6DADC5DADC4DADC3DADC2DADC1DADC0D

Hlavní chod - Praktické zkoušky

Jednoduchý přístup

Začneme prvním jednoduchým programem. Na pozitivní vstup komparátoru připojíme interní 1.1V referenci, a negativní vstup připojíme na AIN1 (PA2). Vzhledem k tomu, že výstup komparátoru je pouze interní, budeme si jeho stav muset nějak signalizovat. PB0 si nakonfigurujeme jako výstup a procesor necháme sledovat bit ACO tedy výstup komparátoru. Jeho hodnotu "zkopírujeme" na výstup PB0. Pokud nemáte k dispozici osciloskop a chcete si příklad vyzkoušet, připojte si na PB0 LEDku. Na AIN1 si přiveďte nějaké analogové napětí, třeba trimrem. Ideálně si na AIN1 připojte ještě voltmetr ať můžete sledovat hodnotu napětí. Při překročení 1.1V by se vám LED měla zhasnout. My zkoušku provedeme za pomoci generátoru, který necháme vytvářet pilovitý průběh s frekvencí 1kHz. Ta by měla stačit aby program stíhal nastavovat výstup. Výsledek vidíte na obrázku č.1. Žlutý průběh je negativní vstup komparátoru (AIN1), modrý signál je pak PB0. Je patrné, že jakmile vstupní signál překročí hodnotu 1.1V přepne se bit ACO což vyústí v nastavení PB0 (modrý průběh) do log.0. Analogicky jakmile napětí klesne pod 1.1V program nastaví PB0 na log.1.

// jednoduché použití komparátoru, pozitivní vstup 1.1V reference, negativní vstup AIN1
#include <avr/io.h>

int main(void){
ACSR = (1<<ACBG);	// na AIN0 zapojíme interní referenci, 
ADCSRB &= ~(1<<ACME);	// nechci zapínat multiplexer
DIDR0 |= (1<<ADC2D) | (1<<ADC1D);	// vypneme vstupní buffery
DDRB |= (1<<DDB0);	// výstup abychom se měli na co dívat 

while(1){
	if(ACSR & (1<<ACO)){PORTB=0b1;}	// sledujeme výstup komparátoru
	else{PORTB=0;}	// a nastavujeme PB0 podle jeho hodnoty
	}
}

Obrázek č.1 - žlutý průběh je srovnáván s vnitřní 1.1V referencí, modrý průběh je výstup komparátoru

Rychlost komparátoru ?

Teď si zkusíme rychlost reakce našeho programu aby jste měli představu v jaké časové škále se bez optimalizace kódu pohybujete. Na vstup pustíme obdélníkový signál a budeme sledovat za jak dlouho komparátor zareaguje. Výsledek je (jak mnozí z vás čekali) drobně znepokojivý (obrázek č.2). Zpoždění je mezi 4 a 11us (čip pracuje na 1MHz). Kde se bere takový rozptyl hodnot (jinak též nazývaný jitter), když by podle datasheetu měl rozptyl dosahovat typicky 1us ? No jasně :) Je to programem. Celý cyklus kontroly hodnoty ACO trvá 4-10 strojových cyklů. Tedy pokud pulz přijde v tom nejvhodnějším okamžiku, tedy těsně před načtením hodnoty z ACO, zareaguje program rychle. Jestliže ale pulz přijde těsně po čtení ACO, musí program projít celou while smyčku než se dostane ke kontrole znovu. Ze zdrojového kódu to není vidět, ale když se podíváte na přeložený kód v assembleru, můžete spočítat instrukce a rozptyl zpoždění předpovídat. Nebo můžete asemblerovský kód editovat a pokusit se smyčku optimalizovat, což by se vám asi podařilo, ale tak do hloubky se problémem zabývat nebudeme. (Klidně to někdo zkuste a pošlete nám výsledky). Přirozeně v drtivě většině aplikací vás tento jitter nebude vůbec zajímat. Navíc pokud by jste potřebovali reagovat rychle, taktovali by jste Atmel na vyšší frkvenci než 1MHz. Pokud by vám to napájecí napětí dovolilo, mohli by jste jít až na 20MHz a celou reakci tak o řád zkrátit.

Obrázek č.2 - zpoždění komparátoru s jednoduchým programem

Problémy !

Co vás ale určitě zajímat bude je výsledek dalšího pokusu. Nechme program stejný a zkusme opět změnit vstupní signál (žlutý). Na vstup pustíme sinusový průběh o nízké frekvenci, dejme tomu 10Hz. Ten přirozeně nebude ideální a bude obsahovat drobný šum. Jeho původem se nebudeme zabývat, ale u většině vašich aplikací tam bude. Na obrázku č.3 vidíte v horní části hrubý pohled na průběh a v dolní části je pak "zoom" a z něj je patrné, že máme problém. Když vstupní signál (žlutý) klesne pod hranici 1.1V, komparátor překlopí, ale pár mikrosekund poté se vrátí a tak několikrát "zakmitá" než se ustálí. Tento problém vás čeká u každého neošetřeného komparátoru, kterému přivede na vstupy stejné hodnoty. Stačí aby jedna z hodnot byla větší jen o pár milivoltů než druhá a komparátor překlopí, šum v řádu milivolt způsobí, že na krátký okamžik je vstupní hodnota větší a pak zase klesne zpět pod referenční hodnotu. A toto se děje nahodile. U analogových komparátorů se tohoto nežádoucího jevu můžete zbavit nastavením hystereze. Tou zařídíte, že hladina pro překlopení komparátoru do log.1 je vyšší než hladina pro překlopení do log.0. Jakmile měřený signál protne úroveň 1.1V (i třeba za pomoci šumu) musí sestoupit pod 1.1V mínus hystereze takže například pod hodnotu 1.08V. Jemný šum, který dočasně zvedl úroveň vstupního signálu o pár milivolt, už nestačí nato aby úroveň snížil o 20mV. Zákmity se pak neobjeví. Přesněji řečeno drasticky se sníží šance, že se objeví. Náš komparátor ale vnitřně nic takového nemá ! (na rozdíl od některých jiných mikrokontrolerů). Budeme si s tím tedy muset nějak poradit. Pokud by vám problematika stále nebyla jasná, zkuste googlit v obrázcích, ten to totiž vysvětlí ze všeho nejlíp !

Obrázek č.3 - Problém ! Absence hystereze.

Řešíme problémy

Předvedeme si dvě možnosti jak se zákmitů zbavit. Prvním z nich je zavést si do komparátoru hysterezi. Existuje několik variant obvodových řešení, my si předvedeme jednu z těch nejjednodušších. Referenci pro komparátor si vytvoříme děličem. Využijeme jeden z pinů procesoru jehož přepínáním budeme drobně ovlivňovat referenční napětí. Kdykoli komparátor překlopí do log.1, přepneme výstup tak abychom referenční hladinu trošku snížili a dostali ji tak "z dosahu šumu". Naopak když komparátor překlopí do log.0, tak referenční hladinu drobně zvýšíme. Spotřebujeme sice jeden pin Atmelu, ale získáme řešení, které funguje "vždy". Jak moc budeme ovlivňovat referenční hladinu záleží na nás. Metoda není nijak závratně přesná, protože hodnoty hystereze kolísají s napájecím napětím, ale bude pro většinu aplikací postačovat. Přirozeně ji nemůžete použít v situacích kdy vyžadujete přesnou a stabilní referenci. Zapojíme obvod podle schématu č.2. Referenční hladina by měla být 870mV pokud je PB3 v log.0 a 910mV pokud je v log.1. Hystereze je tedy 40mV. Hodnoty referenčního napětí můžete spočítat dle vztahu: Uref=(Up*R1*R2 + Ucc*R2*R3)/(R1*R2+R2*R3+R1*R3) kde za Ucc dáte napájecí napětí a za Up dosadíte napětí na výstupu z čipu (v našem případě buď 5V nebo 0V). Vztah uvádím raději obecně, protože ho můžete použít i s jiným napájecím napětím. Na vstup obvodu pustíme sinus s 10% šumu. Zdrojový kód příkladu je pod obrázky. Zapsáním samých nul do ACSR připojujeme pozitivní vstup komparátoru na AIN0 a negativní na AIN1. Záměrně na obrázku č.4 ukazují výstup komparátoru když je zpětná vazba (odpor R3) odpojen a hystereze tedy není zavedena, na obrázku č.5 pak vidíte výsledek po zavedení hystereze (obvod přesně dle schématu č.2).

Schéma č.2foto zapojení

Obrázek 4 - výsledek bez zpětné vazby (bez hystereze) Obrázek 5 - výsledek s hysterezí

// Komparátor s napěťovou hysterezí přivedenou z PA3
#include <avr/io.h>

int main(void){
DDRA |= (1<<DDA3);	// výstup, bude upravovat referenci
ACSR = 0;	// žádná přerušení, vstupy pro komparátor AIN0 a AIN1 
DIDR0 |= (1<<ADC2D) | (1<<ADC1D);	// vypneme vstupní buffery

while(1){
	if(ACSR & (1<<ACO)){PORTA &=~(1<<PORTA3);} // drobně snížíme referenci	
	else{PORTA |= (1<<PORTA3);} // drobně zvýšíme referenci	
	}
}

V některých situacích si můžete pomoct jinak. Předchozí metoda vyžadovala mít vnější referenci a spotřebovala vám jeden pin procesoru. Pokud ale máte "střídavý" nebo obecně nestejnosměrný signál, můžete hysterezi obejít. Jestliže je měřený signál natolik vychovaný, že se běžně příliš dlouho nezdržuje na hodnotě blízko referenční, můžete se zákmitů zbavit dočasným "zablokováním" komparátoru. Metodu lze použít pro detekci střídavých signálů, pulzů s pomalou (i rychlou) dobou náběhu a různých kolísavých a nestálých průběhů. Princip je jednoduchý. Jakmile komparátor překlopí, tak na to program zareaguje a na nějakou dobu výstup komparátoru ignoruje. Po určitém čase (kdy by se měl vstupní signál vzdálit dostatečně daleko od reference) začne program zase výstup komparátoru sledovat. Zjednodušeně řečeno, počká na první zákmit, zareaguje na něj a pak na nějaký čas zavře oči a bude dělat, že ostatní zákmity prostě nevidí.

Jednoduchá softwarová korekce je v následujícím zdrojovém kódu. Určitě bychom si stále vystačili se skenováním bitu ACO jako v předešlém příkladě, ale abychom se někam posunuli zkusíme využít vlajky ACI. Vynulujeme bity ACIS0 a ACIS1 a zároveň zakážeme přerušení vynulováním ACIE. Žádné přerušení se tedy volat nebude, ale vlajka ACI bude signalizovat zda nastala událost, jež by měla spustit přerušení, pokud by bylo povoleno. V našem případě je to jakákoli změna výstupu komparátoru. Ve zdrojovém kódu používám výrazy x &= ~(1<<y) jen pro názornost aby bylo jasné který bit nuluji a který nastavuji do log.1. Celé nastavení by samozřejmě šlo provést jediným zápisem binárního čísla. Případně se spolehnout na to, že po restartu jsou v registru nuly, ale kód by nebyl tak čitelný. V hlavní smyčce pak sledujeme vlajku ACI, pokud je nastavena a došlo ke změně výstupu komparátoru, tak chvíli počkáme (ignorujeme zákmity - "zavíráme oči"). Teprve pak se podíváme jaká je (ustálená) hodnota výstupu komparátoru ACO a podle toho zareagujeme a nastavíme PB0. Nakonec vlajku zápisem log.1 vynulujeme a čekáme na další událost.

// Jednoduchá časová hystereze
#define F_CPU 1000000
#include <avr/io.h>
#include <util/delay.h>

int main(void){
ACSR |= (1<<ACBG);	// na AIN0 zapojíme interní referenci,
// nastavujeme přerušení od komparátoru na jakoukoli změnu, ale přerušení zakazujeme
// jde nám jen o to sledovat vlajku přerušení ACI
// a chceme aby log.1 indikovala jakoukoli změnu výstupu komparátoru
ACSR = ~((1<<ACIS1) | (1<<ACIS0) | (1<<ACIE));
ADCSRB &= ~(1<<ACME);	// nechci zapínat multiplexer
DIDR0 |= (1<<ADC2D) | (1<<ADC1D);	// vypneme vstupní buffery
DDRB |= (1<<DDB0);	// výstup abychom se měli na co dívat

while(1){
	if(ACSR & (1<<ACI)){ // sledujeme vlajku ACI, pokud je nastavena došlo ke změně na výstupu komparátoru
		_delay_us(500);	// umrtvíme komparátor na dostatečnou dobu, aby se signál stihl vzdálit od reference
		if(ACSR & (1<<ACO)){PORTB=0b1;}	// nastavujeme výstup PB0 podle výstupu komparátoru (ACO)
		else{PORTB=0b0;}	
		ACSR |=(1<<ACI); // mažeme vlajku aby mohla být znovu nastavena událostí (změnou výstupu komparátoru)	 	
		}
	}
}

Výsledek (obrázek č.6) už zákmity neobsahuje, ale platíme za to daň v podobě "zdržení" v hlavní smyčce. Pokud bychom měli zároveň kontrolovat nějaké další události a nemohli bychom si takové zpoždění dovolit, museli bychom k tomuto účelu využít nějaký z časovačů, případně nějakou jinou fintu. U mnoha aplikací, vám ale toto řešení nebude vadit. Pokud by jste se ale dostali do bezvýchodné situace a ani jedno z předvedených řešení by vám nestačilo, nic vám nebrání použít externí komparátor a pomocí několika rezistorů si do něj zabudovat libovolnou hysterezi.

Obrázek č.6 - Výsledek s "Časovou" hysterezí - na tomto obrázku není nic zajímavého vidět :D

Milé překvapení

Teď je na řadě předvést si jednoduchou ukázku s přerušením. Budeme detekovat nástupnou hranu signálu. V rutině přerušení pak vyrobíme krátký pulz, podle nějž poznáme, že rutina přerušení proběhla a detekce zafungovala. Zkusíme nyní použít oba vstupy AIN0 a AIN1 a na oba si přivedeme vlastní signál. Na jeden z nich přivedu stejnosměrnou hodnotu a na druhý obdélníkový průběh (abychom neměli starosti se zákmity). U toho všeho zkusíme komparátor trochu potýrat. Sekce "Absolute Maximum Ratings" uvádí, že na žádném pinu čipu nesmí být zápornější napětí jak -0.5V. Jinak řečeno nemělo by ho zničit pokud mu na některý pin přivedeme napětí drobně menší jak GND. Přivedeme tedy na AIN0 (pozitivní vstup komparátoru) stejnosměrnou hodnotu o hodnotě přibližně -80mV ! Na AIN1 (negativní vstup) přivedeme obdélníkové napětí s minimem -200mV (!) a maximem přibližně 800mV. Prozkoumáme tím jestli komparátor dokáže pracovat kompletně pod napájecím napětím čipu. Výsledek uvidíte hned pod zdrojovým kódem.

// rutina přerušení od komparátoru
ISR(ANA_COMP_vect)
{
PORTB=1;	// krátkým pulzem na PB0 indikujeme detekci nástupné hrany
PORTB=0;
}

int main(void){
// AIN0 připojíme na PA1, AIN1 na PA2
// přerušení nastavíme na nástupnou hranu a povolíme ho
ACSR |= (1<<ACIS1) | (1<<ACIS0) | (1<<ACIE);	
ADCSRB &= ~(1<<ACME);	// nechci zapínat multiplexer
DIDR0 |= (1<<ADC2D) | (1<<ADC1D);	// vypneme vstupní buffery
DDRB |= (1<<DDB0);	// výstup abychom se měli na co dívat
sei();	// globální povolení přerušení

while(1){
	asm("nop");	// nic nedělej
	}
}
Obrázek č.7 - Komparátor pracuje i s omezeným záporným napětím

Výsledkem našeho testu je milé překvapení. Na obrázku č.7 vidíte nejen to, že funguje přerušení, ale i to, že komparátor dokáže pracovat pod napájecím napětím. Do jaké míry je tato činnost spolehlivá a bezpečná nechám na posouzení někoho jiného. Při této příležitosti si dovolím malou odbočku k AD převodníku. Schopnost měřit i pod napájecím napětím čipu jsem si ověřil i u AD převodníku v diferenciálním režimu. Díky tomu můžete měřit nabíjecí i zatěžovací proud baterie pouhým rezistorem v dolní větvi. Viz schema č.3. Kladný úbytek napětí na R1 bude odpovídat nabíjení baterie, záporný úbytek pak odpovídá vybíjení. Pokud vás nebude zajímat konkrétní hodnota nabíjecího proudu, pouze informace o tom zda probíhá nabíjení či vybíjení, můžete ke stejnému účelu použít komparátor. Rezistor je potřeba volit o malé hodnotě, aby se vám nemohlo stát že překročíte limit -0.5V. Pro proudy v řádu jednotek ampér bude bohatě postačovat 10-20mOhm. Modrý pulz je jak vidíte opět trochu "rozmazaný", stejně jako v našem prvním pokusu je tu drobná nejistota v rychlosti odezvy programu.

Schéma č.3 - Měření / hlídání proudu baterií

Analogový multiplexer

Nadešel čas vyzkoušet si zda pracuje multiplexer. Připravíme si jednoduchou sestavu. Do nepájivého pole si připojíme čtveřici trimrů. Tři z nich budou "referenční" přivedené na PA0, PA2 a PA3. Na prvním z nich nastavíme hodnotu 0.5V, na druhém 1V a na třetím 1.5V. Čtvrtý trimr připojíme na PA1 a bude soužit coby "neznámý" signál. Na výstup si pro indikaci zapojíme tři LED, na PB0, PB1 a PB2 (viz schéma č.5). Program nejprve nastaví komparátor. Vidíte že do registru ACSR zapisujeme nulu, tedy žádná přerušení a pozitivní vstup komparátoru je připojen na AIN0 (PA1). Nastavením bitu ACME v registru ADCSRB spouštíme analogový multiplexer. Na všech vstupech vypínáme digitální buffery. V nekonečné smyčce pak program nejprve připojí na negativní vstup komparátoru pin PA0, počká až se multiplexer přepne a komparátor zareaguje, pak přečte stav komparátoru a podle toho zda má "neznámý" signál větší nebo menší hodnotu rozsvítí nebo nerozsvítí LED na PB0. Následně připojí na negativní vstup komparátoru pin PA2 na němž je napětí 1V, počká, vyhodnotí stav komparátoru a tak dále. Program si tak střídavě přepíná referenci a hrubě zkoumá jakou amplitudu má neznámý signál na PA1. Podle toho jak natočíte trimr na PA1 tolik hladin překročíte a tomu bude odpovídat i množství LED které budou svítit. V podstatě jste vyrobili improvizovaný dvoubitový AD převodník. Jde jen o prostou demonstraci. Důležité je to, že musíte multiplexeru dát čas na přepnutí. Pokud by jste provedli kontrolu hned po přepnutí, nedostali by jste relevantní výsledek !

Schéma č.5 - 2 bitový AD převodník z komparátoru
// Jednoduchý 2bitový AD převodník z komparátoru
#define F_CPU 1000000
#include <avr/io.h>
#include <util/delay.h>

#define CH0 0b0
#define CH2 0b10
#define CH3 0b11

// na piny PA0,PA2 a PA3, přivedena napětí 0.5V, 1V a 1.5V
// na pinu PA1 (AIN0) trimr
int main(void){
ACSR = 0; // AIN0 - PA1, žádná přerušení
ADCSRB |= (1<<ACME);	// zapneme multiplexer
DIDR0 |= (1<<ADC0D) | (1<<ADC1D) | (1<<ADC2D) | (1<<ADC3D);	// vypneme vstupní buffery
DDRB |= (1<<DDB0) | (1<<DDB1) | (1<<DDB2) ;

while(1){
	ADMUX = CH0;	// multiplexer na ADC0, negativní vstup komparátoru je na PA0
	_delay_us(8);	// čas potřebný k přepnutí analogového multiplexeru	
	if(ACSR & (1<<ACO)){PORTB |= (1<<PORTB0);}else{PORTB &=~(1<<PORTB0);}
	ADMUX = CH2;	// multiplexer na ADC2, negativní vstup komparátoru je na PA2
	_delay_us(8);	// čas potřebný k přepnutí analogového multiplexeru	
	if(ACSR & (1<<ACO)){PORTB |= (1<<PORTB1);}else{PORTB &=~(1<<PORTB1);}
	ADMUX = CH3;	// multiplexer na ADC3, negativní vstup komparátoru je na PA3
	_delay_us(8);	// čas potřebný k přepnutí analogového multiplexeru	
	if(ACSR & (1<<ACO)){PORTB |= (1<<PORTB2);}else{PORTB &=~(1<<PORTB2);}
	}
}
Obrázek č.8 - 2 bitový AD převodník z komparátoru

Zákusek

Smysluplné použití

Teď by jste měli zvládat většinu funkcí komparátoru. Zbývají jen dvě. Komparátorem spuštěný AD převod a komparátorem spuštěná "Input capture" událost časovače. Druhou z nich si předvedeme na následujícím příkladu. Je to totiž ukázková spolupráce dvou periferií. Mějme signál u nějž potřebujeme měřit periodu. Nebo raději obecněji potřebujeme měřit čas mezi příchodem dvou pulzů. Takový aplikací může být velice mnoho. Představte si například měření úsťové rychlosti střely pomocí dvou laserových závor, nebo doby nabíjení kondenzátoru k určení jeho kapacity. Pokud toto měření chcete provádět na signálu, který má obecně analogový průběh, je spolupráce komparátoru a časovače ideální. Pokud se ještě neorientujete v čítačích / časovačích na AVR, berte prosím tuto kapitolu jen jako motivační - co všechno ještě jde. V rychlosti nastíním možné tupé řešení ("arduino" styl :D ). Pollingem budete kontrolovat zda došlo k překlopení komparátoru (bit ACO v registru ACSR) ihned po překlopení spustíte časovač. Opět pollingem čekáte na překlopení komparátoru (příchod druhého pulzu) a pak časovač zastavíte. S tímto přístupem na vás bude čekat stejný problém jako jsme měli v prvním příkladu - jitter. Změřený čas se bude nahodile lišit od reálného. Vysvětlení je také v prvním příkladě. Pokud budete v hlavní smyčce mimo jiné kontrolovat třeba i stav tlačítek (start / stop měření) bude nepřesnost měření o to větší. Čím kratší čas budete měřit, tím horší bude relativní chyba. Atmel vám ale nabízí mnohem elegantnější přístup.

Nastavíme komparátor tak aby byl připojen na "Input Capture" signál časovače 1. Ten spustíme v "Normal" režimu, tedy necháme ho počítat od nuly do stropu (0xffff). Nastavíme mu "input capture" na nástupnou hranu a povolíme si od této události přerušení. Jakmile čítači přijde signál, během jediného taktu procesoru uloží svoji hodnotu do ICR1 registru a vyvolá přerušení. Mezi tím časovač běží dál. V rutině přerušení už máme dost času na to si hodnotu vyzvednout a zpracovat. Vzniká tak minimální možné zpoždění mezi příchodem signálu a zaznamenáním hodnoty časovače. Menší už u tohoto čipu dle mého názoru být nemůže. Toto je gró věci. Všechno další už je jen zpracování dat. Podívejte se na zdrojový kód ukázky. Uvidíte, že v rutině přerušení spočítám rozdíl dvou po sobě následujících časů (tedy okamžik příletu dvou následujících pulzů). Ošetřím situaci kdy časovač přeteče. Jakmile napočítá do 0xffff (65535) začne počítat zase od znova. Může se tedy stát že první pulz přiletí těsně před přetečením časovače a zanechá nám časovou značku například 65000, druhý pulz přiletí až po přetečení a my zaznamenáme čas příchodu 250. Když se nad tím zamyslíte zjistíte, že pulzy jsou od sebe vzdáleny (65535-65000)+250 časových jednotek. Protože je slušné udržovat rutinu přerušení co nejkratší, dám pomocí proměnné posli vědět hlavní smyčce aby obstarala odesílání dat (to je totiž relativně časově náročná operace). Software by šel v tomto případě ještě hodně optimalizovat, ale to je nad rámcem tohoto příkladu. Abych mohl data snadno sledovat stvořil jsem si primitivní funkci, která je po dvou drátech odešle do světa. Před každou nástupnou hranou CLK (PB1) je linka DATA (PB0) nastavena na hodnotu jednoho ze 16ti bitů odesílané zprávy. Viz SPI protokol na internetu. Osciloskopem se schopností dekódovat seriová data si pak výsledek přečtu. Téma jak dostat data z čipu je ale na samotný článek nebo na víc, takže se mu momentálně nebudu věnovat. Nejprimitivnější způsob jak tato data interpretovat je připojit k čipu posuvný registr (nebo dva) a na ně 16tici LED diod. Binární kombinace na diodách by pak odpovídala změřenému času. Ale přirozeně si vymyslíte elegantnější způsob jak exportovat data. Výsledky pokusu vidíte na obrázku č.9 a č.10 hned pod zdrojovým kódem.

// "přesné" měření periody s komparátorem
#define F_CPU 1000000
#include <avr/io.h>
#include <avr/interrupt.h>
// na pin PA2 (AIN1) připojíme trimr k nastavení referenční hodnoty
// na pi PA1 (AIN0) budeme pouštět měřený signál

// prosté definice aby byl program čitelnější a snadno modifikovatelný
// CLK a DATA slouží k jednoduchému odeslání zprávy z čipu (pomocí softwarového "SPI").
#define CLK_H PORTB |=(1<<PORTB1)
#define CLK_L PORTB &=~(1<<PORTB1)
#define CLK_TGL PINB = (1<<PINB1) // tohle je finta (!), sekce 10.1.2 v datasheetu :)
#define DATA_H PORTB |=(1<<PORTB0)
#define DATA_L PORTB &=~(1<<PORTB0)

volatile unsigned int t_stary,t_novy,dt;	// proměnné pro práci s časem
volatile char posli=0;	// rutina přerušení signalizuje hlavní smyčce že má odeslat zprávu

void posli_zpravu(unsigned int data); // fce odesílající výsledky měření

ISR(TIM1_CAPT_vect){
t_novy=ICR1;	// uložíme nový zachycený čas
if(t_novy>=t_stary){	// pokud je větší jak starý čas, tak čítač nepřetekl (nejspíš)
	dt=t_novy-t_stary;	// perioda je tedy rozdíl mezi novým a starým časem
	}
else{	// pokud je nový čas menší jak starý čas, tak čítač přetekl a je potřeba to ošetřit
	dt=((0xffff-t_stary)+t_novy);	// trocha matematiky nikdy nikoho nezabila
	}	 
t_stary=t_novy; // nový čas nám zestárnul a stal se z něj starý čas :)  
posli=1;	// dej na vědomí hlavní smyčce, že máme nová data ať je pošle
}

int main(void){
ACSR |= (1<<ACIC); // připojíme komparátor na "Input Capture" čítače / časovače 1
DIDR0 |= (1<<ADC1D) | (1<<ADC2D);	// vypneme vstupní buffery na PA1 (AIN0) a PA2 (AIN1)
DDRB |= (1<<DDB0) | (1<<DDB1); // konfigurujeme si výstupy

TCCR1A = 0; // konfigurujeme čítač 1 v normálním režimu
// zapínáme digitální filtr na "Input Capture", detekci nástupné hrany a spouštíme časovač s clockem F_CPU/8.
TCCR1B = (1<<ICNC1) | (1<<ICES1)| (1<<CS10); 
TIFR1 |= (1<<ICF1); // pro jistotu mažeme vlajku (co kdyby tam visela "z minula" ?)
TIMSK1 |= (1<<ICIE1); // povolujeme přerušení od capture události 
sei();

while(1){
	if(posli==1){
		posli_zpravu(dt);
		posli=0;
		}
	}
}

void posli_zpravu(unsigned int data){
unsigned int j=(1<<15);	// bude sloužit jako maska
char i;	// do forcyklu
CLK_L;	// připrav linky do neutrálního stavu před vysíláním
DATA_L;
// odešli 16 bitů
for(i=0;i<16;i++){
	if(data & j){DATA_H;}else{DATA_L;} // podívej se jestli je i-tý bit dat v log.1 nebo log.0
	j=j>>1;	// nastav novou masku
	CLK_TGL;	// udělej tick clockem
	asm("nop");	// trochu clock natáhni aby se mi dobře detekoval
	asm("nop");
	CLK_TGL;
	}
DATA_L;	// ukliď komunikační linky do neutrálního stavu
CLK_L;
}
Obrázek č.9 "Přesné" měření vzdálenosti pulzů - světle modrá reference, žlutý měřený signál
Obrázek č.10 Zoom na datový přenos - červená data, tmavě modrá clock

Jen pro zajímavost jsem jako vstupní pulz (žlutý signál) zvolil funkci sinc. Modrý signál je referenční hladina na AIN1 nastavovaná trimrem a filtrovaná kondenzátorem 1nF. Červený a tmavě modrý průběh pak představují datový výstup. Na druhém obrázku je bližší záběr na datový přenos. Cílové zařízení (v tomto případě osciloskop) čte s každou nástupnou hranou clocku (tmavě modrá) hodnotu datové linky (červená). Jako první se přenáší MSB a jako poslední LSB. Celé 16bitové číslo pak tvoří zprávu. Zpráva obsahuje časový rozdíl mezi dvěma po sobě jdoucími pulzy v jednotkách čítače. Čítač jsme nechali běžet na frekvenci čipu, tedy na 1MHz. Číselně tedy signál odpovídá počtu mikrosekund mezi pulzy. Frekvence pulzů je podle generátoru i osciloskopu 728Hz (perioda 1372 až 1373 us). Naše měření ale ukazuje 1324 až 1325 us. Čím je tato odlišnost dána ? Je dána odchylkou vnitřního RC oscilátoru. Čip jsme nechali běžet na vnitřní zdroj hodin, ten se může odchylovat o několik procent od nominální hodnoty 1MHz. Naštěstí ho můžete registrem OSCCAL kalibrovat, ale o tom až jindy. Pro časová měření je samozřejmě nejvhodnější odvodit takt procesoru od krystalového oscilátoru (k čemuž vám slouží piny XTAL1 a XTAL2). Pro jednoduchost jsem se s krystalem neobtěžoval. Alespoň víte kde je chyba až vám příště budou vycházet měřené časy o několik procent posunuté proti očekávaným :)

V programu je ale ještě jedna neošetřená záležitost. Co když čítač přeteče dvakrát nebo třikrát mezi po sobě následujícími pulzy ? Pak už nejste schopni zjistit kolikrát se to stalo a tedy ani zjistit časový úsek mezi pulzy. Můžete to řešit několika způsoby. Změnit frekvenci pro časovač, tak aby všechny vaše měřené úseky spadaly do jeho rozsahu. Tuto podmínku jste ve většině případů schopni splnit. Výjimečně, když potřebujete velmi přesně měřit delší časové úseky, můžete zapnout přerušení od přetečení časovače a počítat kolikrát přetekl. Pak ale nezapomeňte, že do integeru se vám stejně víc jak 65535 nevleze a zvolte větší datový typ. Ale to už odbíháme příliš daleko od komparátoru...

Závěrem

Doufám, že jste z textu získali jistý vhled do problematiky. Nevylučuji, že se v článku mohou objevit chyby, ale všechny zdrojové kódy jsem spouštěl (konec konců vidíte výsledky). Pokud si chcete znalosti upevnit, doporučoval bych vám zkusit si sestavit a napsat nějakou jednoduchou aplikaci. Třeba detekci předmětu odraznou metodou pomocí IR diody a IR fototranzistoru. Také by staálo za to vyzkoušet co dělá komparátor, když pin AIN0 nebo AIN1 používáte jako výstup (případně používáte pull-up rezistor). Pokud vím tak se někde na webu rozebíralo chování a nenašlo to rozuzlení, takže to čeká na někoho kdo to definitivně rozsekne. Já bych o ti informace určitě stál :)

Zdroje

Attiny24A Datasheet

Dodatek

Dovolím si uvést malý dodatek pod čarou. Dlouhodobě vedu diskuzi se svou bývalou střední školou ohledně zavedení AVR (nasmíto 8051) a to mě nutí udržovat si jistou minimální znalost asembleru, aybch v případě potřeby byl schopen nějáký jednoduchý prográmek sesmolit. Abych nezakrněl, rozhodl jsem se vyzkoušet rychlost reakce na komparátor v asembleru a v C. Atiny jsem přepnul na vyšší frekvenci. Proto abych ji mohl snadno měnit, zvolil jsem externí zdroj clocku. Na pin CLKI jsem připojil generátor a nastavil jej na 15MHz (Tiny by mohla běžet až do 20MHz). Berte prosím tento dodatek jen jako ukázku toho jak assembler vypadá. Pokud by se někomu z vás podařilo rutinu napast rychleji, dejte mi prosím vědět :) Program v C je stejný jako v našem prvním případě (až na to, že jako výstup používá PB2). Program v assembleru je uveden níže. Z výsledků se zdá, že překladač dělá svou práci dobře, protože program v C je stejně rychlý jako napsaný v assembleru. Výsledky pokusů jsou na obrázcích 9 a 10. Na tmavě modrém průběhu je trochu patrný clock procesoru. Červený průběh je vstup do komparátoru a světle modrý průběh je reakce programu na pinu PB2. Je vidět, že v nejlepším případě reaguje program v rámci 6ti clocků a trvá mu to 440ns. S takovým vstupním signálem trvá reakce komparátoru typicky 75ns + synchronizace 1-2 strojové cykly. Přijde-li signál v tom nejvhodnějším okamžiku měl by program zareagovat do 3 cyklů. Jeden cyklus při 15MHz trvá přibližně 67ns. Celkové zdržení by pak mělo v nejlepším případě být (3+1)*67ns + 75ns = 343ns. Měřením mi ale vychází 440ns. Kde se bere tento nesoulad mi není známo. Nemá význam nad tím déle hloubat. Pokud budete někdy potřebovat rychlejší reakce, budete se stejně muset spolehnout na jiné obvodové řešení, na externí komparátor a zpracovat jeho signál jinými prostředky (hradlové pole, diskrétní logika).

Obrázek č.9 - Program v assembleru Obrázek č.10 - Program v C
.include "tn24Adef.inc"

.org 0
RJMP START

START:
LDI R16, LOW(RAMEND)
OUT SPL, R16
LDI R16,(1<<DDB2)
OUT DDRB,R16
LDI R16,(1<<ACBG)
OUT ACSR,R16

STOP:
SBIC ACSR, ACO
SBI PORTB,2
SBIS ACSR,ACO
CBI PORTB,2
RJMP STOP