Ovládání portů u AVR

Motivace

Je na čase vymést pavučiny a utřít prach z vašich znalostí elektřiny. Zopakujte si zákony panů Gustava Roberta Kirchhoffa a Georga Ohma, protože se v dalších řádcích vrhneme na ovládání vstupně výstupních portů Atmelu. Nejdůležitější periferii vůbec. Protože všechno co Atmel dělá, dělá pomocí portů. A nic z toho co se děje vně Atmelu není program, všechno je to fyzika a elektronika. Připravte se prosím na to že budeme hodně mávat rukama a jen velmi málo programovat.

Úvod

Ještě než začnu, rád bych jen vyjasnil názvosloví. Vývod (slangově také třeba pin) je ta kovová nožička, která vede z pouzdra mikrokontroléru, ta nožička kterou připojujete někam do obvodu. Anglicky může také nést označení GPIO (General purpose output / input), ale názvosloví v tomto ohledu není příliš jednotné. Vývody se pro jednodušší zacházení sdružují do portů a tvoří skupiny typicky po osmi (nebo po šestnácti). U Atmelů jsou porty pojmenovány písmeny. Takže existuje Port A, Port B, Port C atd. U menších atmelů nejsou porty plně obsazeny. Jinak řečeno třeba Atmega8 má celý Port D a Port B, ale Portu C jeden vývod chybí. Větší atmely jako třeba Atmega16 mají čtveřici portů, tedy 32 vývodů. Každý vývod má svůj název. Vývody portu C se jmenují PC0,PC1, ... až PC7. Číslování je tedy od nuly. Sdružování vývodů do portů má své výhody. S portem se dá zacházet jako s celkem a program tak může chytře používat celou osmici vývodů (ne však nutně). Každý vývod můžete konfigurovat nezvávisle na ostatních, takže vám nic nebrání pracovat s vývody individuálně. Valná většina čipů rodiny AVR má shodné ovládání portů a stejné možnosti. Těch možností není mnoho. Každý pin procesoru může být buď vstup nebo výstup. A když je vstupem můžete u něj zapnout interní pull-up rezistor. Což je prostě rezistor připojený mezi vývod a napájení čipu. Až na pár jemných nuancí je to vlastně všechno co vstupně výstupní porty umí. Možnosti si předvedeme třeba na čipu Atmega16A.

Limitní elektrické vlastnosti

Tři nejnebezpečnější lidé na světě jsou: Technik, který provedl změnu v programu, uživatel, který dostal nápad a programátor, který drží v ruce pájku. Očekávám, že nejčastější čtenáři budou spadat do té třetí kategorie a proto nebude od věci si hned zezačátku, dokud jste čerství vysvětlit pár základních faktů, které platí tvrdě a neúprosně. V datasheetu ke každému integrovanému obvodu je sekce "Absolute Maximum Ratings", kterou je dobré si přečíst a brát ji vážně. Proto se na ni mrkneme společně."Absolute Maximum Ratings" jasně říká, že na žádném pinu kromě Resetu nesmí napětí překročit hodnotu Ucc+0.5V. Napětí Ucc je napájecí napětí čipu. Tedy při napájení 5V nesmíte přivést na vývod větší napětí jak 5.5V. Podobný limit existuje i pro záporné napětí. Na žádný z pinů nesmite přivést napětí nižší jak -0.5V. Každý pin má totiž proti GND a proti Ucc ochrannou diodu. Ta ale slouží pouze k ochraně před statickým nábojem a nebude schopná odvádět větší proudy vzniklé přepětím na kterémkoli vývodu. Reset pin snese až 13V. Tato schopnost ale slouží k HVP programování a z hlediska aplikace je slušné pracovat s Resetem jako s běžným pinem. Dalším limitem je maximální proud výstupu, ten by neměl překročit 40mA. Posledním limitem, který datasheet uvádí je celkový proud všemi napájecími piny. Ten činí 200mA u PDIP pouzder (ty s nožičkami) a 400mA u SMD a MLF pouzder. Že tento limit není problém překročit může ilustrovat nasledující případ. Určitě znáte takové ty dětské aplikace pro jednočip v podobě blikacího vánočního stromečku. Připojíte k Atmelu 30 LED a blikáte s nimi jak zběsilí, do každé LED pouštíte 15mA aby nesvítila jako bludička. Když si teď proudy sečtete zjistíte že 30x15mA dělá bezmála půl ampéry. Půl ampéry, která teče napájením Atmelu a porušuje to co se nikdy porušovat nesmí. Přirozeně některé překročení těchto limitů Atmel snese. Ze zkušenosti bych mohl referovat o mnohém porušení limitů, které nevedlo ke zničení čipu. Třeba o Tině, která vykonávala program a blikala LEDkou i při 12V napájení. Nikdy se ale na něco takového nespoléhejte a vždy si dobře rozmyslete zda používáte vstupy a výstupy v souladu s datasheetem. Murphyho zákony jsou neúprosné a může se vám stát, že při ladění aplikace na nepájivém poli uděláte chybu, která se ale neprojeví a vy si jí nevšimnete. Třeba přivedete 5V na vstup čipu s napájením Ucc=3.3V. Necháte vyrobit desky plošných spojů, osadíte je a nebudete se stačit divit proč vám najednou Atmely hoří :)

