Přihlaste se a využijte web naplno RYBICKY.NET »

8-bit PWM na ATmega328

 

Články » 8-bit PWM na ATmega328   Vytisknout tuto stránku 

8-bit PWM na ATmega328

Publikováno: 31.10.2016 • Autor: © slavko • Rubrika: Osvětlení

Otázka jedného návštevníka akvaristického servera ohľadom PWM na Arduino ma primäla k tomu, čo som už veľmi dlho odkladal, a to práca s čítačmi na ATmega328P. Pôvodná otázka síce bola ohľadom 16-bit čítača Timer/Counter1 a smerovala na Arduino, ale možnosti 16-bit čítača sú rozsiahlejšie, a teda náročnejšie na pochopenie. Za vhodnejšie teda považujem vysvetliť princíp na čítači 8 bitovom.

Cieľom článku je vysvetliť princíp práce 8-bit čítača v režime PWM, popísať a vysvetliť jednotlivé nastavenia a na konci ukázať výsledky simulácie. Ak hľadáte článok, kde nájdete kompletný návod na zostrojenie riadenia ovládania osvetlenia akvária, tak tu ho nenájdete.

Čo to je PWM

Ak viete, čo táto skratka znamená, jednoducho túto časť preskočte. Ak ste však na tento článok narazili práve pri hľadaní informácií o význame skratky PWM, tak ju trochu vysvetlím. PWM znamená anglicky Pulse Width Modulation, čo zvyknem prekladať ako impulzne šírková modulácia.

Vo svete Arduino sú výstupy s podporou tejto modulácie trochu nešťastne označované ako analógové, no to nie je presné. Impulzne šírková modulácia je spôsob, ako v digitálnom svete, kde sa všetko točí len okolo jednotiek a núl (a nič medzi tým) plynulo riadiť veci, ako sú otáčky ventilátora, či v akvaristike čoraz obľúbenejšie, plynulé zhasínanie a rozsvecovanie svetla (a mnoho iných vecí).

Využíva sa pritom vecí ako je zotrvačnosť (pri motoroch), či nedokonalosť ľudských zmyslov (pri svetle). A celá podstata spočíva v tom, že napájanie svetla/motora (ďalej len svetlo) je v rýchlom slede zapínané a vypínané (teda striedajú sa jednotky a nuly).

Poznáte to od mala. Keď je napájanie zapnuté (jedna), svetlo svieti, keď je vypnuté (nula) svetlo nesvieti. Ak však toto zapínanie a vypínanie urobíte dostatočne rýchlo, ľudské oko nepostrehne, že svetlo zhaslo a rozsvietilo sa. Postrehne len, že svieti akosi slabšie. Rôznu intenzitu svetla potom možno dosiahnuť rôznou dobou, po ktorú je svetlo zapnuté a po ktorú je vypnuté, čo sa nazýva strieda (ang. duty) a zvykne sa udávať v percentách.

Ak je svetlo stále vypnuté, má striedu 0 % – teda svetlo nesvieti. Ak je stále zapnuté, má striedu 100 % – svetlo svieti naplno. Ak je rovnakú dobu zapnuté i vypnuté, je strieda 50 % a svetlo svieti polovičným jasom (čo ovšem ľudské oko vníma mierne inak).

Okrem striedy je pri PWM dôležitou vlastnosťou aj frekvencia, teda ako často sa striedanie jednotky a nuly opakuje. Táto frekvencia je dôležitá na to, aby sme (my ľudia) nevideli, že svetlo bliká, ale aby sme mali pocit zníženého jasu. Pri motoroch je zase dôležité, aby sme toto zapínanie a vypínanie nepočuli (a verte mi, že ten zvuk nie je príjemný ani pri malom ventilátore). Na dosiahnutie plynulého zvyšovania a znižovania jasu by mala stačiť frekvencia 25 Hz (pri motoroch 20 kHz), avšak pre istotu sa používa frekvencia vyššia. Nie však zase príliš vysoká, pretože potom nastávajú problémy so spínacími obvodmi. Na riadenie svetla tak úplne stačí frekvencia okolo 100 Hz, no a ventilátory v počítačoch používajú frekvenciu okolo 25 kHz.

Bitová algebra

Úspech Arduino spočíva (podľa mňa) oi. v tom, že skrýva mágiu práce s nastaveniami procesora (v tomto prípade čítačov) a poskytuje programátorom (alebo skôr „programátorom”) relatívne jednoduché rozhranie. Táto jednoduchosť je vyvážená jednak tým, že niektoré operácie trvajú dlhšie ako je nevyhnutne nutné, ale hlavne poskytuje len obmedzenú množinu skutočných možností.

Nastavenia procesora sú uložené v tzv. registroch. Register nie je nič iné, ako konkrétne miesto v pamäti. Každé toto miesto v pamäti má 1 (resp. 2) bajty, teda 8 (resp. 16) bitov a každý bit má svoj presne určený význam. Bit je najmenšia hodnota digitálneho sveta, ktorá môže nadobúdať len dve hodnoty, a to 1 (jedna) alebo 0 (nula) a na prácu s bitmi sa používajú tzv. bitové operácie, ktoré majú svoje vlastné bitové operátory.

Ako prvý spomeniem bitový posun. Je to operácia pri ktorej sú jednotlivé bity posúvané doľava alebo doprava a používa magický operátor << (vľavo), resp. >> (vpravo). V prípade práce s nastaveniami v registroch použijete väčšinou len bitový posun vľavo.

