Naprogramování procesoru AVR

Jelikož práci s procesory AVR považuji za relativně jednoduchou, předkládám tento návod jak jednoduše naprogramovat procesor firmy ATMEL s jádrem AVR. Bude se jednat o kompletní postup a realizovaná aplikace je obdobou "Hello World!" známé z vyšších programovacích jazyků - bude to blikání LEDkou realizované na zpoždění na instrukcích a dále pak i z použitím čítače/časovače + přerušení. Pustíme si oblíbenou hudbu nebo jinak zpříjemníme práci a pustíme se do díla.

I.) Jako první věc musíme sehnat procesor, doporučuji ATmega16-16PU - dle mého názoru procesor s nejlepším poměrem cena/výkon.

Dá se koupit v GM nebo GES - v GMu stojí kolem 80kč.

Pro málo náročné aplikace doporučuji používat ATtiny26L-8PU ( cca 40kč ).

II.) Procesor máme, teď je třeba najít něco čím se dá do/z něj posílat/získávat program + data a konfigurovat ho pomocí FUSE bits.

Program je umístěn ve FLASH paměti a je to vlastně kód, který říká co má procesor zrovna teď dělat, a data jsou umístěna v EEPROM a pro běžnou činnost procesoru nejsou zapotřebí, jedná se jen o nějaké skladiště, které se může, ale nemusí používat. Ovšem nejedná se o paměť operační, tedy SRAM ta programovat přímo nejde!

Zařízení k tomuto určené lze nazvat programátor a jaký si vybrat naleznete zde pro sériový port COM nebo paralelní LPT nebo prostě UTFG. S programátory je nutno trochu experimentovat ( použijte na zapojení procesoru z bodu III. níže ), jelikož ne všechny a hlavně ne všude fungují, pokud by vám nějaký nechtěl fungovat, zkuste jiný nebo zkuste zkrátit délku přívodního kabelu nebo přidejte integrační článek na linku SCK.

Výhodou procesorů AVR je že se dají programovat sériově a to pomocí jen 4 pinů ( MOSI, MISO, SCK a RESET ) a to dokonce přímo v zapojení, odpadá tak manipulace s procesorem při přendávání.

SW na naprogramování/načtení našeho procesoru použijeme PonyProg2000. Lze stáhnout z webu jeho autora.

III.) Teď to vše zadrátujeme dohromady, připojíme k PC a pustíme PonyProg ( PP ). Silně doporučuji vlastnit nepájivé pole. Nedoporučoval bych však takovéto nepájivé pole, koupil jsem ho v GMu a jeho nevýhodou je zhoršená možnost vkládání součástek, vnitřní kontakt je velmi tuhý a často se mi stávalo ( hlavně když bylo pole nové ) že se nožička součástky ohnula aniž by se zasunula dovnitř. Taky mívám problémy s IO, kdy se někdy stane že IO nechce v poli držet a sám ( vlivem pružných kontaktů pole ) a vysune se. Tohle je pole podle mne nekvalitní a nedoporučuji jej, dá se poznat podle okrouhlých dírek, u jiných nepájivých polí jsou ony dírky čtvercové.

Procesor na nepájivém poli zadrátujeme dle schématu níže:

Součástky C1, C2 a Q1 osazovat nemusíme, jelikož procesor implicitně funguje s vnitřním RC oscilátorem na 1MHz, přesto je tam necháme, neboť jej vzápětí přeprogramujeme, tak aby fungoval s tímto externím krystalem. C3 není potřeba vůbec, je to jen filtr u interní reference AD převodníku, který v tomto příkladě používat nebudeme, ale nechal bych ho tam pro další experimenty. L1 a C4 je podle zapojení výrobce, jedná se o filtr pro napájení AD převodníku.

V zapnutém PP dáme možnost Command > Read All a mělo by se objevit něco podobného jako na obrázku níže:

