2014 m. lapkričio 26 d., trečiadienis

AVR ir asemblerio iššūkis IV: PWM

Paskutiniame šio iššūkio įveikinėjimo straipsnyje bandysiu parašyti apie papildomą užduotį - impulso pločio moduliaciją, angl. PWM (pulse width modulation). Turiu perspėti, kad mano žinios apie šį dalyką tik lengvai teorinės, todėl pirma - galiu pridaryti klaidų, ir jei kas mane ištaisys, būsiu dėkingas; antra - rašydamas pats mokysiuosi ir bandysiu parašyti veikiančią, bent minimaliai PWM naudojančią programą.

PWM yra ne vienas tipas ir visai nemažai teorijos su formulėm, kurių nesuprantu kol kas, tai man užteks ir nediduko PWM tų pačių diodų pamirksėjimui: užduotis yra juos uždegti ir užgesinti tolygiai, o ne staiga, kaip iki šiol buvo. Kaip sakoma, ačiū už supratingumą :)


KAS YRA TA IMPULSO PLOČIO MODULIACIJA?
Impulso pločio moduliacija yra technologija, kuri gali praktiškai be nuostolių reguliuoti elektros srovę kažkokiems elektros imtuvams. Principas yra labai paprastas: kuo ilgiau yra įjungta srovė, tuo gali pratekėti daugiau srovės; analogiškai - kuo ilgesnis tarpas tarp impulsų - tuo srovė mažesnė. Tokiu būdu junginėjant daug impulsiukų rezultate turim tiek procentų srovės, kiek procentų laiko įtampa yra įjungta.
Na, su aiškinimu paprastu tekstu man, matau, ne kas, todėl pabandysiu visą tai nupiešti.
Mikrovaldiklio pagrindiniai bendravimo su išoriniu pasauliu išvadai, kaip žinome, yra skaitmeniniai, t.y. arba nulis, arba vienetas, kas mūsų atveju yra arba +5 V (įtampa yra) arba 0 V (įtampos nėra). Žiūrim į grafiką: dantyta linija, prasidedanti nuo U=5V, yra skaitmeninio išėjimo lygis, kuris lygiais intervalais kinta nuo 1 iki 0. Horizontaliosios grafiko ašies apačioje sužymėjau sąlyginį laiką, pažymėtą raide T. Įsivaizduokim, kad T yra vienodi laiko intervalai (jie gali būti labai įvairūs, todėl kol kas jų neįvardinsiu. Žiūrėdami į dantytą liniją (įtampą skaitmeniniame išėjime) laiko atžvilgiu matome, kad, pradžioje buvusi būsenoje "loginis 1" ties puse intervalo įtampa nukrenta iki "loginio 0"; toliau ties pirmo intervalo pabaiga / antro pradžia vėl atsiranda "loginis 1" , kuris ties pusantro laiko tarpo žyme vėl pereina į "loginį 0". Šiuo atveju santykis "loginis 1" ir "loginis 0" yra 1:1, kas 50% viso laiko. Tokiu atveju toks signalas atiduoda 50 procentų įtampos, kas lygu 2,5 v ir pažymėtas žalia linija.

Šiame grafike įtampa yra 25% laiko, todėl išėjime įtampa bus 1,25 v. Analogiškai ir didesnei įtampai: tarkim, jeigu įtampa bus 75% laiko, įtampa bus 3,75 v.
Pagal šiuos papaišymus akivaizdu, kodėl technologija vadinama impulso pločio moduliacija: kuo impulso plotis didesnis, tuo išėjimo įtampa didesnė, ir atvirkščiai.
Šio principo naudojimas kur kas platesnis, nei gali pasirodyti iš pirmo žvilgsnio. Kiekvienuose namuose esantis pavyzdys - lygintuvas, kuris, naudodamas termoreguliatorių, arba išsijungia, arba įsijungia. Kuo ilgesni įjungtos srovės intervalai, tuo lygintuvas karštesnis - analogija išėjimo įtampai. Taikant šią technologiją pagaminti visi impulsiniai maitinimo blokai, šviesos reguliatoriai (dimeriai), variklių sukimosi greičio reguliatoriai, skiriasi tik junginėjimo dažnis: lygintuvo atveju jis gali siekti iki įtampos lygio perjungimo kas keletą minučių, tuo tarpu impulsiniuose maitinimo blokuose dažnis siekia dešimtis ir šimtus kHz.
Pačioje pradžioje paminėjau, kad srovės reguliavimas vykdomas beveik be nuostolių. Kodėl? Tradiciniuose maitinimo blokuose įtampos sumažinimui naudojami įrenginiai, kurie, atduodami norimą dalį energijos, jos perteklių išspinduliuoja šilumos pavidalu. Toks srovės reguliavimo būdas neekonomiškas, nes energijos suvartojama daug daugiau, nei atiduodama galutiniam vartotojui - prietaisui. Impulsinių maitinimo blokų atveju tuo metu, kai išėjime yra "loginis 0" energija beveik nevartojama, o naudojama tik tuo metu, kai yr a"loginis 1". Žinoma, realiomis sąlygomis tas "beveik" nėra lygus nuliui, tačiau turint omenyje, kad impulsinių maitinimo plokų naudingumo koeficiewnta sgali siekti 90 procentų, o tradicinių analoginių - iki 60 procentų, skirtumas juntamas.
Iki dabar aprašiau tik stabilios įtampos gavimą PWM technologijos pagalba. Tačiau gyvenime (o ir mano užduotyje) reikalinga kintama reikšmė, ir ne bet kaip, o tolygiai :) . Vėlgi, tai nėra sudėtinga: priklausomai nuo poreikio didinti ar mažinti įtampą reikia keisti impulso plotį. Norint aukštesnės įtampos - impulso plotis turi didėti, norint mažinti - impulsas turi siaurėti. Grafiškai viskas atrodys šitaip:

Melsvai nuspalvoti impulsai: nuo 0 iki 7T impulso plotis tolygiai mažėja, nuo 8T iki galo - didėja. Žalia kreivė - išėjimo įtampa, kuri pradžioje tolygiai mažėja, po to - didėja.  Štai taip generuojamas analoginis signalas skaitmeninio signalo pagalba.
Yra keletas niuansų, kurių čia giliai neaptarinėsiu.
Prietaisai, kurie maitinami ar valdomi tokiais impulsais turi būti pakankamai inertiški, t.y. kuo inertiškesnis prietaisas - tuo junginėjimo dažnis gali būti mažesnis (pavyzdys - tas pats lygintuvas). Inertiškumui mažėjant, junginėjimo dažnis turi būti didinamas. Tarkim, šviesos reguliatorius, vadinamas dimeriu, valdydamas lemputes, turi dirbti apie 100 Hz dažniu, kad nesimatytų mirgėjimo. Impulsiniai maitinimo blokai dirba dideliu dažniu todėl, kad įranga (o dažniausiai impulsiniais maitinimo blokais maitinama elektronika) yra pakankamai jautri įtampos pokyčiams ir trukdžiams maitinime, kurie, didinant dažnį, lengviau sumažinami.
Antra - realiai netgi dideliu dažniu junginėjant įtampą ji vis tiek kyla iki maksimumo ir leidžiasi iki nulio, o tai tiksliuose įtaisuose, kokie, tarkim, yra maitinimo blokai skaitemninei technikai, nėra toleruojama. Tokiu atveju išėjime dedamas kondensatorius, kuris sulygina impulsiukus, veikdamas kaip amortizatorius: įtampai neleidžia nukristi iki nulio, taip pat neleidžia pakilti iki maksimumo. Tačiau mums to kol kas nekreikės.
Taip pat čia neaptarinėsiu PWM signalu formuojamo kintamos srovės signalo bei PWM signalo moduliavimo principų, ką norėdami sėkmingai rasite internete. O ir signalo tipas, kai signalas visada prasideda ties laiko intervalo pradžia (paskutinio paveikslėlio atveju - ties 1T, 2T ir t.t.) yra ne vienintelis - kaip atskaitos tašką vietoj inervalo pradžios galima pasirinkti ir jo vidurį ar pabaigą.