Ako najjednoduchší príklad zoberiem číslo 1, ktoré v binárnej forme vyzerá 0000 0001, a ktorého posledný (nultý) bit chcem posunúť o 5 vľavo:

x = (1 << 5);

Výsledkom bude 0010 0000 = 32, ak si to spočítate, jednotka je posunutá o päť pozícií vľavo. Pri programoch pre AVR, založených na AVR LibC sa stretnete aj so zápisom:

x = _BV(5);

Je to to isté, len je použité makro jazyka C, ktoré trochu sprehľadňuje zápis.

Výsledok by mal byť vždy 8-bitový, ak pri posune nejaké jednotky prídu za „okraj”, tak sú jednoducho zahodené, no a doplňované sú vždy 0.

Okrem bitových posunov, vstupujú do hry bitové logické funkcie: bitový súčet (OR – |), bitový súčin (AND - &) a bitová negácia (NOT – ~). Ich podstata je prostá, pri bitovom súčte je výsledok jedna, ak aspoň jeden bit (teda i viac) je jedna. Pri bitovom súčine je to obdobne, len výsledok je nula, ak aspoň jeden bit (alebo i viac) je nula. Pri bitovej negácii je vždy výsledok vždy opakom, tzn. čo bolo nula bude jedna a naopak. Vo všetkých prípadoch je to pekne bit po bite, nie číslo ako celok (preto bitové operácie).

A B A | B A & B ~ A ~ B
0 0 0 0 1 1
0 1 1 0 1 0
1 0 1 0 0 1
1 1 1 1 0 0

Práve tieto funkcie sa používajú na nastavenie jednotlivých bitov, napr. ak je potrebné nastaviť v poradí štvrtý bit premennej na jedna, stačí si pripraviť pomocou bitového posunu tzv. bitovú masku. Pretože nastavujem štvrtý bit, použijem posun vľavo o 4:

x = (1 << 4);      // = 0001 0000

Divíte sa, že píšem o štvrtom bite a výsledkom je piaty? No, celá veda spočíva v tom, že bity sa číslujú od nuly…

Predošlý príklad však nie len nastaví štvrtý bit, ale zároveň aj vymaže (nastaví 0) všetky ostatné, preto na zmenu konkrétneho bitu sa tento bitový posun kombinuje s bitovým súčtom (OR), aby ostatné bity ostali nezmenené:

x = x | (1 << 4);

Alebo to isté skrátene:

x |= (1 << 4);      // resp: x |= _BV(4);

Ak chcete tento bit vymazať (na 0), je potrebné spojiť viacero operácií, jednak je potrebné výsledok bitového posunu negovať a potom použiť bitový súčin:

x = x & ~(1 << 4);

Alebo znova skrátene:

x &= ~(1 << 4);     // resp: x &= ~(_BV(4));

Samozrejme, možno nastaviť alebo vymazať viac bitov naraz. Pri nastavovaní treba jednotlivé spočítať bitovým súčtom. Takže na nastavenie 4. a 6. bitu možno použiť:

x |= (1 << 4) | (1 << 6);

Pri mazaní treba negované hodnoty vynásobiť bitovým súčinom:

x &= ~(1 << 4) & ~(1 << 6);

Dôležité je uvedomiť si, že pomocou tejto mágie (operátory |= a &=) nemožno v jednom príkaze niektoré bity mazať a iné nastaviť, ale vždy možno buď len nastavovať (na 1) alebo len mazať (na 0).

V programoch je, pri práci s registrami, dobrým zvykom nepoužívať konkrétne číslo bitu, ale jeho symbolické pomenovanie, ktoré je v knižnici definované pre daný typ procesora. Takto síce vzniká pre neznalého nezrozumiteľný zápis, zato je takýto kód ľahko prenositeľný na iné typy procesorov (ľahko neznamená bezpracne). Potom sa ľahko stretnete so zápisom, napr.:

TCCR0B |= (1<<CS01) | (1<<CS00);

A hoci to vyzerá tak trochu ako zaklínadlo, už teraz by ste mali rozumieť, že sú v registri TCCR0B nastavované (na 1) bity CS01 a CS00, potom už len stačí nájsť v dokumentácii ich význam.

Čítače ATmega328P

Mikroradič ATmega328P, ktorý je srdcom (či skôr mozgom) populárnych vývojových dosiek Arduino, ponúka zabudovanú hardvérovú podporu impulzne šírkovej modulácie. Poskytuje celkom 6 vývodov s hardvérovou podporou PWM, pričom každý vývod môže v jednom okamžiku ponúkať inú striedu. Tieto vývody vždy tvoria dvojice (celkom tri) a každá táto dvojica je napojená na spoločný obvod, ktorým sa nastavuje frekvencia, tzn. oba vývody pracujú na rovnakej frekvencii, no každá dvojica môže mať frekvenciu inú.

Ak ste zvedaví, prečo čítač/časovač, tak je to len rôzne pomenovanie obdobnej funkcie. Časovač je obvod, ktorý v pravidelných intervaloch (teda akoby v zadanom čase) spúšťa požadovanú akciu. Naproti tomu čítač počíta (pripočítava alebo odpočítava) hodnotu počítadla a vlastne aj časovač využíva čítač. Ja budem používať pojem čítač, pretože pri PWM nie je cieľom v opakovanom intervale niečo vykonávať, ale len generovať signál.

Rozlíšenie čítačov