elektrické parametry vstupů

Vstup Atmelu zaručeně vnímá jako log.1 napětí které je větší než 0.6Ucc a jako log.0 napětí které je nižší jak 0.2Ucc. Může se stát, že vstup bude správně pracovat i mimo tyto limity, ale datasheet vám to nezaručuje. Zajímat vás tyto hodnoty bodou hlavně v situacích kdy budete chtít aby váš Atmel běžící s 5V napájením přijímal data z 3.3V systému. Vývody XTAL a Reset mají úrovně drobně jinačí, v případě potřeby je dohledáte v datasheetu v sekci "Electrical Characteristics". Do vstupů může téct jistý malý zbytkový proud. Datasheet ho charakterizuje jako menší než 1uA, ale z měření vím, že to typicky bývá o několik řádů menší. Jedinou vyjímkou je situace kdy na vstup přivádíte nějákou analogovou hodnotu, tedy mezi limity pro log.1 a log.0. Pak může do vstupů téct nějáký měřitelný proud. Takovým sitaucím by jste se měli vyhnout. V případě že na pin potřebujete přivádět analogovou hodnotu využijte možnosti vypnout vstupní buffer (jinak řečeno odpojit celou vstupní logickou část pinu). Jak to provést diskutuji na konci článku o komparátorech.

elektrické parametry výstupů

Výstup Atmelu má push-pull topologii. To znamená, že v log.1 dokáže dodávat proud a v log.0 dokáže odebírat proud z vnějšího obvodu. Výstupy jsou dost "tvrdé" na to aby mohly přímo ovládat LED (přes rezistor přirozeně !). "Tvrdost" výstupu se pozná podle toho jak moc protákající proud ovlivňuje jeho výstupní napětí. Ideálně tvrdý výstup má nulový výstupní odpor a ať skrze něj protéká jakýkoli proud, je jeho napětí stálé. Reálné porty ale tak moc tvrdé nejsou. Jak se proudové zatížení projeví na jejich napětí shrnují grafy v datasheetu v sekci 28.0.7. Z nich je patrné, že výstupní odpor se pohybuje okolo 25Ohmů při 5V napájení a 50Ohmů při napájení 2.7V. to znamená, že pokud zatížíte čip napájený 5V proudem 20mA, bude výstupní napěťová úroveň v log.1 přibližně 4.5V. Podobné paramery platí i pro opačnou situaci, kdy držíte výstup v log.0 a vnějším obvodem vám do výstupu teče proud. Tady je na místě malá poznámka o výkonu. Jestliže protéká výstupem proud 20mA a je na něm úbytek 0.5V (má tedy vnitřní odpor 25 Ohm) je na něm i výkonová ztráta P=U*I = 0.5*0.02 = 10mW. Ta se projevuje uvnitř atmelu a pokud podobným způsobem zatížíte více výstupů, může ztrátový výkon přispívat k ohřevu pouzdra. To vás většinou zajímat nebude, ale pokud budete využívat vnitřní teplotní senzor je potřeba k tomu přihlédnout.

Pull-up rezistor

Na každém vývodu nakonfigurovaném jako vstup je možné zapnout vnitřní pull-up rezistor. Ten vám v mnohých aplikacích ušetří pracné připojování pull-up rezistoru z vnějšku. Jeho hodnota je podle datasheetu mezi 20-50kOhm, ale můžete si ji snadno změřit přesně. Stačí pull-up rezistor zapnout a pak na vývod připojit proti zemi známý rezistor s hodnotou řádově v desítkách kOhm. Ten vytvoří s interním pull-up rezistorem dělič, na vašem rezistoru změříte napětí a s trochou matematiky se dopracujete k hodnotě interního pull-up rezistoru. Druhou možností je měřit přímo ohmmetrem, ale pak dbejte na správnou polaritu a připojte plus měřáku na Ucc. Mínus měřáku pak na příslušný vývod. Já na Attiny2313A naměřil hodnoty v rozptylu od 35KOhm do 36kOhm. Na resetu jsem pak naměřil 69kOhm (datasheet uvádí 30-85kOhm). Reset má pevný pull-up rezistor, takže ho nemůžete vypnout. Reset musíte držet v neutrálním stavu v log.1 (restartujete log.0), takže tuto vlastnost oceníte. Obecně díky internímu pull-up rezistoru se vám značně zjednodušuje návrh obvodů. Tlačítka, klávesnice, enkodéry, to všechno díky tomu můžete připojovat přímo na vývody Atmelu. Typicky také obvody připojené k externímu přerušení budou vyžadovat pull-up rezistor protože mají výstupy s otevřeným kolektorem. Díky tomu je jich pak možné připojovat na jeden vstup Atmelu více.

