14. Keskeytykset ja niiden käyttäminen 3

Alunperin julkaistu: 12.2.2017

Viimeksi muokattu: sunnuntai 12.2.2017

Kaksi edellistä osaa on käsitellyt keskeytyksiä, joissa keskeytyksen on aiheuttanut kytkin, joka on ollut kytkettynä portteihin P1.3 tai P1.4. Tässä osassa jatketaan vielä keskeytysten pohdintaa lisää, koska mikrokontrollereiden ohjelmoinnissa keskeytysten käyttö on enemmän sääntö kuin poikkeus.

Uutena asiana tulee kuitenkin ajastimen, eli timer-lohkon käyttö MSP430G2231 mikrokontrollerilla. Ennen kuin mennään varsinaiseen ohjelmaan ja esimerkkiin, selitetään hieman mikä ajastin eli timer ylipäätään on.

Mikrokontrollerin ajastimista

Mikrokontrollereilla on yleensä useita erilaisia ajastimia, joita voidaan käyttää luomaan ajastettuja keskeytyksiä tai naksuttelemaan porttipinniä tietyssä tahdissa. Näitä voidaan myös käyttää PWM-ohjausten (Pulse Width Modulation) signaalien luomiseen. Lisäksi Launchpadillä timer-lohkoja käytetään myös kapasitiivisten antureiden osana.

Ajastin eli timer on yksinkertaisesti kuten herätyskello. Se asetetaan 'hälyttämään' tietyn ajan kuluttua ja kun aika on tullut täyteen tai viisari naksahtaa hälytysviisarin kohdalle, niin summeri tai muu hälytin alkaa soimaan.

Tällä mikrokontrollerilla ei tietenkään sisäistä "herätyskelloa" saatikka äänilähdettä ole, mutta ajastin voidaan asettaa aiheuttamaan keskeytys, minkä perusteella ohjelma voi ohjata jotain ulkoista summeria tai mitä tahansa laitetta. Käyttökohteita on paljon ja jokainen varmaan keksii omanlaisensa esimerkin tästä aiheesta. (Monipuolisimmilla MSP430 mikrokontrollereilla on RTC-lohko (Real-Time Clock = reaaliaikakello), joka voidaan asettaa ohjelmallisesti hälyttämään, mutta Launchpadin MSP430G2231 mikrokontrollerista sellainen puuttuu).

MSP430G2231 ajastin (Timer A)

MSP430G2231 -mikrokontrollerilla on yksi 16-bittinen ajastin eli timer. Tämä on nimeltään Timer A. 16-bittinen timer tarkoittaa sitä, että maksimiarvo, mihin timer voi laskea tai 'tikittää' on 216-1 tai heksana 0xFFFF. Kun laskuri on laskenut maksimiarvoonsa, laskenta lähtee alusta.

Keskeytyksiä voidaan generoida esimerkiksi laskurin ylimenosta (overflow), jolloin laskuri on päässyt suurimpaan arvoonsa ja aloittaa laskentakierroksen alusta. Keskeytyksiä voidaan myös generoida erilaisten vertailijoiden (capture/compare) avulla, missä vertailurekisteriin asetetaan haluttu arvo. Kun laskuri on laskenut samaan arvoon kuin mikä on vertailurekisterissä, niin vertailurekisterin keskeytys tai porttipinnin "naksutus" tapahtuu.

Timer-lohkoa ohjataan siis rekistereiden avulla. Timerin käyttöön ja ohjaukseen liittyykin olennaisesti viisi rekisteriä: TACTL, TAR, TACCRx, TACCTLx ja TAIV. Nämä rekisterit on lyhyesti esitelty alla.

16-bit. timer/counter (ajastin/laskuri) rekisteri TAR

Ensimmäinen läpi käytävä rekisteri on nimeltään TAR. Tämä on kuten otsikkokin kertoo 16-bittinen rekisteri. Tämä on se rekisteri, mikä toimii kaiken laskennan perustana timerissa. TAR -rekisteri voi laskea joko ylöspäin (eli 0..1..2..n) tai alaspäin (n..34...33..32..) ja sen alkuarvon voi aina asettaa ohjelmallisesti. Jos tätä rekisteriä muokkaa, niin on suositeltavaa että laskurin/ajastimen toiminta pysäytetään muokkauksen ajaksi. TAR-rekisteristä selviää siis se luku, missä laskuri on ns. menossa.

