DEMO 1

Dlouho jsem si lámal hlavu s tím jak vůbec tento článek nazvat. Nejtěžší bylo zjistit proč jsem vůbec ukázku vytvářel. Napsal jsem ji proto abych předvedl jaké jsou možnosti čipů řady Atxmega a abych ukázal, že ty nejlepší funkce vznikají spoluprací periferií. V tomto příkladě se setkává vnitřní DA převodník, SPI rozhraní a externí RAM. Dohromady je pojí DMA modul a dává vzniknout zajímavému generátoru průběhů. Zpočátku jsem si myslel, že výsledkem bude plnohodnotný "Arbitrary waveform generator", ale omezené možnosti DMA na čipech řady "E" mě přinutily poněkud slevit ze svých požadavků. Je možné, že nebudete všem použitým periferiím dostatečně rozumět, v takovém případě berete tutoriál jen jako motivační ukázku možností čipů Atxmega.

Co to teda je ?

Příklad představuje "one-shot" generátor průběhů. Termínem "One-shot" označuji generátor, který na jeden startovací pokyn vygeneruje jeden průběh. Nepracuje tedy periodicky, ale pouze pulzně. Průběh se může skládat až ze 32768 vzorků (a při jiné volbě externí RAM až z dvojnásobku) s vertikálním rozlišením 8bitů. Rozsah výstupního napětí je 0-2.5V. K jeho realizaci potřebujete libovolný čip Atxmega řady E5 a externí seriovou RAM kompatibilní s IP12B256I (která je k dostání za korunu a padesát haléřů třeba zde). Případně si můžete pětikorunu připlatit a obstarat si verzi s dvojnásobnou pamětí (zde). Dále už budete potřebovat pouze referenci (například TL431). Pokud by vás napadlo generátor používat k něčemu praktickému, tak asi nebude od věci vybavit jeho výstup nějakým operačním zesilovačem a rozšířit si tak rozsah výstupního napětí. Se vším se určitě vlezete do stokoruny.

Jak to celé funguje ?

Atxmega řady "E" obsahuje slušný dvoukanálový DA převodník, schopný převádět až 1 milion vzorků za sekundu (1Msps) s rozlišením až 12 bitů. Plně rozvinout jeho schopnosti (2x1MSps s 12bit) lze ale pouze z interní RAM (příklad se připravuje). Mnohdy vám ale velikost RAM v čipu prostě nestačí a požadovaný průběh se vám do ní nevleze. Takovou situaci můžete řešit mnoha postupy. Jeden z nich je využít externí RAM. Pokud váš průběh generujete relativně pomalu, dejme tomu 10-50ksps (což bohatě postačí na generování zvuků), máte snadnou práci. Můžete v podstatě libovolným způsobem přistupovat k RAM, číst z ní data a předávat je DA převodníku. Jakmile začnete vyžadovat vyšší rychlosti vzorkování (0.1-1Msps), dostane se váš program do časového presu. A právě postup jak se s ním vypořádat uvidíte v tomto příkladě.

Práci aplikace si můžeme představit v několika základních bodech

Než se ale pustíme do komentování zdrojového kódu, seznámíme se ve zkratce s použitou RAM.

Externí RAM

Použitá paměť IP12B256 (případně IP12B512) má konvenční SPI sběrnici, je teoreticky schopná komunikovat s taktem až 20MHz, její kapacita je 32kB (respektive 64kB) a pracuje s 3V logikou. Komunikace s ní je jednoduchá a může probíhat v režimech 0 a 3 (viz SPI). Umožňuje i sekvenční zápis a sekvenční čtení. Má užitečný mód nazvaný Virtual Chip - VRTM díky němuž lze efektivně zmenšit kapacitu paměti a načítat z ní data v nekonečné smyčce. Což je funkce, která se úplně ideálně hodí třeba ke konstrukci funkčního generátoru. Základní funkce pro zacházení s pamětí jsou v souborech ip12bxxx_sram.h a ip12bxxx_sram.c. Funkce přirozeně spoléhají na vhodnou konfiguraci SPI. Při pokusech komunikovat s RAM na vyšších datových rychlostech (16Mb/s) jsem se ale setkal se zvláštními problémy, které měly jednoduchou příčinu. Zapomenutý filtrační kondenzátor. Což je přesně ten typ chyby, který dokáže zůstat relativně dlouho skrytý a projevuje se velice prazvláštně a sporadicky. A proto vám jen pro inspiraci předvedu co může nadělat za paseku. Podívejte se na následující dva průběhy S1 a S2.



