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: 22764, Oldal olvasási idő: kb. 9 perc
Lapozás: OK   4 / 8

Programmegszakítások

A mikrovezérlő programjának végrehajtása, utasításainak sorrendje többnyire előre megszabott, előre kiszámítható módon történik. Vannak azonban kivételek, amikor a program normális menete megszakad:

Programmegszakítások: általában külső vagy belső hardver esemény hatására következnek be (ha előzetesen engedélyeztük!), jelezve, hogy valamelyik periféria vagy alrendszer sürgős kiszolgálást kér. Ilyenkor a főprogram futása megszakad, s egy, a megszakítást kiszolgáló eljárásra kerül a vezérlés, melynek befejeztével a főprogram a megszakítás helyétől tovább folytatódik.  

Újraindítás (reset): általában ezt is hardver események váltják ki. Pl. az RST láb lehúzása, a tápfeszültség leesése, vagy más katasztrofális esemény, ami a program normális folytatását lehetetlenné teszi. Újraindítás véletlenül is bekövetkezhet ha a program elején a Wathchdog időzítőt elfelejtjük kikapcsolni. Minden RESET esemény azzal jár, hogy a program egy jól meghatározott állapotból újraindul.

A kivételek közül most a programmegszakításokkal ismerkedünk meg:

Képzeljük el, milyen volna, ha a mobiltelefont hívásjelzés nélkül használnánk! Időnként elővennénk a zsebünkből, s beleszólnánk: "Halló, van valaki a vonalban?". Bizonyára nevetségesnek tűnik, pedig nagyon sok program így kezeli az eseményeket (ezt hívják lekérdezéses, vagy angolul polling módnak). A fenti módszer hátránya nyilvánvaló: vagy túl gyakran nézegetjük a telefont, fölöslegesen pazarolva ezzel az időt, vagy túl ritkán vesszük elő, s akkor lemaradhatunk egy fontos hívásról.

Sokkal hatékonyabb az a módszer, ha a telefon rendelkezik hívásjelzéssel, ami a programmegszakításhoz (interrupt) hasonlítható: ha hívás érkezik, cseng a telefon. Abbahagyom, amit éppen csinálok, s felveszem a telefont (kiszolgálom az interruptot). A hívás befejeztével visszatérhetek a korábbi tevékenység folytatásához.

A mikrovezérlők felépítése a programmegszakítások révén lehetővé teszi, hogy:

  • a program hasznos tevékenységgel töltse az idejét, míg az események bekövetkezésére vár
  • az esemény bekövetkeztekor viszont haladéktalanul reagáljon

A programmegszakítás (interrupt) tehát azt jelenti, hogy a program futása egy külső vagy belső esemény bekövetkezte miatt megszakad, s majd csak a programmemória egy másik helyén elhelyezett utasítássorozat (a progammegszakítás kiszolgálását végző kód) lefutása után tér vissza a CPU az eredeti program folytatásához, ahogy ezt az alábbi ábrán láthatjuk.

4_1. ábra: A program normál menetének megszakítás

Természetesen ahhoz, hogy a programmegszakítás kiszolgálása után zavartalanul folytatódhasson a program futása, a programmegszakítási alrendszernek el kell mentenie a futó program állapotát (azt, hogy melyik a soron következő utasítás, s hogy mi volt a státuszbitek tartalma), visszatéréskor pedig helyre kell állítania.

Az MSP430 mikrovezérlők vektoros megszakítási rendszerrel rendelkeznek. Ez azt jelenti, hogy a memória végén elhelyezett táblázatban minden megszakításjelzőhöz tartozik egy-egy bejegyzés, ezek az ún. vektorok. A vektorokba írhatjuk be a megszakításokat kiszolgáló eljárások kezdőcímeit. A megszakítási vektorok az 0xFFC0–0xFFFD címtartományban helyezkednek el, a 0xFFFE címen pedig a RESET vektor helyezkedik el. Az itt található (ide beírt) címről indul el a program bekapcsoláskor, illetve minden újraindításkor. A vektorok címkiosztását, prioritását és az egyes vektorokhoz kapcsolódó jelzőbitek nevét a mikrovezérlő adatlapja tartalmazza, táblázatos formában. A vektorok sorrendje egyúttal a prioritást is megszabja. Több, egyidejű megszakítási kérelem közül a magasabb prioritású érvényesül, s az a magasabb prioritású, amelynek vektora nagyobb memóriacímen található.

