Reklama

ATtiny24 analogový komparátor – 3. Díl Praktické ukázky II

Úvod

Tímto dílem zakončíme seriál o analogovém komparátoru mikrokontroléru ATtiny24. Dočtete se zde, jak použít přerušení od komparátoru, jak využít analogový multiplexor u komparátoru a jak smysluplně použít nabité vědomosti.

Práce pomocí přerušení

Teď je na řadě předvést si jednoduchou ukázku s přerušením. Budeme detekovat vzestupnou hranu signálu. V rutině přerušení pak vyrobíme krátký pulz, podle kterého 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 potrápit. Sekce “Absolute Maximum Ratings” uvádí, že na žádném pinu čipu nesmí být zápornější napětí jak -0.5 V. 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ě -80 mV! Na AIN1 (negativní vstup) přivedeme obdélníkové napětí s minimem -200 mV (!) 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.

 

001: // rutina přerušení od komparátoru
002: ISR(ANA_COMP_vect)
003: {
004: PORTB=1;// krátkým pulzem na PB0 indikujeme detekci nástupné hrany
005: PORTB=0;
006: }
007:
008: int main(void){
009: // AIN0 připojíme na PA1, AIN1 na PA2
010: // přerušení nastavíme na nástupnou hranu a povolíme ho
011: ACSR |= (1<<ACIS1) | (1<<ACIS0) | (1<<ACIE);
012: ADCSRB &= ~(1<<ACME);// nechci zapínat multiplexer
013: // vypneme vstupní buffery
014: DIDR0 |= (1<<ADC2D) | (1<<ADC1D);
015: DDRB |= (1<<DDB0);// výstup abychom se měli na co dívat
016: sei();// globální povolení přerušení
017:
018: while(1){
019: asm("nop");// nic nedělej
020: }
021: }
Vzorové zdrojové kódy slouží pouze k jednoduché demonstraci funkce, proto často obsahují globální proměnné, neobsahují většinou ukazatele, dále také neobsahují kontroly proměnných, definování nevyužitých pinů mikrokontroléru a podobné správné programátorské návyky. Proto je nutné tyto kódy brát s patřičnou rezervou.
 

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

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

Výsledkem našeho testu je milé překvapení. Na Obr. 1. 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 A/D převodníku. Schopnost měřit i pod napájecím napětím čipu jsem si ověřil i u A/D 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 Obr. 2. 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.5 V. Pro proudy v řádu jednotek ampér bude bohatě postačovat 10-20 mOhm. 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.

Obr. 2: Měření / hlídání proudu baterií

Obr. 2: Měření / hlídání proudu baterií
Analogový multiplexor

Nadešel čas vyzkoušet si, zda pracuje multiplexor. 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.5 V, na druhém 1 V a na třetím 1.5 V. Čtvrtý trimr připojíme na PA1 a bude sloužit coby “neznámý” signál. Na výstup si pro indikaci zapojíme tři LED, na PB0, PB1 a PB2 (viz Obr. 3.). 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ý multiplexor. 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 multiplexor 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í 1 V, počká, vyhodnotí stav komparátoru a tak dále. Program si tak střídavě přepíná referenci a hrubě zkoumá jakou úroveň 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ý A/D převodník. Jde jen o prostou demonstraci. Důležité je to, že musíte multiplexoru dát čas na přepnutí. Pokud byste provedli kontrolu hned po přepnutí, nedostali byste relevantní výsledek!

Obr. 3: Měření / hlídání proudu baterií