Timer A kontrollirekisteri TACTL

TACTL-rekisteri ohjaa timerin toimintaa. Tämän rekisterin avulla timer voidaan pysäyttää ja käynnistää. Timerin kellon lähdettä ja laskentasuuntaa voidaan muuttaa ja keskeytyksiä hallita. Kyseessä on siis timerin toiminnan kannalta olennainen rekisteri.

TACCRx Capture/Compare rekisteri

Tähän rekisteriin puolestaan voidaan asettaa arvoja, joita verrataan TAR-rekisteriin. x-merkki rekisterin nimen perässä kertoo siitä, että x:n paikalle voidaan laittaa numero. Eri MSP430 sarjan mikrokontrollereilla on eri määrä TACCRx -rekistereitä, minkä vuoksi rekisteri nimetään käyttöohjemanuaalissakin tällä tavalla. Esimerkkien kautta näiden käyttö tulee selville.

TACCTLx Capture/Compare kontrollirekisteri

Jos olet huomannut nimeämisissä jotain logiikkaa, niin arvasitkin että tämä rekisteri ohjaa capture/compare -lohkon toimintaa. Helpommin sanottuna tämä rekisteri ohjaa 'vertailijan' toimintaa. Tämän rekisterin avulla voidaan ohjata tapahtuuko signaaleiden vertailu nousevalla vai laskevalla reunalla yms. Edelleen tämän rekisterin käyttö tulee parhaiten selville kun tutustutaan esimerkkikoodeihin tarkemmin.

TAIV eli keskeytysrekisteri

Otsikko kertoo tarpeeksi, mutta muutama sananen tästä lienee kuitenkin paikallaan. Tämä keskeytysrekisteri pitää sisällään tietoa siitä, tuliko keskeytys laskurin ylivuodosta (overflow) vai capture/comparesta eli 'vertailijalta' . Itse asiassa enempää tietoa tästä rekisteristä ei saa irti.

Timer ja kellosignaali

Koska timer toimii jollakin kellotaajuudella aivan kuten CPU:kin, niin on tärkeää ymmärtää mistä kellosignaali otetaan. On myös hyvä tietää, että CPU:n kellotaajuus ei aina ole sama kuin timerin kellotaajuus, vaikka se voi toki olla jos ohjelmoija niin haluaa.

MSP430G2231 timeria voidaan kellottaa käytännössä neljästä eri lähteestä, jotka ovat TACLK, ACLK, DCOCLK ja SMCLK. TACLK on kytkettynä porttipinniin P1.0, joten jos haluaa niin tähän pinniin voi syöttää ulkoista kellosignaalia (jännitetasot huomioon ottaen) - tähän pinniin ei kuitenkaan kytketä Launchpadin mukana tullutta kidettä!

DCOCLK (Digitally Controlled Oscillator Clock) on mikrokontrollerin CPU:n käyttämä kello ja se toimii ilman rekistereihin kirjoitettuja muutoksia vakiotaajuudella n. 1,1 Mhz.

SMCLK (Sub-System Master Clock) on sisäinen kello mikä kellottaa mikrokontrollerin eri moduuleiden taikka sisäisten laitteiden (peripheral) toimintaa. SMCLK kellon nopeutta voidaan myös erikseen säätää ja sille voidaan asettaa jakajia jotka pienentävät kellotaajuuden nopeutta.

ACLK (Auxiliary Clock) on puolestaan timerin kellolähteenä silloin kun Launchpadin mukana tullutta kidettä halutaan käyttää. Se täytyy toki juottaa paikalleen ensin. Alla olevassa kuvassa kide on juotettu paikalleen. Joten jos et vielä Launchpadiisi kidettä ole laittanut, niin nyt olisi sopiva hetki puuhastella sen kanssa. Kidettä tarvitaan eteen tulevissa esimerkeissä.

Timer A esimerkki 1: keskeytys ylivuodosta