Pokud to hlásí nějaké errory, hledejte chybu :-). Hlavně zda-li máte zvolen správný typ procesoru, nebo typ programátoru ( COM / LPT ), atd. Připomínám že ne všechny programátory fungují u všech procesorů a na všech počítačích, sám mám několik verzí a některé fungují s ATmega16, ale ATmega8 se jim již nelíbí, některé jiné fungují zapojené na nepájivém poli, ale jak se spájí na plošák tak nefungují. Chce to trpělivost, já jsem měl štěstí a hned první programátor mi fungoval dobře, tak ho možná taky zkuste - zde.

Pokud vše funguje jak má, přepneme zdroj systémového clocku na externí krystal. Můžeme však klidně používat interní RC, ten je však nepřesný a nestabilní, já interní RC oscilátor používám jen když má procesor málo pinů a potřeba přesného timingu není vysoká, avšak u ATmega16 žádné další piny nezískáme, jde spíše o procesory jiné, např. ATtiny26.

Nastavte FUSE bits a to příkazem Command > Security and Configuration Bits... > Read dle obrázku a pak jen Write.

Jde o to hlavně nastavit bity SUTx a CKSELx. Které určují dobu za jakou se po zapnutí napájení zapne samotné jádro procesoru ( Start Up Time ) a zdroj clocku/oscilátoru ( Clock Select ). Tak a teď nám to šlape na 16MHz.

IV.) Teď potřebujeme prostředí, ve kterém budeme psát zdrojové kódy. Vše o WinAVR jsem již popsal zde, proto se nebudu opakovat. A napíšu popis vývoje programu v AVR Studio 4.12, jednoznačně doporučuji používat C místo assembleru, i pokud jste zcela začátečník.

Můžete zkusit i Code Vision.

ATMEL k procesorům AVR dodává zdarma i vývojový sw, který obsahuje compiler assembleru a debuger ( simulátor ), nově od verze 4.12 může AVR Studio používat compiler jazyka C ( GNU AVR GCC ) od WinAVR ( k nalezení na této stránce ), AVR Studio stáhneme z webu ATMELu, včetně Service Packu - dlouho jsem AVR studio nepoužíval jelikož se mi nezdála velikost výsledného kódu, nějak špatně tam fungovala optimalizace, což s nasazením SP pominulo.

Stáhneme AVR studio, SP i WinAVR, vše nainstalujeme a zapneme AVR studio, dáme New Project a zvolíme AVR GCC, projekt nazveme blik a dáme Next, zde zvolíme AVR Simulator a procesor ATmega16, pak nakonec Finish.

Ještě dáme Project > Configuration Options na záložce General zadáme frekvenci 16MHz a hlavně změníme stupeň optimalizace na standardní - Optimalization -Os.

Tak a jdeme psát zdrojový kód, uděláme blikání LEDkou, která bude připojena na Port A a pin 0 ( PA.0 ), zpoždění bude realizováno na instrukcích - nebude tedy nijak přesně stanoveno.

Zdrojový kód vypada takto:

#include <avr/io.h>			// definice periferii procesoru

//--------------------------------------------- >> Zpozdovaci procedura <<

void delay ( uint32_t time )
{
	while ( time-- );
}

//============================================= >> HLAVNI FUNKCE MAIN <<

int main( void )
{

//--------------------------------------------- >> I/O PORTY <<
	//0 - VSTUP, 1 - VYSTUP

	DDRA	= 0b00000001;

//--------------------------------------------- >> Nekonecna smycka <<
	while( 1 )
	{
		PORTA = PINA ^ 0b00000001;
		delay( 1e6 );
	}

	return 0;
}
				

Asi to je trochu nečitelné, tady je přímo zdroj.