Obr. 3: Měření / hlídání proudu baterií
001: // Jednoduchý 2bitový AD převodník z komparátoru
002: #define F_CPU 1000000
003: #include <avr/io.h>
004: #include <util/delay.h>
005:
006: #define CH0 0b0
007: #define CH2 0b10
008: #define CH3 0b11
009:
010: // na piny PA0,PA2 a PA3, přivedena napětí 0.5V, 1V a 1.5V
011: // na pinu PA1 (AIN0) trimr
012: int main(void){
013: ACSR = 0;// AIN0 – PA1, žádná přerušení
014: ADCSRB |= (1<<ACME);// zapneme multiplexer
015: // vypneme vstupní buffery
016: DIDR0 |= (1<<ADC0D) | (1<<ADC1D) | (1<<ADC2D) | (1<<ADC3D);
017: DDRB |= (1<<DDB0) | (1<<DDB1) | (1<<DDB2) ;
018:
019: while(1){
020: ADMUX = CH0;// multiplexer na ADC0, negativní vstup komparátoru
021: // je na PA0
022: _delay_us(8);// čas potřebný k přepnutí analogového multiplexeru
023: if(ACSR & (1<<ACO)){PORTB |= (1<<PORTB0);}else{PORTB &=~(1<<PORTB0);}
024: ADMUX = CH2;// multiplexer na ADC2, negativní vstup komparátoru
025: // je na PA2
026: _delay_us(8);// čas potřebný k přepnutí analogového multiplexeru
027: if(ACSR & (1<<ACO)){PORTB |= (1<<PORTB1);}else{PORTB &=~(1<<PORTB1);}
028: ADMUX = CH3;// multiplexer na ADC3, negativní vstup komparátoru
029: // je na PA3
030: _delay_us(8);// čas potřebný k přepnutí analogového multiplexeru
031: if(ACSR & (1<<ACO)){PORTB |= (1<<PORTB2);}else{PORTB &=~(1<<PORTB2);}
032: }
033: }
Vzorové zdrojové kódy slouží pouze k jednoduché demonstraci funkce, proto často obsahují globální proměnné, neobsahují většinou ukazatele, dále také neobsahují kontroly proměnných, definování nevyužitých pinů mikrokontroléru a podobné správné programátorské návyky. Proto je nutné tyto kódy brát s patřičnou rezervou.
 

Obr. 4: 2 bitový AD převodník z komparátoru

Obr. 4: 2 bitový AD převodník z komparátoru
Smysluplné použití

Teď byste měli zvládat většinu funkcí komparátoru. Zbývají jen dvě. Komparátorem spuštěný A/D 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ých 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 předchozího dílu – “jitter”. Změřený čas se bude nahodile lišit od reálného. Vysvětlení je také v prvním příkladu předchozího dílu. 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 vzestupnou 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 začátku. 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) plus 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 vzestupnou 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 sériová 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. 5. a Obr. 6. hned pod zdrojovým kódem.

 

001: // "přesné" měření periody s komparátorem
002: #define F_CPU 1000000
003: #include <avr/io.h>
004: #include <avr/interrupt.h>
005: // na pin PA2 (AIN1) připojíme
006: // trimr k nastavení referenční hodnoty
007: // na pi PA1 (AIN0) budeme pouštět měřený signál
008:
009: // prosté definice aby byl program čitelnější
010: // a snadno modifikovatelný
011: // CLK a DATA slouží k jednoduchému odeslání
012: // zprávy z čipu (pomocí softwarového "SPI").
013: #define CLK_H PORTB |=(1<<PORTB1)
014: #define CLK_L PORTB &=~(1<<PORTB1)
015: #define CLK_TGL PINB = (1<<PINB1)// tohle je finta (!), sekce 10.1.2 v datasheetu :)
016: #define DATA_H PORTB |=(1<<PORTB0)
017: #define DATA_L PORTB &=~(1<<PORTB0)
018:
019: // proměnné pro práci s časem
020: volatile unsigned int t_stary,t_novy,dt;
021: volatile char posli=0;// rutina přerušení signalizuje hlavní smyčce
022: // že má odeslat zprávu
023:
024: // fce odesílající výsledky měření
025: void posli_zpravu(unsigned int data);
026:
027: ISR(TIM1_CAPT_vect){
028: t_novy=ICR1;// uložíme nový zachycený čas
029: if(t_novy>=t_stary){// pokud je větší jak starý čas, tak
030: // čítač nepřetekl (nejspíš)
031: dt=t_novy–t_stary;// perioda je tedy rozdíl mezi novým a starým časem
032: }
033: else{// pokud je nový čas menší jak starý čas, tak
034: // čítač přetekl a je potřeba to ošetřit
035: dt=((0xffff–t_stary)+t_novy);// trocha matematiky nikdy nikoho nezabila
036: }
037: t_stary=t_novy;// nový čas nám zestárnul a stal se z něj starý čas :)
038: posli=1;// dej na vědomí hlavní smyčce, že máme nová data
039: // ať je pošle
040: }
041:
042: int main(void){
043: ACSR |= (1<<ACIC);// připojíme komparátor na “Input Capture”
044: // čítače / časovače 1
045: // vypneme vstupní buffery na PA1 (AIN0) a PA2 (AIN1)
046: DIDR0 |= (1<<ADC1D) | (1<<ADC2D);
047: // konfigurujeme si výstupy
048: DDRB |= (1<<DDB0) | (1<<DDB1);
049:
050: TCCR1A = 0;// konfigurujeme čítač 1 v normálním režimu
051: // zapínáme digitální filtr na "Input Capture",
052: // detekci nástupné hrany a spouštíme časovač
053: // s clockem F_CPU/8.
054: TCCR1B = (1<<ICNC1) | (1<<ICES1)| (1<<CS10);
055: TIFR1 |= (1<<ICF1);// pro jistotu mažeme vlajku
056: // (co kdyby tam visela “z minula” ?)
057: TIMSK1 |= (1<<ICIE1);// povolujeme přerušení od capture události
058: sei();
059:
060: while(1){
061: if(posli==1){
062: posli_zpravu(dt);
063: posli=0;
064: }
065: }
066: }
067:
068: void posli_zpravu(unsigned int data){
069: unsigned int j=(1<<15);// bude sloužit jako maska
070: char i;// do forcyklu
071: CLK_L;// připrav linky do neutrálního stavu před vysíláním
072: DATA_L;
060: // odešli 16 bitů
073: for(i=0;i<16;i++){
074: // podívej se jestli je i-tý bit dat v log.1 nebo log.0
075: if(data & j){DATA_H;}else{DATA_L;}
076: j=j>>1;// nastav novou masku
077: CLK_TGL;// udělej tick clockem
078: asm("nop");// trochu clock natáhni aby se mi dobře detekoval
079: asm("nop");
080: CLK_TGL;
081: }
082: DATA_L;// ukliď komunikační linky do neutrálního stavu
083: CLK_L;
084: }
Vzorové zdrojové kódy slouží pouze k jednoduché demonstraci funkce, proto často obsahují globální proměnné, neobsahují většinou ukazatele, dále také neobsahují kontroly proměnných, definování nevyužitých pinů mikrokontroléru a podobné správné programátorské návyky. Proto je nutné tyto kódy brát s patřičnou rezervou.
 