ATmega328P poskytuje tri čítače (0 – 2) a v dokumentácii sú anglicky nazývané timer/counter. Dva z nich sú 8-bitové a jeden je 16-bitový. Čo to znamená? No, 8-bitový čítač poskytuje 256 hodnôt striedy (0 – 255), zatiaľčo 16-bitový by mohol poskytovať až 65 536, no v skutočnosti poskytuje pre PWM max 10 bitov, tzn. 1 024 (0 – 1 023) hodnôt striedy, no i tak poskytuje oveľa jemnejšie riadenie jasu. Akvaristi sa vo všeobecnosti zhodujú, že 256 hodnôt je málo a skok o jednu hodnotu jasu nie je plynulý, ale zubatý, preto odporúčajú použitie 16-bitového čítača. Rozlíšenie PWM je teda počet hodnôt striedy, ktoré čítač poskytuje a úzko súvisí s počtom bitov čítača.

8-bitové čítače majú rozlíšenie pevné, 16 bitový má rozlíšenie voliteľné v rozsahu 8, 9 a 10 bitov (Arduino ho nastavuje do režimu 8 bitov). A aby to nebolo také jednoduché, každý z čítačov môže pracovať v režime nastaviteľnej maximálnej hodnoty, tá je však nutná v prípade potreby presnej frekvencie a spomeniem ju len okrajovo.

Delič čítača

Okrem rozlíšenia sa čítače líšia aj možnosťami nastavenia frekvencie. V každom z čítačov je k dispozícii obvod, ktorý odvodzuje frekvenciu čítača od frekvencie procesora a umožňuje použiť jej podiel a tento obvod sa nazýva delič. Čítače 0 a 1 poskytujú delič 1, 8, 64, 256 a 1 024, čítač 2 poskytuje okrem týchto dvoch hodnôt ešte aj 32 a 128. Tento delič, ako som spomínal, delí frekvenciu procesora a touto rýchlosťou z deliča potom čítač zvyšuje svoju hodnotu vždy o jedna (resp -1).

Ak teda procesor beží s frekvenciu (hodín) 16 MHz, a vy zvolíte napr. delič 64, bude čítač pripočítavať hodnotu rýchlosťou 16 MHz/ 64 = 250 kHz. 250 kHz (v tomto prípade) však nie je frekvencia PWM, je to frekvencia zvyšovania hodnoty čítača, ktorý počíta od nuly do maxima, takže frekvencia PWM závisí aj od počtu krokov čítača, ktorý udáva jeho maximum. Ak by to maximum bolo 255 (8-bit), bude frekvencia PWM 250 kHz / 256 = 976,5 Hz.

Čítače 0 a 1 môžu ako zdroj frekvencie používať aj vývod procesora (T0, resp T1), ale toto nie je, v prípade PWM veľmi užitočné a spomínam to len pre úplnosť.

Generovanie výstupu

Okrem deliča má čítač i obvod generovania výstupu, v prípade PWM sú k dispozícii dva režimy, tzv. neinvertujúci a invertujúci. Pri neinvertujúcom je minimálna strieda pri hodnote 0, pri invertujúcom je to naopak, minimálna strieda je na vrchole čítača (255, resp. 1 024). Analogicky, maximálna strieda je pri neinvertujúcom na vrchole, no pri invertujúcom pri hodnote 0.

Okrem toho je možné použiť aj režim prepínania výstupu, ale ten je jednak dostupný vždy len jednom výstupe čítača a jednak nie je na generovanie PWM veľmi užitočný a používa sa generovanie signálu so zadanou frekvenciou so striedou 50 %.

Režimy PWM

Ešte vládzete? Ešte stále to nie je všetko. Každý čítač poskytuje viacero režimov PWM. Všetky tri poskytujú tzv. Rýchle PWM (ang. Fast PWM) a Fázovo presné PWM (Phase Correct PWM), okrem toho čítač 1 poskytuje aj Fázovo a frekvenčne presné PWM (Phase and Frequency Correct PWM). Nechcem ísť celkom do podrobností, lebo sa bojím, že nakoniec napíšem telenovelu, ale zameriam sa na základný rozdiel.

Rýchle PWM pracuje v jednosmernom režime čítača, tzn. čítač postupne zvyšuje svoju hodnotu od nuly po maximum a potom začína znova od nuly, a tak dookola. Niekde medzi tým je výstup vždy prepnutý (podľa nastavenia), čím vytvára striedu.

Fázovo (a frekvenčne) presné PWM používa čítača v obojsmernom režime, tzn. čítač počíta od nuly po vrchol a potom od vrcholu k nule, a tak dookola. Ak to z môjho popisu nevyplynulo, každý kolobeh čítača trvá dva krát tak dlho ako pri Rýchlom PWM, pretože raz počíta smerom hore a potom smerom dole, takže pri rovnakom nastavení deliča poskytuje Fázovo presné PWM polovičnú frekvenciu oproti Rýchlemu PWM.

8-bitové čítače

Po (nie celkom) krátkom teoretickom úvode nastal čas presunúť sa na konkrétne príklady nastavenia PWM na 8-bit čítačoch ATmega328P. Ako som už spomenul, ATmega328P má dva 8-bit čítače, a to čítač 0 a čítač 2. Ich možnosti sú si veľmi podobné a jediný dôvod prečo ich budem miešať oba, je ten, že používatelia Arduino by na nastavenia čítača 0 nemali radšej siahať, pretože si rozhodia (pokazia, zmenia, atď) časovanie pomocou, v Arduino tak obľúbených, funkcií delay() a millis() a pre nich je k dispozícii čítač 2 (pozor, ten zase v Arduino používa knižnica tone a viacero ďalších).