int main( void ) je hlavní funkce a zde začne procesor pracovat po resetu/startu, tato funkce se nesmí nikdy ukončit, jelikož další činnost není definována, při skončení této funkce by došlo k nějakému nedefinovanému stavu, proto je před návratovou hodnotou return 0 vložena nekonečná smyčka while( 1 ), procesor, respektive jako Program Counter, nemůže z této smyčky vystoupit ( respektive může, např. při obsluze přerušení nebo při volání funkce, avšak se vždycky po ukončení dané funkce/rutiny vrací zpět ). Smyčka je nekonečná, jelikož podmínka je vždy pravdivá ( nepravda je 0 a pravda je různá od nepravdy, tedy cokoli různé od 0 ), u jiných autorů můžete taky narazit na for( ; ; ).

Před nekonečnou smyčkou se provádí úkony, které stačí udělat jen jednou, tedy inicializace. V našem případě je zde jen nastavení směru toku dat na PORTu A - pin PA.0 ( DDRA - Data Direction Register A ) je nastaven jako výstup, zde na tento pin pak připojíme LED s vhodným odporem, výstupní budič portu procesoru je schopen dodávat proud 40mA/pin. 0b00000001 je binární zápis čísla 1 ( nastavení bitu 0 ), používám jej zcela úmyslně, jelikož z tohoto zápisu je jednoduché poznat, který bit je nastaven. Např.: 0b01000100 ( nastaveny bity 2 a 6 ) je dekadicky 68, použít se dá i zápis hexadecimální a 68 je v něm 0x44 nebo taky $44.

Jak tedy naše LEDka bliká? Jednoduše střídáním stavu log.0 ( 0V ) a log.1 ( 5V ) na pinu portu. PORTA je registr do kterého se zapisuje hodnota, kterou chceme zobrazit na Portu ( PORTA = 0b00000001; LEDku rozsvítí a PORTA = 0b00000000; LEDku zase zhasne ). Čtením PINA což je registr obsahující hodnotu na pinu ( PORTA je výstupní registr proto do něj zapisujeme ) zjistíme zdali LEDka svítí či ne a provedením funkce XOR, což je v jazyku C "^" dosáhneme střídáním stavu log.0 nebo log.1.

Teď naše LEDka bliká, avšak s několika megaherzovou frekvencí :-(. Proto je hned za příkaz, který střídá stavy na pinu PA.0, dána funkce delay() která dělá zpoždění. Funkce přijímá jako argument kladné ( unsigned ) 32bitové číslo tedy 0 až 4 294 967 296, které znamená dobu zpoždění. V těle funkce pak vidíme opět cyklus while(), který trvá tak dlouho pokud jeho argument je pravdivý ( různý od 0 ), avšak onen argument není konstantní, ale z každým cyklem se zmenšuje o 1 ( dekrement ), to je dáno zápisem dvou mínus u podmínky ( time-- ). Zápisem ( 1e6 ) se určila ona doba zpoždění na 1 000 000 ( 1e6 = 1 * 10^6 ), tohle pak přibližně odpovídá 1,33Hz frekvenci blikání.

Ve zdrojovém kódu ještě vidíme #include <avr/io.h>, což slouží pro definici názvů registrů k jejich adrese. U procesoru to funguje tak, že veškeré periferie ( porty, časovače, AD převodník, atd ) jsou namapovány do paměti ( SRAM ), proto přístup na tyto periferie je jednoduchý a to zápisem nebo čtením z určité adresy v paměti. Tedy každá periferie je reprezentována ve skutečnosti adresou pamětí, což je nepraktické a header file io.h obsahuje přiřazení názvu k adrese paměti, proto ve zdrojovém kódu píšeme např. PORTA, PINA atd. místo psaní adres, dá se to přirovnat k DNS u webové služby.

V.) Tak a naše blikátko s procesorem je téměř živé. Po opsáni kódu do AVR Studio dáme volbu Build > Build, tím pustíme překlad ( compiler ) a jeho výsledek v podobě souboru \blik\default\blik.hex ( pokud máte v kódu syntaktickou chybu překlad neproběhne dokonce a compiler vám vypíše chybu - v takovém případě se soubor blik.hex nevytvoří ) nahrajeme do PP přes příkaz File > Open Program (FLASH) File..., teď dáme Command > Write Program (FLASH) a tímto nahrajeme program do procesoru, pokud vše proběhne hladce, LEDka začne blikat.