Ensimmäiseksi timerin käytöstä olen päättänyt kirjoittaa otsikon mukaisen esimerkin, koska ylivuoto on mielestäni helpoin ymmärtää ainakin aluksi. Tässä esimerkissä laskurin annetaan vapaasti 'juosta' koko ajan. Kun laskuri tulee täyteen, niin se ylivuotaa tai pyörähtää ympäri, jolloin aiheutuu keskeytyslipun TAIFG asettuminen.

Kuten aiemmin on mainittu, niin timer on 16-bittinen. Tämä siis tarkoittaa että laskuri voi laskea aina lukuun 216 = 65536 asti. Koska laskenta lähtee nollasta liikkeelle, niin laskurin askelluksia on kuitenkin yksi vähemmän, eli 65535. Tässä vaiheessa kun tiedämme mihin lukuun timer voi laskea, meidän täytyy seuraavaksi selvittää millä kellotaajuudella timerimme toimii. Koska tässä esimerkissä käytämme MSP430G2231 mikrokontrollerin DCO:n (Digitally Controlled Oscillator) oletustaajuutta sen käynnistymishetkellä, niin kellotaajuudeksi tulee noin 1,1 megahertsiä.

Kun kellotaajuus ja laskurin yläraja on selvillä voimme laskea millä taajuudella ylivuotoa tapahtuu. Tämä tapahtuu siten, että jaetaan kellotaajuus askelilla, jotka laskuri voi ottaa. Eli:

1,1 MHz / (216-1) = 1100000/65535 = 16,784 hertsiä.

Laskurin ylivuoto tapahtuu siis tällä taajudella. Hienoa. Nyt voimme käyttää vaikkapa lediä ilmaisemaan kun ylivuoto tapahtuu. Pitemmittä puheitta koodi tulee alla. Aluksi siis pääohjelman koodi:

#include <msp430g2231.h>

void main(void)
{
     WDTCTL = WDTPW + WDTHOLD; // watchdog pysäytys 
     P1DIR |= BIT0; // LED1 lähdöksi 
     P1OUT &= ~BIT0; // LED1 pois päältä
     ​// asetetaan jakaja 0, laskentamoodi jatkuva ja 1,1 MHz kello
     TACTL |= ID_0 + TAIE + TASSEL_2; 
     __enable_interrupt(); // sallitaan globaalit keskeytykset 
     TACTL |= MC_2; // käynnistetään laskuri
     while(1)
     {
          // ei tehdä mitään
     }
}

Pääohjelman kulku ja alustukset

Pääohjelmassa asetetaan ensin vahtikoira pois päältä, punainen led lähdöksi ja pois päältä. Sen jälkeen kirjoitetaan TACTL rekisteriin arvot, joiden kuvaus on alla:

ID_0 = Input divider, eli taajuuden jakaja. Tällä voidaan asettaa jaetaanko timerille tulevaa kelloa mitenkään. Mahdolliset jakajat ovat 1,2,4 ja 8. Jakolukuja vastaavat arvot ovat ID_0 - ID_3.

TAIE = Timer A Interrupt Enable eli ylivuodon keskeytyksen sallinta.

TASSEL_2 = Timer A Source Select, eli timerin kellon valinta. Tällä valitaan mitä kelloa timer käyttää. Mahdolliset kellot on selitetty yllä olevissa kappaleissa mutta selvyyden nimissä mahdolliset arvot ovat:
TASSEL_0 = ulkoinen kellotulo porttipinniin P1.0
TASSEL_1 = ACLK, eli kide
TASSEL_2 = SMCLK, eli niin sanotusti "lisälaitekello"
(TASSEL_3 = INCLK joka on tässä tapauksessa sama asia kuin TASSEL0.)

Lopuksi sallimme globaalit keskeytykset ja käynnistämme laskurin kirjoittamalla TACTL-rekisteriin arvon MC_2, joka käynnistää laskurin ja asettaa sen jatkuvaan laskentatilaan. Alla on vielä selvennetty muut arvot:

MC_0 = pysäyttää laskurin
MC_1 = Ylöspäin laskeva. Timer pysähtyy kun se saavuttaa saman arvon mikä on TA0CCR0-rekisteriin asetettu.
MC_2 = Ylöspäin laskeva, mutta timer ei pysähdy vaan aloittaa kierroksen aina uudetaan nollasta.
MC_3 = Ylös- ja alaspäin laskeva, eli laskuri laskee arvoon joka on TA0CCR0 rekisterissä ja saavutettuaan sen arvon aloittaa laskemaan alaspäin kohti nollaa. Tämmöinen "jumppaava" tapa.