Jak porty ovládat

Ovládání najdete snad v každém návodu na internetu. Takže budu stručný. Každý port má registr DDRx (Data Direction Register), kde si za "x" dosaďte písmenný název portu (A,B,C...). Každý bit v tomto registru ovládá jeden z vývodů portu. Zápisem log.1 do příslušného bitu DDRx je příslušný pin nastaven jako výstup. Příklad: Jestliže chci PB3 nastavit jako výstup musím zapsat do DDRB registru log.1 na třetí místo (při číslování od nuly !). Například kombinace DDRB=0b00010010 nastaví PB1 a PB4 jako výstupy. Po restartu čipu jsou všechny DDRx vynulované, tedy všechny vývody jsou vstupem. Druhý registr nese název PORTx, kde si za x dosaďte písmenné označení portu. Tedy například PORTB, PORTC atd. Každý bit v tomto registru se opět váže k příslušnému vývodu portu (stejně jako u DDRx). Role každého bitu v registru PORTx je dvojí a záleží na tom jestli je daný pin nastaven jako vstup nebo jako výstup. Jestliže je pin nastaven jako vstup tak hodnota příslušného bitu v PORTx registru zapíná nebo vypíná interní pull-up rezistor. Log.1 ho zapíná, log.0 vypíná. Chci-li tedy zapnout pull-up rezistor na pinu PA0, musím do DDRA zapsat na nultou pozici nulu (konfigurace jako vstup) a pak do PORTA na nultou pozici jedničku (zapínám pull-up). Pokud je vývod nastaven jako výstup, pak hodnota v registru PORTx rozhoduje o tom zda na výstupu bude log.1 nebo log.0. Zapsáním log.1 na nultou pozici v PORTA i DDRA nastavíte PA0 jako výstup s log.1 (tedy Ucc). Pro každý pin tedy existují čtyři varianty. Vstup, vstup s pull-up rezistorem, výstup s log.0 a výstup s log.1. Předveďme si to na několika příkladech.

DDRA = 0b00000010;
PORTA = 0b100000011;

Tento zápis nastaví PA1 jako výstup a nastaví na něj log.1, piny PA0 a PA7 nastaví jako vstupy se zapnutým pull-up rezistorem. A to je prostě vše :) Přirozeně ale v céčku existuje mnoho cest jak následující příklad zapsat. Přímý zápis v binární podobě potkáte zřídkakdy, je totiž mizerně čitelný. Musíte odpočítávat jedničky a nuly aby jste zjistili jaká je konfigurace pinů. Můžete použít čitelnější formu:
DDRA = (1<<DDA1);
PORTA = (1<<PORTA7) | (1<<PORTA1) | (1<<PORTA0);

která je z hlediska jazyka úplně stejná jako zápis:
DDRA = (1<<1);
PORTA = (1<<7) | (1<<1) | (1<<0);

Všechny tři varianty projdou překladem se stejným výsledkem. Je jen na vás jakou formu zvolíte. Doporučuji ale tu nejčitelnější (tedy tu s názvy bitů). Pomůže vám to vyznat se ve vlastním kódu. Krom toho když jakoukoli jinou variantu pošlete někam na forum aby vám poradili co máte špatně, tak vás odpálkují. Nikdo totiž nemá náladu listovat datasheetem a odpočítávat jedničky a nuly ;) Všechny tyto tři varianty skýtají jisté úskalí. Zapisují do registru přímo danou hodnotu (takže prostě přepíšou VŠECHNY jeho bity). Velice často ale budete potřebovat v registru změnit jen jeden nebo dva bity a všechny ostatní nechat v původním stavu. K tomu vám v C poslouží následující konstrukce:
PORTA = PORTA | (1<<PORTA7) | (1<<PORTA5);
což je ekvivalenní jako
PORTA |= (1<<PORTA7) | (1<<PORTA5);
což je ekvivalentní jako
PORTA |= (1<<7) | (1<<5);
což je ekvivalentní jako
PORTA |= 0b10100000;
udělá prostou věc, nastaví 7. a 5. bit registru PORTA do log.1 a ostatní bity ponechá nezměněné. Druhý zápis je nejčastější a asi i nejčitelnější - používejte ho.

Opačná situace nastane když chcete vynulovat některý bit v registru. K tomu slouží konstrukce

