Arduino ir mygtukai: analoginiai pertraukimai

Posted: 2013-09-26 in Darbeliai
Žymos:, , ,

Jei pamenate, kadaise rašiau apie tokį dalyką, kaip mygtukų ir laidų taupymas su Arduino mikrovaldikliu. Ten pateikiau paprastą pavyzduką: kas kažkiek laiko su analogRead(…) funkcija nuskaitoma analoginio įvado reikšmė ir pažiūrima, ar ji atitinka kokio nors mygtuko reikšmę. Analoginės mygtukų klaviatūros, beje, naudojamos net ir rimtoje buitinėje technikoje, pavyzdžiui, televizoriuose. Juk paprasčiau klaviatūros valdymą perdavinėt dviem laidais, nei kokiais penkiais, ar ne?

Minėtasis valdymas su analogRead(…) pavyzdžiu turi trūkumų. Galbūt kokiam nors pasižaidimui ir neturi, bet rimtesniam projektui turi tikrai. Kodėl gi? O todėl gi, kad analogRead(…) funkcija yra „kvaila“: ji užprašo analoginį ketiklį atlikti darbą ir po to laukia, kol darbas bus atliktas. Tiksliai nežinau, bet tas analoginis konvertavimas užtrunka kažkiek laiko, nėra jis žaibiškas. O kol laukiama, kada pagaliau konvertavimas bus baigtas, tuščiai eikvojami procesoriaus taktai.

Mikrovaldikliuose, kad žinotumėte, įvairiais būdais stengiamasi procesoriaus taktų naudoti kuo mažiau ir sukišti kuo daugiau logikos į visokius pertraukimus. Na, kad ji būtų naudojama tik tada, kada jau mirtinai reikia, o ne nuolat būtų tikrinama „Na, ar galima jau? Dar ne? O dabar?“ ir t.t. Mikrovaldikliai dėl to ir yra „mikro“, kad juose to procesoriaus toli gražu ne per daug.

Kokia išeitis? Nagi ir čia pasinaudoti pertraukimu. AVR procesoriuose yra tokia pertraukimų rūšis: analoginio keitiklio darbo pabaigos pertraukimas. Na, kitaip sakant, programa liepia analoginiam keitikliui padaryti konvertavimą ir jį „pamiršta“, nurodo, kad konvertavimas bus apdorotas pertraukimo. Programa tęsiasi toliau, o analoginis keitiklis tuo tarpu dirba pats sau, atskirai nuo centrinio procesoriaus. Kai darbą baigia ir turi ką parodyti, tada iškviečia pertraukimo paprogramę. Na, o šioje jau galima ir kažką nuveikti. Gerumas tame, kad kol analoginis keitiklis dirba, dirba ir centrinis procesorius, jame sukasi mikrovaldiklio programa.

Šis pertraukimas ypač naudingas visokiems mygtukams. Kad vartotojui atrodytų, jog valdiklis į jo prisilietimą reaguoja tuoj pat, mygtukų tikrinimas turi būti atliekamas gan dažnai. Tačiau jeigu pagrindinė programa kažką veikia ir gan gausiai, o su kiekvienu ciklu dar nuskaitoma ir keitiklio reikšmė, tai tiek valdiklio reakcija gali pasirodyti lėtoka, tiek nuolat gaišinamas programos laikas laukiant keitiklio reikšmės. O jeigu išnaudojami keitiklio pertraukimai, tai pertraukimo paprogramėje sparčiai patikrinama ar keitiklio reikšmė yra viena iš laukiamų ir jeigu ne — sprunkama lauk ir valdymas vėl atiduodamas centrinei programai. Taigi sutaupoma keliolika procesoriaus taktų — o užimtam mikrovaldikliui tai gali būti labai daug.