Huom! Eri rekisterien arvot ja merkitykset löytyvät myös käyttöohjemanuaalista, elimsp430x2xx Family User's Guide -dokumentista.

Keskeytysrutiini

Noniin. Nyt on saatu pääohjelma selitettyä, joten on aika kirjoittaa keskeytys! Ja sehän tulee tässä:

#pragma vector = TIMERA1_VECTOR
__interrupt void ylivuoto(void)
{
     P1OUT ^= BIT0; // käännetään ledin tila 
     TACTL &= ~TAIFG; // nollataan keskeytysbitti/-lippu
}

Keskeytysvektori on edellisen osan esimerkistä poiketen tietysti timer-lohkon keskeytys. Pragma -taikasanalla kerrotaan kääntäjälle ensin että kyseessä on keskeytysvektoriin liittyvä tapahtuma ja toisella taikasanalla __interrupt kerrotaan, että kyseessä on keskeytysrutiini. Loppu koodista selittänee itse itsensä, mutta erityisen tärkeää on nollata TAIFG keskeytyslippu, sillä muutoin ohjelma ei toimi oikein vaan mikrokontrolleri resetoituu muutaman keskeytyksen jälkeen.

Ohjelman toiminta

Kun ohjelma käännetään ja ladataan mikrokontrollerille, niin ohjelman tutkiminen voi alkaa. Jos laitat ohjelman ajoon, niin huomaat kuinka punainen led vilkkuu tiheään tahtiin. Tämä tahti on aikaisemmin laskemamme 16,784 hertsiä. Tahtia voidaan hidastaa muuttamalla esimerkiksi timerin jakajaa. Joten ei muuta kuin kokeilemaan!

Timer A esimerkki 2: yhden sekunnin ajastin

Koska usein tarvitsee tehdä jotain muutakin kuin vain välkytellä lediä, niin tehdäänpä nyt sitten jotain hyödyllistä - tosin ei jätetä ledin vilkutusta tässäkään pois ;). Jos et vieläkään ole juottanut launchpadin 32 kilohertsin kidettä paikalleen NIIN TEE SE NYT! Jos et myöskään aio käyttää kiteelle varattuja pinnejä jatkossa, voit poistaa kaksi 0-ohmista vastusta R29 ja R28. Näiden poisto ei välttämättä ole tarpeellista, mutta itse poistin ne etteivät hajakapasitanssit häiritse kiteen toimintaa. Kuva vastuksettomasta versiosta alla. (Muista katkaista virta ennen operointia).

Tässä esimerkissä tehdään yhden sekunnin ajastin, joka toimii perustana esimerkiksi kellosovellukselle. Launchpadin mukana tuleva 32768 hertsin kide on tarkoitettu juuri tällaiseen sovellukseen. Kide on vähävirtainen ja tarkka. Tarkkuus on tietenkin tärkeää kellossa.

Käytämme kyseistä kidettä laskurin kellona, joten laskuri saa 32768 nousevaa ja laskevaa reunaa sekunnissa. Laskurimme on edelleen 16-bittinen, eli sen maksimiarvo on 65536. Ja kuten huomaat, niin laskurin maksimiarvo on vähemmän kuin 32768 hertsiä. Näin ollen meidän tarvitsee antaa laskurin juosta vain arvoon 32767 (jälleen yksi askellus vähennetään laskurin arvosta). Saatu luku tarvitsee nyt vain ladata vertailurekisteriin TACCR0 ja TACTL rekisteriin täytyy laskurin tila asettaa niin, että laskuri hyppää takaisin nollaan kun se on laskenut samaan arvoon kuin mikä vertailurekisterissä on.

MSP430G2231 mikrokontrollerin sisäinen logiikka osaa käynnistää kiteen silloin, kun jokin laite (tässä tapauksessa siis timer) sitä pyytää, joten kidettä ei sen erityisemmin tarvitse alustaa muuten kuin kytkemällä pinnien sisäinen kapasitanssi päälle.. Pääohjelman ja keskeytysrutiinin koodi on alla.