PORTA = PORTA & ~((1<<PORTA7) | (1<<PORTA5));
a nebo zkrácená forma:
PORTA &=~((1<<PORTA7) | (1<<PORTA5));
a nebo
PORTA &= 0b01011111;
Všechny tři udělají to samé, vynulují 5. a 7. bit v registru PORTA. Druhá varianta je také nejčastější, zvykněte si na ni. Pokud v tomto okamžiku nerozumíte zápisům, zagooglete a zrekapitulujte si bitové operátory v C. Za makry PORTA7 se skrývá jen pořadové číslo bitu, v tomto případě číslo 7. Může se vám zdát že v případě portů je košaté rozepisování zbytečně pracné, že přece stačí namísto (1<<PORTA7) zapsat prostě (1<<7) a každý to pochopí. A budete mít asi pravdu, ale jiné registry nemají tak jednoduché pojmenování bitů jako PORTy a tam by zápis (1<<7) byl nečitelný. A proto vám doporučuji dodržovat štábní kulturu a psát to takhle. Konec konců ošoupaná klávesnice je dobrou známkou pogramátora :D

Občas se vám může stát že budete potřebovat přepnout hodnotu bitu v registru. Typicky právě u portů. K tomu můžete využít tento příkaz:
PORTB ^= (1<<PORTB7); 
který přepne stav pinu PB7. Novější atmely umožňují ještě jeden způsob jak to provést. K němu ale potřebujete znát poslední registr sloužící k ovládání portů. A tím je PINx (PINA, PINB...). Ten slouží ke čtení vstupního stavu. Ať už máte nastaven vývod jako vstup nebo výstup, můžete si přečíst jaká je na něm logická hodnota. Číst budete vždy celý registr PINx (i když vás třeba celý nezajímá). Každý jeho bit reflektuje stav na příslušném pinu. Jedinou vyjímkou kdy si z PIN registru nemůžete nic smysluplného přečíst je pokud máte vypnutý vstupní buffer. Příkazem:
x = PINC; 
si prostě přečtete stav celé osmice vývodů na portu C. Jak s informací naložíte je pak na vás. My si to za chvíli ukážeme. U novějších členů rodiny AVR dostal regisr PINx ještě jednu roli. slouží k přepínání výstupů. Zápisem log.1 do některého z bitů registru PINx přepnete stav příslušných bitů registru PORTx. Jinak řečeno jestliže chci přepnout hodnoty 5. a 7. bitu v registru PORTB (a tím typicky přepnout i stav vývodů) stačí mi zapsat:
PINB = (1<<PIN7) | (1<<PIN5); 
a nebo též
PINB = 0b10100000;
(všimněte si že do nej pouze zapisuji, ne jako v případě "nastavení bitu" o pár řádků výše). Bity 5 a 7 v registru PORTB pak změní svoji hodnotu.

Pull-up rezistory se dají globálně vypnout nastavením bitu PUD v registru SFIOR (u Attiny v MCUCR). Což asi najde primární uplatnění při přechodech do režimu spánku. Na co uspávat čip aby odebíral jednotky mikroampér, když skrze pullup rezistory tečou desítky že ;)

U všech výše zmíněných operací je potřeba brát zřetel na to, že jde o sekvence čtení - modifikace - zápis. Například příkaz
PORTA |= (1<<PORTA7) | (1<<PORTA5);
se ve skutečnosti realizuje instrukcí čtení (přečte se stav PORTA). Jeho hodnota se pak upraví (logickým součtem) a výsledek se opět zapíše do registru PORTA. A to je obecně rizikové. Ve většině případů vám to nemusí vadit, ale existují situace kdy může vzniknout průšvih. Představte si že program provádí výše zmíněnou operaci. Nejprve přečte stav registru PORTA (aby ho nadále mohl upravoat) a v tom okamžiku přijde přerušení. V rutině přerušení se změní hodnota registru PORTA (přerušení třeba manipuluje s nějakým jiným pinem). Když přerušení skončí, program pokračuje ve rozdělané činnosti. Upravuje hodnotu kterou načetl z PORTA. Jenže ta je špatě ! Stav regisru PORTA se mezi tím změnil. Program tedy dokončí úpravu a upravený starý stav registru PORTA zase zpět zapíše. A změna kterou v něm udělala rutina přerušení je zrušená... Průšvih ! Pokud vám něco takového hrozí, budete muset při podobné operaci blokovat přerušení. K tomu slouží funkce cli() a sei(). Takové operaci, která se skládá z několika kroků se říká že není atomická - tedy že ji jde přerušit. A je potřeba s tím počítat. To jen aby jste se nedivili. A teď k praktickým ukázkám.
Chcete-li zjistit stav bitu v registru můžete pužít konstrukci
if(PINA & (1<<PINA4)){ 
// udělej něco když je na PA4 log.1 
} 
nebo opačnou:
if(!(PINA & (1<<PINA4))){ 
// udělej něco když je na PA4 log.0         
}  
Komunikaci s otevřeným kolektorem (za předpokladu že je připojen vnější pull-up rezistor) třeba na pinu PD1 můžete realizovat následovně:
PORTD &=~(1<<PORTD1); // pokud je PD1 výstup, je na něm držena log.0
DDRD |= (1<<DDD1); // zápis log.0 na "otevřený kolektor"  
DDRD &=~(1<<DDD1); // zápis log.1 na "otevřený kolektor"  
V této konfiguraci můžete spojovat výstupy čipů (což jinak nesmíte !). Jen si dávejte pozor na parazitní kapacity. Této konfigurace navíc využívá třeba i I2C periferie.