VI.)Teď ještě uvedu kód pro použití s čítačem časovačem, generovaná frekvence bude daná a přesná 10Hz. Postup založení projektu je stejný jako v předchozím případě.

Zdrojový kód vypada takto:

#include <avr/io.h>		// definice periferii procesoru
#include <avr/interrupt.h>	// definice funkci pro rizeni preruseni

//============================================= >> HLAVNI FUNKCE MAIN <<

int main( void )
{

//--------------------------------------------- >> I/O PORTY <<
	//0 - VSTUP, 1 - VYSTUP

	DDRA	= 0b00000001;

//--------------------------------------------- >> 16b T/C 1 <<
	// mode 12 - CTC
	TCCR1B	|= ( 1 << WGM13 ) | ( 1 << WGM12 );
	TCCR1A	|= ( 0 << WGM11 ) | ( 0 << WGM10 );
	// top pro citac
	ICR1	= 12500 - 1;
	// clk / 64
	TCCR1B	|= ( 0 << CS12 ) | ( 1 << CS11 ) | ( 1 << CS10 );
	// povoleni preruseni IC
	TIMSK	|= ( 1 << TICIE1 );

	// globalni maska preruseni
	sei();

//--------------------------------------------- >> Nekonecna smycka <<
	while( 1 );
	return 0;
}

//--------------------------------------------- >> Obsluha preruseni <<
SIGNAL ( SIG_INPUT_CAPTURE1 )
{
	PORTA = PINA ^ 0b00000001;
}
				

Zdrojový soubor blik2.c.

Zde vidíme, že hlavní funkce main() jen inicializuje port a čítač/časovač 1 a v nekonečné smyčce již nedělá nic, vše totiž obstarává přerušení v podobě rutiny SIGNAL ( SIG_INPUT_CAPTURE1 ). Čítač/časovač je nakonfigurován v režimu Clear Timer on Compare Match, což znamená že čítač je nulován při dosažení určité hodnoty, tady je tato hodnota v registru ICR1, tedy 12499. Jak je tato hodnota stanovena? Jednoduše :-) potřebujeme frekvenci 10Hz to znamená periodu 100ms, příkaz PORTA = PINA ^ 0b00000001; překlápí pin takže potřebujeme interval 50ms. Procesor pracuje s cyklem 62,5ns ( dáno krystalem 16MHz - procesory AVR mají strojový cyklus roven periodě oscilátoru, nijak jej nedělí, jak to např dělají PIC či x51 ), pro 50ms tedy potřebujeme 50ms / 62,5ns = 800 000 cyklů, při před děličce čítače 1/64 ( dáno bity CS1x - Clock Source - v registru TCCR1B - Timer Counter Controll Register 1B ) je to 12 500 cyklů, čítač čítá od 0 tedy hodnota v ICR1 je 12 499. Pak již jen stačí povolit příslušné přerušení ( TIMSK |= ( 1 << TICIE1 ); ) a globální masku pro povolení všech přerušení ( sei(); ) - toto je funkce definována v #include <avr/interrupt.h>.

( 1 << WGM13 ) - toto je způsob jak nastavit bit u kterého máme definovaný název. Pro nastavení jen požadovaných bitů používáme funkci OR, v C je to "|".

Při napsání kódu do AVR Studia dáme zase Build atd atd jak je to popsáno v kroku V.), LEDka pak bliká asi 10x rychleji než předtím avšak přesně s periodou jakou chceme.

Tak a takto jednoduše to vše šlape, to je pro tentokrát vše. Dotazy/spam posílejte, budu se snažit něco Vám odpovědět :-)

Aktualizováno 12.02.2007