V popise to má za následok, že číslo čítača v názve registrov, či ich bitov, nahradzuje písmenom n, no v programe je to nutné zadať presne, tzn. 0 alebo 2!

Registre

Čítače (ostatne celý procesor) používa tzv. registre, v ktorých má uložené nastavenie alebo do nich ukladá informáciu o svojej činnosti. V prípade 8-bit čítačov sa jedná o registre:

Counter0 Counter2 Názov
TCCR0A TCCR2A Timer/Counter Control Register A
TCCR0B TCCR2B Timer/Counter Control Register B
TCNT0 TCNT2 Timer/Counter Register
OCR0A OCR2A Output Compare Register A
OCR0B OCR2B Output Compare Register B

Samotné nastavenia sú uložené v registroch TCCRnA a TCCRnB (n – číslo čítača). Ako som spomenul, každý bit týchto registrov má svoj presne daný význam, tak najprv symbolické pomenovanie jednotlivých bitov:

TCCRnA – Timer/Counter0 a 2 Control Register A
Bit COMnA1
7
COMnA0
6
COMnB1
5
COMnB0
4

3

2
WGMn1
1
WGMn0
0
Čítanie/Zápis R/W R/W R/W R/W R R R/W R/W
Poč. hodnota 0 0 0 0 0 0 0 0
TCCRnB – Timer/Counter0 a 2 Control Register B
Bit FOCnA
7
FOCnB
6

5

4
WGMn2
3
CSn2
2
CSn1
1
CSn0
0
Čítanie/Zápis R/W R/W R R R/W R/W R/W R/W
Poč. hodnota 0 0 0 0 0 0 0 0

Bity týchto registrov sú pomenované tak, aby bolo na prvý pohľad vidno, ktoré patria k sebe:

COMnA[1:0]
Slúžia na nastavenie režimu výstupu OCRnA
COMnB[1:0]
Slúžia na nastavenie režimu výstupu OCRnB
FOCnA a FOCnB
Slúžia na vynútenie stavu OCRnA a OCRnB
WGMn[2:0]
Slúžia na nastavenie režimu čítača – všimnite si, že sú rozdelené medzi oba registre
CSn[2:0]
Slúžia na nastavenie deliča čítača

Register TCNTn uchováva hodnotu čítača a registre OCRnA/OCRnB zase uchovávajú (v prípade PWM) hodnotu striedy, teda kedy má byť výstup prepnutý z 0 na 1 (resp. naopak). Prečo sú dva? No, na začiatku som spomenul, že výstupy PWM tvoria dvojice (vždy napojené na rovnaký čítač), takže jeden register riadi striedu PWM na jednom výstupe a druhý na druhom.

Čítače používajú ešte aj registre TIMSKn a TIFRn. Tieto slúžia na prácu s prerušeniami, ktoré v prípade PWM nemajú veľké využitie, a tak ich v tabuľke ani neuvádzam.

Okrem registrov čítača vstupuje ešte do hry register smeru vývodov DDRX:

DDRx – The Port B a D Data Direction Register
Bit PX7
7
PX6
6
PX5
5
PX4
4
PX3
3
PX2
2
PX1
1
PX0
0
Čítanie/Zápis R/W R/W R/W R/W R/W R/W R/W R/W
Poč. hodnota 0 0 0 0 0 0 0 0

Nastavenie bitu (na 1) udáva, že príslušný vývod je výstup a naopak, jeho vymazanie (na 0) nastavuje príslušný vývod ako vstup. Aby vývody pracovali v režime PWM, je nutnou podmienkou ich nastavenie do režimu výstup.

Vývody PWM

F

Ako posledné v tejto časti mi ostáva prezradiť, kde vývody PWM hľadať. Vývody procesora majú väčšinou viac funkcií, a ktorá z nich je použitá, to závisí na nastavení. Ktoré sú to vývody? No, je to presne dané a závisí to na konkrétnom type procesora.

Pripravil som na to obrázok s popisom vývodov ATmega328P, kde môžete vidieť číselné označenie vývodov Arduino (hnedé), označenie vývodov z dokumentácie (a tiež knižnice AVR LibC, ktorú Arduino na pozadí používa) a priradenie funkcie PWM – každý čítač inou farbou. Sú to vývody s textom OCnX, kde číslo n udáva číslo čítača, a písmeno X udáva kanál PWM daného čítača (A/B):

Ako sami vidíte, v prípade 8-bit čítačov sa jedná o vývody (v zátvorke číslo digitálneho vývodu v Arduino):

Čítač OCnA OCnB
Čítač 0 PD6 (6) PD5 (5)
Čítač 2 PB3 (11) PD3 (3)

Rýchle PWM

Prvý z režimov PWM 8-bit čítačov je Rýchle PWM (Fast PWM). Čítače poskytujú dve formy toho režimu, pričom v oboch čítač ráta od nuly po vrchol, potom skočí späť na nulu, a tak dookola. Dve formy Rýchleho PWM sa líšia v hodnote vrcholu:

