Fórum témák

» Több friss téma
Cikkek » Launchpad: ismerkedés az MSP430 mikrovezérlőkkel II.
Launchpad: ismerkedés az MSP430 mikrovezérlőkkel II.
Szerző: icserny, idő: Nov 3, 2011, Olvasva: 22573, Oldal olvasási idő: kb. 17 perc
Lapozás: OK   8 / 8

Az impulzus-szélesség moduláció (PWM) alapjai

A mikrovezérlők alkalmazásainál gyakran előforduló feladat, hogy hogy valamilyen mennyiséget (pl. kimenő feszültséget, vagy egy fogyasztó teljesítményét) folyamatosan vagy fokozatosan kell szabályozni. Mivel a Launchpad-hoz kapott mikrovezérlőinken nincs analóg kimenet, ezért élnünk kell azzal a lehetőséggel, hogy analóg feszültségjelei (teljesítményjelei) helyettesíthetők digitális impulzussorozat-jelekkel, amelyek hosszabb időtartamra vonatkoztatott átlagfeszültsége egyenértékű az analóg feszültségjellel. A digitális impulzussorozat frekvenciáját úgy kell (elegendően nagyra) megválasztani, hogy az, a vezérelt vagy szabályozott eszköz megfelelő működését biztosítsa. Például a szabályozott fényforrás folyamatos működésűnek látsszon (a szemünk ne vegyen észre villogást), vagy egy egyenáramú motor ne lökésszerűen változó szögsebességgel forogjon.

A digitális jelsorozattal történő vezérlés egyik jól használható változatai az impulzus-szélesség moduláció (PWM = Pulse Width Modulation), amelynél olyan állandó periódusidejű (és frekvenciájú) jeleket keltünk, ahol az a szabályozás a jel kitöltési tényezőjének változtatásával történik. Néhány különböző kitöltési tényezőjű PWM jelet mutat be a következő ábra:

8_1. ábra: Azonos periódusú, de különböző kitöltésű impulzus-sorozatok
(az ábrát John Davies "MSP430 Microcontroller Basics" c. könyvéből kölcsönöztük)

A fentihez hasonló jeleket szoftveresen is könnyű előállítani, de a Timer_A egység Capture/Compare csatornáinak kombinált alkalmazásával hardveres támogatást is kapunk hozzá, ami számunkra azt jelenti, hogy a CPU szabad az egyéb feladatok végrehajtására, a jelek időzítése pedig pontosabb.

PWM jelek előállításához a Timer-t "fölfelé számlálás adott értékig" módban használjuk (MCx = 1), s a kívánt értéket a TACCR0 regiszterbe írjuk. Ez az érték, és a bemenő jel frekvenciája szabja meg a a PWM jel periódusát, illetve frekvenciáját.

f_{PWM} = f_{SMCLK} / {TACCR0 + 1}

A 0. Capture/Compare csatorna kimenete - a TACCR0 regiszter fentebb vázolt speciális szerepénél fogva - csak igen korlátozottan használható PWM-hez, pl. átbillentés módban (OUTMODx = 4, toggle mode) állandóan 50 %-os kitöltéssel rendelkező jelet kelthetünk vele.

Az 1. Capture/Compare csatorna azonban többféle hasznos üzemmóddal is rendelkezik. Például reset/set módban (OUTMODx = 7) a TACCR1 regiszterbe írt szám elérésekor a TA1 kimenet törlődik (alacsony szintre vált), a számláló túlcsordulásakor pedig '1'-be áll. A kitöltési arányt a TACCR1 regiszterbe írt számnak és és TACCR0 + 1-nek a hányadosa adja meg. Egy másik lehetőség a set/reset (OUTMODx = 2) üzemmód használata, ami a 7-es módban előállított jelnek az inverzét állítja elő.

Az alábbi ábrán egy konkrét példán keresztül mutatjuk be az egyes csatornák megszakítási és kimenő jeleinek alakulását a számlálás előrehaladásának függvényében. A számláló modul itt "fölfelé számlálás egy edott értékig" (MCx=1) módban van.

A TAR jelzésű sor mutatja a számláló regiszter állapotát, TAIFG pedig a számláló túlcsordulását jelző bit bebillenésének helyét (amikor a számláló a maximális értékről nullára vált). CCIFG0 és CCIFG1 a Capture/Compare csatornák megszakításjelző bitjei.

Amint látható, a CCIFG0 bit a TACCR0 regiszterben megadott szám (az ábrán ez az érték 4) elérésekor billen be, tehát egy óraütéssel hamarabb, mint TAIFG. A CCIFG1 bit pedig akkor billen '1'-be, amikor a számláló eléri a TACCR1 regiszterben megadott számot (az ábrán ez az érték 2).

8_2. ábra: A Capture/Compare csatornák kimenő jelei
(az ábrát John Davies "MSP430 Microcontroller Basics" c. könyvéből kölcsönöztük)