Tiek teorijos. Liūdnoji dalis: programuotojams mėgėjams draugiška Arduino bibliotekų aibė neturi analoginio-skaitmeninio keitiklio (toliau — ADC) pertraukimų valdymo. Na, kokio nors tokio „draugiško“, iš serijos attachInterrupt. Todėl jei norėsite išnaudoti ADC pertraukimus, turėsite įsigilinti į AVR procesoriaus ADC registrus, jų reikšmes ir prasmes bei sužinoti dar keletą subtilybių. Aš turiu prisipažinti, kad man tas ADC registrų valdymas pasirodė paprastas ir lengvas. Ypač palyginus su laikmačiais ir visokiais PWM režimais. Tad manau, kad ir jums tai nepasirodys kažkoks tai kostmosas.

Trumpai apie du ADC režimus su pertraukimu. Yra „nuolatinis“ režimas ir „vienkartinis“. „Nuolatiniame“ režime ADC sukasi ištisai ir su kiekvienos reikšmės keitimu iškviečia pertraukimo paprogramę. Na ir sukasi sau automatiškai. „Vienkartiniame“ režime programuotojas turi nurodyti: „dabar pradėk konvertavimą“. Konvertavimui įvykus bus iškviesta pertraukimo paprogramė. Tačiau ji bus įvykdyta ir viskas. ADC sustos ir nieko nedarys tol, kol vėl neliepsim pradėti konvertavimo. Aišku, šį „vienkartinį“ režimą galima paversti tokiu „pusiau-nuolatiniu“, jeigu pertraukimo paprogramės pabaigoje įdėsime paliepimą vėl atlikti konvertavimą.

Dirbant su ADC pertraukimais yra vienas sunkumas: labai lengva susipainioti, iš kurio gi ADC įvado skaitoma reikšmė. Na, ta prasme, jeigu ji nuskaitinėjama iš kelių įvadų. Aišku, tą galima sužinoti tiesiog per kelis kartus išsitestavus ir išsiaiškinus, kaip reikalas veikia.

Dėl šios „painiavos“ aš labiausiai mėgstu „pusiau-nuolatinį“ režimą. Nes tada prieš liepdamas atlikti konvertavimą galiu pakeisti ADC įvadą ir tikrai žinosiu, kad kitąkart iššokus pertraukimo paprogramei konvertuota reikšmė ir bus iš to nurodyto įvado. O štai jei „nuolatiniame“ režime pakeisiu kanalą, tai kitąkart įvykus pertraukimo paprogramei gausiu reikšmę iš… prieš tai buvusio nustatyto kanalo. Kodėl, paklausite? Ogi todėl, kad „nuolatiniame“ režime ADC darbas vyksta lygiagrečiai su procesoriaus darbu. ADC baigia konvertavimo ciklą, iššaukia pertraukimo paprogramę ir… vėl pradeda naują konvertavimo ciklą, o paprogramę tuo metu doroja procesorius. Taigi, paprogramėje pakeitus kanalo reikšmę ir jai vėl „pasirodžius eteryje“ ADC reikšmė bus dar „ne iš to“ kanalo. Be to, šiuo atveju dar ir pertraukimas iškvietinėjamas dažniau, t.y. nuolat, su kiekvienu ADC konvertavimu. Tai dažniausiai bereikalingas procesoriaus apkrovimas, nebent darote kokią nors žiedinio buferio duomenų analizę.

Tai va, tiek teorijos. Toliau aš papasakosiu ir parodysiu, kaipgi spėlioti nuspaustų mygtukų reikšmes naudojantis „pusiau-nuolatiniu“ ADC pertraukimų metodu.

Pirmiausia pradėsime nuo ADC režimo, konfigūracijos ir būklės registro ADCSRA. Registras yra aštuonių bitukų ir visi jie naudojami kokiam nors reikalui nustatyti.

Štai iškarpėlė iš ATMega328P specifikacijos lakšto:

AVR ADC pertraukimų konfigūracijos ir būklės registras ADCSRA | Darau, blė

