Docela rychlé PWM na Attiny ...

Abstrakt:

Jestli jste někdy uvažovali využívat PWM ke generování audiosignálu na Atmelech, neměl by vám tento článek uniknout. Prohlédneme si v něm totiž možnosti 64MHz časovače na Attiny85 a jen tak mimochodem naučíme Atmel vydávat docela pěkné tóny :)

úvod:

Než začnete číst, doporučoval bych vám alespoň letmo se seznámit s použitím pulze šířkové modulace (PWM) jako improvizovaného DA převodníku. Problematika je to naštěstí hodně diskutovaná, takže nebudete mít nouzi o zdroje. Nabídnu vám malý výběr:

Pokud se ale v problematice příliš neorientujete, ať už z důvodů nedostatečných matematických základů nebo prostého nedostatku trpělivosti, nezoufejte. V návodu všechny důležité faktory ovlivňující činnost DA převodníku zmíním pouze kvalitativně. Tedy tak aby vám bylo jasné, které faktory na sobě závisí a nebudu vás mučit exaktními vztahy. Mě se do toho totiž také moc nechce...

PWM jako DAC:

Pod pulzně šířkovou modulací (PWM) si představte jako signál s pevnou periodou v němž se dle vaší volby přelévá doba log.1 a doba log.0. Signál s periodou dejme tomu 1ms může vypadat tak že 90% doby (tedy 0.9ms) je v log.1 a zbylých 10% doby (0.1ms) je v log.0. Takový signál má střídu (Duty cycle) 90%. Pokud takový signál přivedete na RC článek (integrační člen) dojde k jeho vyhlazení. Tedy za předpokladu, že vámi generovaný signál je schopen dodávat nějáký rozumý proud. Což výstup atmelu je. Míra vyhlazení závisí na tom jak velký odpor a jak velkou kapacitu do integračního článku zapojíte. Zvolíte-li dejme tomu odpor o hodnotě 1kOhm a kondenzátor o kapacitě 1uF můžete odhadovat, že k nabití kondenzátoru dojde řádově za dobu 2*R*C = 2ms. Jestliže na tento článek přivedete PWM signál bude docházet střídavě k nabíjení a vybíjení kodenzátoru. Článek se bude nabíjet clou dobu kdy je PWM signál v log.1 a vybíjet když je signál v log.0. Pokud bude perioda PWM signálu výrazně nižší jak náš řádový odhad doby nabití kondenzátoru, nestihne se kondenzátor během jedné periody ani nabít ani vybít. Vlastně se míra jeho nabití stihe změnit jen málo. Dejme tomu že zvolíte periodu PWM signálu například 100us. Za tak krátký čas se kondenzátor stihne nabít a vybít (připomínám že PWM signál je vždy čst času v log. a část v log.0) jen málo. A protože "výstupem" integračního článku je právě napětí na kondenzátoru tak dochází k "vyhlezení". Velmi divoký signál na vstupu (třeba v rozsahu 0-5V) se na výstupu projeví jen jako mírné kolísání v rozsahu odhadem 0.1V (mrkněte na žlutý průběh na obrázku d2).