Mi történik a mikrovezérlőben, amikor egy esemény megszakítást kér?

Ahhoz, hogy egy periféria megszakítási kérelme érvényesüljön, előzetesen engedélyezni kell az adott perifériára vonatkozóan a megszakítást. Az első részben említettük, hogy az I/O portok esetében engedélyezhetjük, hogy a digitális bemenetnek kapcsolt láb megszakítást okozzon, ha állapota megváltozik. Ehhez a P1IE vagy P2IE regiszter megfelelő bitjét '1'-be kell állítani. A megszakítási kérelem érvényesülésének ezen kívül az is feltétele, hogy a mikrovezérlőben a programmegszakítási rendszer általánosságban is engedélyezve legyen. Van ugyanis egy GIE (General Interrupt Enable = általános megszakítási engedélyezés) bit, amelynek segítségével a megszakítási rendszer egészében engedélyezhető vagy tiltható.

A mikrovezérlőben egy programmegszakítási kérelem érvényesülésekor a következő dolgok történnek:

  1. a CPU befejezi annak az utasításnak a végrehajtását, amelynek során a programmegszakítási kérelem (valamelyik megszakításjelző bit bebillenése) történt. Ha a CPU alvó állapotban volt, akkor pedig bekapcsolódik az MCLK órajel.
  2. A programszámláló (PC) értéke elmentésre kerül a veremtár tetején
  3. Az állapotjelző regiszter (SR) értéke elmentésre kerül a veremtár tetején
  4. A megszakítási kérelmek közül kiválasztásra kerül a legmagasabb prioritású (ha egyszerre több kérelem futott be)
  5. Ha a vektorhoz csak egy megszakítási jelzőbit tartozik, akkor az automatikusan törlésre kerül.
  6. Az SR státusz regiszter is törlésre kerül, ami két következménnyel jár: egyrészt a további (maszkolható) megszakítások letiltásra kerülnek (mivel az SR regiszter GIE bitje is nullázódik), másrészt az esetleges energiatakarékos üzemmód felfüggesztésre kerül, a CPU aktív módba kerül.
  7. Az interrupt vektorban található cím bemásolódik a PC programszámlálóba, a CPU elkezdi végrehajtani a megszakítást kiszolgáló eljárást.

A fentiek összesen 6 CPU utasítás-ciklust vesznek igénybe. Ha a CPU a megszakításkor aktív módban volt, akkor ehhez még hozzá kell adni a megszakított utasítás végrehajtási idejét is, ami legkedvezőtlenebb esetben szintén 6 órajel-ciklus lehet. Így tehát a programmegszakítási kérelem megjelenése és a megszakítást kiszolgáló eljárás megkezdése közötti megszakítási késedelem (interrupt latency) akár 12 órajel-ciklus is lehet. 

Minden megszakítást kiszolgáló eljárásnak a RETI utasítással kell végződnie (C nyelvű programoknál nem találkozunk ezzel, hiszen a fordító teszi oda helyettünk), melynek hatására a következő dolgok történnek:

  1. Az SR státusz regiszter értéke visszaállításra kerül a veremtár tetejéről. GIE, illetve az energiatakarékos beállítások visszaállítódnak a megszakítás előtti állapotukba. Ez tehát újraengedélyezi a megszakításokat.
  2. A PC programszámláló értéke is visszaállításra kerül a veremtár tetejéről. A CPU ott folytatja a programot, ahol megszakításkor abbahagyta (vagy visszatér alvó állapotba, ha a megszakításkor nem futott a program).

Program megszakítása nyomógombbal

Az alábbi példában a főprogram LED2-t villogtatja, végtelen ciklusban. Ez lesz az a tevékenység, amelyet egy külső esemény (példánkban az S2 nyomógomb lenyomása) megszakít. A megszakítás kiszolgálásakor átbillentjük LED1 állapotát. Végeredményben tehát a program folyamatosan villogtatja a zöld LED-et, mi pedig eközben az S2 nyomógombbal ki-be kapcsolgathatjuk a piros LED-et. Az olvasó fantáziájára bízzuk, hogy a LED villogtatás és kapcsolgatás helyére képzeljen tetszés szerinti bonyolut és hasznos tevékenységet.