Az 1. Capture/Compare csatorna kimenő jelét az OUT1 képviseli. mely  a leggyakrabban használt kétféle mód (Reset/Set és Set/Reset) esetére az ábrán látható módon alakul: OUTMODx=7 beállítás esetén a TAIFG bebillenésekor OUT1 magas szintre vált, a TACCR1-be írt szám elérésekor pedig törlődik. A Set/Reset módban (OUTMODx=3) ez pont fordítva történik, tehát a kimenő jel az előző eset inverze lesz.

A 0. Capture/Compare csatorna kimenete OUT0, ennek jelakja a már említett Toggle módban egy 50 %-os kitöltésű négyszögjel. A kimenet ugyanis minden periódusban ellenkezőjére változik, így ugyanannyi ideig van bekapcsolt mint kikapcsolt állapotban.

Természesen a fentieken kívül sokféle üzemmód és használati mód megvalósítható, melyek részletese megtárgyalása meghaladja ennek a cikknek a lehetőségeit. Az alábbiakban csak néhány egyszerű példát mutatunk be, amelyek talán segítenek a leggyakrabban használt módok megértésében.

Szoftveres PWM

Az alábbi programban a Timer egység nélkül, csupán szoftveres időzítésekkel állítunk elő időben változó kitöltésű négyszögjelet. A programban egyébként LED1 fényerejét változtatjuk folyamatosan, mintha a LED "lélegezne". A CPU órajele MCLK = 1 MHz lesz. 66,67 Hz PWM frekvenciát választva periódusonként 15 000 ciklus esik (150 db 0,1 ms-os óraütés), ez a bekapcsolt és kikapcsolt állapot összideje. A LED kb. 1,5 s-os félperiódusa alatt 100 lépésben lineárisan növekvő kitöltést adunk meg, a következő 100 periódusban pedig csökkenő kitöltéssel lépkedünk vissza.

8_1. lista: a pwm_soft program listája

  1. #include "io430.h"
  2. #include "stdint.h"
  3.  
  4. //--- Késleltető eljárás. A késleltetést 100 us egységben kell megadni.
  5. void delay_100us(uint16_t delay_data) {
  6.   while(delay_data--) __delay_cycles(80);
  7. }
  8.  
  9. int main(void) {
  10. int i;  
  11.   WDTCTL = WDTPW|WDTHOLD;    // Letiltjuk a watchdog időzítőt
  12.   BCSCTL1= CALBC1_1MHZ;      // A gyárilag kalibrált 1 MHz
  13.   DCOCTL= CALDCO_1MHZ;       // DCO frekvencia beállítása
  14.   P1DIR |= BIT0;             // P1.0 (LED1) kimenet legyen  
  15.  
  16.   while(1) {
  17.     for (i=1; i<100; i++) {
  18.        P1OUT |= BIT0;        //LED1 bekapcsolása
  19.        delay_100us(i);       //i-vel arányos idejű késleltetés
  20.        P1OUT &= ~BIT0;       //LED1 kikapcsolása
  21.        delay_100us(150-i);   //Kiegészítő idejű késleltetés
  22.     }
  23.     for (i=1; i<100; i++) {
  24.        P1OUT |= BIT0;        //LED1 bekapcsolása
  25.        delay_100us(100-i);   //i-vel arányos idejű késleltetés
  26.        P1OUT &= ~BIT0;       //LED1 kikapcsolása
  27.        delay_100us(50+i);    //Kiegészítő idejű késleltetés
  28.     }
  29.   }
  30. }

A program elején definiálnunk kell egy késleltető eljárást, ugyanis a __delay_cycles() beépített függvényt csak konstans paraméterrel hívhatjuk meg (vagy fordítási időben kiértékelhető kifejezéssel), változóval nem. A késleltető eljárásban azzal trükközünk, hogy egy for ciklust szerveztünk, melynek ciklusmagja egy fix idejű (estünkben 100 µs) késleltetést tartalmaz, amit tetszőleges számszor ismételhetünk. A beépített függvény meghívása és a ciklus szervezése is időt vesz igénybe, ezért megpróbáltuk ezeket kompenzálni, a  __delay_cycles() függvény paraméterének csökkentésével.

Az inicializáló részben a gyárilag kalibrált 1 MHz-es DCO frekvenciát állítottuk be, s kimenetnek állítjuk a P1.0 lábat, amelyhez LED1 csatlakozik.

A végtelen ciklus magjában két for ciklus található. Az elsőben fokozatosan növeljük a bekapcsolt állapot időtartamát, a kikapcsolt állapot időtartamával pedig mindig 150 ciklusra (150 x 100 µs = 15 ms-ra) egészítjük ki. A második for ciklusban ennek a fordítottját csináljuk: fokozatosan csökkentjük a bekapcsolt állapot időtartamát.

A program legnagyobb előnye, hogy egyszerű és alkalmas a PWM szemléltetésére, ugyanakkor teljesen lefoglalja a CPU-t, az időzítése pedig pontatlan, mivel a kimenő jel átmeneteit szoftveresen kapcsolgatjuk. A LED sem pontosan azt csinálja, amit várunk tőle, mert a fényerő növekedése-csökkenése nem lineárisan történik. Ebben azonban nem a program, hanem a szemünk a ludas, amely logaritmikus érzékenysége miatt exponenciálisan növekvő teljesítmény hatására látna egyenletesen növekvő fényességet. A felmerült problémákat a következő programban próbáljuk orvosolni.  