Kiekvienas bitukas turi savo pavadinimą. Tas yra gerai, nes ir AVR bibliotekose yra atitinkamos konstantos, kurias galima naudoti. Tiesa, pradinei konfigūracijai aš jų nenaudoju, aš įsidedu komentarą su registro išklotinėmis ir nusistatinėju bitukus dvejetainėmis konstantomis. Man taip patogiau.

Kadangi ieškosime galimybės paleisti „pusiau-nuolatinį“ režimą, apie viską pasakosiu iš jo pusės. Pirmiausia programos pradžioje reikia susikonfigūruoti norimą ADC režimą.

ADEN bitukas įjungia arba išjungia ADC. Mes jį nustatysime į vienetuką, nes, aišku, norime ADC įjungti.

ADSC bitukas liepia ADC pradėti darbą — analoginį-skaitmeninį konvertavimą. Pirmai pradžiai nustatom jį į nuliuką, nes iš pradžių, kol dar viskas nesuderinta, mums to konvertavimo nereikia.

ADATE bitukas liepia ADC reaguoti į signalą ir automatiškai pradėti konvertavimą. Tas mums nebūtina, tad nustatome nuliuką.

ADIF bitukas liepia ADC atlikti signalų palyginimą, bet čia visai kita opera ir mes tuo neužsiimsime. Nuliukas.

ADIE bitukas įgalina ADC pertraukimus. Yess!.. mums kaip tik šito ir reikia. Vienetukas, be abejo.

Trys ADPSx nustato procesoriaus taktinio dažnio daliklį. Pagal specifikacijų lakštą ADC darbo dažnis turi būti apie 50–200 kHz. Kadangi mano beveik visi AVR procesoriukai sukasi su kristalu, kurio dažnis yra 16 MHz, reikia susirasti tokį daliklio dydį, kad šį dažnį padalinus gautumėm reikiamą reikšmę. Pagal lakštą daliklis 128 iš 16 MHz padaro apie 125 kHz, o ši reikšmė patenka į reikiamą intervalą. Taigi visus tris ADPSx bitukus nustatome į vienetukus.

Jei koduojate su Arduino, tai dabar setup() paprogramėje galite išdidžiai įrašyti:

	// ADEN  | ADSC  | ADATE | ADIF | ADIE | ADPS2 | ADPS1 | ADPS0
	ADCSRA = B10001111;

Aš va šitaip įsirašau komentarą su registro bitukų pavadinimais, tada, atsivertus lakštą, paprasčiau įrašyti atitinkamus bitukus keičiant konfigūraciją. Yra žmonių, kurie visaip kitaip išsidirbinėja, bet man asmeniškai šitas būdas patinka labiausiai. Na, tiesiog taip paprasčiau.

Taigi paruošėme ADC darbui su norimais nustatymais. Dabar reikia suderinti ir ADC multiplekserį: nurodyti atraminės įtampos šaltinį bei ADC įvadą. Štai ADCMUX registro išklotinė pagal specifikacijos lakštą:

AVR ADC multiplekserio nustatymų registras ADCMUX | Darau, blė

REFSx registrais galima nustatyti kelis variantus, su kokia atramine įtampa atlikti ADC konvertavimą. Jeigu turite eilinį Arduino ar šiaip paprastą AVR schemutę be visokių ten gudrių „hakų“, tai nesukite galvos ir visada REFS1 nustatykite į nuliuką, o REFS0 — į vienetuką. Taip ADC nurodoma, kad atraminė įtampa — tai tiesiog maitinimo įtampa, be jokių ten gudrybių. Mygtukams nuskaitinėti to visiškai per akis.