PWM IR MIKROVALDIKLIS
Kaip jau turbūt aišku, norint tolygiai uždegti ar užgesinti šviesos diodą mums reikės daug kartų junginėti mikrovaldiklio išvadą. Nors diodas užsidega labai greitai (Wikipedia.org sako, kad užsidegimo laikas gali būti mikrosekundė ir dar mažiau), mirksėjimo nematysime dėl akių savybės, kuri nepastebi greitai besikeičiančių vaizdų, juos suliedama.
PWM moduliavimui AVR šeimos mikrovaldikliuose būna skirtas PWM išėjimas bei tam skirti nustatymų registrai, ko šįkart neliesiu. Rašysim programą, kuri atliks amžiną ciklą, įjungiantį ir išjungiantį mikrovaldiklio išvadą. Kadangi programoje impulso plotis kis tolygiai, naudosiu tik sudėties ir atimties operacijas. Funkcinė diagrama atrodo šitaip:

Pirmas žingsnis - nustatomas kintamasis T, kuris lems viso uždegimo ciklo ilgį. Pradžiai jis prilyginamas nuliui.
Antras žingsnis - T padidinamas vienetu (daugelyje programavimo kalbų užrašymas X++ reiškia kintamojo X padidinimą vienetu).
Trečias žingsnis - uždegamas šviesos diodas.
Ketvirtas žingsnis - šviesos diodas laikomas įjungtas, kol išlaukiamas laikas T, pirmo ciklo apsisukimo atveju - 1.
Penktas žingsnis - šviesos diodas užgesinamas.
Šeštas žingsnis - šviesos diodas užgesinta sbūna tol, kol išlaukiamas laikas 1000-T, šiuo atveju - 999. Skaičius 1000 šioje programoje visiškai atsitiktinis, ir tik kai parašysiu programą, įsitikinsiu, kad jo užtenka. Kitas dalykas - skaičiui virš 255 reikės naudoti du registrus, nes vieno 8 bitų registro neužteks. Na, kaip nors.
Ir septintas žingsnis - tikrinama, ar T pasiekė maksimalią reikšmę, t.y. 1000. Jeigu dar nepasiekė - ciklas kartojamas dar kartą; jeigu pasiekė - T iš naujo nunulinamas ir ciklas pradedamas iš naujo.
Tokios programos metu diodas turėtų tolygiai užsidegti, tada, pasiekęs maksimumą, staiga užgesti ir vėl kartoti ciklą.