Čím bude kapacita nebo odpor v RC článku větší tím delší bude doba nabíjení kondenzátoru a tím hladší bude průběh za RC článkem. To je první kvalitativní závislost na kterou musíte brát zřetel. Kromě toho je asi také zjevné že čím bude kratší perioda PWM signálu (tedy čím vyšší bude jeho frekvence), tím méně času bude mít kondenzátor na nabíjení a vybíjení a tím méně se na něm stihne napětí změnit. A tedy tím hladší bude výstupní signál. To je druhá kvalitativní závislost kterou musíte mít na paměti při použití PWM jako DA převodníku. Zůstala však nezodpovězena důležitá otázka. Na jaké hodnotě se napětí na kondenzátoru ustálí. Zase můžete nasadit selský rozum a říct si. Když pustím PWM signál se střídou 99%, tedy většinu času bude v log.1 (a kondenzátor se bude nabíjet) a jen krátkou dobu bude trávit v log.0 (kondenzátor se vybíjí) tak by se měl kondenzátor vlastně udržovat skoro nabitý. Tedy jeho napětí by mělo být velmi blízko napětí vaší log.1 (dejme tomu 5V). Naopak jestliže bude střída hodně nízká, například 1%, bude se kondenzátor většinu času vybíjet a jeho napětí by tedy mělo být nízké (blízko log.0) tedy 0V. No a když střídu budete postupně měnit, mělo by napětí kondenzátoru postupně přecházet od jedné zmíněné krajní hodnoty ke druhé. A potěším vás faktem že se to bude dít docela lineárně. Jinak řečeno že mezi vyhlezeným napětím a střídou bude přímá úměra. Čím větší střída, tím vyšší napětí na kondenzátoru. Matematicky se na to můžete dívat tak že zhruba platí U = Ucc*DCL. Kde DCL je střída v rozsahu 0-1 (0 odpovídá 0%, 1 odpovídá 100% a třeba 0.32 odpovídá 32%).

Teď by jste si mohli říct, vždyť to je vlastně velice jednoduché. A taky že je ! Ale znáte svět, všechno má své ale. Pokud vám jde o to vytvořit napětí o hodnotě dejme domu 2.35V tak prostě vytvoříte RC článek. Aby výstupní napětí nebylo moc zvlněné tak schválně zvolíte relativně veliký odpor dejme tomu 100kOhm a pořádnou kapacitu dejme tomu 100uF a ještě si to posichrujete vysokou frekvencí PWM signálu (dejme tomu 100kHz). Máte-li 5V napájecí napětí tak střídu zvolíte 2.35/5 = 0,47 (tedy 47%). Spustíte signál, chvíli počkáte a napětí je na světě. Hotovo. Jenže za chvíli vás hodnota 2.35V začne nudit a vy budete chtít hodnotu změnit. A tady je schované právě to "ale". Změníte prostě střídu PWM signálu (například na 78%) a kondenzátor se začne postupně nabíjet na novou hodnotu (0.78*5 = 3.9V). Jenže mu to bude setsakramentsky dlouho trvat. Kondenzátoru s kapacitou 100uF bude přes odpor 100kOhm trvat nabíjení řádově R*C = 10s .. přesněji desítky sekund ! A to určitě chtít nebudete :) Takže co s tím. No přirozeně, chcete-li aby se kondenzátor nabíjel rychleji, musíte buď snížit jeho kapacitu nebo předřazený odpor. Prostě musíte snížit dobu nabíjení. Můžeme tedy zformulovat třetí kvalitativní závislost. Čím je doba nabíjení RC článku větší, tím pomaleji reaguje jeho výstupní hodnota na změnu střídy. A naopak čím je doba nabíjení RC článku menší tím rychleji reaguje jeho výstup na změnu střídy. Chcete-li měnit hodnotu relativně rychle (třeba v řádu ms) je potřeba volit dobu nabíjení kondenzátoru krátkou. Krátká doba nabíjení ale vede k většímu zvlnění výstupního signálu (nezapomínejte že kondenzátor se střídavě nabíjí a vybíjí v rámci periody vstupního PWM signálu). Stojíte tedy před kompromisem, buď bude signál hladký s malým nebo nepatrným zvlněním, ale bude mít malou "dynamiku", jinak řečeno nebude možné ho příliš rychle měnit. A nebo bude signál obsahovat větší zvlnění, ale bude schopen svou hodnotu měnit relativně rychle. Obě situace můžete vidět na obrázcích d3 a d4. Nezřídka se dostanete do situace kdy budete po signálu chtít obě protichůdné vlastnosti. Tedy aby byl hladký a aby se dal rychle měnit. A tady právě budete rádi za to, že můžete měnit periodu PWM signálu. Protože čím je jeho perioda nižší (a tedy frekvence vyšší) tím je menší zvlnění. Tím vším jsem vám chtěl vysvětlit že frekvence PWM je velice důležitým faktorem který rozhoduje co s naším improvizovaným DA převodníkem ještě jde a co už je nad jeho možnosti.