Rýchle PWM s premenlivým vrcholom je vhodné tam, kde je potrebné presnejšie doladenie frekvencie PWM alebo tam, kde je potrebná premenlivá frekvencia PWM, čo ani jedno nie je prípad ovládania jasu osvetlenia, preto ostanem pri prvom, tzn. pri PWM s pevným vrcholom v maxime.

Mimochodom, pri premenlivom vrchole je možné nastavovať striedu len jedného výstupu, pretože register OCRnA neuchováva striedu, ale hodnotu vrcholu.

Postup nastavenia Rýchleho PWM je vlastne prostý:

  1. nastaviť režim čítača
  2. nastaviť vývody do režimu výstup
  3. nastaviť režim výstupu
  4. nastaviť hodnotu deliča

Ono vlastne poradie týchto krokov až také dôležité nie je, len si treba uvedomiť, že nastavením deliča na inú hodnotu ako 0 čítač (a teda aj PWM) spúšťa, preto je vhodné aby to bol posledný krok.

Pamätáte, ako som písal, že bity registrov (teda premenných všeobecne) sa dajú v jednom kroku len nastavovať alebo len mazať? Toto môže spôsobovať problém, ak už v registri bola pred tým nejaká hodnota, pretože nemusí byť nová hodnota zapísaná správne. Najmä začiatočníkom to môže spôsobiť problém, a tak zvyknem na začiatku nastavenia úplne vymazať, aby som začínal s čistým stolom:

// vymazať nastavenia čítača 0
TCCR0A = 0;
TCCR0B = 0;

Režim čítača

Ako som spomínal, na výber sú dva režimy a nastavujú sa bitmi WGMn[2:0]. V prípade Rýchleho PWM sú k dispozícii spomínané dve možnosti, a to režim 3 a režim 7:

Režimy Čítač 0 a Čítač 2
Režim WGMn2 WGMn1 WGMn0 Timer 0 Timer 2
3 0 1 1 Fast PWM (MAX) Fast PWM (MAX)
7 1 1 1 Fast PWM (OCRA) Fast PWM (OCRA)

Pretože na riadenie osvetlenia úplne postačí režim s pevným vrcholom, nastavím režim 3, tzn. potrebujem nastaviť (na 1) bity WGMn0 a WGMn1 registra TCCR0A:

// nastaviť režim Rýchle PWM s maximom
TCCR0A |= (1<<WGM01) | (1<<WGM00);

Režim vývodov

Interným zapojením je zaistené, že vývod bude v režime PWM len ak je nastavený ako výstup. Takže je nutné nastaviť vývody čítača 0 (PD5 a PD6) do režimu výstup, tzn potrebujem nastaviť (na 1) bity PD6 a PD5 registra DDRD:

// nastaviť vývody PWM ako výstupy
DDRD |= (1<<PD6) | (1<<PD5);

Režim výstupu

Ďalším krokom je nastavenie režimu výstupu čítača. Toto nastavenie je uložené v bitoch COMnA[1:0] pre výstup PD6/PB3 a v bitoch COMnB[1:0] pre výstup PD5/PD3. V prípade Rýchleho PWM na riadenie osvetlenia možno vybrať vlastne len z dvoch možností:

OCnA
COMnX1 COMnX0 Rýchle PWM
1 0
  • vymaže OCnX pri zhode,
  • nastaví OCnX pri nule
1 1
  • nastaví OCnX pri zhode,
  • vymaže OCnX pri nule

Ak sú oba bity nula je PWM z vývodov odpojené a vývody možno používať ako bežné digitálne výstupy. Ostatné dve možnosti vyberajú spomínaný neinvertujúci a invertujúci režim. Aby som demonštroval rozdiel medzi nimi, nastavím jeden vývod (OC0A/PD6) do neinvertujúceho režimu a druhý (OC0B/PD5) do režimu invertujúceho. Takže potrebujem nastaviť (na 1) bity COM0A1, COM0B1 a COM0B0 registra TCCR0A:

// nastaviť režim výstupov
TCCR0A |= (1<<COM0A1); // neinvertujúci pre OC0A
TCCR0A |= (1<<COM0B1) | (1<<COM0B0); // invertujúci pre OC0B

Nastavenie deliča

Nakoniec som si nechal nastavenie deliča, ktorý definuje frekvenciu PWM. Jeho nastavenie je v bitoch CSn[2:1] registra TCCRnB a tu sa oba 8-bit čítače trochu líšia. Čítač 0 poskytuje možnosť použitia zdroja frekvencie z externého výstupu, zatiaľčo čítač 2 nie a namiesto toho poskytuje ďalšie možnosti delenia frekvencie hodín procesora:

Nastavenie deliča
CSx2 CSx1 CSx0 Čítač 0 Čítač 2
0 0 0 zastavený zastavený
0 0 1 1/1 1/1
0 1 0 1/8 1/8
0 1 1 1/64 1/32
1 0 0 1/256 1/64
1 0 1 1/1 024 1/128
1 1 0 T0 failling 1/256
1 1 1 T0 rising 1/1 024

Akú hodnotu deliča zvoliť? Frekvencia PWM je daná vzťahom:

F

TOP som zvolil na maxime (tj. 255), frekvenciu procesora (FCPU) predpokladám 16 MHz, takže frekvencie vychádzajú takto:

Delič Frekv. PWM
1 62,75 kHz
8 7,84 kHz
32 1,95 kHz
64 976,5 Hz
128 488,2 Hz
256 244 Hz
1 024 61 Hz

