logo_elektromys.eu

/ Event Systém I |

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.

Blokový diagram na němž je vidět Event systém (oranžově) včetně všech jeho zdrojů a příjemců. [z Datasheetu]

/ Konfigurace Event systému |

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:

Seznam všech zdrojů pro synchronní kanály najdete v tabulce Table 14-3 v datasheetu. Některé zdroje jsou společné pro oba synchronní kanály, ty najdete v horní části tabulky (signály od timerů TCB0 a TCA0). Další zdroje se liší. Na kanál SYNCCH0 lze přivést signály z PORTA a PORTC. Na SYNCCH1 lze přivést libovolný pin z PORTB. Přivádíte-li do Event systému signály z vnějšku, musíte si hlídat do kterých kanálů lze signál z vámi zvoleného pinu vést. Předběhnu a prozradím, že například z PORTB můžete do systému zavést maximálně dva signály (jeden na SYNCCH1 a jeden na ASYNCCH1). Pro zápis do každého z šesti registrů (a tedy pro volbu zdroje pro každý z kanálů event systému) existuje v hlavičkovém souboru výčtový typ (uvedený v závorce v seznamu výše) se všemi volbami. Konkrétně budeme-li chtít připojit k Synchronnímu kanálu 1 (SYNCCH1) signál z pinu PB1. Musíme zapsat:

EVSYS.SYNCCH1 = EVSYS_SYNCCH1_PORTB_PIN1_gc;

Pokud bychom tam chtěli připojit signál například z PA3, tak máme smůlu. Zmíněná tabulka jasně říká, že PA3 na tento kanál nevede. Situace u asynchronních signálů je podobná. Čtyři kanály - čtyři registry v nichž vybíráme zdroj. Seznam zdrojů je v tabulce Table 14-2, která má čtyři sloupce. Krom společných signálů (například od komparátoru nebo timeru TCD) jsou zde také individuální. Signály z PORTA mohou vést jen na kanál ASYNCCH0, PORTB na ASYNCCH1 a PORTC na ASYNCCH2. Všimněte si, že signály z PORTů je možné posílat jako synchronní i asynchronní, podle toho jaké potřebujete. Nelámejte si zatím hlavu s tím co jsou všechny ty zdroje zač. Budete je poznávat postupně jak se budete seznamovat s periferiemi.

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.

Do ASYNCUSER registrů je možné v našem případě zapsat hodnotu 0 až 6. Do SYNCUSER smíte zapsat pouze hodnoty 0 až 2 (odpadá možnost přijímat ze 4 asnychronních kanálů). Hodnoty mají následující význam (jak se to ostatně píše v obou tabulkách): Přirozeně si je nemusíte pamatovat neboť na to máte výčtové typy ("makra") v hlavičkových souborech. Ta vás mohou ale nepříjemně zmást. Pokusím se problém demonstrovat na následujícím příkladě. Na přístrojové desce vašeho auta najdete 6 různě pojmenovaných knoflíků v řadě, budete předpokládat, že k něčemu slouží a možná si začnete lámat hlavu na co asi jsou. Každý asi slouží k něčemu jinému že ? Po čase zjistíte, že všechny dělají jedno a to samé - otevírají okénko u řidiče. A stejně je to s našimi "makry". Máme dvě tabulky, dvě sady hodnot ale celkem 13 výčtových typů :D z nichž 11 dělá totéž i přes to, že se jmenují trochu jinak. Osvětlíme si to na konkrétním příkladě. Dejme tomu, že chceme přijímač ASYNCUSER9 připojit ke kanálu ASYNCCH1. Což mimochodem znamená, že chceme poslat eventy z kanálu ASYNCCH1 ven z čipu (na PB2). Korektní zápis vypadá takto:
EVSYS.ASYNCUSER9 = EVSYS_ASYNCUSER9_ASYNCCH1_gc;
Ale klidně to mohu zapsat takhle:
EVSYS.ASYNCUSER9 = EVSYS_ASYNCUSER0_ASYNCCH1_gc;
nebo takhle
EVSYS.ASYNCUSER9 = EVSYS_ASYNCUSER7_ASYNCCH1_gc;
a tak dále ...
Dovedu si představit, že by bohatě stačilo jedno univerzální "makro" například v této podobě 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í.

/ SW vytváření Eventů |

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.

/ Příklad A) Event-out |

Ú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)
}
Takhle vypadají Eventy. PB2 - EVOUT1, PC2 - EVOUT2. (Neřešte tvar pulzů, zapnul jsem filtr v osciloskopu, měřit v pásmu desítek MHz poctivě by bylo velmi pracné)

/ Příklad B) eventy z PORTů |

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)
}
Modrá - vstup do event systému skrze PB1
Žlutá - stav Asynchronního kanálu (ASYNCCH1)
Červená - signál stav Synchronního kanálu (SYNCCH1)
Modrá - vstup do event systému skrze PB1
Žlutá - stav Asynchronního kanálu (ASYNCCH1)
Červená - signál stav Synchronního kanálu (SYNCCH1)

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.

/ Příklad C) eventy z TCA |

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:

Jak vidno všechno jsou to události, které se odehrají v jednom okamžiku a vyvolají event. Event bude mít opět podobu pulzu v trvání jednoho taktu (v našem případě 50ns). První tři zdroje jsou compare události a znáte je z tutoriálu o TCA. Poslední možnost, TCA0_OVF_LUNF, je dvojí, podle toho v jakém režimu je TCA nastaven (zda 16bit nebo 2x8bit). V případě 16bit režimu jde o událost přetečení timeru. V 2x8bit režimu je to podtečení "Low" timeru (ve 2x8bit režimu čítá směrem dolů, takže podtéká). TCA0_HUNF dává smysl jen pokud je timer v 2x8bit režimu a pak je to podtečení "High" timeru. My si pro ukázku zvolíme události CMP0 a CMP1. Přivedeme si je na kanály SYNCCH0 a SYNCCH1 a budeme osciloskopem sledovat kdy tam eventy vznikají. Timer taktujeme 20MHz a necháme ho počítat do 200, takže přetéká každých 10us. V tomto tempu se nám bude celý děj opakovat. Do CMP0 zapíšeme hodnotu 20 - takže compare událost nastane 1us po přetečení timeru. Do CMP1 zapíšeme 40 a druhá compare událost se odehraje 2us po přetečení timeru a tedy 1us po události compare 0. Eventy, které těmito událostmi vznikají by měly mít odstup 1us. Že tomu tak je dokládají oscilogramy pod zdrojovým kódem.

/* 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 od Compare 1 (červená) je zpožděn o 1us za eventem od Compare 0 (žlutá)
Perioda eventů také odpovídá předpokladu 10us.

| Závěr /

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ů.

| Odkazy /

Home
| V1.02 15.1.2019 /
| By Michal Dudka (m.dudka@seznam.cz) /