Většina pinů může mít ještě alternativní funkci. Alternativní funkce pinů jsou uvedeny na začátku datasheetu v sekci "Pin Configurations". Tedy na obrázku, kde je rozpis vývodů Atmelu a jsou to ty názvy, které jsou v závorkách. Pokud je alternativních funkcí více, jsou odděleny lomítkem. Pro přehlednost uvedu stručný výčet těchto alternativních funkcí pro Atmega16.

Použití alternativní funkce většinou zapínáte v příslušné periferii. Jestliže tedy třeba pin PD0 má sloužit jako RX jednotky USART (a vy jí ovládání tohoto pinu povolíte), nebude záležet jak jste jej nakonfigurovali v registrech DDRD a PORTD. Některé piny ale budou vyžadovat jisté nastavení, například funkce OC0A na PB3, i když čítači předáte řízení pinu PB3, musíte jej mít nastavený jako výstup. Některé alternativní funkce se dokonce na vašem nastavení vůbec neprojeví například INT0 na PD2 může fungovat a vy nadále můžete používat příslušný pin. Někdy dokonce vaše nastavení pinu ovlivní funkci jiné periferie. Například nastavením pinu XCK (PB0) jako výstup může přepnout jednotku USART do synchronního režimu. Dále je třeba dát pozor na to, že některé piny mají alternativní funkci nastavenou pomocí Fuses pevně. Třeba u ATmega16 (32 a dalších) piny PC2,PC3,PC4 a PC5 sloužící k JTAG programování a debugování čipu. Takže bez změny fuses je vůbec nemůžete používat - mají pevně zapnutou alternativní funkci (pokud JTAG nepoužíváte, doporučuji ho ve fuses vypnout a piny si zpřístupnit). Podobně je na tom PD6 u Atmega8 který je pomocí fuses implicitně překonfigurován jako reset (takže ho také nemůžete ovládat). Ale ten prosím nezkoušejte změnit ! Znepřístupnili by jste si ISP rozhraní :D Podrobnější informace o tom kdy a za jakých okolností jsou jednotlivé piny přidělované periferiím se dočtete v datasheetu.

Elektronika kolem portů

V následujících odstavcích si Vám dovolím nabídnout malou kuchařku na typické problémy s nimiž se můžete při práci s mikrokontroléry setkat.

Co dělat když chci spínat vyšší napětí než je napájecí napětí čipu ?

Můžete využít například některou z variant ze schematu č.1. Sepnutím tranzistoru (log.1 z Atmelu) se výstup uzemní. Rozepnutím tranzistoru (log.0 z Atmelu) se skrze pull-up rezistor (R1 nebo R3) přivede na výstup napětí (v tomto případě 12V). Celek tedy invertuje logickou úroveň. Z výstupu nesmí odetékat přiliš velký proud, protože takto postavený zdroj napětí je "měkký" a jeho výstupní napětí s odebíraným proudem klesá. Měkkost zdroje určuje hodnota pullup rezistoru (R1 respektive R3). Čím je jeho hodnota menší tím je výstup tvrdší a tím větší proud lze dodávat aniž by došlo ke znatelnému poklesu napětí na výstupu. Problém je v tom, že se zmenšováním odporu roste odběr ve stavu kdy je tranzistor sepnut (tedy kdy je výstup v log.0). Nelze tedy odpor zmenšovat donekonečna. Pokud potřebujete spínat trochu větší proud a tyto varianty vám nevyhovují, najdete postup v další kapitole. Varianta s bipolárním tranzistorem A) vyžaduje ještě rezistor R2, který omezuje proud do báze. Varianta B) s unipolárním tranzistorem je v tomto jenodušší a navíc její výstupní napětí dokáže dosáhnout nižších hodnot jak předchozí varianta. Bohužel ale varianta B) s FET nemusí být funkční při nízkém pracovním napětí (Atmega16A může pracovat už od 2.7V a jiné varianty už od 1.8V). Tak nízké napětí pak nemusí stačit na sepnutí tranzistoru a v takovém případě se musíte vrátit k variantě A). Pokud potřebujete transformovat logické výstupy například ze 3V na 5V určitě se poohlédněte po nějákém integrovaném obvodu, zjednoduší vám to práci (já s oblibou používám 74LVC4245). Přirozeně vám ale nikdo nebrání některou z těchto variant použít. Počítejte ale s citelným omezením na maximální provozní frekvenci. Jako ilustrace může posloužit záznam na obrázku č.1. Tranzistor BC548 v zapojení A) s pullup rezistorem o hodnotě 330R a odporem do báze 1k má značné spoždění při přechodu ze sepnutého stavu do rozepnutého. Spoždění přibližně 1.5us není pro většinu aplikací kritické, ale při komunikaci mezi logickým obvody, kde se počítá se spožděním hrubě pod mikrosekundu by to mohlo mít neblahé následky. A to byly hodnoty odporů voleny s ohledem na minimalizaci spoždění.