PWM hardveres támogatással

Az alábbi programban a Timer_A modul nyújtotta hardveres támogatást használjuk a PWM jel előállítására. A program LED2 fényerejét folyamatosan változtatja,  mintha a LED "lélegezne". A program előzményei és a kiinduláshoz felhasznált forrásai:

  1.  J. Martin: "Breathing LED effect with the LaunchPad"
  2. Rusty Haddock: demo-breathing-led (Part Deux)

A Timer órajele SMCLK = 1 MHz lesz. 100 Hz PWM frekvenciát választva 10 000 impulzust kell leszámolni, tehát TACCR0 = 9999 legyen. A LED kb. 2 s-os félperiódusa alatt 200 lépésben négyzetesen növekvő kitöltést adunk meg, a következő 200 periódusban pedig csökkenő kitöltéssel lépkedünk vissza. Technikai okoból azonban nem hajthatunk végre 200 lépést, hanem csak 198-at, mert ha a TACCR1 < TACCR0 feltétel nem teljesül, akkor nem kapunk megszakítást az 1. sorszámú Capture/Compare csatornából, s "elakad" a programunk!

Algoritmus:  A kitöltést exponenciális helyett négyzetesen növekedő számokkal állítjuk be, tehát a 0, 1, 4, 9, 16, 25, ... stb. sorozatot állítjuk elő. Mivel 200 lépésnél ez már 40 000 volna, ami négyszer több a periódusnál, ezért az előállított négyzetszámot néggyel osztjuk. A négyzetszámok  sorozatát Rusty Haddock programjához hasonlóan a páratlan számok felösszegzésével állítjuk elő, a néggyel történő osztást pedig logikai eltolással végezzük el. Ilyen módon kiküszöböltük a szorzás és az osztás műveleteket, ami azért fontos, mert ezeket a műveleteket a Launchpadhoz kapott mikrovezérlőink hardveresen nem támogatják, szoftveresen pedig túlságosan időigényes volnának. A kiszámolt kitöltési tényezőt a new_ccr1 nevű változóban fogjuk tárolni.

Üzemmód beállítások

  • Timer:  MCx=1 (felfelé számlálás), megszakítást nem használunk
  • CCR0: üzemmód állítás nincs, kimenetet és interruptot nem használunk. TACCR0 = 10 000 - 1 (a periódus)
  • CCR1: OUTMOD = 7 (reset/set mód). A kimenetet P1.6-ra irányítjuk, így LED2 közvetlenül vezérelhető a PWM jellel. CCIFG1 megszakítását engedélyezzük, s a megszakításokban változtatjuk a kitöltést.

8_2. lista: a pwm_led program listája

  1. #include "io430.h"
  2. #include "stdint.h"
  3.  
  4. uint16_t idx = 0;            // Index a kitöltési tényezők számításához
  5. uint16_t next_sqr = 1;       // A következő négyzetszám
  6. uint16_t sqr_step = 3;       // Új növekmény a négyzetszámok kiszámításához
  7. uint16_t new_ccr1 = 1;       // A kitöltési tényező
  8.  
  9. int main(void) {
  10.   WDTCTL = WDTPW|WDTHOLD;    // Letiltjuk a watchdog időzítőt
  11.   BCSCTL1= CALBC1_1MHZ;      // A gyárilag kalibrált 1 MHz
  12.   DCOCTL= CALDCO_1MHZ;       // DCO frekvencia beállítása
  13.   P1DIR |= BIT6;             // P1.6 (LED2) kimenet legyen  
  14.   P1SEL |= BIT6;             // P1.6 Timer_A TA1 kimenete legyen
  15.   TACCR0 = 9999;             // PWM frekvencia = 1 MHz/10000 = 100 Hz
  16.   TACCR1 = 0;                // TACCR1(= fényesség) induló értéke
  17.   TACTL = TASSEL_2 |         // Timer_A forrása SMCLK legyen
  18.                MC_1|         // Felfelé számlálás TACCR0-ig
  19.               TACLR;         // A számlálót töröljük
  20.   TACCTL1 = OUTMOD_7|        // Reset/set amikor TAR=TACCR1, vagy =0
  21.                 CCIE;        // Megszakítás, ha a számlálás TACCR1-hoz ér
  22.   __low_power_mode_0();      // LPM0 mód: MCLK=ki, GIE=be
  23. }
  24.  
  25. //--- TACCR1 megszakítás kiszolgálása ---------------------------------
  26. #pragma vector=TIMERA1_VECTOR
  27. __interrupt void ta1_isr(void) {
  28.   TACCTL1 &= ~CCIFG;         // A megszakítási jelzőbit törlése
  29.   while(TAR <= new_ccr1);    // Várunk, amíg TAR nagyobb lesz,
  30.                              // mint az új kitöltés
  31.   TACCR1 = new_ccr1;
  32.   new_ccr1 = next_sqr>>2;    // Az új kitöltési tényező
  33.   idx++;                     // A futó index növelése
  34.   if (idx < 199) {           // Az első 195 lépésben felfelé lépünk  
  35.      next_sqr += sqr_step;
  36.      sqr_step += 2;
  37.   } else if (idx < 397) {    // A második 195 lépésben lefelé lépünk
  38.      sqr_step -= 2;
  39.      next_sqr -= sqr_step;
  40.   } else
  41.     idx = 0;                 // A fel-le periódus vége
  42. }