ADLAR bitukas sukeičia vietomis ADC reikšmės baitus. Na, kadangi AVR ADC yra dešimties bitų, tai į vieną baitą netelpa. Todėl reikšmė grąžinama per du vieno baito registrus. ADLAR nustatymas į vienetuką aukštesnįjį baitą sumaino su žemesniuoju. Čia tokia subtilybė yra, kad kartais užtenka tik vieno baito tikslumo, o registrai atlaisvinami nuskaičius aukštesnįjį baitą. Tad jei reikia tik „pusės“ reikšmės, galima registrus sukeisti vietomis ir iš karto atlaisvinti. Na, kol kas geriau nesigilinkit, kam to reikia, o jei labai norėsit, tai prie alaus bokalo galėsiu papasakot. Šiaip statom čia nuliuką, t.y. nieko nekeičiam ir gyvenam laimingai.

Ketvirtas bitukas nenaudojamas. Statom čia nulį.

O štai keturi MUXx bitukai nurodo kurį gi AVR įvadą konvertuoti. Jei mygtukams apsisprendėte kokį nors konkretų naudoti (aš, pavyzdžiui, naudoju trečią), tai dabar jau galima čia tą reikšmę ir „įkalti“.

Ką gi, setup() paprogramėje išdidžiai įrašome:

	// REFS1 | REFS0 | ADLAR |  –-  | MUX3 | MUX2  | MUX1  | MUX0
	ADMUX = B01000011;

Va ir viskas. ADC „susetupintas“. Kita eilutė po šitų turėtų kažkur būti sei() — globalus pertraukimų įjungimas, o po to ir paliepimas ADC pradėti konvertavimą:

ADCSRA |= 1<<ADSC;

Čia, kaip galbūt supratote, į jau minėto ADCSRA registro ADSC bituką įrašomas vienetas: ADC liepiama pradėti konvertavimo darbą. Šis bitukas paties ADC po to automatiškai vėl nustatomas į nuliuką ir po pertraukimo ADC sustoja.

Dabar dar reikia apsirašyti paprogramę ADC konvertavimo pabaigos pertraukimui „gaudyti“. Tarkime, kad ji kažką ten padarys, o po to jos pabaigoje vėl liepsime pradėti naują ADC konvertavimo ciklą — tai ir bus „pusiau-nuolatinis“ ADC režimas:

volatile int adc = 0;

ISR(ADC_vect) {
    // Pirmiausia nuskaitom žemesnįjį registrą ADCL.
    adc = ADCL;
    // Ir tik po to — ADCH. Jei darysime priešingai, ADCL registre
    // galime gauti visiškai nenuspėjamą reikšmę! Turėkite ant omens!
    // Kadangi ADCH yra aukštesnysis baitas, stumtelime jį draugiškai.
    adc |= ADCH << 8;

    // Na, o čia darome kažką su kintamojo „adc“ reikšme. Sprendžiam,
    // pavyzdžiui, koks gi čia mygtukas buvo nuspaustas ar kažką.

    // Ir vėl liepiam ADC pradėti darbą iš naujo!
    ADCSRA |= 1<<ADSC;
}

Tai va tokios tokelės tokeliausios. Tikiuosi, kad norėdami viską pakopijuoti suprasite, kaip iš šitų kodo gabaliukų programą susidėlioti 🙂 Sėkmės visokios bandantis ADC pertraukimus ir kitas gudrybes.

Reklama
Komentarai
  1. n/a parašė:

    sveiki,
    “apie 50–200 MHz“ tikriausiai turetu buti “apie 50–200 kHz“

Parašykite komentarą

Įveskite savo duomenis žemiau arba prisijunkite per socialinį tinklą:

WordPress.com Logo

Jūs komentuojate naudodamiesi savo WordPress.com paskyra. Atsijungti / Keisti )

Twitter picture

Jūs komentuojate naudodamiesi savo Twitter paskyra. Atsijungti / Keisti )

Facebook photo

Jūs komentuojate naudodamiesi savo Facebook paskyra. Atsijungti / Keisti )

Google+ photo

Jūs komentuojate naudodamiesi savo Google+ paskyra. Atsijungti / Keisti )

Connecting to %s