12. Keskeytykset ja niiden käyttäminen 1

Julkaistu: sunnuntai 12.2.2017

Tällä kertaa epistolan aiheena on keskeytykset ja niiden käyttäminen ja pureudutaan myös siihen, milloin on järkevää käyttää keskeytystä. Aihe on yksinkertainen, joten pyritään selityskin pitämään sellaisena.

Keskeytys on - kuten nimestä voi päätellä, jonkin toiminnan keskeyttävä "poikkeus". Kun mikrokontrollerin CPU havaitsee keskeytyksen, niin CPU:n sen hetkinen ohjelman suoritus pysähtyy ja CPU siirtyy suorittamaan ohjelmaa keskeytyskutsuun. Englanniksi puhuttaessa käytetään termiä interrupt, joka on suora käännös sanalle keskeytys. Tämä oli aika tekninen ilmaisu, joten otetaan maanläheisempi esimerkki.

Ajatellaan vaikkapa kytkintä ja sen painamista. Aikaisemmin on kytkintä luettu ns. pollaamalla taikka 'kyselemällä' input-pinnin tilaa jossakin ehtolauseessa. Eli on säännöllisesti tarkistettu, onko kytkintä painettu vai ei. Vaikka pollaavassa menetelmässä ei sinänsä mitään väärää ole, se ei kuitenkaan ole kaikista järkevin tapa toteuttaa tämän tyyppistä toimintoa. Pollaus toimii luotettavasti niin kauan, kuin ohjelmoija on varma siitä, että CPU suorittaa kyseisen koodirivin. Jos kytkintä pitäisi kuitenkin pystyä lukemaan milloin tahansa mistä tahansa, niin ainoa vaihtoehto on käyttää keskeytystä. Ja tämähän sopii meille.

Jos ihmettelet, mitä keskeytys fyysisesti tai pikemminkin elektronisesti on, niin keskeytys on elektroniikalla toteutettu tapahtuma, joka kykenee ohjaamaan mikrokontrollerin prosessorin toimintaa.

Keskeytysrutiini

Keskeytyksissä olennaisena asiana on keskeytysrutiini, eli se ohjelman osa mihin ohjelman suoritus "hyppää", kun keskeytys on havaittu. Keskeytysrutiini on lähes kuin mikä tahansa muu aliohjelma tai funktio, mutta pieniä eroja siinä kuitenkin on. Keskeytysrutiinin kirjoittamiseksi tarvitsee esikääntäjälle (engl. preprocessor) kertoa, että kirjoitettavat rivit kuuluvat keskeytyskäsittelijälle. Voi kuulostaa hankalalta, mutta otetaanpa esimerkki kytkimen painalluksen havaitsemisesta keskeytyksen avulla.

Ohjelmaesimerkki

Ajatellaan, että haluamme käyttää Launchpadin MSP430G2231 mikrokontrollerin porttipinniä P1.3 aiheuttamaan keskeytyksen, kun kytkintä S2 painetaan Launchpadilla. Koska kytkin S2 on kytketty ylösvetovastuksella mikrokontrolleriin kiinni, niin keskeytys täytyy asettaa laskevalle reunalle. Alla oleva kuva havainnollistaa mitä pinnissä P1.3 tapahtuu kun kytkintä painetaan.

Pääohjelmassa täytyy siis oikea porttipinni ensin alustaa ja keskeytys sallia sekä pinnille, että globaalisti. Alustetaan myös punainen LED1 lähdöksi painalluksen havaitsemista varten. Eli alustus ja pääohjelma näin:

#include <msp430g2231.h>