shrnutí:

Větší časová konstanta (součin R*C) snižuje zvlnění a zpomaluje dynamiku (a naopak). Větší frekvence PWM snižuje zvlnění (a s dynamikou nesouvisí). Z toho plyne, že pro signály s nizkým zvlněním a vysokou dynamikou (možností rychlé změny) potřebujeme vysokou frekvenci PWM.

Pár technických poznámek:

Rezistor v RC článku nesmíte z praktických důvodů volit příliš malý. Čím je jeho hodnota menší, tím je nabíjecí a vybíjecí proud kondenzátoru větší. A tento proud musí dodávat nebo odebírat Atmel. Přílišné zatěžování výstupu by mohlo vyústit k mírným změnám výstupích úrovní (log.1 by mohla namísto dejme tomu 5V dosahovat například pouze 4.7V). Je proto vhodné volit zatěžovací proud maximálně v jednotkách mA. Odpor rezistoru by tedy měl být vyšší jak přibližně 2kOhm.

Dále je nutné výstup RC článku příliš nezatěžovat (tedy neodebírat z něj větší proud). Zatěžováním by docházelo k vybíjení kondenzátoru a narušování činnosti DA převodníku. Na výstup je tedy velmi vhodné připojit například operační zesilovač jako sledovač napětí. Výstup ze sledovače je pak možné zatěžovat v rámci možnosí operačního zesilovače (tedy typicky klidně jednotky mA). Typicky budete chtít tento operační zesilovač napájet stejným napětím jako Atmel (což zjednodušuje návrh obvodu), pak musíte volit tzv "Rail to Rail" operační zesilovač (které jsou dnes snadno k sehnání).


Obrázek d2 - Zvlnění při filtraci PWM signálu RC článekem. Žlutý průběh - napětí na kondenzátoru, modrý průběh napětí na vstupu RC článku (PWM)

Obrázek d3 - RC článek 10kOhm a 4.7nF. Zvlnění je relativně velké (přibližně 80mV), doba přeběhu ale relativně malá - přibližně 120us na změnu o 0.5V

Obrázek d4 - RC článek 10kOhm a 10nF. Zvlnění je relativně malé (přibližně 40mV), doba přeběhu ale relativně velká - přibližně 200us na změnu o 0.5V (modrého signálu si nevšímejte, je z invertovaného kanálu)

Zakousněme se do praxe:

Frekvence PWM na běžných Atmelech (Atmega328, Atmega16 atd.) nemůže překročit clock čipu. Taktujete-li tedy Atmel třeba 1MHz, nemůže být frekvence PWM vyšší. To je dáno možnostmi časovačů. Existují-ale i Atmely, které jsou v tomto směru lépe vybaveny. Například kterýkoli z čipů Attiny25/45/85 nebo Attiny261/461/861 obsahuje PLL schopné generovat kmitočet až 64MHz a časovač, který s takovou frekvencí umí pracovat. Tím tyto čipy získávají schopnost generovat pomocí PWM relativně hladké analogové průběhy v řádu jednotek až desítek kilohertz. A přímo se nabízejí na generování audio signálu (mimo jiných velmi zajímavých aplikací v oblasti řízení motorů nebo regulace výkonu). Abych na vás ale hned nevytasil hotový příklad, budeme postupovat jako obvykle krůček po krůčku a nejprve si ukážeme jak na Attiny45 nakonfigurovat PLL a čítač tak aby generoval obdelíkový průběh s frekvencí 32MHz.

Příklad A) CTC mód - generování 32MHz