Na začiatku som spomenul, že čokoľvek nad 25 Hz stačí, takže možno pokojne použiť ľubovoľnú hodnotu deliča, ja som zvolil delič 1/64, ktorý by mi mal poskytnúť výslednú frekvenciu necelý 1 kHz. V praxi však budete voliť frekvenciu tak, aby s ňou obvod dokázal pracovať. Ja mám napr. prúdový zdroj, ktorý dokáže pracovať s frekvenciou PWM len do 300 Hz, takže by som musel zvoliť delič 1/256.

Takže, na nastavenie zvoleného deliča (64) potrebujem nastaviť (na 1) bity CS01 a CS00 registra TCCR0B:

// nastavenie deliča na 64
TCCR0B |= (1<<CS01) | (1<<CS00);

Pozor, ak si chcete nastavenie prispôsobiť na čítač 2, delič 64 má inú kombináciu bitov!

Nastavenie striedy

Posledným krokom je nastavenie striedy, avšak tá toto už nie je ani tak krokom nastavenia čítača, ale krokom nastavenia výstupu. Strieda sa nastavuje v registri OCRnA, resp. OCRnB, tzn. pre každý vývod čítača samostatne. Tu už nevstupuje žiadna mágia a stačí jednoducho priradiť číselnú hodnotu, a to dokonca i desiatkovo.

Aby som videl, či to funguje, nastavenie striedy som urobil v cykle, kde sú postupne nastavované hodnoty od 0, a to po 25. Pretože takto nedosiahnem maximálnu hodnotu (255), po skončení cyklu nastavujem natvrdo hodnotu striedy na 255. Aby bol vidieť rozdiel medzi invertujúcim a neinvertujúcim režimom, nastavuje v cykle rovnakú striedu pre oba kanály čítača. Po každom nastavení som pridal krátku pauzu, aby bolo vidno niekoľko rovnakých priebehov PWM za sebou.

Takže samotné nastavovanie:

for (uint8_t i=0;i<250;i+=25)
{
OCR0A = i;
OCR0B = i;
_delay_ms(DELAY);
}

OCR0A = 255;
OCR0B = 255;
_delay_ms(DELAY);

Celý program

Celé som to neprogramoval v prostredí Arduino, ale použil som čisté C (ako som spomenul, práca s čítačom 0 v Arduino je problematická). Celý program potom vyzerá takto:

#ifndef F_CPU
#define F_CPU 16000000UL // CPU frequency
#endif

#include < avr/io.h >
#include < util/delay.h >

#define DELAY 10

int main(void)
{
// reset timer settings
TCCR0A = 0;
TCCR0B = 0;

// set PWM pin as output
DDRD |= (1<<PD6) | (1<<PD5);

// set timer mode Fast PWM
TCCR0A |= (1<<WGM01) | (1<<WGM00); // Fast PWM with TOP=MAX

// set output mode
TCCR0A |= (1<<COM0A1); // non-inverting mode for OC0A
TCCR0A |= (1<<COM0B1) | (1<<COM0B0); // inverting mode for OC0B

// set prescaller
TCCR0B |= (1<<CS01) | (1<<CS00); // 64

// main loop
while (1)
{
for (uint8_t i=0;i<250;i+=25)
{
OCR0A = i;
OCR0B = i;
_delay_ms(DELAY);
}

OCR0A = 255;
OCR0B = 255;
_delay_ms(DELAY);
}
}

Ak to chcete vyskúšať priamo z Arduino, jednoducho zmeňte nastavenia tak, aby ste používali čítač 2, to čo je vo vnútri cyklu while(1) dajte do funkcie loop(), a to čo je pred cyklom while(1) dajte do funckie setup(). No a, samozrejme, to čo je pred funkciou main() dajte na začiatok súboru .ino.

Program som zostavil pomocou avr-gcc a prehnal ho simulátorom simulavr, z ktorého som výstup zobrazil pomocou programu gtkwave, no a výsledok je tu pre vás:

F

Rýchle PWM

Na obrázku môžete vidieť ako postupne rastie hodnota striedy (OCRX) i ako sa mení priebeh na jednotlivých výstupoch v závislosti na zvolenom režime výstupu (D6 – neinvertujúci, D5 – invertujúci). Za zmienku stoja hraničné stavy, kedy je strieda nastavená na 0, resp. 255, ale k tomu sa ešte vrátim.

Fázovo presné PWM

Druhý z režimov PWM 8-bit čítača je Fázovo presné PWM (Phase Correct PWM), ktorý sa od Rýchleho PWM líši v obojsmernom počítaní čítača. I tento režim je dostupný v dvoch formách, a v oboch čítač ráta najprv od nuly po vrchol a potom od vrcholu späť k nule, a tak dookola. Dve formy Fázovo presného PWM sa opäť líšia tým, aký je ich vrchol:

I v tomto prípade stačí na riadenie osvetlenie forma s pevným vrcholom v maxime.

Pretože celé nastavenie je vlastne rovnaké, líšia sa len niektoré bity, popíšem postup v skrátenej forme, kde už nebudem všetko podrobne vysvetľovať.

Postup nastavenia Fázovo presného PWM je rovnaký:

  1. nastaviť režim čítača
  2. nastaviť vývody do režimu výstup
  3. nastaviť režim výstupu
  4. nastaviť hodnotu deliča

I tentokrát je dobrý nápad najprv vymazať nastavenia:

// vymazať nastavenia čítača 0
TCCR0A = 0;
TCCR0B = 0;
DDRD = 0;

Režim čítača