#include <msp430g2231.h>
unsigned int sekunnit=0; // luodaan globaali muuttuja joka pitää sekunnit tallessa 

void main(void)
{
     WDTCTL = WDTPW + WDTHOLD; // watchdog pysäytys 
     P1DIR |= BIT0+BIT6;                // LED1 ja LED2 lähdöksi
     P1OUT &= ~(BIT0+BIT6);           // LED1 ja LED2 pois päältä 

     BCSCTL3 = XCAP_3;      // asetetaan 12,5 pF:n sisäiset kondensaattorit aktiiviseksi
     CCTL0 = CCIE;           // sallitaan vertailijan keskeytys 
​     // asetetaan jakaja 0, laskentamoodi vertaileva ja ACLK-kello. 
     TACTL |= ID_0 + TASSEL_1;  
     CCR0 = 32767;           // ladataan vertailurekisterin arvo 1 sekunnin keskeytystä varten

     __enable_interrupt();      // sallitaan globaalit keskeytykset 
     TACTL |= MC_1;      // käynnistetään laskuri

     while(1)
     {
          // sytytetään/sammutetaan vihreä led joka viides sekunti 
          if(sekunnit == 5)
          {
               P1OUT ^= BIT6; // käännetään ledin ohjaus toiseen tilaan
               sekunnit = 0; // nollataan sekuntilaskuri jotta vertailu onnistuu joka kierroksella
          }
     }
}

// huomaa että tässä tapauksessa keskeytysvektori on TIMERA0
#pragma vector = TIMERA0_VECTOR  
__interrupt void TimerA0(void)
{
     P1OUT ^= BIT0;
     sekunnit++;
}

Ohjelman kulku ja alustukset

Kun ohjelma käynnistyy, niin asetukset ladataan rekistereihin. Koodissa on kommentoituna olennaisimmat asiat, joten suurempia selityksiä ei varmaankaan tarvita tuttujen asioiden osalta.

Joukossa on kuitenkin pari uutta asiaa, kuten BCSCTL3 rekisterin käyttö, missä asetetaan kiteen vaatimat kondensaattorit aktiivisiksi. Kiteissä tarvitaan yleensä aina jonkin verran kapasitanssia värähtelyn aikaan saamiseksi ja MSP430-sarjan mikrokontrollereissa on mahdollista käyttää sisäisiä kapasitansseja kidettä avittamaan.

Kun kide-asetukset on tehty, niin CCTL0 -rekisteriin kirjoitetaan bitti CCIE joka sallii capture/compare lohkon keskeytyksen. Tässä kohtaa voi myös käyttää rekisteriä TA0CCTL0, joka on käytännössä sama asia kuin CCTL0. CCTL0 on vain lyhennetty versio TA0CCTL0 rekisterin nimestä. Jos kurkkaat tiedostoon msp430g2231.h, niin löydät sieltä rekisterin nimien selitykset ja lyhenteet joista tämä asia käy ilmi.

CCR0 -rekisteriin on ladattu tarvitsemamme vertailuarvo, minkä perusteella 1 sekunnin keskeytys muodostuu. Yhden sekunnin välein asetetaan punainen led eri tilaan ja sen huomaat kun ajat ohjelman koodia launchpadilla. CCR0 on myös lyhenne TA0CCR0 rekisterin nimestä, joka edelleen käy ilmi kurkkaamalla tiedostoon msp430g2231.h.

Laskuri käynnistetään TACTL rekisteriin kirjoittamalla MC_1, joka kertoo timer-lohkolle että nyt lasketaan kohti vertailuarvoa joka on rekisterissä CCR0.

Pääohjelmassa käännetään vihreän ledin tila toiseksi joka viides sekunti ja nollataan sekuntimuuttuja, jotta vertailu toimii oikein. Keskeytyksessä kasvatetaan sekunnit-muuttujaa ja asetetaan punainen led toiseen tilaan.

Ohjelman toiminta

Kun ajat ohjelmaa, huomaat kuinka punainen led vilkkuu sekunnin välein ja joka viides sekunti vihreä led vaihtaa tilaansa. Näin on saatu aikaiseksi jokaisen aikaa ylläpitävän laitteen sielu ;).