Zajisté si vzpomenete že čítače na Atmelech mají tzv CTC mód. Tedy mód v němž je možné měnit strop a tudíž i periodu čítače. A také si možná vzpomenete na to, že je možné jistý výstupní pin (OCx) využít ke generování frekvence. To samé jde i u čítače / časovače 1 na výše zmiňovaých čipech. Letmý pohled do datasheetu mi napoví že výstup OC1B (použitelný k tomuto účelu) je na pinu PB4 a výstup OC1A se stejnými možnostmi na PB1. V rámci příkladu si předvedeme jak na OC1B generovat frekvenci až do 32MHz. Nejprve si ale vydláždíme cestu a podíváme se na možnosti clocku uvnitř čipu. Prohlédněte si obrázek a1. Vlevo jsou dva bloky oscilátorů. Vstupy XTAL1 a XTAL2 vám dávají vědět že čip je možné taktovat krystalovým oscilátorem, což se hodí zvlášť pokud vyžadujete generování přesných frekvencí. Druhý oscilátor je váš starý známý 8MHz RC oscilátor, který je součástí snad každého Atmelu. Teď zaměřte svůj zrak na pravou stranu schématu jsou tam výstupy PCK a SYSTEM CLOCK. SYSTEM CLOCK jde do jádra čipu a k většině periferií a je to prostě ten clock na který jste u atmelu zvyklý. Všimněte si, že je ho možné dělit předděličkou (prescaler). Dělící poměr volíte pomocí bitů CKSEL softwarově, takže můžete takt čipu (a tedy i spotřebu) přizpůsobovat aktuálním potřebám. Před předděličkou je multiplexer ovládaný fuses (!) CKSEL, který vám umožňuje zvolit si jeden ze tří zdrojů taktu pro čip. Buď externí signál (ať už ryze externí nebo realizovaný pomocí krystalu), interní 8MHz RC oscilátor a interní 16MHz signál z PLL. Vrátíme-li se vlevo k 8MHz RC oscilátoru vidíme že jeho signál je možné přivést k PLL a to buď v nezměněné podobě nebo podělený na 4MHz. PLL pak frekvenci násobí 8x. Díky tomu je možné připravit PCK signál s frekvencí buď 64MHz nebo 32MHz. 32MHz varianta je zde pro nízkonapěťové aplikace. Jestliže je napájecí napětí nižší jak 2.7V není možné 64MHz signál používat a je potřeba pomocí bitu LSM zvolit 32MHz. Samotné PLL lze pak spustit buď bitem PLLE a nebo volbou fuses (CKSEL). Jestliže tedy volíte clock čipu jiný než 16MHz musíte si pak softwarově spustit PLL modul sami (což v příkladu uděláme).


Obrázek a1 - schéma clocku PLL Attiny25/45/85 (z datasheetu)

Časovač 1 se od běžných časovačů, které znáte z jiných členů rodiny AVR drobně liší. V prvé řadě má komplexnější předděličku, pomocí níž je možné dělit clock časovače 14 hodnotami (1,2,4,8 ... 16384). Taktovat je ho možné ze systémového clocku, tedy stejnou frekvencí jako celý Atmel a nebo asynchronním clockem z PLL (tedy 64 respektive 32MHz). Výběr clocku se provádí bitem PCKE v registru PLLCSR (který mimo jiné slouží k ovládání PLL). Namísto běžných dvou compare jednotek má časovač tři. Třetí compare jednotka slouží k nastavování stropu časovače (registr OCR1C). Další drobná změna je v použití vlajky TOV. Zatímco u běžných časovačů dojde k jejímu nastavení jen v situaci, že časovač přeteče přes hodnotu 0xFF, tady dochází k jejímu nastavení kdykoli časovač přeteče přes svůj strop (tedy typicky přes hodnotu OCR1C). Nastavení vlajky TOV může přirozeně vyvolat přerušení. Dalších změn doznaly módy čítače. Jsou jen dva. Mód "Normal", a mód "PWM". V Normal módu lze čítači zvolit strop buď 0xFF nebo hodnotu v registru OCR1C. Výběr módu se provádí bitem CTC1 v registru TCCR1 a jeho nastavením se zapíná CTC (strop je roven OCR1C). Abych vás ale nazahrnul informacemi příliš rychle, necháme si mód PWM do dalšího příkladu. Časovač v Normal módu (ať už má jeho strop jakoukoli hodnotu) může ovládat piny OC1A a OC1B tak jak jste zvyklí. Režim Normal tedy poslouží buď ke gnerování periodických přerušení, nějákému jednorázovému časování nebo jako generátor frekvence. Vhodnou kombinací bitů COM1A1 a COM1A0 můžete nastavit "toggle" (tedy přepnutí) OC1A výstupu při přetečení časovače. Stejné možnosti má i výstup OC1B. V příkladu spustíme časovač s frekvencí 64MHz a strop mu nastavíme na nulu. Čítač tedy přeteče s každým tiknutím 64MHz clocku. Při přetečení přepne svůj pin OC1B a protože pin musí přepnout svou hodnotu dvakrát aby vznikla celá perioda, budeme pozorovat na výstupu OC1B průběh s poloviční frekvencí, tedy 32MHz.