S1 - správný průběh komunikace


S1 - chybný průběh komunikace - zapomenutý filtrační kondenzátor na napájení RAM

Na obrázku S1 vidíte, že změna dat probíhá přesně tam kde by podle SPI protokolu v režimu 3 měla. Tedy v místech kde je clock v log.0. Ideálně ihned s příchodem sestupné hrany aby se data měly čas do příchodu vzestupné hrany ustálit. Naopak na obrázku S2 vidíte, že při zapomenutí filtrace napájení dochází k rozpadu komunikace a RAM nestíhá změnit hodnotu datové linky včas (po 4. sestupné hraně clocku). To vyústí v chybnou interpretaci dat Atmelem, protože ten je čte se 4. vzestupnou hranou, kdy ještě nejsou připravena.

Program

Zdrojový kód je relativně rozsáhlý a můžete si jej stáhnout ve dvou verzích.

Ze začátku probíhá inicializace komponent. Čip taktujeme frekvencí 32MHz z externího krystalu. Pokud se smíříte s nepřesným časovým rozlišením generátoru tak vám bude stačit i interní 32MHz RC oscilátor.

SPI

SPI inicializujeme v režimu 3 s přenosovou rychlostí 8Mb/s. Při maximální rychlosti 16Mb/s už RAM nestíhá. Konkrétní důvod proč to tak je se mi zjistit nepodařilo. Využíváme Bufferovaný mód SPI. Ten přidává na vysílací stranu jednoúrovňový buffer. Aplikace tak může předa k vysílání další byte dat aniž by ten předchozí byl celý odeslán. Na přijímací straně je pak dvouúrovňový buffer. Ten působí jisté komplikace. Nesmíte ho totiž zapomínat čistit, jinak ztratíte přehled o tom co v něm je. Funkce pro práci s RAM s tím počítají a jsou napsány pro SPI v bufferovaném režimu.

DAC a časovač

Konfigurace DA převodníku začíná jeho kalibrací. Přesněji řečeno načtením kalibračních konstant z FLASH paměti. Ty jsou tam uloženy od výroby čipu a obsahují informace o stejnosměrném posuvu (offset) a měřítku (scale) převodníku. Ty zapisujete do registrů DACA.CH0OFFSETCAL a DACA.CH0GAINCAL. Tam přirozeně můžete zapsat libovolné hodnoty a korigovat jimi chyby celého analogového systému (tedy například včetně zesilovačů připojených na výstup). Z převodníku využíváme jen kanál 0, referenci si bereme z pinu REFA na který máme připojený obvod TL431 s napětím 2.5V. Kvůli rychlosti (o níž bude řeč v sekci do DMA) používáme DA převodník pouze v 8bitovém režimu a proto nastavujeme zarovnání dat vlevo. Trigrování převodu provádíme pomocí event kanálu 0. Čímž se plynule přesouváme k časování. Na Event kanál 0 totiž přivádíme signál od přetečení časovače TCC4. Jeho konfigurace je přímočará, běží v Normal módu (tedy počítá stále dokola od nuly do hodnoty TCC4.PER). Jen pro zajímavost a snadné ladění si signál z Event kanálu 0 vyvedeme na výstup EVOUT (PD7).

Příprava průběhu

Příprava průběhu spočívá ve výpočtu a nahrání vzorků do RAM. Obstarává ji funkce fill_sram(). Celá funkce je přirozeně pouze demonstrativní a slouží jen k tomu abych měl co generovat. Pokud příklad převezmete a budete ho upravovat k jiným účelům, bude tato funkce asi to první co změníte. Nelámal jsem si s ní moc hlavu. Plnění paměti by teoreticky mohlo probíhat rychleji za použití DMA. Ale vzhledem k tomu, že tvar průběhu vypočítávám pomocí čísel s plovoucí desetinnou tečkou (float), nebude mít optimalizace rychlosti zápisu do RAM valný význam. Funkce má tři argumenty. Prvním z nich vybíráte do které části RAM se má průběh zapsat (adresa prvního vzorku), druhým argumentem pak vybíráte kolik vzorků celkově připraví. Poslední argument může nabývat hodnot 1 a 2 a jen volí jeden ze dvou ukázkových signálů. V příkladu používám celou RAM, takže první vzorek leží na adrese 0 a vzorků je celkem 32768.