Ako som spomínal, na výber sú dva režimy a i v tomto režime PWM sa nastavujú bitmi WGMn[2:0]. V prípade Rýchleho PWM sú k dispozícii spomínané dve možnosti, a to režim 3 a režim 7:

Režimy Timer 0 a Timer 2
Režim WGMn2 WGMn1 WGMn0 Timer 0 Timer 2
1 0 0 1 Phase Corr. PWM (MAX) Phase Corr. PWM (MAX)
5 1 0 1 Phase Corr. PWM (OCRA) Phase Corr. PWM (OCRA)

Pretože na riadenie osvetlenia úplne postačí režim s pevným vrcholom, nastavím režim 1, tzn. potrebujem nastaviť (na 1) bit WGMn0 registra TCCR0A:

// nastaviť režim Fázovo presné PWM s maximom
TCCR0A |= (1<<WGM00);

Režim vývodov

Nastavenie vývodov je rovnaké, tzn. treba nastaviť vývody čítača 0 (PD5 a PD6) do režimu výstup, tzn. nastaviť (na 1) bity PD5 a PD6 registra DDRD:

// nastaviť vývody PWM ako výstupy
DDRD |= (1<<PD6) | (1<<PD5);

Režim výstupu

Ďalším krokom je nastavenie režimu výstupu čítača. Toto nastavenie je uložené v bitoch COMnA[1:0] pre výstup PD6/PB3 a v bitoch COMnB[1:0] pre výstup PD5/PD3. I v prípade Fázovo presného PWM na riadenie osvetlenia možno vybrať vlastne len z dvoch možností:

OCnA
COMnX1 COMnX0 Phase Correct PWM
1 0
  • vymaže OCnA pri zhode smerom hore,
  • nastaví OCnA pri zhode smerom dole
1 1
  • nastaví OCnA pri zhode smerom hore,
  • vymaže OCnA pri zhode smerom dole

I v tomto režime, ak sú oba bity nula je PWM z vývodov odpojené a vývody možno používať ako bežné digitálne výstupy. A znova je k dispozícii možnosť vybrať si medzi neinvertujúcim a invertujúcim režimom. Rozdiel je v tom, kedy dochádza k prepnutiu vývodu. Vždy sa vývody prepínajú pri zhode so striedou, no raz pri ceste hore (čítanie od nula po vrchol) a druhý krát pri ceste dolu (pri čítaní od vrcholu k nule).

Znova nastavím jeden vývod (OC0A/PD6) do neinvertujúceho režimu a druhý (OC0B/PD5) do režimu invertujúceho:

// nastaviť režim výstupov
TCCR0A |= (1<<COM0A1); // neinvertujúci pre OC0A
TCCR0A |= (1<<COM0B1) | (1<<COM0B0); // invertujúci pre OC0B

Nastavenie deliča

nastavenie deliča je úplne rovnaké ako pri Rýchlom PWM, rozdiel je však vo výslednej frekvencii PWM. Ten rozdiel je založený na tom, že čítač pracuje obojsmerne, takže v jednom cykle počíta najprv hore a potom dole, takže výsledná frekvencia PWM je približne polovičná:

F

Dostupné frekvencie pre 16 MHz teda sú:

Delič Frekv. PWM
1 31,3 kHz
8 3,9 kHz
32 980 Hz
64 490 Hz
128 245 Hz
256 122,5 Hz
1 024 30,6 Hz

Napriek tomu, že sú frekvencie polovičné, na riadenia osvetlenia i tak postačia všetky, preto znova volím delič 64. Na jeho nastavenie potrebujem nastaviť (na 1) bity CS01 a CS00:

// nastavenie deliča na 64
TCCR0B |= (1<<CS01) | (1<<CS00);

Nastavenie striedy

Nastavenie striedy je úplne rovnaké ako pri Rýchlom PWM:

for (uint8_t i=0;i<250;i+=25)
{
OCR0A = i;
OCR0B = i;
_delay_ms(DELAY);
}

OCR0A = 255;
OCR0B = 255;
_delay_ms(DELAY);

Celý program

Program som zostavil úplne rovnako, tentokrát ako celok vyzerá takto:

#ifndef F_CPU
#define F_CPU 16000000UL
#endif

#include < avr/io.h >
#include < util/delay.h >

#define DELAY 10

int main(void)
{
// reset timer settings
TCCR0A = 0;
TCCR0B = 0;

// set PWM pin as output
DDRD |= (1<<PD6) | (1<<PD5);

// set timer mode Phase Correct PWM
TCCR0A |= (1<<WGM00); // Phase Correct PWM with TOP=MAX

// set output mode
TCCR0A |= (1<<COM0A1); // non-inverting mode for OCR0A
TCCR0A |= (1<<COM0B1) | (1<<COM0B0); // inverting mode for OCR0B

// set prescaller
TCCR0B |= (1<<CS01) | (1<<CS00); // 64

// main loop
while (1)
{
for (uint8_t i=0;i<250;i += 25)
{
OCR0A = i;
OCR0B = i;
_delay_ms(DELAY);
}

OCR0A = 255;
OCR0B = 255;
_delay_ms(DELAY);
}
}

A úplne rovnako som i vygeneroval a pripravil priebeh zmeny výstupov:

F

Fázovo presné PWM

Na prvý pohľad môžete vidieť, že signál PWM má nižšiu frekvenciu, pretože vidno menej zmien stavov na každú zmenu striedy (OCRA/OCRB).