// A) CTC mód - generování 32MHz 
#include <avr/io.h>
#define F_CPU 16000000
#include <util/delay.h>

int main(void){
	DDRB |= (1<<DDB4);	// PB4 (OC1B) výstup
	PLLCSR |= (1<<PLLE); // start PLL na 64MHz  
	_delay_us(100); // počkat na stabilizaci PLL
	while(!(PLLCSR & (1<<PLOCK))){}	// čekat na připravenost PLL 
	PLLCSR |= (1<<PCKE); // Zvolit clock časovači (64MHz)

	OCR1C = 0;	// strop časovače (OCR1C)	
	GTCCR = (1<<COM1B0); // Přidělím časovači výstup OC1B ("Toggle on compare match")
	TCCR1 = (1<<CTC1) | (1<<CS10) ; // CTC mód, clock PCK bez předděličky

	while(1){
	asm("nop"); // není co dělat ...
	}
}

Výsledný průběh na pinu OC1B (PB4) můžete vidět na obrázku a2. Všimněte si že frekvence přiliš neodpovídá 32MHz. Důvod leží v nepřesnosti interního 8MHz oscilátoru od nějž je 64MHz signál pro časovač odvozen. Do jisté míry můžete tuto nepřesnost korigovat jeho kalibrací, která se provádí zápisem do OSCCAL. Protože lze ale RC osciátor přeladit přibližně od 50% do 150%, tedy klidně až na 12MHz, je na místě zmínit, že pro výstup PLL je tu limit v podobě přibližně 85MHz, který by nemělo jít překročit (12MHz by jinak odpovídal výstup z PLL o frekvenci 96MHz).


Obrázek a2 - 32MHz výstup na OC1B (PB4)

Příklad B) 1MHz PWM

Z předchozího příkladu už umíme ovládat clock časovače a teď je čas podívat se jak u něj fungují PWM výstupy. Ty totiž také doznali jistých změn oproti běžným časovačům na které jste zvyklí. Určitě jste si už všimli v rozpisce vývodů Attiny, že krom výstupů OC1A a OC1B jsou tu i výstupy OC1A a OC1B s pruhem. Na nich je možné generovat negovanou verzi průběhů z OC1A a OC1B. Vhodnou kombinací COM1A1 a COM1A0 je možné zvolit, že na výstupu OC1A s pruhem (od teď jej budu značit jako !OC1A) má být invertovaný signál oproti výstupu OC1A. A stejné platí i pro výstupy OC1B a !OC1B. Lze přirozeně volit i variantu kdy výstup !OC1A či !OC1B nemá žádnou roli a časovač s ním nijak nemanipuluje. Bylo by to krásně jednoduché, kdyby tu ale nebylo další "ale". Signál na výstupu !OC1 nemusí být přesnou invertovanou kopií signálu z OC1. Pomocí "Dead time" generátoru je možné vkládat jisté časové úseky mezi změny stavů obou signálů. To se hodí pro řízení spínacích tranzistorů H-můstků, které potřebují jistý čas k zavření. Tím si ale teď nelamte hlavu, to je problematika na další článek. Bohatě postačí když budete vědět, že to jde. Málem bych zapomněl na ještě jedno milé překvapení - střída PWM signálu jde měnit v plném rozsahu od 0 do 100% (což třeba u 8bit časovače Atmega16/32 nejde). Teď už nám nic nebrání předvést si další příklad. V něm bychom si chtěli připravit PWM signál o frekvenci 1MHz. S čítači běžícímu s clockem 64MHz bychom měli nastavit strop na 63 (počítáme od nuly). Já ale v příkladu nastavil strop o jedno vyšší, protože má RC oscilátor drobně vyšší frekvenci a našich 64MHz má reálnou hodnotu spíše 66-68MHz. Tak se tím nenechte zmást. PWM mód se volí nastavením bitu PWM1A nebo PWM1B (podle toho který z kanálů chcete povolit). Nastavením bitu COM1B0 jsem vybral variantu kdy jsou čítači přiděleny oba výstupy OC1B i !OC1B. Hodnotu PWM jsem nastavil na 8/(64+1) = 12.3%.