schema č.1 - posílení výstupů Atmelu


obrázek č.1 - rychlost spínání varianty A) - červený průběh výstup Atmelu, modrý průběh výstup obvodu (kolektor tranzistoru), žlutý průběh báze tranzistoru

Potřebuji spínat větší proudy ?

Pokud je stačí spínat v dolní větvi (tedy kladný konec zátěže je připojen ke zdroji) je situace jednodušší (viz schéma č.2) a můžete použít variantu A) nebo B). U varianty A) si ale pohlídejte aby proud do báze dostačoval požadovanému proudu kolektorem a příslušně tomu zmenšete rezistor R2. Zesilovací činitel signálních tranzistorů bývá mezi 100-500, takže ke spínání čtvrt ampéry stačí pustit do báze pár miliampér. U výkonových bipolárních tranzistorů bývá zesilovací činitel 20-50 a pro sepnutí proudu například 3A může být nutné pustit do báze proud větší jak 60mA a ten z výstupu atmelu nedostanete. V takovém případě můžete použít "darlington" tranzistor (E). Vždy ale dávejte pozor na výkonové zatížení a s ním spojený ohřev. Pokud takových výstupů potřebujete víc, můžete využít tranzistorových polí ULN2003 (nebo podobných). Tuto variantu uvítáte třeba při buzení multiplexovaného sedmisegmentového displeje. Varianta B) žádnými úbytky sice netrpí, ale zase je potřeba dávat pozor na provoz při malém napětí Atmelu, kdy by nemuselo dojít ke kompletnímu otevření tranzistoru. Zvláště výkonové MOSFETy s provozním napětím 60V a vyšší potřebují ke spolehlivému sepnutí třeba 4.5V. Ke spínání v horní větvi obecně poslouží varianty C) a D). Opět je na místě pouvažovat zda nepoužít integrovaný obvod - tzv. High side switch, který typicky obsahuje proudový limit, ochranu proti přehřátí a odpojení vstupu. Zvláště pokud hrozí že dojde ke zkratování výstupu. Na trhu jich je k dostání široká paleta, například jednoduchý obvod BTS3110 nebo inteligentější obvody jako například BTS5090 se schopností navíc měřit proud zátěží.


schema č.2 - posílení výstupů Atmelu

Výjmečně může nastat případ kdy potřebujete spínat větší proudy v horní větvi a shodou okolností má napájecí napětí pro zátěž stejnou hodnotu jako napájecí napětí čipu. Často se tím setkáte třeba při bateriovém napájení. Pak můžete využít variant F) a G) ze schematu č.3. Přivedením log.0 na vstup obvodu dojde k sepnutí tranzistoru a proud začne protékat do zátěže. Tak jak ve všech předchozím případech platí že provoz unipolárního tranzisotoru (varianta F) začne být problematický při nižších provozních napětích (3V a menší) a naopak provoz bipolárního tranzistoru (varianta G) vyžaduje zajistit při vyšších výstupních proudech (jak dejme tomu 0.5A) nutný proud z báze.


schema č.3 - posílení výstupů Atmelu když je napájecí napětí čipu shodné s napětím pro zátěž

Následující variantu vám příliš nedoporučuji, ale uznávám, že může v určitých situacích ušetřit součástky. Teoreticky můžete k posílení proudu spojit několik výstupů dohromady například tak jak je na schematu č.4. V takovém případě musím zdůraznit že vás datasheet varuje že by celkový součet proudů neměl překročit 100mA (všimněte si že nepíšu nesmí, ale pouze neměl). To je ale ta nejmenší komplikace. Jakmile takto vývody spojíte a nakonfigurujete je jako výstupy musíte zařídit aby na nich vždy byly stejné hodnoty. Jinak řečeno nesmí se vám stát že jeden z výstupů bude v log.0 a druhý v log.1, v takovém případě by nastal zkrat a překročili by jste proudový limit pro vývody. Musíte to tedy v programu pečlivě ošetřit. Z toho také plyne že smíte sdružovat výstupy jen v rámci jednoho portu. Pokud by vás napadlo spojit třeba PD7 a PC0 jak je na schématu (s označením chybného zapojení), vězte že pak už nedokážete zaručit aby na obou portech byla stejná hodnota. Jakmile se totiž rozhodnete změnit hodnotu například na portu C, nemůžete ve stejný okamžik měnit hodnotu i na portu D ... (samozřejmě i to lze zachránitmalou oklikou. Nejprve jeden port přenastavíte jako vstup, pak na druhém změníte výstupní hodnotu, pak ji změníte na prvním a pak první zase překonfigurujete ze vstupu na výstup.)