Hraničné stavy striedy

Na záver článku sa vrátim k pôvodnej otázke, v ktorej sa autor pýtal, prečo nemá LEDky celkom zhasnuté, keď nastavil striedu na 0. Odpovede sa rôznili, ale nakoniec vyplynulo, že problém bol medzi klávesnicou a stoličkou – pýtajúci sa priznal, že to takto dopadne, ak niekto použije cudzí kód, bez toho aby mu rozumel…

Asi každý bude očakávať, že pri striede 0 (= 0 %) budú LED zhasnuté, a naopak pri maximálnej striede 255 (= 100 %) budú LED svietiť na plný výkon. Realita však môže byť trochu iná a závisí na zvolenom režime PWM (a je mimochodom popísaná v dokumentácii, hoc trochu nejasne pre maximum).

Pozrite sa na obrázok, kde som zvýraznil hraničné hodnoty striedy (0 a 255). Na vysvetlenie, vidíte pod sebou dva priebehy, horná časť obrázku je pri Rýchlom PWM a dolná pri Fázovo presnom PWM. Žltým obdĺžnikom sú orámované hraničné stavy (0 a 255). V každom priebehu sú dva vývody (D5 a D6) a ich príslušné registre striedy (OCRA a OCRB). V riadku registra striedy vidíte hodnotu striedy. Vývod D6 je v neinvertujúcom režime (tzn. nižšie číslo v OCRA, nižšia strieda signálu) a vývod D5 je v invertujúcom (tzn. nižšie číslov OCRA, vyššia strieda signálu).

Určite i sami vidíte, že na hornom obrázku (Rýchle PWM) je výsledok akýsi divný. Pri minimálnej striede by mal byť vývod D6 (neinvertujúci) trvalo v 0, ale nie je a vidno krátke špičky (s najväčšou pravdepodobnosťou práve tieto mali za následok to slabé svietenie LED v pôvodnej otázke). Ešte zaujímavejšie to je pri maximálnej striede (255). Tu možno očakávať signál trvalo v 1, aby LED svietili maximálnym jasom, ale ako vidno, je to presne naopak a LED nebude svietiť vôbec! Arduino (pri použití funkcie analogWrite() to rieši interne tak, že pri týchto hraničných stavoch vypne na vývode PWM a výstup natvrdo nastaví do stavu 1 resp. 0, a to môže niektorých „vývojárov” prekvapiť.

F

Rozdiel hraničných hodnôt

Na dolnom obrázku (Fázovo presné PWM) je situácia iná a zodpovedá očakávaniam, tzn. pri hodnote 0 je aj strieda signálu nulová a pri hodnote 255 je strieda maximálna (100 %).

Za zmienku ešte určite stojí to, že zmena striedy nenastáva hneď po zmene hodnoty OCRA/B, ale až po dokončení cyklu čítača, teda až po tom, ako dopočíta späť k nule smerom dole – a to sú tie opačné stavy na začiatku hraničných stavov, s tým sa veľa urobiť nedá.

Záver

Čo dodať na záver? Som naozaj vďačný autorovi pôvodnej otázky, pretože ma primäl k tomu, aby som vyskúšal simulovať PWM a vygenerovať jeho výstupný signál. Nikdy som to predtým neskúšal, pretože som si z nejakého dôvodu myslel, že to simulátor nedokáže, resp. že dokáže poskytovať len výstup digitálnych vývodov. Možno to bol dôsledok môjho sklamania, že simulátor nepodporuje časovač watchdog.

Pretože mi na overenie výsledkov pokusov netreba osciloskop, možno sa v budúcnosti odhodlám takto popísať aj 16-bit čítač.

A ešte, aby som nezabudol, miestny redakčný systém ma prinútil pridať medzery okolo zobáčikov v príkazoch „#include ...”, tak ak chcete kód kopírovať, opravte si to. A mimochodom, kód môžete voľne použiť...



 

Za správnost informací zodpovídá autor článku, dotazy směřujte na autora. Hodnocení článku hvězdičkami provádí redakce. K článku se vyjádřete pomocí palců (líbilo se / nelíbilo se).

Hodnocení
*****

Líbilo se: 10x Nelíbilo se: 1x Zveřejněno: 31.10.2016 Upraveno: 31.10.2016 Přečteno: 506x

Schválili: slavko *** 02.11.16 • vaaclav *** 02.11.16 • romant *** 03.11.16

Související články
09.11.2014*****Univerzální ovládání osvětlení1362x
Další články z rubriky Osvětlení
02.08.2012*****Výroba LED osvětlení pod vodu856x
16.12.2010*****Noční LED osvětlení6005x
27.11.2015*****Instalace druhé zářivky do setu PACIFIC 54L1429x
13.10.2012*****Kryt na akvárium s LED osvětlením2147x
17.12.2010*****Levný „provizorní“ kryt2687x
16.09.2011*****LED osvětlení a jeho regulace2176x
29.12.2013*****LED osvětlení akvária s čipy CREE3796x
04.09.2012*****Odvětrání vzduchu pod krytem1009x
01.08.2009*****Výroba světelné rampy5476x
05.04.2015*****LED osvětlení MAXXO2766x

Komentáře návštěvníků

x Funkce je dostupná pouze pro přihlášené uživatele

Další články tohoto autora
Žádné další články



© RYBICKY.NET - https://rybicky.net/clanky/1605-8bit-pwm-na-atmega328