// B) 1MHz PWM
#include <avr/io.h>
#define F_CPU 16000000
#include <util/delay.h>

int main(void){
	DDRB |= (1<<DDB4) | (1<<DDB3);	// PB4 (OC1B) výstup
	PLLCSR |= (1<<PLLE); // start PLL na 64MHz
	_delay_us(100); // počkat na stabilizaci PLL (dle datasheetu)
	while(!(PLLCSR & (1<<PLOCK))){}	// čekat na připravenost PLL (podle datasheetu)
	PLLCSR |= (1<<PCKE); // Pustit vysokou frekvenci do časovače (asynchronní režim)

	OCR1C = 64;	// strop časovače (OCR1C)
	OCR1B = 8; // hodnota PWM 8/(64+1) (12.3%)		
	GTCCR = (1<<COM1B0) | (1<<PWM1B); 	// povolit PWM na OC1B i na !OC1B.
	TCCR1 = (1<<CTC1) | (1<<CS10) ; // CTC mód, clock PCK bez předděličky

	while(1){
	asm("nop"); // není co dělat ...
	}
}

Na obrázku b1 si můžete prohlédnou oba výstupní signály. Žlutý průběh odpovídá signálu !OC1B (PB3) a zelený průběh je OC1B (PB4). Pro zajímavost jsem na dalším obrázku (b2) předvedl výstup s minimální hodnotou střídy. Z něj je patrné, že z Atmelu lze vytvářet pulzy o šířce přibližně 15ns.


Obrázek b1 - Komplementární PWM signál. Žlutý průběh je !OC1B (PB3) a zelený průběh je OC1B (PB4)

Obrázek b2 - Minimální šířka pulzu z PWM módu (při 64MHz clocku časovače)

Příklad C)

Předchozí dva příklady posloužily jen jako "seznamovací" s časovačem 1. Ve třetím a posledním příkladu si předvedeme trochu plnohodnotnější využití. Naprogramujeme jednoduchý generátor sinusového průběhu. Časovač 1 bude sloužit ke generování PWM. Časovač 0 poslouží jako zdroj periodického přerušení a bude sloužit k časování generovaného průběhu. Vzorky generovaného průběhu (různé střídy PWM) budeme načítat z flash paměti, protože tam lze v případě potřeby uložit daleko delší sekvenci (třeba i akustický signál - mluvenou řeč).


Schema 1 - Zapojení RC článku na výstup Attiny (bez sledovače a se sledovačem)

