Tutoriály běžně začínají blikáním LEDkou, já si ale dovolím začít jinak. Předpokládám totiž, že většina čtenářů už má nějaké zkušenosti s programováním mikrokontrolérů a začnu proto clockem. TinyAVR 1-series mají uživatelsky příjemný systém clocku. Ten se na rozdíl od předchozích AVR řídí softwarově. Díky tomu odpadá řada starostí se "zaseknutím" čipu špatnou volbou fuses. Krom toho všeho se vám tím otevírají možnosti nastavovat clock podle aktuálních potřeb, což oceníte hlavně u low-power aplikací. V prvním dílu tutoriálu začneme jen zlehka a vyzkoušíme si možnosti vnitřních zdrojů cloku.
Systém distribuce clocku je vcelku přehledně vyobrazen v datasheetu. K výběru máme clekem 4 zdroje clocku.Všimněte si že mezi nimi není (!) možnost taktovat čip pomocí externího vysokofrekvenčního krystalu.
Pro přehlednost uvedu seznam registrů a bitů sloužících k řízení clocku (neděste se jejich množstvím):
Na několika příkladech si zkusíme nakonfigurovat clock z interních oscilátorů. Před tím se ale zmíním o CCP. Některé klíčové registry (například ty sloužící ke konfiguraci clocku) mají ochranu proti nechtěnému přepsání. Zapisovat do nich lze jedině po "odemčení". K odemčení musíte do registru CCP (Configuration Change Protection) zapsat odpovídající klíč. Zápis do chráněného registru pak musíte stihnout během čtyř strojových cyklů, jinak řečeno hned. Máte-li během konfigurace povolené přerušení musíte ho před operací zakázat, jinak nemáte záruku, že se vám zápis úspěšně podaří.
Jak už bylo řečeno interní oscilátor může běžet na 20MHz nebo 16MHz a přepnout mezi režimy lze pomocí fuses. Továrně je ve fuses zvolena frekvence 20MHz. Čip automaticky po startu běží z tohoto oscilátoru s děličkou 6x. Což si můžete sami ověřit pohledem do registrů po spuštění debugu. Frekvence po startu je tedy 3.33MHz. Protože se na clock chceme podívat, "vypustíme" ho funkcí clockout - nastavením bitu CLKOUT v reigstru MCLKCTRLA. V tomtéž registru se také volí zdroj clocku a tak pro jistotu a lepší čitelnost programu připojíme i makro s jeho volbou. Nezapomeneme si v CPP povolit zápis. Makro CCP_IOREG_gc je již zmiňovaný klíč. Po vykonání obou příkazů bychom na PB5 měli vidět signál s frekvencí 3.33MHz. V dalším kroku musíme vypnout děličku 6x. Opět si nejprve zápis "odemkneme" a poté vynulujeme registr MCLKCTRLB. Tím jednak změníme děličku na dělení 2x, ale hlavně vynulováním bitu PEN ji vypneme (a pak už nám na samotném dělicím poměru nesejde). Od tohoto okamžiku bude na PB5 20MHz signál a váš čip poběží na maximální frekvenci.
// Nastaví clock 20MHz (z interního 20MHz bez děličky) a vypustí clock na CLKOUT (PB5) void clock_20MHz(void){ // v případě potřeby zde dočasně vypněte přerušení CCP = CCP_IOREG_gc; // odemyká zápis do chráněného registru CLKCTRL.MCLKCTRLA = CLKCTRL_CLKOUT_bm | CLKCTRL_CLKSEL_OSC20M_gc; // vypouští clock na PB5 (CLKOUT), vybírá 20MHz oscilátor CCP = CCP_IOREG_gc; // odemyká zápis do chráněného registru CLKCTRL.MCLKCTRLB = 0; // vypne prescaler (děličku) }
Všimněte si, že frekvence není přesných 20MHz, ale přibližně 20.25MHz - tedy o 1.25% vyšší. To není žádná hrůza, ale dá se to ještě zlepšit. Frekvenci můžete kalibrovat v 64 dosti nehomogenních krocích. O přibližné velikosti kroku referuje Figure 35-70 v datasheetu. Na mám kusu stačilo udělat jeden krok dolů a frekvence sedla perfektně. Každý Atmel projde při výrobě měřením frekvence a má v paměti (v sekci SIGROW) uloženou odchylku od 20MHz (respektive 16MHz). Tu můžete využít například ke kompenzaci baudrate UARTu a dalším účelům. Změřené odchylky jsou celkem 4, dvě pro 20MHz oscilátor (při 5V a 3V napájení) a dvě pro 16MHz oscilátor (opět pro 5V a 3V). Odchylka je uložena jako jeden byte v paměti pravděpodobně ve formátu int8_t a udává počet 1024tin (z frekvence oscilátoru). Já si přečetl odchylku pro 20MHz a 5V a byla "14", tedy 20MHz*14/1024 = 0.27MHz, což je v dobré shodě s tím co jsme naměřili (0.25MHz). A jak se to celé dělá ? Vcelku snadno, vzor jak načíst odchylky najdete ve zdrojovém kódu níže. Za ním následují funkce které inkrementují nebo dekrementují kalibrační konstantu a můžete je použít k ručnímu ladění. Samotná kalibrační konstanta je v registru OSC20MCALIBA a ten je tradičně chráněn proti přepsání.
volatile int8_t osc_error[4]; // vyčte odchylky frekvence - jen pro zajímavost osc_error[0] = SIGROW.OSC20ERR5V; // Odchylka od 20MHz při 5V napájení osc_error[1] = SIGROW.OSC20ERR3V; // Odchylka od 20MHz při 3V napájení osc_error[2] = SIGROW.OSC16ERR5V; // Odchylka od 16MHz při 5V napájení osc_error[3] = SIGROW.OSC16ERR3V; // Odchylka od 16MHz při 3V napájení // funkce pro ladění frekvence interního oscilátoru void clock_tune_up(void){ uint8_t tmp = CLKCTRL.OSC20MCALIBA & 0x3F; // přečte aktuální kalibrační hodnotu if(tmp < 0x3F){ // maximální kalibrační hodnota je 0x3F (63) tmp++; // přidáme krok CPU_CCP = CCP_IOREG_gc; // odemyká zápis do chráněného registru CLKCTRL.OSC20MCALIBA = tmp; // zapíše novou hodnotu kalibračního registru } } void clock_tune_down(void){ uint8_t tmp = CLKCTRL.OSC20MCALIBA & 0x3F; if(tmp > 0){ // minimální kalibrační hodnota je 0 tmp--; // ubereme krok CPU_CCP = CCP_IOREG_gc; // odemyká zápis do chráněného registru CLKCTRL.OSC20MCALIBA = tmp; // zapíše novou hodnotu kalibračního registru } }
Dalším funkcím 20MHz oscilátoru se věnovat nebudeme, takže jen pro rekapitulaci si můžete prohlédnout kód který čip taktuje 2MHz a využívá uživatelské kalibrační hodnoty. Nejprve nahraje kalibrační hodnotu, nastaví jako zdroj clocku 20MHz oscilátor, vypustí clock na PB5 a pak povolí děličku a nastaví jí dělicí poměr 10x.
// Nastaví clock 2MHz (z interního 20MHz s děličkou 10x) s individuální kalibrací a vypustí clock na CLKOUT (PB5) // vstupním argumentem je kalibrační hodnota void clock_2MHz(uint8_t kalibrace){ // v případě potřeby zde dočasně vypněte přerušení CCP = CCP_IOREG_gc; // odemyká zápis do chráněného registru CLKCTRL.OSC20MCALIBA = kalibrace; // zapíše novou hodnotu kalibračního registru (zjištěnou empiricky) CCP = CCP_IOREG_gc; // odemyká zápis do chráněného registru CLKCTRL.MCLKCTRLA = CLKCTRL_CLKOUT_bm | CLKCTRL_CLKSEL_OSC20M_gc; // vypouští clock na PB5 (CLKOUT), vybírá 20MHz oscilátor CCP = CCP_IOREG_gc; // odemyká zápis do chráněného registru CLKCTRL.MCLKCTRLB = CLKCTRL_PDIV_10X_gc | CLKCTRL_PEN_bm; // F_CPU = 20MHz / 10 = 2MHz, PEN = Prescaler Enable }
Jen pro kompletnost uvedu ještě kód, který nastaví jako zdroj clocku 32kHz ULP oscilátor s děličkou 0. Teď asi není na místě se mu věnovat blíže, protože jeho využití bude spadat spíš do kapitol o low-power technikách. Takže bez komentáře. Soubor se všemi kusy kódu si můžete stáhnout. Doufám že vás tento krátký tutoriál neodradil a setkáme se u některého z dalších dílů.
// Nastaví clock na ~32kHz vnitřní oscilátor a vypustí clock na CLKOUT (PB5) void clock_ULP32(void){ CCP = CCP_IOREG_gc; // odemyká zápis do chráněného registru CLKCTRL.MCLKCTRLB = 0; // vypne prescaler (děličku) CCP = CCP_IOREG_gc; // odemyká zápis do chráněného registru CLKCTRL.MCLKCTRLA = CLKCTRL_CLKOUT_bm | CLKCTRL_CLKSEL_OSCULP32K_gc; // vypouští clock na PB5 (CLKOUT), vybírá 20MHz oscilátor }
Home
| V1.01 14.12.2018 /
| By Michal Dudka (m.dudka@seznam.cz) /