A program elején most is beállítjuk a gyárilag kalibrált 1 MHz-es DCO frekvenciát, s ez lesz MCLK és SMCLK forrása/frekvenciája. A P1.6 lábat kimenetnek állítjuk be, s a P1SEL 6. bitjének '1'-be állításával a TA1 kimenethez rendeljük hozzá. Ez azt jelenti, hogy az adatlapban (vagy cikksorozatunk 1. részében a 4_1. ábrán) bemutatott bekötési rajzon feltüntetett alternatív funkciók közül P1.6-nál most TA0.1 lesz érvényben, a kimenetet tehát most Timer_A-nak az 1. számú Capture/Compare csatornája vezérli.

A PWM módhoz TACCR0-ba írjuk a periódust megszabó számot. Talán túlzott finomkodásnak tűnik, hogy 10000 helyett 9999-et írunk bele, de szokjuk meg, hogy a periódus TACCR0 + 1! A TACCR1 regiszterbe pedig a kitöltést írjuk. A kitöltés 0 és TACCR0 közötti szám lehet. Előbbi a 0, utóbbi a 100 %-os kitöltési tényezőnek felel meg. Arra azonban kínosan ügyelnünk kell, hogy TACCR1 ne haladja meg TACCR0 értékét, mert akkor nem kapunk megszakítást (TAR sohasem éri el TACCR1 értékét)!

TACTL beállításánál bemenő jelnek az SMCLK órajelet választottuk (TASSELx=2), s a "felfelé számlálás adott értékig" módot (MCx=1). Előosztást nem használunk, tehát IDx=0 (nem írtuk ki, mert úgyis nulla...).