4_1. lista: A p1_3_interrupt.c program listája

  1. #include  "io430.h"
  2. #include "intrinsics.h"
  3.  
  4. void main(void) {
  5.   WDTCTL = WDTPW + WDTHOLD;  // Letiltjuk a watchdog időzítőt
  6. //--- P1 PORT beállítása -----------------------------------------------------  
  7.   P1OUT = 0;                 // Minden P1.x alacsony állapotba
  8.   P1DIR = ~BIT3;             // P1.3 bemenet, a többi kimenet  
  9. //--- P2 PORT beállítása kimenetnek     --------------------------------------    
  10.   P2SEL = 0;                 // P2.x legyen digitális I/O
  11.   P2DIR = 0xFF;              // Minden P2.x legyen kimenet
  12.   P2OUT = 0;                 // Minden P2.x alacsony állapotba
  13. //----------------------------------------------------------------------------
  14. // P1.3 bemenet beállítása: interrupt minden lefutó él beérkezésekor
  15. //----------------------------------------------------------------------------  
  16. //P1REN |= BIT3;             // P1.3 felhúzását itt engedélyezhetnénk
  17.   P1IES |= BIT3;             // P1.3 lefutó élre érzékeny
  18.   P1IFG &= ~BIT3;            // P1.3 IFG törlése
  19.   P1IE |= BIT3;              // P1.3 interrupt engedélyezése
  20. //--- Programmegszakítás engedélyezése és a fő programhurok ------------------  
  21.   __enable_interrupt();      // A programmegszakítás engedélyezése
  22.   while(1) {
  23.     P1OUT ^= BIT6;           // LED2 átkapcsolása
  24.     __delay_cycles(250000);  // Kb. 250 ms várakozás
  25.   }
  26. }
  27.  
  28. //--- Port 1 programmegszakítás kiszolgálása ---------------------------------
  29. #pragma vector=PORT1_VECTOR
  30. __interrupt void Port_1(void) {
  31.   P1OUT ^= BIT0;             // LED1 (P1.0) átbillentése
  32.   P1IFG &= ~BIT3;            // P1.3 interrupt jelzőbit törlése
  33. }


A program elején letiltjuk az "őrkutyát" (WDT), az S2 nyomógombhoz tartozó P1.3 lábat bemenetnek, P1 és P2 összes többi lábát pedig kimenetnek álltjuk be, és nullára húzzuk. Ha már beforrasztottuk az órakvarcot, akkor a P2 portra vonatkozó beállításokat hagyjuk ki!

A P1.3 bemenetnél most nem használjuk a belső felhúzást, mivel a Launchpad kártyán külső felhúzó ellenállás csatlakozik hozzá. Megjegyzésként beírva azonban megmutattuk, hogy hogyan lehetne engedélyezni a belső felhúzást. Ez még jól jöhet saját áramkör építésénél... A P1IES regiszter megfelelő bitjét '1'-be állítva előírjuk, hogy lefutó élre legyen a bemenet, akkor kérjen programmegszakítást, amikor magas szintről alacsony szintre vált a bemeneten a jel. a felhúzás miatt ugyanis a magas szint az alapállapot, s akkor változik alacsony szintre, amikor az S2 nyomógombot megnyomjuk. (A Launchpad kapcsolási rajzát a cikksorozat első részében találjuk meg.)

A P1.3-hoz tartozó megszakítás engedélyezése a P1IE regiszter 3. bitjének '1'-be állításával történik. Szokjuk meg, hogy a megszakítás engedélyezése előtt rutinszerűen mindig töröljük a hozzá tartozó megszakításkérő bitet.

A beállítások után még hátra van, hogy az SR regiszter GIE bitjének '1'-be állításával általánosságban is engedélyezzük a megszakítási alrendszer működését. Ezt a beállítást azonban C nyelvű utasítással nem tehetjük meg, hanem az __interrupt_enable() beépített függvényt kell meghívnunk. A végtelen ciklusban a szokásos módon villogtatjuk a zöld LED-et.

A megszakítás kiszolgálásakor csak átkapcsoljuk LED1 állapotát, s töröljük a megszakítási kérelmet jelző bitet. A PORT1_VECTOR-hoz több megszakításkérő bit tartozik, ezért nem törlődik automatikusan.