ASEMBLERIS
Rašydamas programą supratau, kad visgi kol kas vengsiu dvigubų registrų skaičiams, didesniems nei 8 bitai, saugoti :) Tokiu atveju turėsiu naudoti ciklą cikle. Gali būti, kad programa su dviem registrais ir vienu skaičiumi taptų elegantiškesnė, bet man šiam kartui tiks ir taip. Taigi, išvargtas asembleris :)
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
.include "tn2313def.inc" ; mikrovaldiklio aprasymas .def laikinas = R16 ; registrams suteikiami vardai.def t = R17.def laikinas1= R18.def velinimas = R19 .equ raudonas = 0 ; prievado bitams suteikiami pavadinimai.equ zalias = 1.equ melynas = 2 .CSEG ; dirbame programiniame segmente.ORG $0000 ; programa prasideda nuo 0x0000 adreso rjmp RESET ; pertraukimo vektorius RESET: ldi laikinas, low(RAMEND) ;registrui priskiriamas STACK adresas out SPL, laikinas ;irasomas adresas i specialuji registra  ldi laikinas, 0b00000111 ;PB0-PB2 - isejimai out DDRB, laikinas  cbi PortB, raudonas ;uzgesinami visi diodai cbi PortB, zalias cbi PortB, melynas  ldi velinimas, 20 ;kintamasis, nurodantis uzdelsima MAIN:  ldi t, 0 ;nunulinamas kintamasis t loop1: ;pirmo ciklo pradzia - diodu uzdegimas  inc t ; t padidinamas vienetu   sbi PortB, raudonas ;uzdegami diodai   sbi PortB, zalias   sbi PortB, melynas  rcall velinimas1 ; laukia laika = t   cbi PortB, raudonas ;uzgesinami diodai   cbi PortB, zalias   cbi PortB, melynas  rcall velinimas2 ;laukia laika 255-t  ldi laikinas, 255   cp t, laikinas ;sulygina registrus t ir laikinas  brne loop1 ; jeigu registrai nelygus - cikla kartoja vel loop2: ;antro ciklo pradzia - diodu uzgesinimas  dec t ;t sumazinamas vienetu   sbi PortB, raudonas ;uzdegami diodai   sbi PortB, zalias   sbi PortB, melynas  rcall velinimas1  ; laukia laika = t   cbi PortB, raudonas ;uzgesinami diodai   cbi PortB, zalias   cbi PortB, melynas  rcall velinimas2 ;laukia laika 255-t  ldi laikinas, 0  cp t, laikinas ;sulygina registrus t ir laikinas  brne loop2 ; jeigu registrai nelygus - cikla kartoja vel rjmp main ; paprogramiu pradzia VELINIMAS1:  mov laikinas, t ;registra t kopijuoja i registra laikinas zyme1:  mov laikinas1, velinimas ;registrui laikinas1 priskiria velinimo ilgi  zyme2: ;vidinio velinimo ciklo pradzia  dec laikinas1 ; laikinas1 sumazinamas vienetu  brne zyme2 ;jeigu laikinas1>0 kartoja vidinio velinimo cikla dec laikinas ;sumazina registra laikinas vienetu brne zyme1 ;jeigu laikinas1 >0 kartoja velinimo cikla  ret ;grizta is paprogrames VELINIMAS2:  ldi laikinas, 255 ; registrui laikinas priskiriama reiksme sub laikinas, t ; is registro laikinas atimamas registras t zyme3:   mov laikinas1, velinimas ;registrui laikinas1 priskiria velinimo ilgi  zyme4: ;vidinio velinimo ciklo pradzia  dec laikinas1 ; laikinas1 sumazinamas vienetu  brne zyme4 ;jeigu laikinas1>0 kartoja vidinio velinimo cikla dec laikinas ;sumazina registra laikinas vienetu brne zyme3 ;jeigu laikinas1 >0 kartoja velinimo cikla  ret ;grizta is paprogrames
Programa ne tik uždega šviesos diodus, bet juos ir užgesina.
Praeituose straipsniuose aptartų komandų nebeaptarinėsiu - kam dubliuotis, be to, parašiau komentarus. Aptarsiu tik bendrą vaizdą bei naujas komandas.
3-6 eilutėse įsivardinau registrus. Registras R17 tarnaus kaip impulso pločio skaitliukas - t; R19 taps velinimo kintamuoju, kad norint pakeisti nereikėtų kaitalioti keliose vietose programos kode. 27 eilutėje jam priskiriamas skaičius 20 - tiek kartų suksis vidinis ciklas vėlinimo paprogramėse.
3 eilutėje nunulinamas impulso pločio skaitliukas - pirmiausia dėl to, kad programai prasidėjus jis būtų žinomas, antra - praėjus visą ciklą (uždegimo ir užgesinimo) jį reikia nunulinti.