schema č.4 - paralelní spojení výstupů pro posílení výstupního proudu.

Vstupy a tlačítka

Skoro jistě budete chtít vybavit svou aplikaci různými spínači nebo tlačítky, které budou typicky sloužit pro ovládání lidskou obsluhou. Nejjednodušší způsob jak je můžete připojit k mikrokontroléru (schema č.5), spočívá v jejich připojení takzvaně "proti zemi". Jeden konec tlačítka připojíte na zem a druhý konec na libovolný vývod Atmelu. Poté uvnitř atmelu zapnete interní pull-up rezistor a vše je připraveno. Jestliže tlačítko není stisknuto, dostane se na vstup skrze interní pull-up rezistor napětí Ucc, tedy log.1. Jestliže obsluha tlačítko stiskne, spojí se vybraný vývod Atmelu se zemí a bude na něm log.0. Vzhledem k tomu, že stisk tlačítka lidskou rukou je vždy relativně pomalý, má program spoustu času zjistit, že k němu došlo. Člověk je schopný stisknout tlačítko nanejvýš tak 5 krát za sekundu, program má tedy řádově desítky až stovky milisekund času. Díky tomu stačí aby program kontroloval stisk talčítka jen několikrát za sekundu. Když si ale zkusíte takto jednoduše tlačítko k Atmelu připojit, budete nemile překvapeni výsledkem. Mechanický kontakt bude vytvářet zákmity. Jakmile prst začne působit na hmatník tlačítka, začnou se kontakty uvnitř tlačítka přibližovat. Během přibližování se ale jemně chvějí, a jakmile se přiblíží na velmi malou vzdálenost tak se vlivem chvění mohou několikrát po sobě spojit a zase rozpojit. Případně se nespojit dokonale a na krátkou chvíli vznikne mezi kontakty tlačítka přechodový odpor. Tyto jevy vyústí ve velmi nepěkný průběh napětí, který můžete vidět na obrázku x1 a kterému se česky říká zákmity (anglicky bouncing).


schema č.5 - připojení tlačítek k Atmelu.


obrázek x1 - několik průběhů neošetřených zákmitů mechanického tlačítka. Vlevo je tlačítko plně stisknuté, v pravo je plně uvolněné.

Jestliže by program počítal stisky tlačítka dostatečně rychle (a programy umí počítat velmi rychle), dokázal by mezi stisky tlačítek započítat i zákmity. To je přirozeně nežádoucí, protože program by pak jeden stisk tlačítky vyhodnotil jako několik stisků. Tyto problémy je možné ošetřit mnoha cestami. Softwarové ošetření zákmitů spočívá v tom že necháte program záměrně sledovat tlačítka jen jednou za nějáký relativně dlouhý čas, dejme tomu 20ms. Softwarové ošetření se vám ale nemusí zamlouvat (třeba z důvodů lenosti :D ) a tak si můžete pomoci hardwarovým řešením. Stačí tlačítko přemostit kondenzátorem o vhodné hodnotě. Během krátkých zákmitů se přes pull-up rezistor nestihne filtrační kondenzátor nabít a zákmity se tak odfiltrují. Protože interní pull-up rezistor má hodnotu přibližně 35kOhm, zvolil jsem pro ukázku kondenzátor o kapacitě 100nF. Doba nabíjení kondenzátoru by v takovém případě měla být řádově 4ms. Dá se tedy odhadovat, že během zákmitů dlouhých řádově desítky mikrosekund se kondenzátor stihne nabít na napětí řádově desítky mV. Taková napětí bude mikrokontrolér spolehlivě považovat za log.0 a zákmity tak budou ošetřeny. Tomuto procesu se anglicky říká debouncing. Na obrázku x2 je pak vidět výsledek takového ošetření. Záměrně jsem stiskl tlačítko dvakrát po sobě co nejrychleji aby bylo vidět, že se během nich dokáže kondenzátor spolehlivě nabít a žádný ze stisků tím neutrpí.


obrázek x2 - Ošetření zákmitů tlačítka. Pull-up rezistor má hodnotu 35kOhm (interní) a filtrační kondenzátor kapacitu 100nF. Doba vzestupné hrany je přibližně 8.5ms. V horním části obrázku je vidět celý průběh dvou krátkých stisknutí tlačítka.

