Pro ty z vás, kteří disponují zkušeností se staršími AVR bude tento díl konečně něčím úplně novým. Podíváme se totiž na Event systém - na mechanismus jakým mezi sebou "signalizují" periferie. V principu jde o to, že událost v jedné periferii může provést akci v jiné periferii. Například přetečení jednoho timeru můžeme čítat v jiném timeru. Nebo můžeme do timeru zavést výstup komparátoru. Případně signál z libovolného pinu nebo klidně zase z timeru přivést do ADC a na základě toho spustit převod. Navíc to vše může probíhat bez součinnosti jádra, takže i v režimech spánku. Už tak slušný potenciál event systému ještě umocňuje existence CCL (Custom Configurable Logic) - tedy kus logiky, která může s trochou nadsázky fungovat jako miniaturní hradlové políčko. Event systém a CCL je to vlastně to hlavní proč jsem se rozhodl moderním AVR podívat na zoubek. Jste zvědaví ? Doufám že ano.
Event systém v naší Attiny416 tvoří 6 kanálů (linek). Dva kanály jsou synchronní a eventy (ať už je to cokoli) se v nich šíří spolu s taktem čipu. Po synchronních kanálech se tedy nic nešíří v režimech spánku ve kterých je clock vypnutý. Zbylé čtyři kanály jsou asynchronní. Signál se po nich šíří nezávisle na clocku. To má hned dvě výhody. První, že se eventy šíří "okamžitě" i když je čip taktován nižším clockem a druhou, že se šíří i když čip spí a clock netiká vůbec. Zdroje eventů (event generator) jsou různé periferie, nebo přesněji řečeno různé signály z periferií. Jedna periferie může mít více různých zdrojů eventů. Přijímače eventů (event user) jsou opět periferie, které s přijatým eventem nakládají různě. Každý event kanál může mít pouze jeden zdroj a libovolné množství "posluchačů / příjemců / uživatelů". Například již zmíněný výstup timeru můžeme zavést zároveň do ADC, timeru a ještě ho vypustit vybraným vývodem do vnější elektroniky. Blokové znázornění celé sítě je patrné z figure 3-1. Koukněte na oranžový svislý blok s názvem "Event Routing Network". Šipky u jednotlivých periferií naznačují kterým směrem mohou eventy procházet a je z nich zřejmé co jsou zdroje a co příjemci.
Konfigurace Event systému se může zdát nepřehledná. Hlavně kvůli zvoleným názvům registrů (a odpovídajících maker a datových typů v hlavičkovém souboru). Abychom se zbytečně neztráceli, uděláme si v nich pořádek hned ze začátku. Už jsme říkali, že každý event kanál může mít jen jeden (nebo žádný) zdroj. Ten volíte v registrech:
EVSYS.SYNCCH1 = EVSYS_SYNCCH1_PORTB_PIN1_gc;
Tím bychom měli vyřešenou otázku zdrojů eventů. Teď se ještě podíváme na konfiguraci příjemců ("uživatelů"). Příjemci jsou dvojího typu - synchronní a asynchronní. Synchronní příjemci jsou v Tiny416 jen dva (TCA0 a USART0) a lze k nim přivádět pouze synchronní eventy. Asynchronních příjemců je více (11) a mohou přijímat jak synchronní tak asynchronní signály. Každý příjemce má svůj vlastní registr do něhož lze zapsat zda a na kterém kanálu má poslouchat. Příjemce mapují tabulky Table 14-4 a Table 14-5. Já registry (a tedy i příjemce) pro lepší přehlednost sepíšu do seznamu.
EVSYS.ASYNCUSER9 = EVSYS_ASYNCUSER9_ASYNCCH1_gc;
EVSYS.ASYNCUSER9 = EVSYS_ASYNCUSER0_ASYNCCH1_gc;
EVSYS.ASYNCUSER9 = EVSYS_ASYNCUSER7_ASYNCCH1_gc;
EVSYS_ASYNCUSER_ASYNCCH1_gc
. Takže se nenechte zmást ! Je tu sice 2*3+11*7=83 maker ale doopravdy jde jen o 7 jednoduchých a logických hodnot (ze kterého kanálu má přijímač odebírat eventy) ! Příčina této nepřehledné situace tkví v tom, že si vývojáři Atmelu nechávají otevřená vrátka pro možnost některým přijímačům omezit přístup k některým kanálům. Možná se s tím na větších čipech setkáme a ta hromada kódu získá nějaké opodstatnění.
Posledním prvkem Event systému je možnost vytvářet eventy softwarově. K tomu slouží dva "strobe" registry. SYNCSTROBE k vytváření synchronních eventů (tedy eventů na kanálech SYNCCH0 a SYNCCH1) a ASYNCSTROBE k vytváření eventů na asynchronních kanálech (ASYNCCH0 až ASYNCCH3). Vytváření má jednoduchý mechanismus. Do registru zapíšete jedničky na pozice těch kanálů na nichž chcete vygenerovat event. Například chci-li generovat event na kanále ASYNCCH1 zapíšu do registru ASYNCSTROBE hodnotu 0b10. Žádná makra k tomu v hlavičkových souborech nenajdete. A jak takový SW event vypadá ? Okamžitě po tom co zapíšete do "strobe" se vytvoří na vámi označených kanálech pulzy opačné polarity než je stávající hodnota na kanále. Máte-li například na event kanále SYNCCH0 log.0 tak po provedení příkazu SYNCSTROBE=1;
se na kanále vygeneruje kladný pulz v trvání jedné periody vašeho taktu (při 20MHz to bude 50ns). Softwarové vytváření eventů oceníte asi nejčastěji při ladění aplikací. Předvedeme si to v následujícím příkladu.
Úvodní příklad bude něco jako "blikání LEDkou" - tedy základní seznámení s eventy. Abychom mohli eventy sledovat a nebo přivést z čipu do vnější elektroniky máme tři "Event výstupy" (EVOUT0, EVOUT1 a EVOUT2). Protože EVOUT0 vede na PA2 a na našem Xnano kitu koliduje s USARTem z mEDBG, budeme si vše demonstrovat na zbylých dvou výstupech (PB2 a PC2). Funkce EVOUT oběma pinům přidělíme v registru PORTMUX. Výstup EVOUT1 (ASYNCUSER9) necháme přijímat eventy z kanálu ASYNCCH1. EVOUT2 (ASYNCUSER10) bude přijímat z kanálu SYNCCH1. Program necháme přibližně 500x za sekundu vygenerovat SW eventy na oba kanály (zápisem do "strobe" registrů). Měli bychom pozorovat 50ns kladný pulz nejprve na EVOUT1 a hned potom stejný pulz na EVOUT2.
/* tutorial TinyAVR 1-Series * Event systém I - SW vytváření eventů * PB2 - Event output (EVOUT 1) * PC2 - Event output (EVOUT 2) */ /* Ve fuses zvolen 20MHz oscilátor (lze volit ještě 16MHz) */ #define F_CPU 20000000UL #include <util/delay.h> #include <avr/io.h> void clock_20MHz(void); int main(void){ clock_20MHz(); // taktujeme na 20MHz // konfigurace výstupů (budoucích "uživatelů" eventů) PORTB.DIRSET = PIN2_bm; // PB2 (EVOUT 1) PORTC.DIRSET = PIN2_bm; // PC2 (EVOUT 2) // Povolíme funkce EVEOUT1 a EVOUT2 PORTMUX.CTRLA |= PORTMUX_EVOUT1_bm | PORTMUX_EVOUT2_bm; // Přijímači EVOUT1 přiřadit eventy z kanálu ASYNCCH1 EVSYS.ASYNCUSER9 = EVSYS_ASYNCUSER9_ASYNCCH1_gc; // Přijímači EVOUT2 přiřadit eventy z kanálu SYNCCH1 EVSYS.ASYNCUSER10 = EVSYS_ASYNCUSER10_SYNCCH1_gc; while (1){ _delay_ms(2); EVSYS.ASYNCSTROBE = 1<<1; // generuj event na asynchronním kanále 1 EVSYS.SYNCSTROBE = 1<<1; // generuj event na synchronním kanále 1 // není co dělat... } } // Nastaví clock 20MHz (z interního 20MHz bez děličky) 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_CLKSEL_OSC20M_gc; // 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) }
Z předchozího příkladu umíme konfigurovat přijímač eventů (konkrétně funkci EVOUT) a teď je na čase podívat se i na nějaký generátor. Jako první se nabízí zavést do event kanálu signál z PORTu. V takovém případě kanálem nechodí nějaké izolované eventy (pulzy) ale je tam logická hodnota jakou čip vidí na vybraném pinu. Demonstrujeme si to na pinu PB1. Ten nastavíme jako vstup a jako zdroj pro kanál SYNCCH1 i pro kanál ASYNCCH1. Stejně jako v předchozím příkladě využijeme funkce EVOUT1 a EVOUT2 ke sledování dějů na obou kanálech.
/* tutorial TinyAVR 1-Series * Event systém I - synchronní a asynchronní event z GPIO * PB2 - Event output (EVOUT 1) * PC2 - Event output (EVOUT 2) * PB1 - input (zdroj eventů) */ /* Ve fuses zvolen 20MHz oscilátor (lze volit ještě 16MHz) */ #define F_CPU 20000000UL #include <avr/io.h> void clock_20MHz(void); int main(void){ clock_20MHz(); // taktujeme na 20MHz // konfigurace vstupů (a zdroje eventů) PORTB.DIRCLR = PIN1_bm; // PB1 vstup (krmíme do něj signál z vnějšku) // Zdrojem pro Asynchronní event kanál 1 je pin PB1 EVSYS.ASYNCCH1 = EVSYS_ASYNCCH1_PORTB_PIN1_gc; // Zdrojem pro Synchronní event kanál 1 je taky pin PB1 EVSYS.SYNCCH1 = EVSYS_SYNCCH1_PORTB_PIN1_gc; // konfigurace výstupů ("uživatelů" eventů) PORTB.DIRSET = PIN2_bm; // PB2 (EVOUT 1) PORTC.DIRSET = PIN2_bm; // PC2 (EVOUT 2) // Povolíme EVEOUT1 a EVOUT2 PORTMUX.CTRLA |= PORTMUX_EVOUT1_bm | PORTMUX_EVOUT2_bm; // Na EVOUT 1 připojit kanál ASYNCCH1 EVSYS.ASYNCUSER9 = EVSYS_ASYNCUSER9_ASYNCCH1_gc; // Na EVOUT 2 připojit kaná SYNCCH1 EVSYS.ASYNCUSER10 = EVSYS_ASYNCUSER10_SYNCCH1_gc; while (1){ // není co dělat... } } // Nastaví clock 20MHz (z interního 20MHz bez děličky) 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_CLKSEL_OSC20M_gc; // 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) }
Na oscilogramech můžete vidět, že stav asynchronního kanálu (žlutá) kopíruje s malým zpožděním stav pinu (PB1). Synchronní kanál, jak víme, "aktualizuje" svou hodnotu s každým "tiknutím" clocku (v našem případě každých 50ns). Clock v našem čipu není nijak svázán se signálem, který přivádíme na vstup. Takže někdy "tikne" těsně po příchodu hrany na vstupu, někdy naopak těsně před tím a změna hodnoty přijde tedy až s dalším "tiknutím". Takhle vzniká synchronizační jitter o šířce jedné periody clocku (50ns). Naštěstí vás to nemusí nijak znepokojovat, neboť většina signálů, které jste kdy čipem zpracovávali prošla úplně stejnou synchronizací a nikdy vás to netrápilo.
Poslední příklad je jen taková rozcvička. Vybírat event kanálům zdroje už umíme, přijímače si taky umíme nastavit, takže jen pro upevnění znalostí si vyzkoušíme generovat eventy timerem TCA. Opět si eventy vypustíme pomocí EVOUT1 a EVOUT2. Jak už víme z tabulek, TCA může generovat eventy jen na synchronních kanálech. V timeru TCA0 existuje celkem pět událostí , které mohou být zdroje eventů. Jsou to:
/* tutorial TinyAVR 1-Series * Event systém I - Synchronní Eventy z TCA0 * PB2 - Event output (EVOUT 1) * PC2 - Event output (EVOUT 2) */ /* Ve fuses zvolen 20MHz oscilátor (lze volit ještě 16MHz) */ #define F_CPU 20000000UL #include <avr/io.h> void clock_20MHz(void); void init_tca(void); int main(void){ clock_20MHz(); // taktujeme na 20MHz // konfigurace výstupů ("uživatelů" eventů) PORTB.DIRSET = PIN2_bm; // PB2 (EVOUT 1) PORTC.DIRSET = PIN2_bm; // PC2 (EVOUT 2) // Povolíme EVEOUT1 a EVOUT2 PORTMUX.CTRLA |= PORTMUX_EVOUT1_bm | PORTMUX_EVOUT2_bm; // EVOUT 1 přiřadit eventy z kanálu SYNC0 EVSYS.ASYNCUSER9 = EVSYS_ASYNCUSER9_SYNCCH0_gc; // EVOUT 2 přiřadit eventy z kanálu SYNC1 EVSYS.ASYNCUSER10 = EVSYS_ASYNCUSER10_SYNCCH1_gc; // na kanál SYNC0 přivést událost Compare 0 z TCA EVSYS.SYNCCH0 = EVSYS_SYNCCH0_TCA0_CMP0_gc; // na kanál SYNC1 přivést událost Compare 1 z TCA EVSYS.SYNCCH1 = EVSYS_SYNCCH1_TCA0_CMP1_gc; // konfigurovat a spustit TCA init_tca(); while (1){ // není co dělat... } } // TCA se stropem 200 (perioda timeru 10us) void init_tca(void){ TCA0.SINGLE.PER = 199; // strop 200 TCA0.SINGLE.CMP0 = 19; // compare 0 na 20 TCA0.SINGLE.CMP1 = 39; // compare 1 na 40 // spustit timer s clockem čipu (20MHz) TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV1_gc | TCA_SINGLE_ENABLE_bm; } // Nastaví clock 20MHz (z interního 20MHz bez děličky) 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_CLKSEL_OSC20M_gc; // 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) }
Event systém není úplná novinka, velice podobný mechanismus můžete najít i na čipech Atxmega, kde má ještě širší využití. Abychom mohli jeho potenciál plně rozvinout, budeme se muset ještě seznámit s dalšími periferiemi. Teprve pak začne event sytém plnit svou roli. Závěrem upozorním ty z vás kteří experimentují na jiném čipu než Tiny416, že mohou mít trošku jiné rozdělení zdrojů a přijímačů, zvlášť pokud má jejich čip více timerů nebo jiný počet event kanálů. Doufám že to bylo vše srozumitelné a těším se na shledanou u dalších dílů.
Home
| V1.02 15.1.2019 /
| By Michal Dudka (m.dudka@seznam.cz) /