32333435363738394041424344
 loop1: ;pirmo ciklo pradzia - diodu uzdegimas  inc t ; t padidinamas vienetu   sbi PortB, raudonas ;uzdegami diodai   sbi PortB, zalias   sbi PortB, melynas  rcall velinimas1 ; laukia laika = t   cbi PortB, raudonas ;uzgesinami diodai   cbi PortB, zalias   cbi PortB, melynas  rcall velinimas2 ;laukia laika 255-t  ldi laikinas, 255   cp t, laikinas ;sulygina registrus t ir laikinas  brne loop1 ; jeigu registrai nelygus - cikla kartoja vel
Čia yra diodų uždegimo ciklas. Prisiminus PWM grafikus galima bandyti susigaudyti, kodėl yra taip, o ne kitaip :)
Pirmiausia 33 eilutėje skaitliukas t padidinamas vienetu. Tada, uždegus diodus 34-36 eilutėse, einama į vėlinimo paprogramę, kur uždelsiamas laikas t (pirmo ciklo atveju - 1). Į vidinį vėlinimo ciklą nereikia kreipti dėmesio, nes jis nekintantis. Po uždelsimo diodai 38-40 eilutėse užgesinami, ir 41 eilutėje laukiamas laikas 255-t. Tokiu atveju pirmą kartą sukantis ciklui diodas šviečia 1/255 laiko dalį, būna užgesęs 244/255 laiko dalis.
42-44 eilutėse vykdomas sąlyginis programos šakojimas: 43 eilutėje sulyginant registrus (ComPare) žiūrima, ar t lygus 255. Jeigu ne - liepiama kartoti ciklą loop1.
Antro ciklo apsisukimo metu diodas švies 2t laiką, bus užgesęs 253t, ir taip toliau, kol švietimo laikas pasieks 255, o užgesimo - 0. 43 eilutėje sulyginus registrus rezultatas bus "lygu", todėl programa į loop1 nebeperšoks, o eis prie sekančios komandos.

45464748495051525354555657
  loop2: ;antro ciklo pradzia - diodu uzgesinimas  dec t ;t sumazinamas vienetu   sbi PortB, raudonas ;uzdegami diodai   sbi PortB, zalias   sbi PortB, melynas  rcall velinimas1  ; laukia laika = t   cbi PortB, raudonas ;uzgesinami diodai   cbi PortB, zalias   cbi PortB, melynas  rcall velinimas2 ;laukia laika 255-t  ldi laikinas, 0  cp t, laikinas ;sulygina registrus t ir laikinas  brne loop2 ; jeigu registrai nelygus - cikla kartoja vel
Analogiškas ciklas prieš tai buvusiam, tik kintamasis t ne didinamas, o sumažinamas vienetu po kiekvieno ciklo apsisukimo. Tokiu būdu diodai šviečia vis trumpesnį laiką, ir vis ilgiau būna užgesę.

Ciklas VELINIMAS1 - tiesiog du ciklai vienas kito viduje, analogiški praeito straipsnio ciklams. VELINIMAS2 yra nauja komanda - sub, kuri vykdo atimties veiksmą - iš registro laikinas atima registrą t, dėl ko vėlinimas trunka 255-t laiką.

Įrašius programą į mikrovaldiklį, visi šviesos diodai tolygiai užsidega ir užgęsta maždaug kas 1 sekundę. Jeigu pas jus mikrovaldiklis veikia kitokiu dažniu - tereikės pakeisti skaičių, priskiriamą registrui velinimas 20-oje  eilutėje.

Tiek šiam kartui. dalykui, kuriam reikės atsiskaitinėti, manau, užteks. O kokių užduočių, kurias bus įdomu ir verta aprašyti čia, gausiu ateityje - bus matyti :) Gero asemblerio!

P.S. Turit pastabų - rašykite, jei kas negerai - ištaisysiu.

1 komentaras:


  1. Hmm it appears like your blog ate my first comment (it was super long) so I guess I'll just sum it up what I submitted and say, I'm thoroughly enjoying your blog. I as well am an aspiring blog writer but I'm still new to everything. Do you have any suggestions for novice blog writers? I'd really appreciate it. google mail sign in

    AtsakytiPanaikinti