Aby byl princip činnosti obvodu co nejzřetelnější, záměrně jsem mačkal tlačítko "špatně". Výsledek těchto pokusů vidíte na obrázku x3. Velké množství velmi nepěkných zákmitů nepřerostlo hodnotu přibližně 650mV. Z datasheetu Atmelu vyčtete že maximální úroveň napětí, při které čip vstup zaručeně vyhodnotí jako log.0 je 0.2Vcc, při napájení 3.3V je to tedy 660mV. Naše zákmity jsou v tomto případě nebezpěčně blízko. Bylo by tedy vhodnější zvolit kapacitu filtračního kondenzátoru raději vyšší, například 1uF.


obrázek x3 - Příklad nedostatečného ošetření zákmitů - aneb co leze z tlačítka když ho mačká vaše kočka.

Příklad - Vstup tlačítka (dektece stisku)

Protože kapitola byla hodně teoretická, jste určitě hladoví po nějákém příkladu. Tak alespoň jeden jsem pro vás připravil. Jedná se o jednoduchý program, detekujicí stisk tlačítka. Na výstup PA0 vyvedeme LED diodu, která se bude přepínat s každým stiskem. Rutinu detekujicí pouze jeden stisk tlačítka budete jistě potřebovat, takže bude žádoucí trochu si ji okomentovat. Tlačítko připojíme na pin PA2, který nakonfigurujeme jako vstup s interním pull-up rezistorem. Pak necháme program skenovat stav 3 bitu v registru PINA, který obsahuje stav na vstupu PA2. Pomocí proměnné stav_tlacitka pak budeme detekovat sestupnou hranu na PA2. Jakmile k ní dojde, přepneme LED a vynulováním proměnné stav_tlacitka zabráníme tím dalšímu vstup do těla podmínky. Teprve až dojde k uvolnění tlačítka tak zápisem jedničky do stav_tlacitka podmínku opět odemčeme. Všimněte si použití maker. Při ovládání různých logických vstupů a výstupu je velmi vhodné využívat makra (a dobře je pojmenovávat). Jednak kvůli tomu, že je pak kód čitelnější a srozumitelnější. A hlavně proto, že je pak snazší měnit pozice pinů. Pokud bych chtěl LED přesunout na vývod PA5, stačilo by mi změnit makro LED_PREPNI a přirozeně i inicializaci portu A. Pokud bych makro nepoužil, musel bych ručně projít celý zdrojový kód a všechny příkazy ovládání LED ručně přepsat. Je skoro jisté, že bych během této úpravy některé řádky přehlédl. Ještě mnohem lepší by bylo pomocí maker vyřešit i inicializaci portů, ale nebudeme příklad komplikovat. Zkuste si na příkladu mimo jiné i co se stane když ošetření zákmitů neprovedete.

// A) Vstup tlačítka (dektece stisku)
#include <avr/io.h>

#define TLACITKO (PINA & (1<<PINA2))
#define LED_PREPNI PORTA ^= (1<<PORTA1)

char stav_tlacitka=1;

int main(void){
DDRA =  (1<<DDA1); // PA0 výstup, PA2-PA7 vstupy
PORTA = (1<<PORTA2); // na PA2 zapínáme pull-up

while(1){
 if((!TLACITKO) && stav_tlacitka){
  LED_PREPNI; // prepni stav LED1 (na PA1)
  stav_tlacitka = 0; // "zamkni" tlacitko az do uvolneni (dektece sestupne hrany)
 }
 if(TLACITKO){
  stav_tlacitka = 1; // "odemci" tlacitko (evidentne bylo uvolneno)
  }
 }
}

Odkazy k tématu


Poznámka pod čarou aneb co nikdy nedělat

Vzhledem k rychle se rozrustajícímu křemíkovému nebi je asi vhodné probrat pár zakázaných postupů.

1. Nikdy nespojujte dva výstupy (jedině pokud pracují s otevřeným kolektorem) Pokud totiž jeden z nich bude v log.0 a jeden v log.1 dojde defakto ke zkratu a skoro jistě překročíte limit proudu pro vývod.

2. Nikdy nepřipojujte výstup na jakékoli pevné napětí opět hrozí překročení limitu proudu pro vývod.

3. Nikdy nepřipojujte na výstup LED bez ochranného odporu ... ale tohle snad patří do školky :)

4. Nikdy nepřipojujte na vstupy vyšší napětí než je dovoleno. Nezapomínejte na to hlavně pokud pracujete s nižším napětím. Stačí si nevšimnout, že na Atmel napájený 3V cpete výstup z 5V logiky.

5. Nikdy nepokládejte Atmel na polystyrenovou nebo igelitovou podložku, statická elektřina je nebezpečná.

Trochu zábavného vzdělávání

Kdysi jsem narazil na vtipná videa poukazující na klasické chyby při zacházení se vstupy / výstupy mikrokontroléru. Jestli si s jejich ovládáním moc nevěříte, mrkněte na ně zde.

Home
V1.1
By Michal Dudka (m.dudka@seznam.cz)