A programmegszakítás kiszolgálása C nyelven roppant egyszerűen megadható:

  1. A #pragma vector = .... direktívával megmondjuk, hogy az utána következő függvény melyik megszakítási vektrohoz tartozik. A megszakítási vektorok hivatalos nevei az adatlapból, vagy a mikrovezérlőhöz tartozó fejléc állományból (pl. msp430g2231.h) olvasható ki.  A PORT1_VECTOR tehát kötött név, a P1 port megszakítási vektorának kötelezően ezt a nevet kell használnunk. A kiszolgáló függvény Port_1 neve viszont tetszőleges, írhattunk volna helyette kismacskát is.
  2. A függvény deklarációja előtti __interrupt módosítóval jelezzük a fordítónak, hogy a programmegszakítást kiszolgáló eljárásról van szó, tehát gondoskodjon a szükséges regiszter mentésekről/visszaállításokról, s visszatéréskor ne RETURN, hanem RETI utasítással térjen vissza.

  1. #pragma vector=PORT1_VECTOR
  2. __interrupt void Port_1(void) {
  3.   ...
  4. }

Megjegyzések:

  • A programmegszakítást kiszolgáló eljárásoknak nem lehet sem formális paramétere, sem visszatérési értéke. Ezért áll a deklarációban a két "void" szó.
  • Itt most nem foglalkoztunk a nyomógomb pergésmentesítésével, teljes mértékben ráhagyatkoztunk a felhúzó ellenállás és a nyomógombbal párhuzamosan kötött 100 nF-os kondenzátor időállandójából (4,7 ms) adódó hardveres szűrésre, ezért ne lepődjünk meg, ha  a működésben olykor némi bizonytalanságot tapasztalunk!

Program menetének befolyásolása

Az előző programban a főprogram és a megszakítás egymástól teljesen függetlenül tették a dolgukat. Most nézzünk egy olyan példát is, amikor a megszakítás befolyásolja a főprogram további menetét! Az egyszerűség kedvéért az előző programot variáljuk meg egy kicsit: a főprogramban nem egy, hanem nyolc tevékenység közül lehet választani egy szoftveres kapcsoló és egy jelző segítségével. Az S2 gomb megnyomásakor mindig eggyel léptetjük a jelzőt, ügyelve, hogy annak értéke csak 0 és 7 közötti szám lehet. A jelzőt "volatile" (illékony) módosítóval kell deklarálni. Ez arra figyelmezteti a fordítót, hogy a változó értéke máshol (jelen esetben az interrupt kiszolgáló eljárásban) is módosul, minden felhasználáskor tehát a tárból elő kell venni az aktuális értéket.