TACCTL1 beállításánál az OUTMODx=7 üzemmódot választottuk (TACCR1 elérésekor Reset, azaz TA1=0 lesz, TACCR0 elérésekor pedig Set, azaz TA1=1 lesz.. A TACCL1 regiszter beállításánál kell engedélyeznünk a CCIFG1 megszakítást is, a CCIE bit '1'-be állításával..

A beállítások után a főprogramnak nem lesz több dolga, ezért aludni küldhetjük (MCLK lekapcsolható). Vigyáznunk kell azonban arra, hogy SMCLK és  DCO nem kapcsolható le, ezért itt most csak az LPM0 módot használhatjuk. Az energiatakarékos módot beállító __low_power_mode_0() függvényhívás egyúttal az interrupt engedélyezését is elvégzi (az SR státusz regiszter GIE bitjét is '1'-be állítja.

A CCIFG1 megszakításhoz a megosztott használatú TIMERA1_VECTOR tartozik. Mivel más megszakítást nem használunk, most nem muszáj a TAIV regisztert nézegetni. Ekkor viszont nekünk kell gondoskodnunk a megszakítási jelzőbit törléséről.

A TIMERA1_VECTOR megszakítást kiszolgáló eljárás elején módosítjuk a TACCR1 regisztert, de előbb megvárjuk, hogy a számláló meghaladja a beírandó értéket. Ha erre nem ügyelünk, a LED fényének zavaró villózásai jelzik, hogy bizonyos esetekben a kívánt jelalaktól eltérő tranzienseket produkálunk.

A soron következő kitöltési tényező kiszámolása és elraktározása után gondoskodnunk kell a következő négyzetszám előállításáról. Az első 198 lépésben fölfelé lépkedünk a számokkal (azért nem megyünk el egészen 200-ig, mert ott már elérnénk vagy meghaladnánk a TACCR0-ba írt számot, s akkor már nem kapunk CCIFG1 megszakítást). A második 198 lépésben visszafelé lépkedünk a négyzetszámokon, tehát fokozatosan csökkentjük a kitöltést.

Bár a program azt csinálja, amit vártunk tőle, de vannak gyengéi, amelyeket könnyen kitapasztalhatunk, ha a megszakítást kiszolgáló ta1_isr() függvényből kihagyjuk a  while(TAR <= new_ccr1);  várakozást, vagy megváltoztatjuk a beírt indexhatárokat (199, 397). Előbbi növelésénél elakad a program, mert nem keletkeznek megszakítások, utóbbi megváltoztatásánál pedig "nem találunk" vissza a számsorozat előállításhoz használt segédváltozók (next_sqr, sqr_step) kezdőértékeihez, s emiatt a számsorozat minden ciklusban eltolódik, s "elhülyül" a program. Csúnya megoldás az is, hogy a megszakítást kiszolgáló eljárásba várakozást teszünk, s bizonyos alkalmazásoknál zavaró lehet  az is, hogy a 100 %-os kitöltés nem állítható be.

Ezeknek a problémáknak egy részét legkönnyebben úgy kerülhetjük el, ha a kitöltés beállítását nem a CCIFG1, hanem a CCIFG0 megszakításban végezzük el! Itt viszont rögtön felmerül az újabb probléma: a 8_2. ábrán látható, hogy a CCIFG0 megszakítás és a TAR számláló túlcsordulás között csak egy óraütésnyi időkülönbség van. Az 1 MHz-es SMCLK használata esetén ez egyetlen utasításciklusnyi idő, ami alatt el sem jutunk a megszakítást kiszolgáló eljáráshoz, nem hogy beállíthatnánk a TACCR1 regiszter értékét. Így a nagyon kis kitöltések esetén sorra elkésünk a beállítással.

Mi lesz ennek a következménye? Az, hogy a Reset/Set módra való tekintettel TAR túlcsordulásakor automatikusan '1'-be álla a TA1 kimenet, és a periódus végéig '1'-ben is marad (TA1 kimenőjele a 100 %-os kitöltésnek felel meg), mert amikor a TACCR1-el való egyezésnek be kellene következnie, akkor még nem írtuk bele a kívánt (kis) értéket. Ezen a problémát úgy tudjuk elkerülni, ha a CPU működését ütemező MCLK órajel sokkal nagyobb frekvenciájú, mint Timer_A bemenőjele. Egy kézenfekvő választás: MCLK = 1 MHz (DCO), Timer_A bemenőjele pedig ACLK (kvarccal 32 kHz, vagy VLO választásával 12 kHz).

PWM kitöltés beállítása CCIFG0 megszakításkor

Az alábbi program az előző példa módosított változata. Itt most Timer_A bemenőjele a VLO 12 MHz-es órajele lesz, s a TIMERA0_VECTOR(CCIFG0)  megszakításakor frissítjük a PWM kitöltés értékét. Az ACLK = 12 kHz-es órajel miatt a 100 Hz-es PWM frekvencia biztosításához 1200 impulzust kell leszámolni, tehát TACCR0 = 119 legyen. A LED kb. 2 s-os félperiódusa alatt 200 lépésben négyzetesen növekvő kitöltést adunk meg, a következő 200 periódusban pedig csökkenő kitöltéssel lépkedünk vissza. Most  végrehajthatunk 200 lépést, nem kell a TACCR1 < TACCR0 feltétel teljesülésével foglalkoznunk, mert a 0. sorszámú Capture/Compare csatornából garantáltan kapunk megszakítást, 100 %-os kitöltésnél sem "akad el" a programunk!

Arra viszont ügyelnünk kell, hogy most kevesebb órajelből áll egy periódus, ezért csak kisebb felbontással (durvább lépésekben) tudjuk szabályozni a  kitöltést. Ha nem akarjuk a 100 %-os kitöltést többszörösen meghaladni, akkor jóval nagyobb számmal kel leosztani a 200 lépésben 40 000-ig terjedő négyzetszámokat. Az alábbi programban 512-vel osztunk (9 bites eltolás jobbra), így 200 lépésnél 75 lesz a TACCR1 regiszter maximális értéke 120 helyett (nem érjük el a 100 %-os kitöltést). Tulajdonképpen 333-mal kellene osztanunk, de ez nem oldható meg gazdaságosan, ezért maradtunk a kisebb fényerőnél.

Üzemmód beállítások

  • Timer:  MCx=1 (felfelé számlálás), megszakítást nem használunk
  • CCR0: üzemmód állítás nincs, a CCIFG0 megszakítást engedélyezzük, s a megszakításokban változtatjuk a kitöltést (a TACCR1 regiszter tartalmát). A PWM frekvencia 100 Hz, ennek beállításához TACCR0 = 120 - 1 = 119
  • CCR1: OUTMOD = 7 (reset/set mód). A kimenetet P1.6-ra irányítjuk, így LED2 közvetlenül vezérelhető a PWM jellel. A CCIFG1 megszakítást most nem engedélyezzük.

8_3. lista: a pwm_led2 program listája

  1. #include "io430.h"
  2. #include "stdint.h"
  3.  
  4. uint16_t idx = 0;            // Index a kitöltési tényezők számításához
  5. uint16_t next_sqr = 1;       // A következő négyzetszám
  6. uint16_t sqr_step = 3;       // Új növekmény a négyzetszámok kiszámításához
  7. uint16_t new_ccr1 = 1;       // A kitöltési tényező
  8.  
  9. int main(void) {
  10.   WDTCTL = WDTPW|WDTHOLD;    // Letiltjuk a watchdog időzítőt
  11.   BCSCTL1= CALBC1_1MHZ;      // A gyárilag kalibrált 1 MHz
  12.   DCOCTL= CALDCO_1MHZ;       // DCO frekvencia beállítása
  13.   BCSCTL3 |= LFXT1S_2;       // ACLK forrása: VLO
  14.   P1DIR |= BIT6;             // P1.6 (LED2) kimenet legyen  
  15.   P1SEL |= BIT6;             // P1.6 Timer_A TA1 kimenete legyen
  16.   TACCR0 = 119;              // PWM frekvencia = 12 kHz/120 = 100 Hz
  17.   TACCR1 = 0;                // TACCR1(= fényesség) induló értéke
  18.   TACTL = TASSEL_1 |         // Timer_A forrása ACLK legyen
  19.                MC_1|         // Felfelé számlálás TACCR0-ig
  20.               TACLR;         // A számlálót töröljük
  21.   TACCTL0 =     CCIE;        // Megszakítás, ha a számlálás TACCR0-hoz ér
  22.   TACCTL1 = OUTMOD_7;        // Reset/set amikor TAR=TACCR1, vagy =0
  23.   __low_power_mode_3();      // LPM3 mód: MCLK,SMCLK,DCO=ki, GIE=be
  24. }
  25.  
  26. //--- TACCR0 megszakítás kiszolgálása ---------------------------------
  27. //--- CCIFG0 automatikusan törlődik!
  28. #pragma vector=TIMERA0_VECTOR
  29. __interrupt void ccr0_isr(void) {
  30.   TACCR1 = new_ccr1;         // Beállítjuk a kitöltést
  31.   new_ccr1 = (next_sqr>>9);  // Az új kitöltési tényező
  32.   if (idx < 200) {           // Az első 170 lépésben felfelé lépünk  
  33.      next_sqr += sqr_step;
  34.      sqr_step += 2;
  35.      idx++;                  // A futó index növelése
  36.   } else if (idx < 400) {    // A második 170 lépésben lefelé lépünk
  37.      sqr_step -= 2;
  38.      next_sqr -= sqr_step;
  39.      idx++;                  // A futó index növelése    
  40.   } else {
  41.     idx = 0;                 // A fel-le periódus vége, sipirc vissza!
  42.   }      
  43. }

A főprogramban az 8_2. programhoz képest megváltozott üzemmódok miatt az alábbi változtatásokra van szükség:

  • A BCSCTL3 regiszterben be kell állítani, hogy ACLK forrása VLO legyen.
  • A 100 Hz-es PWM frekvenciához most TACCR0 = 119; beállításra van szükség.
  • TACTL beállításánál a korábbi TASSEL_2 helyett most TASSEL_1 kell (SMCLK helyett ACLK választása).
  • A megszakítást engedélyező CCIE bitet most nem TACCTL1-nél hanem TACCTL0-nál állítjuk '1'-be. (Ehhez a megszakításhoz a TIMERA0_VECTOR tartozik).
  • A főprogram az inicializálás után most "mélyebben alhat" (LPM0 helyett LPM3 módban), mert ACLK használata miatt DCO és SMCLK is kikapcsolható.

A megszakítás kiszolgálásánál a 8_2. programhoz képest a következő eltérések vannak:

  • A TIMERA0_VECTOR megszakítási vektort használjuk. Ez nem megosztott használatú, ezért a CCIFG0 megszakításjelző bit automatikusan törlődik.
  • Nem kell várakozni a TAR >= TACCR1 feltételre.
  • Nem kell aggódni amiatt, hogy elérjük vagy meghaladjuk a 100 %-os kitöltést.

Összegzés:

Amint láttuk, ez a változat robosztusabb (nem akad el) és egyszerűbb, tisztább (nincs várakozás a megszakításban) . Ennek ára az volt, hogy a korábbi 10 000 lépés helyett itt legfeljebb 120 lépésben tudjuk változtatni a kitöltést. Nagyobb frekvenciájú Timer órajel esetén viszont gondban vagyunk a kitöltés alkalmas időben történő beállításával. John Davies "MSP430 Microcontroller Basics" c. könyvében kimerítően elemzi ezt a problémát (8.6.6 alfejezet). A problémák gyökere az, hogy TACCR1 regiszter nem kettős bufferelésű, a Timer_A típusú időzítő tehát nem ad elegendő hardver támogatást a PWM gond nélküli használatához. A nagyobb kiépítettségű MSP430 mikrovezérlők Timer_B típusú időzítője alkalmasabb lenne a PWM használatához.

Két, független PWM csatorna egyidejű vezérlése

A Launchpad kártyához kapott mikrovezérlőink hivatalosan csak egy PWM csatorna használatára alkalmasak (TAR számlálja a futó időt, TACCR0 állítja be a periódust, TACCR1 pedig a kitöltést). Egy kis szerencsével vagy trükközéssel azonban megoldható, hogy TACCR0 felszabaduljon, és egy második PWM csatorna vezérlésre használhassuk. Az egyik lehetőség az, ha úgy választjuk meg az órajelet és a PWM frekvenciát, hogy egy PWM periódus 2^16 impulzus legyen. Ekkor természetesen a PWM frekvencia mindkét csatornában azonos kell, hogy legyen.

A másik, jóval rugalmasabb használatot jelentő lehetőség az, hogy a számlálót szabadonfutó módban járatjuk, s a TACCR0 és TACCR1 regiszterekbe mindig a következő rész-ciklus (a bekapcsolt vagy kikapcsolt állapot időtartama) időzítését írjuk ( a TAR regiszter pillanatnyi állapotához hozzáadva a kívánt várakozási időhöz tartozó óraütések számát). Erre mutatunk be most egy példát, amelyben LED1 1 s-os periódussal villog, állandó 1/60 kitöltéssel. Ez ugyan nem a szokványos módja a PWM használatának, de nem akartam bonyolítani a programot a kitöltési tényező változtatásával.  LED2 a korábbiakhoz hasonlóan a "lélegző LED" műsorát adja elő: fényereje fokozatosan erősödik, majd elhalványul. Periódusa ~3,4 s

Üzemmód beállítások

  • Timer:  MCx=2 (szabadonfutó mód), órajele ACLK/VLO=12 kHz, megszakítást nem használunk
  • CCR0: OUTMOD=4 (Toggle, azaz átbillentés üzemmód ), a CCIFG0 megszakítást engedélyezzük. A PWM frekvencia itt 1 Hz, ami 12000 óraütés. Az 1/60 kitöltésnek megfelelően felváltva 200, illetve 11800-at adunk hozzá TACCR0 korábbi állapotához. Mivel P1.0 kimenet, ahová LED1 csatlakozik, nem állítható be TA0 kimenetnek, ezért a megszakításban szoftveresen billentjük át LED1 állapotát. Ez az interrupt késedelme miatt tartalmaz némi bizonytalanságot, nem ad olyan hardveres pontosságú időzítést, mint a TA0 kimenet.
  • CCR1: OUTMOD=4 (Toggle, azaz átbillentés üzemmód ), a CCIFG1 megszakítást engedélyezzük. A PWM frekvencia itt 100 Hz, ami 120 óraütés. A kitöltési tényező kiszámítása ugyanúgy történik, mint az előző két programban. A TACCR1 regisztert felváltva vagy a kiszámolt kitöltéssel, vagy a kitöltést a periódusra kiegészítő számmal kell feltölteni.
  • A beállítások után a főprogramnak nincs több teendője, ezért energiatakarékos módba tehetjük. Mivel a számláló működéséhez csak az ACLK órajel szükséges, az LPM3 módot használjuk.

A fenti beállításokat tartalmazó főprogram az alábbi listában látható. A megszakításokat kiszolgáló eljárásokat külön listákon mutatjuk be.

8_4. lista: a pwm_cont program főprogramjának listája

  1. #include "io430.h"
  2. #include "stdint.h"
  3.  
  4. uint16_t idx = 0;            // Index a kitöltési tényezők számításához
  5. uint16_t next_sqr = 1;       // A következő négyzetszám
  6. uint16_t sqr_step = 3;       // Új növekmény a négyzetszámok kiszámításához
  7. uint16_t new_ccr1 = 2;       // A kitöltési tényező
  8. uint16_t the_rest = 118;     // A kikapcsolt állapot időtartama
  9.  
  10. int main(void) {
  11.   WDTCTL = WDTPW|WDTHOLD;    // Letiltjuk a watchdog időzítőt
  12.   BCSCTL1= CALBC1_1MHZ;      // A gyárilag kalibrált 1 MHz
  13.   DCOCTL= CALDCO_1MHZ;       // DCO frekvencia beállítása
  14.   BCSCTL3 |= LFXT1S_2;       // ACLK forrása legyen VLO
  15.   P1OUT = 0;                 // Kimenetek 0-ról indulnak
  16.   P1DIR |= BIT0|BIT6;        // P1.0 és P1.6 kimenet legyen  
  17.   P1SEL |= BIT6;             // P1.6 Timer_A TA1 kimenete legyen
  18.   TACCR0 = 12000;            // TACCR0 kivár 1 s-ot
  19.   TACCR1 = 12000;            // TACCR1 kivár 1 s-ot
  20.   TACTL = TASSEL_1 |         // Timer_A forrása ACLK legyen
  21.                MC_2|         // szabadonfutó mód
  22.               TACLR;         // A számlálót induláskor töröljük
  23.   TACCTL0 = OUTMOD_4|CCIE;   // Toggle mód és megszakítás
  24.   TACCTL1 = OUTMOD_4|CCIE;   // Toggle mód és megszakítás
  25. //--- A főprogram itt befejezi a ténykedést és aludni tér.
  26.   __low_power_mode_3();      // LPM3 mód: MCLK,SMCLK,DCO=ki, GIE=be
  27. }

A TIMERA0_VECTOR-hoz tartozó CCIFG0 megszakítás kiszolgálásakor a megszakításjelző bit automatikusan törlődik, nem kell foglalkoznunk vele. A Toggle üzemmód elvileg a kimenetet is beállítaná, de mivel LED1 nem olyan kivezetésre van kötve, ami definiálható volna TA0 kimenetként, ezért a LED állapotát nekünk kell átbillenteni szoftveresen. Ezen kívül arról kell még gondoskodnunk, hogy TACCR0 új értékét beállítsuk. Azt LED1 pillanatnyi állapota dönti el, hogy milyen ciklus következik, ezért a P1OUT regiszter 0. bitjét vizsgáljuk. A P1OUT_bit.P0 elérési formát az IAR fordítóhoz tartozó fejléc állományokban szerepló únió (union) deklarációk teszik lehetővé (a Code Composer Studio-ban nem találtam ilyen lehetőséget). Ezek nélkül csak a kevésbé szemléletes P1OUT&BIT0 kifejezést használhatnánk. Az 1 s-os periódusidőhöz tartozó 12 000 óraimpulzust az 1:60 arányú kitöltésnek megfelelően 200 : 11800 arányban osztottuk fel. A CCIFG0 megszakítást kiszolgáló eljárás listája így néz ki:

8_5. lista: a CCIFG0 megszakítás kiszolgálása a pwm_cont programban

  1. #pragma vector=TIMERA0_VECTOR
  2. __interrupt void ccr0_isr(void) {
  3.   if(P1OUT_bit.P0) {
  4.     P1OUT &= ~BIT0;          // LED1 kikapcsolása
  5.     TACCR0 += 11800;         // Ennyi ideig marad kikapcsolva
  6.   } else {
  7.     P1OUT |= BIT0;           // LED1 bekapcsolása
  8.     TACCR0 += 200;           // Ennyi ideig marad bekapcsolva
  9.   }
  10. }


A TIMERA1_VECTOR-hoz tartozó CCIFG1 megszakítás kiszolgálásakor nem foglalkozunk a TAIV regiszterrel, mivel most csak ez az egyetlen forrása lehet a megszakításnak. A megszakításjelző bit azonban így nem törlődik automatikusan, nekünk kell törölni. A Toggle üzemmód automatikusan billegteti a kimenetet. Ehhez a főprogramban beállítottuk, hogy a P1.6 kimenet, amelyre LED2 van kötve,  TA1 kimenetként működjön. Így már csak arról kell gondoskodnunk, hogy TACCR1 új értékét beállítsuk. Azt P1.6 pillanatnyi állapota dönti el, hogy milyen ciklus következik, ehhez a P1IN regiszter 6. bitjét vizsgáljuk. A P1IN_bit.P6 elérési forma helyett a Code Composer Studio-ban a kevésbé szemléletes P1IN&BIT6 kifejezést kellene használnunk. 

A kitöltési tényezőt ahhoz hasonló módon állítjuk elő, ahogy a korábbi programok "lélegző LED" megoldásainál. Itt most valamivel rövidebb ciklusokat szerveztünk, mint az előző programban (170 lépés felfelé, 170 lépés lefelé), ezért elegendő a négyzetszámokat 256-tal történő osztással (8-bites eltolással) skálázni. Tapasztalati úton azonban arra a következtetésre jutottunk, hogy 2-nél rövidebb kitöltéssel már nem működik helyesen a program, a kiszámított kitöltéshez ezért mindig hozzáadunk kettőt.. A CCIFG1 megszakítást kiszolgáló eljárás listája így néz ki:

8_6. lista: a CCIFG1 megszakítás kiszolgálása a pwm_cont programban

  1. #pragma vector=TIMERA1_VECTOR
  2. __interrupt void ccr1_isr(void) {
  3.   if(P1IN_bit.P6) {
  4.     TACCR1 += new_ccr1;        // Beállítjuk a kitöltést
  5.     new_ccr1 = (next_sqr>>8)+2;// Az új kitöltési tényező kiszámolása
  6.     the_rest = 120-new_ccr1;   // A kikapcsolt állapot időtartama
  7.     idx++;                     // A futó index növelése
  8.     if (idx < 170) {           // Az első 170 lépésben felfelé lépünk  
  9.        next_sqr += sqr_step;
  10.        sqr_step += 2;
  11.     } else if (idx < 340) {    // A második 170 lépésben lefelé lépünk
  12.        sqr_step -= 2;
  13.        next_sqr -= sqr_step;
  14.     } else idx = 0;            // Új periódus kezdődik
  15.   } else {
  16.     TACCR1 += (the_rest);  // A kikapcsolt állapot ideje
  17.   }
  18.   TACCTL1 &= ~CCIFG;           // A megszakítási jelzőbit törlése
  19. }

További lehetőségek

Az eddigi mintaprogramokban korántsem merítettük még ki Timer_A üzemmódjainak és felhasználási mó lehetséges módjait. Nem foglalkoztunk például sem a bemeneti jelrögzítés (Input Capture) móddal, sem a Compare egység speciális üzemmódjaival. Ezekre valószínűleg sor kerül még cikksorozatunk későbbi részeiben. Addig is nagy szeretettel ajánlom John Davies: MSP430 Microcontroller Basics ("MSP430 mikrovezérlő alapok") könyvét amelyik kimerítően foglalkozik ezekkel.

Letöltések

Fórumok

Következő: »»   8 / 8
Értékeléshez bejelentkezés szükséges!
Bejelentkezés

Belépés

Hirdetés
Lapoda.hu     XDT.hu     HEStore.hu
Az oldalon sütiket használunk a helyes működéshez. Bővebb információt az adatvédelmi szabályzatban olvashatsz. Megértettem