void main(void)
 {
 WDTCTL = WDTPW + WDTHOLD; // watchdog pysäytys
 P1DIR |= BIT0; // LED1 (P1.0) lähdöksi
 P1DIR &= ~BIT3; // S2 (P1.3) kytkin tuloksi
 P1OUT &= ~BIT0; // LED1 pois päältä
 P1IE |= BIT3; // S2 kytkimen keskeytyksen sallinta
 P1IES |= BIT3; // S2:n keskeytys laskevalle reunalle, eli kun kytkintä painetaan
 P1IFG &= ~BIT3; // Nollataan keskeytyslippu varmuuden vuoksi
 __enable_interrupt(); // viimeiseksi alustuskomennoksi sallitaan globaalit keskeytykset

while(1)
 {
 // ei tehdä mitään
 }
 }

Uutena asiana tässä tuli porttipinnien rekisterit sekä komento __enable_interrupt(). Edellä mainittu komento sallii siis globaalit keskeytykset. Globaali keskeytysten sallinta tarkoittaa yksinkertaisesti sitä, että kaikki aktivoidut keskeytykset sallitaan. Aktivoidulla keskeytyksellä tarkoitan esimerkiksi yllä olevan ohjelman kytkimen keskeytyksen sallintaa rekisterillä P1IE.

Kun pääohjelma on valmis, niin seuraavaksi täytyy kirjoittaa keskeytysrutiini, joka käsittelee kytkimen painalluksen. Tämä tehdään pääohjelman alle seuraavasti:

#pragma vector=PORT1_VECTOR
__interrupt void P1_keskeytys(void)
{
     P1OUT ^= BIT0; // toggletetaan lediä, eli vilkutetaan sitä päällä ja pois 
     P1IFG &= ~BIT3; // nollataan keskeytyslippu, jotta keskeytyskäsitteljästä päästään pois 
}

Yllä on kirjoitettu jokseenkin erilaisen näköinen funktio kuin aiemmin, joten seuraavaksi valaisen hieman mitä yllä olevat rivit tarkoittavat. Itse funktion sisältö lienee jo tuttua, joten sitä ei varmaankaan tarvitse käsitellä.

#pragma on esikääntäjälle (engl. preprocessor) varattu sana, minkä avulla Code Composer Studion kääntäjä ja linkkeri osaavat yhdistää oikeat asiat oikeaan paikkaan. Samalla rivillä on myös komento vector = PORT1_VECTOR, joka kertoo yhdessä #pragma -sanan kanssa, että toiminto liittyy portista 1 tulevaan tietoon. Eli tässä tapauksessa portissa 1 olevien pinnien keskeytystoimintoon.

Seuraavalla rivillä, __interrupt -sana kertoo kääntäjälle, että seuraava funktio, joka kirjoitetaan kuuluu esiteltyyn keskeytykseen. void P1_keskeytys(void) on muutoin normaali funktion esittely, mutta sitä ei kuitenkaan tarvitse tiedoston alussa esitellä kuten ns. "normaali" funktio yleensä esitellään. Funktion nimen voi siis itse valita, kuten tässäkin on tehty (luonnollisesti pitää noudattaa nimeämissääntöjä).

Ohjelman kulku

Ohjelman käynnistyessä, pääohjelma alustaa porttipinnit oikeaan tilaan ja sallii globaalit keskeytykset. Tämän jälkeen ohjelman suoritus siirtyy while -silmukan sisään, missä ohjelma pysyy kunnes virta katkaistaan tai CPU:n suoritus keskeytetään tekemällämme keskeytyksellä. Heti kun nappia painetaan, while -silmukan suoritus keskeytyy ja ohjelman suoritus siirtyy keskeytysrutiinin suoritukseen ja punainen led syttyy. Kun nappia painetaan uudestaan, punainen led sammuu. Vóila, keskeytys toimii ja on opittu vilkuttamaan lediä uudella tavalla.

Otan tähän loppuun vielä lyhyen yhteenvedon keskeytyksistä. Eli keskeytysten toimimiseksi täytyy aktivoida haluttu keskeytys, kirjoittaa keskeytysrutiini ja sallia globaalit keskeytykset. Seuraavassa osassa käydään läpi kuinka voidaan havaita esimerkiksi useamman kytkimen keskeytykset.