4_2. lista: A p1_3_change_state.c program listája

  1. #include  "io430.h"
  2. #include "intrinsics.h"
  3.  
  4. //--- Az interrupt szinten módosított változó 'volatile' legyen!
  5. volatile int jelzo = 0;      // Jelző a főprogram irányításához
  6.  
  7. void main(void) {
  8.   WDTCTL = WDTPW + WDTHOLD;  // Letiltjuk a watchdog időzítőt
  9. //--- P1 PORT beállítása --------------------------------------------  
  10.   P1OUT = 0;                 // Minden P1.x alacsony állapotba
  11.   P1DIR = ~BIT3;             // P1.3 bemenet, a többi kimenet  
  12. //--- P2 PORT beállítása kimenetnek     -----------------------------    
  13.   P2SEL = 0;                 // P2.x legyen digitális I/O
  14.   P2DIR = 0xFF;              // Minden P2.x legyen kimenet
  15.   P2OUT = 0;                 // Minden P2.x alacsony állapotba
  16. //-------------------------------------------------------------------
  17. // P1.3 bemenet beállítása: interrupt minden lefutó él beérkezésekor
  18. //-------------------------------------------------------------------  
  19. //P1REN |= BIT3;             // P1.3 felhúzását itt engedélyezhetnénk
  20.   P1IES |= BIT3;             // P1.3 lefutó élre érzékeny
  21.   P1IFG &= ~BIT3;            // P1.3 IFG törlése
  22.   P1IE |= BIT3;              // P1.3 interrupt engedélyezése
  23. //--- Programmegszakítás engedélyezése és a fő programhurok ---------  
  24.   __enable_interrupt();      // A programmegszakítás engedélyezése
  25.   while(1) {
  26.     switch(jelzo) {
  27.     case 0:
  28.       P1OUT &= BIT0;         // Kimenetek törlése
  29.       P1OUT ^= BIT0;         // LED1 átkapcsolása
  30.     __delay_cycles(1000000); // Kb. 1000 ms várakozás
  31.     break;
  32.     case 1:
  33.       P1OUT &= BIT0;         // Kimenetek törlése
  34.       P1OUT ^= BIT0;         // LED1 átkapcsolása
  35.       __delay_cycles(500000);// Kb. 500 ms várakozás
  36.     break;
  37.     case 2:
  38.       P1OUT &= BIT0;         // Kimenetek törlése
  39.       P1OUT ^= BIT0;         // LED1 átkapcsolása
  40.       __delay_cycles(250000);// Kb. 250 ms várakozás
  41.     break;
  42.     case 3:
  43.       P1OUT &= BIT0;         // Kimenetek törlése
  44.       P1OUT ^= BIT0;         // LED1 átkapcsolása
  45.       __delay_cycles(125000);// Kb. 125 ms várakozás
  46.     break;
  47.     case 4:
  48.       P1OUT &= BIT6;         // Kimenetek törlése
  49.       P1OUT ^= BIT6;         // LED1 átkapcsolása
  50.     __delay_cycles(1000000); // Kb. 1000 ms várakozás
  51.     break;
  52.     case 5:
  53.       P1OUT &= BIT6;         // Kimenetek törlése
  54.       P1OUT ^= BIT6;         // LED1 átkapcsolása
  55.       __delay_cycles(500000);// Kb. 500 ms várakozás
  56.     break;
  57.     case 6:
  58.       P1OUT &= BIT6;         // Kimenetek törlése
  59.       P1OUT ^= BIT6;         // LED1 átkapcsolása
  60.       __delay_cycles(250000);// Kb. 250 ms várakozás
  61.     break;
  62.     case 7:
  63.       P1OUT &= BIT6;         // Kimenetek törlése
  64.       P1OUT ^= BIT6;         // LED1 átkapcsolása
  65.       __delay_cycles(125000);// Kb. 1000 ms várakozás
  66.     break;
  67.     }    
  68.   }
  69. }
  70.  
  71. //--- Port 1 programmegszakítás kiszolgálása ------------------------
  72. #pragma vector=PORT1_VECTOR
  73. __interrupt void Port_1(void)
  74. {
  75.   jelzo++;                   // következő állapotba vált
  76.   jelzo &= 0x07;             // jelzo nem lehet 7-nél nagyobb!  
  77.   P1IFG &= ~BIT3;            // P1.3 interrupt jelzőbit törlése
  78. }


A jelzo nevű változó deklarálásától eltekintve a program első része megegyezik az előzővel. Eltérés csak a végtelen ciklusban és az interrupt kiszolgáló eljárásban van. A végtelen ciklusban a jelzo változó értéke szerint elágaztatjuk a programot egy switch (kapcsoló) utasítás segítségével. Minden case ... break közötti szakasz egy-egy külön ágat jelent a programban. Mivel hol az egyik, hogy a másik LED-et villogtatjuk, a rend kedvéért minden ágban egy törléssel kezdjük: nullázzuk a villogtatni nem kívánt LED kimenetet. A késleltetéseket úgy választottuk meg, hogy a várakozási idők minden lépésben feleződnek.

A megszakítás kiszolgálásánál léptetjük a jelzőt, és gondoskodunk róla, hogy az a 0-7 intervallumban maradjon. Az előző programhoz hasonlóan itt is törölnünk kell a megszakításjelző bitet.

Az I/O portokon kívül programmegszakítást okozhat az "őrkutya" (WDT), az időzítő/számláló (timer), az analóg-digitális átalakító (ADC), a szinkron soros port (USI), az NMI bemenetre adott külső jel, s néhány hardver hibajel (oszcillátor hiba, flash memória hozzáférés megsértése). Ezek közül jónéhánnyal találkozunk majd a későbbiekben.


A cikk még nem ért véget, lapozz!
Következő: »»   4 / 8
Értékeléshez bejelentkezés szükséges!
Bejelentkezés

Belépés

Hirdetés
XDT.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