Jádro věci - DMA

Doteď jsme řešili jakousi "přípravu" a teď přijde to hlavní. Konfigurace DMA. Nejprve si ujasníme co potřebujeme. Časovač dává skrze Event kanál 0 pokyny DA převodníku aby vykonal převod. Jakmile DA převodník svůj převod dokončí je hladový. A v tom okamžiku mu potřebujeme přenést nová data. Tady ale vyvstává jistá komplikace. Po SPI probíhá duplexní komunikace, takže abychom mohli jeden byte dat z RAM přečíst musíme jí jeden byte dat (dummy) odeslat ! Jakmile "dummy" data odešleme, v přijímacím bufferu na nás bude čekat odpověď RAM (hodnota jednoho vzorků) a ty potřebujeme dopravit do DA převodníku. Je to relativně komplikovaná situace a budeme k ní potřebovat oba standardní kanály DMA. Kanál 0 bude obstarávat přenos "dummy" byte do SPI. Tento přenos bude spouštěn Event kanálem 0. Tedy v okamžiku kdy dostane DA převodník pokyn k převodu, DMA začne pracovat na výměně dat mezi RAM a Atmelem. Transport dat z přijímacího bufferu SPI do DA převodníku bude obsluhovat kanál 2 a požadavky mu bude dávat SPI periferie v okamžiku kdy přijme byte z RAM. Oba kanály budou pracovat s fixními adresami bez inkrementace, protože přenáší data pouze z jedné paměťové buňky do druhé. A přirozeně poběží v režimu "single" v němž přenáší jeden byte dat na jeden požadavek (request). Před spuštěním DMA musíme připravit RAM na sekvenční čtení. Což uděláme jako obvykle odesláním příkazu pro čtení a zapsáním dvou bytů adresy z níž chceme číst. Ta se přirozeně musí shodovat s adresou kterou jsme předali funkci fill_sram(), jinak si přečteme nějakou špatnou oblast RAM. Pak už jen ve správném pořadí spustíme oba DMA kanály a počkáme si na dokončení přenosu. A tím je vše hotovo.

Výsledky

Tajně jsem doufal, že generátor poběží s vzorkovací frekvencí 1Msps. Jak už jsem ale uvedl, nepodařilo se mi zprovoznit RAM s komunikační rychlostí 16Mb/s. A na rychlosti 8Mb/s už není možné každou us zásobovat DA převodník daty. Celý proces tedy běží bohužel jen na 500ksps. Vzorkovací frekvenci můžete měnit hodnotou makra PERIOD, kterým se nastavuje perioda časovače. Kdo bude chtít může si metodou pokusu a omylu vzorkovací frekvenci zvednout. S trochou štěstí by se mohl dostat třeba na 750ksps. Ale dost řečí, pojďme se podívat na výsledky. Na obrázcích P1 a P2 vidíte celkový pohled na dva generované průběhy. Na modrém kanále jsou pro informaci data přenášená z RAM. I když to tak na první pohled nevypadá oba průběhy využívají celých 32kB RAM. U dvojitého pulzu (součet dvou gaussovských průběhů) to vypadá že je dat méně, ale jen to jen zdání, celý zbytek času kdy je signál v nule jsou z RAM čtena data o hodnotě 0 a tudíž se jeví na modrém průběhu jako by tam nebyla.



P1 - Modulovaný sinus


P2 - Dvojitý gaussovský pulz

Na dalším obrázku (P3) pak vidíte výřez. Je na něm krásně patrný schodovitý průběh výstupu DA převodníku i přenášená data.


P3 - Detail na dvojitý gaussovský pulz

Závěrem

U takto rozsáhlého příkladu už není možné komentovat zdrojový kód do detailu a vysvětlovat do podrobna nastavení každého registru. Čtenář už musí jednotlivým periferiím trochu rozumět a soustředit se spíše na celkový koncept. Doufám že si z příkladu odnesete alespoň jistý vhlede do toho jak v mikrokontrolérech Xmega periferie splupracují. A i kdyby ne, pořád doufám že vás ukázka namotivuje ke přečtení dalších (a jednodušších) tutoriálů.


Vrabčí hnízdo, ale víc nebylo potřeba.