Opominuta ještě zůstala otázka jakou volit frekvenci PWM. Jestliže clock časovače 1 běží na 64MHz, lze generovat až 250kHz PWM s plným 8 bitovým rozlišením (tedy střídu PWM signálu lze měnit ve 256 krocích). V takové situaci můžeme po filtraci vytvářet až 256 napěťových úrovní. Teoreticky je tedy krok našeho DA převodníku přibližně 20mV při 5V napájení. Ne vždy ale budete potřebovat takové rozlišení. Mnohdy se spokojíte třeba se 64 kroky (tedy rozlišením přibližně 80mV při 5V napájení). V takovém případě můžete zvýšit frekvenci čítače až na 1MHz. A jak už jsme říkali, vyšší frekvence otevírá možnosti k vyšší dynamice výstupního signálu. Při generování akustického signálu je pak možné dosahovat vyšších tónů. Snižování rozlišení ale vede k deformaci generovaných signálů (stávájí se schodovitými). Takže výběr frekvence PWM, rozlišení a parametrů RC článku bude vždy kompromisem voleným podle typu cílové aplikace. V našem příkladě si ponecháme plné 8 bitové rozlišení. Frekvence PWM tedy bude 64MHz/256 = 250kHz. Parametry RC článku zvolíme R=10kOhm a C=10nF a sinusový signál budeme generovat ze 32 vzorků.

Časovač 1 bude autonomně generovat PWM signál na výstupu OC1B jehož střída (a tedy i výstupní napětí z RC článku) bude odpovídat aktuální hodnotě v OCR1B. Časovač 0 bude v pravidelných intervalech volat rutinu přerušení. V ní pak změníme hodnotu v OCR1B, což povede ke změně výstupního napětí (a postupnému generování analogového průběhu "bod po bodu"). Jednotlivé hodnoty výstupního průběhu (hodnoty střídy) máme uloženy ve flash paměti v poli sine. Perioda (a tedy i frekvence) generovaného průběhu pak bude odpovídat periodě přerušení od časovače 0. Pro vygenerování jedné periody signálu potřebujeme do OCR1B postupně nahrát všech 32 vzorků. Zovlíme-li tedy periodu časovače 0 například 15.625us, pak bude perioda generovaného signálu rovna 32*15.625 = 500us a frekvence tedy 2kHz. Abychom časovači 0 mohli snadno měnit periodu spustíme ho v CTC režimu a jeho strop (a tedy i periodu) budeme ovládat registrem OCR0A. Přerušení bude přirozeně od Compare A události. Pokud nezapomeneme že čítač čítá od nuly můežeme si matematicky vyjádřit jakou hodnotu je nutné zapsat do OCR0A aby náš program generoval požadovanou frekvenci (výstupního sinusového průběhu).
OCR0A = F_T0 / (SAMPLES* f_gen) - 1;
kde F_T0 je frekvence clocku čítače 0, SAMPLES je počet bodů z nějž generujeme výstupní průběh, f_gen je požadované frekvence průběhu. Konkrétně tedy třeba pro sinus s frekvencí 2kHz je OCR0A = 16000000/(32*2000)-1 = 249. Za F_T0 jsem dosadil 16MHz protože clock čítače 0 mám bez předděličky a procesor taktuji frekvencí 16MHz (zvoleno pomocí fuses). Jistě si dokážete všimnou, že nižší frekvence (a tedy větší perioda) by vyústila v hodnotu větší jak 255. Takové hodnoty ale nelze nastavit 8 bitovému čítači jako strop, v takovém případě musíte zvolit clock čítače 0 s nějákou předděličkou. Dálší podrobnosti snad není třeba rozebírat. Očekávám, že čítač 0 ovládat umíte :)

// E) - generování průběhu z flash paměti
/*
RC filtr 10k-10n, PWM na 250kHz (na OC1B)
Generovaný sinus na 2kHz 
OCR0A = F_CPU / (SAMPLES* f_gen) - 1;
kde f_gen je frekvence generovaného průběhu
F_CPU je clock časovače 0 (tady F_CPU)
SAMPLES je počet vzorků generovaného průběhu na periodu
*/

#include <avr/io.h>
#define F_CPU 16000000
#include <util/delay.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>

#define SAMPLES 32

// vzorky (analogové hodnoty) generovaného sinu
const char sine[SAMPLES] PROGMEM = {
	0x80,0x98,0xb0,0xc6,0xda,0xea,0xf5,0xfd,
	0xff,0xfd,0xf5,0xea,0xda,0xc6,0xb0,0x98,
	0x80,0x67,0x4f,0x39,0x25,0x15,0xa,0x2,
0x0,0x2,0xa,0x15,0x25,0x39,0x4f,0x67};