Obr. 5:

Obr. 5: “Přesné” měření vzdálenosti pulzů – světle modrá reference, žlutý měřený signál

Obr. 6: Zoom na datový přenos - červená data, tmavě modrá clock

Obr. 6: 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 1 nF. Č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 1 MHz. Číselně tedy signál odpovídá počtu mikrosekund mezi pulzy. Frekvence pulzů je podle generátoru i osciloskopu 728 Hz (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 1 MHz. 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ětší hodnota jak 65535 nevejde a proto 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 stá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 ty informace určitě stál :)

Dodatek

Dovolím si uvést malý dodatek pod čarou. Dlouhodobě vedu diskuzi se svou bývalou střední školou ohledně zavedení AVR (namísto 8051) a to mě nutí udržovat si jistou minimální znalost Assembleru, abych v případě potřeby byl schopen nějaký jednoduchý prográmek sesmolit. Abych nezakrněl, rozhodl jsem se vyzkoušet rychlost reakce na komparátor v Assembleru a v C. ATtiny jsem přepnul na vyšší frekvenci. Proto, abych ji mohl snadno měnit, zvolil jsem externí zdroj hodin. Na pin CLKI jsem připojil generátor a nastavil jej na 15 MHz (ATtiny by mohla běžet až do 20 MHz). Berte prosím tento dodatek jen jako ukázku toho jak Assembler vypadá. Pokud by se někomu z vás podařilo rutinu napsat 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 programy napsané v C i v Assembleru jsou stejně rychlé. Výsledky pokusů jsou na Obr. 7. a Obr. 8. Na tmavě modrém průběhu jsou trochu patrné hodiny 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 cyklů a trvá mu to 440 ns. S takovým vstupním signálem trvá reakce komparátoru typicky 75 ns plus 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 15 MHz trvá přibližně 67 ns. Celkové zdržení by pak mělo v nejlepším případě být (3+1)*67 ns + 75 ns = 343 ns. Měřením mi ale vychází 440 ns. 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. 7: Program v assembleru

Obr. 7: Program v assembleru

Obr. 8: Program v C

Obr. 8: 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

 
Autor: Michal Dudka
 

[1] ATMEL. 8-bit Microcontroller with 2K/4K/8K Bytes In-System Programmable Flash ATtiny24A ATtiny44A ATtiny84A.[online] citováno 24. července 2017. Dostupné na www: http://www.atmel.com/images/doc8183.pdf
Následující a předchozí příspěvek v kategorii:

 
Předchozí: ATtiny24 analogový komparátor – 2. Díl Praktické ukázky I

 
Tajned facebook
 

Za případné chyby v textu, ve zdrojovém kódě, nebo ve schématickém zapojení se omlouváme.
AUTOŘI NEBEROU ŽÁDNOU ODPOVĚDNOST ZA PŘÍPADNÉ ÚJMY NA ZDRAVÍ ČI MAJETKU.