volatile uint8_t idx=0; // index generovaného vzorku

int main(void){
	DDRB |= (1<<DDB4);	// PB4 výstup
	PLLCSR |= (1<<PLLE);	// start PLL
	_delay_us(100); // počkat na stabilizaci PLL (dle datasheetu)
	while(!(PLLCSR & (1<<PLOCK))){}	// počkej než je PLL ready (podle datasheetu)
	PLLCSR |= (1<<PCKE);	// Pusť vysokou frekvenci do timeru

	OCR1C = 0xff;	// strop časovače 1
	OCR1B = 0x80; // počáteční hodnota střídy
	GTCCR = (1<<PWM1B) | (1<<COM1B1); // PWM jen na kanále OC1B
	TCCR1 = (1<<CTC1) | (1<<CS10) ; // CTC mód, žádná předdělička
	
	OCR0A = 249; // 15.625us na vzorek, 32 vzorků -> 500us na periodu (2kHz) sinus
	TIMSK = (1<<OCIE0A); // povolit přerušení od přetečení (nastavení nové hodnoty PWM)
	TCCR0A = (1<<WGM01); // CTC mód čítače 0
	TCCR0B = (1<<CS00); // čítač 0 bez předděličky 
	sei(); // globální povolení přerušení

	while(1){
	asm("nop"); // není co dělat ...
	}
}

ISR(TIM0_COMPA_vect){
	OCR1B=pgm_read_byte(&sine[idx]); // nastavit novou hodnotu střídy čítače 1
	idx++; // příště načíst další hodnotu sinu
	if(idx>=SAMPLES){idx=0;} // začít generovat znovu od začátku
}

S příkladem je přirozeně možné různě laborovat. Volbou OCR1C můžete měnit strop časovače 1. Tím pádem i frekvenci PWM. volbou nižšího stropu ale omezuejte rozlišení PWM a je proto potřebné tomu přizpůsobit i vzorky generovaného průběhu (můj sinus obsahuje hodnotu 0xff, kterou by s nižším stropem čítače 1 nebylo možné generovat !). Přirozeně můžete dále laborovat s parametry RC článku. Změnou předděličky a OCR0A můžete měnit frekvenci generovaného průběhu. Změnou počtu vzorků (SAMPLES) a jejich hodnoty pak také tvar generovaného průběhu. Je tu tedy více než dost prostoru pro experimentování. Výsledky několika pokusů můžete vidět na obrázcích c1, c2 a c3. Kdy jsem pouze měnil OCR1A a volil frekvenci generovaného sinu na 2,4 a 8 kHz. Všimněte si že vlivem filtrace na RC článku dochází se zvyšující se frekvenci ke snižování amplitudy a posouvání fáze (proti generovanému PWM).


Obrázek c1 - sinus 2kHz ze 32 vzorků, 250kHz PWM, RC 10kOhm, 10nF. Fázový pouv lze pozorovat podle posunu minima střídy (v modrém průběhu) a minima sinu (žlutý průbhě)

Obrázek c1 - sinus 4kHz ze 32 vzorků, 250kHz PWM, RC 10kOhm, 10nF. Sledujte pokles amplitudy proti 2kHz

Obrázek c1 - sinus 8kHz ze 32 vzorků, 250kHz PWM, RC 10kOhm, 10nF.

Závěrem

Doufám, že jste získali jistý vhled do možností generování analogových průběhů pomocí PWM. A také doufám, že někoho z vás možnosti Attiny nadchnou a pokusí se vygenerovat z Atmelu třeba kus mluvené řeči. K připojení reproduktoru už chybí jen vhodná volba RC článku a nějáký jednoduchý audio zesilovač (třeba v podobě integrovaného obvodu). K vypočítání hodnot generovaného průběhu by měl určitě stačit i libovolný tabulkový editor. Otrlejší z vás můžou těch 32 vzorků napočítat i s pomocí kalkulačky :D

Odkazy

Home
V1.00 2015
By Michal Dudka (m.dudka@seznam.cz)