Programmegszakítások

A fejezet tartalma:

A programmegszakítás alapjai

A mikrovezérlőnek programfutás közben sok esetben különböző eseményekre kell reagálnia. Ilyen események lehetnek például nyomógombok állapotváltozásai (lenyomás, felengedés), kapacitív érzékelő vagy analóg komparátor billenési szintjeinek átlépése, ADC konverzió befejezése, időzítőn beállított idő letelte. Ennél sokkal bonyolultabb esetek is adódnak, amikor például a mikrovezérlő USB vagy soros vonalon kommunikál a számítógéppel, vagy más időkritikus folyamatot kell kiszolgálni. Az ilyen jellegű feladatok ellátásának megtervezésekor fel kell mérni, hogy elég hatékony lesz-e a programunk, vagy belefullad a teendőkbe?       

Az egyszerű programokban többnyire a lekérdezéses (polling) módszert használjuk az események bekövetkeztének vizsgálatára, de ez nem túl hatékony módszer, komolyabb programokban nem célravezető megközelítés:
Képzeljük el, milyen volna, ha a mobiltelefont hívásjelzés nélkül használnánk, s 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 az eddigi programjaink többnyire így kezelték az eseményeket. A fenti módszer hátránya is 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 a megszakítást). 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 programmegszakítás (interrupt) 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 programmegszakí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.


1 ábra: A program normál menetének megszakítása

Természetesen ahhoz, hogy a programmegszakítás kiszolgálása után zavartalanul folytatódhasson a program futása, programmegszakításkor el kell menteni a futó program állapotát (beleértve azt is, hogy melyik a soron következő utasítás, s hogy mi volt a státuszbitek tartalma), visszatéréskor pedig helyre kell állítani. A hatékony, és "takarékos" működés érdekében az állapotmentések és visszaállítás egy része automatikusan (hardveresen megtörténik), a többit pedig - amennyiben egyáltalán szükséges - szoftveresen kell végeznünk.

Interrupt kiszolgáló rutin

Azt a kódrészletet, amelyik a programmegszakítás kiszolgálást végzi, interrupt kiszolgáló rutinnak (angolul Interrupt Service Routine, vagy röviden ISR) nevezzük. Az 1. ábrát nézve, azt hihetnénk, hogy ez egy szubrutin, amelyet a főprogram meghívott. A valóságban azonban nem a főprogram hívja meg, hanem automatikusan, a hardver által kiváltott esemény hatására aktiválódik. Például, ha a soros porton egy adat érkezik, akkor az interrupt kiszolgáló rutin kiolvassa az UART port regiszteréből az adatot, elmenti egy pufferterületre, és visszaadja a vezérlést. Azt is szokták mondani, hogy az interrupt kiszolgálása a háttérben történik, a főprogram pedig az előtérben fut.

Az ISR meghívhat más szubrutinokat is, sőt, előfordulhat az is, hogy pont azt a szubrutint hívja meg, amelynek futását félbeszakította. Ha ilyen esetek előfordulását megengedjük, akkor a program tervezésénél ügyelnünk kell arra, hogy újra meghívható (reentrant) legyen az eljárás, azaz statikus változók helyett dinamikus tárfoglalást használjon a paraméterei és a lokális változói tárolására, s az újrabelépések száma lehetőleg minél kisebb legyen (nehogy túlcsorduljon a veremtár a dinamikus helyfoglalások miatt).

Az MKL25Z128VLK4 mikrovezérlő megszakítási rendszere

Az MKL25Z128VLK4 mikrovezérlő ARM Cortex-M0+ maggal rendelkezik, megszakítási rendszerét a Cortex™-M0+ Devices Generic User Guide dokumentum 2.3 alfejezete ismerteti részletesen. Az ARM Cortex-M0+ mikrovezérlők megszakítási rendszere az NVIC beépített megszakításvezérlőn (Nested and Vectored Interrupt Controller) alapul, ami legfeljebb 48 kivételt (Exceptions) kezel. Az első 16 kivétel az ARM Cortex-M0+ processzormagból származó megszakítási jel, a következő 32 pedig a perifériák és a külső forrásból származó megszakítási kérelmek. Szűkebb szóhasználatban csak ez utóbbi 32 forrást nevezzük megszakításnak (Interrupts) és számozzuk 0-tól 31-ig (Interrupt #0 - Interrupt #31). Különleges prioritása és vektorának elhelyezkedése miatt az ARM Cortex-M0+ processzormagból származó kivételek közé soroljuk az NMI nem maszkolható megszakítást is, annak ellenére, hogy az külső forrásból fogad megszakítási kérelmet.

2. ábra: Az ARM Cortex-M0+ mikrovezérlők megszakítási rendszerének vázlata

A megszakítási rendszer tulajdonságai

A megszakítások priorizálhatók - az Interrupt #0 - Interrupt #31 megszakítások prioritása  egyenként beállítható 0-3 közötti értékre. A kisebb szám magasabb prioritási szintet jelent. A kivételek prioritása kötött.

A megszakítások egymásba  skatulyázhatók - a  magasabb prioritású megszakítási kérelem beérkezése megszakíthatja az alacsonyabb prioritású megszakítás kiszolgálását (nesting - erre utal az NVIC első betűje).

A megszakítási rendszer vektoros - a megszakítási kérelem beérkezése és érvényesülésekor egy ún. vektortáblából vett címre adódik át a vezérlés a megszakítást kiszolgáló eljáráshoz. Mind a 48 kivételnek, illetve megszakításnak külön bejegyzése van a vektortáblában, így a megszakítási forrás vizsgálata nélkül közvetlenül a megfelelő kiszolgáló eljáráshoz kerül a vezérlés.

A megszakítási késedelem 15 órajelciklus - a megszakítási jel beérkezése és a megszakítást kiszolgáló eljárásba lépés között eltelt idő (interrupt latency) 15 órajelciklus. Ennek során befejeződik az éppen végrehajtás alatt álló utasítás és automatikusan elmentésre kerülnek a veremtárba a legfontosabb regiszterek. A megszakítás kiszolgálása után újabb 15 órajelciklusba kerül az elmentett regiszterek visszaállítása és a megszakított programba történő visszalépés.

Megszakítások láncolása (Interrupt tail-chaining) - ha egy megszakítás kiszolgálása közben újabb megszakítási kérelem érkezett, akkor az első megszakítás végén nem történik meg a regiszterek helyreállítása, hanem az újabb megszakítási kérelem kiszolgálására kerül a vezérlés. Ez a váltás csak 6 órajelciklust vesz igénybe, szemben a regiszter visszatöltés majd újra elmentés 2x15 órajelciklusával.

Későn érkező megszakítás (late arrival) - egy újabb optimalizálási lehetőség, hogy a megszakításba lépéskor a regiszterek elmentése végén az NVIC még egyszer kiértékel, hogy melyik a legmagasabb prioritású érvényes megszakítási kérelem. Ha a regiszterek elmentése közben késve beérkezik egy magasabb prioritású kérelem, akkor annak a kiszolgálása kezdődik meg a korábban érkező alacsonyabb prioritású kérelem helyett.

Az MKL25Z128VLK4 mikrovezérlőben minden interrupt forráshoz tartozik egy jelzőbit (interrupt flag), ami '1'-be áll be, amikor a hozzá tartozó esemény bekövetkezik. Ahhoz hogy egy ilyen megszakítási kérelem (az '1' állapotba  billent jelzőbit) érvényesülhessen, további feltételeknek is teljesülnie kell.

A megszakítások maszkolhatók - A telefonos analógiánál maradva: van olyan időszak, amikor  nem akarjuk, hogy hívásokkal zavarjanak bennünket a moziban, színházban, vagy egy munkahelyi értekezleten. Ilyenkor kikapcsoljuk a hívásjelzést, és az esetleges hívásokat figyelmen kívül hagyjuk. A mikrovezérlőkben szinte minden interrupt forráshoz találunk egy engedélyező bitet, amit '1'-be kell állítani ahhoz, hogy a hozzá tartozó programmegszakítási kérelem érvényesüljön. Ha az engedélyező bit '0', akkor az adott programmegszakítás "maszkolva van", vagyis le van tiltva. Az engedélyező bit '0' állapota csak a programmegszakítási kérelem érvényesülését akadályozza meg (nem ugrik el a program az interrupt forrásához tartozó ISR vektorhoz), az interrupt jelzőbit ettől még bebillenhet és programozott lekérdezéssel (polling) vizsgálható. Vannak az MKL25Z128VLK4 mikrovezérlőben olyan megszakítások, amelyek nem maszkolhatók (nem tilthatók le). Ilyen az NMI (nem maszkolható megszakítás)

Az MKL25Z128VLK4 mikrovezérlőben egy programmegszakítási kérelem érvényesülésekor a következő dolgok történnek:
Visszatéréskor a programmegszakítás kiszolgálása végén egy RETURN utasítás (BX <Reg> vagy POP {<Reg1>,<Reg2>,..,PC} utasítás) végrehajtásakor a Link Regiszterbe előzőleg beállított EXC_RETURN feltétel miatt beindul a  "visszatérés programmegszakításból" (return from interrupt) mechanizmus, mellyel visszatérünk a félbehagyott program folytatásához. Ennek hatására automatikusan helyreállításra kerülnek a programmegszakításkor automatikusan elmentett regiszterek  és a programszámláló is visszaáll a veremtárban elmentett eredeti értékre, s a főprogram folytatódhat, mintha mi sem történt volna.

A megszakítási vektorok táblázata

Mivel az ARM Cortex-M mikrovezérlőknél minden megszakítás a saját megszakításkiszolgáló eljárásához ugrik, lennie kell a memóriában egy kereső táblázatnak, amely a megszakításokhoz tartozó kiszolgáló eljárások belépési címeit tartalmazza. A táblázat két részre osztható: az első 16 bejegyzés a rendszermaghoz tartozik, s a hozzájuk tartozó megszakításokat kivételnek (exception) nevezzük. Ennek a táblázatrésznek fontos szerepe van induláskor és újrainduláskor (RESET) is, mivel a táblázat első két szava az MSP veremmutató kezdőértékét és a legelső végrehajtandó utasítás címét tartalmazza. A táblázat második része az ARM Cortex-M0/M0+ mikrovezérlőknél perifériákhoz tartozó megszakítások (interrupts) vektorait tartalmazza (max. 32 db). A kivételeket és a megszakításokat az is megkülönbözteti, hogy a CMSIS Core modul a kivételekhez negatív, a periféria megszakításokhoz pedig nemnegatív sorszámokat rendel.


3. ábra: A megszakítási vektorok táblázata elhelyezkedése a memóriában

A megszakítási vektorok táblázata alapértelmezetten a memória legelején (a 0x0000 0000 címtől kezdődően) helyezkedik el, de a táblázat áthelyezhető.

1. táblázat: A rendszerkivétel vektorok táblázata (a megszakítási vektorok táblázatának első 16 eleme)
Cím
Vektor
IRQ
Prioritás
Forrás
Rövid leírás
0x0000_0000
0
-
-
ARM mag
MSP kezdőértéke
0x0000_0004 1

-3
ARM mag RESET vektor
0x0000_0008 2
-14
-2
ARM mag NMI nem maszkolható megszakítás
0x0000_000C 3
-13
-1
ARM mag Hiba megszakítások
0x0000_0010 4
-
-




    Fenntartott
0x0000_0014 5
-
-
0x0000_0018 6
-
-
0x0000_001C 7
-
-
0x0000_0020 8
-
-
0x0000_0024 9
-
-
0x0000_0028 10
-
-
0x0000_002C 11
-5
állítható
ARM mag SVC Supervisor hívása
0x0000_0030 12
-
-

   Fenntartott
0x0000_0034 13
-
-
0x0000_0038 14
-2
állítható
ARM mag PENDSV Beállítható megszakításkérelem
0x0000_003C 15
-1
állítható
ARM mag SysTick megszakítás
 

2. táblázat: A periféria megszakításvektorok táblázata (a megszakítási vektorok táblázatának folytatása)
Cím
Vektor
IRQn
IPR
Forrás
Rövid leírás
0x0000_0040
16
0
0
DMA
DMA 0. csatorna átvitel vége vagy hiba
0x0000_0044 17
1
0
DMA DMA 1. csatorna átvitel vége vagy hiba
0x0000_0048 18
2
0
DMA DMA 2. csatorna átvitel vége vagy hiba
0x0000_004C 19
3
0
DMA DMA 3. csatorna átvitel vége vagy hiba
0x0000_0050 20
4
1
-
-
0x0000_0054 21
5
1
FTFA

0x0000_0058 22
6
1
PMC
Alacsony feszültség  észlelése és figyelmeztetés
0x0000_005C 23
7
1
LLWU
Ébresztés kis szivárgású  állapotból
0x0000_0060 24
8
2
I2C0

0x0000_0064 25
9
2
I2C1

0x0000_0068 26
10
2
SPI0

0x0000_006C 27
11
2
SPI1

0x0000_0070 28
12
3
UART0
Állapotjelzők és hiba
0x0000_0074 29
13
3
UART1
Állapotjelzők és hiba
0x0000_0078 30
14
3
UART2
Állapotjelzők és hiba
0x0000_007C 30
15
3
ADC0

0x0000_0080 32
16
4
CMP0

0x0000_0084 33
17
4
TPM0

0x0000_0088 34
18
4
TPM1

0x0000_008C 35
19
4
TPM2

0x0000_0090 36
20
5
RTC
Riasztás megszakítás
0x0000_0094 37
21
5
RTC
Másodpercenkénti megszakítás
0x0000_0098 38
22
5
PIT

0x0000_009C 39
23
5
-
-
0x0000_00A0 40
24
6
USB OTG

0x0000_00A4 41
25
6
DAC0

0x0000_00A8 42
26
6
TSI0

0x0000_00AC 43
27
6
MCG

0x0000_00B0 44
28
7
LPTMR0

0x0000_00B4 45
29
7
-

0x0000_00B8 46
30
7
PORTA

0x0000_00BC 47
31
7
PORTD

Megjegyzés: A táblázat IPR oszlopában az adott megszakítás prioritásának beállításában érintett NVIC_IPRx regiszter sorszáma (x = 0 .. 7) van feltüntetve.                        

Az NVIC regiszterkészlete

A megszakítási rendszert vezérlő modul (NVIC) regiszterei a 32 db. perifériás megszakítás egyenkénti engedélyezésére, tiltására, emulálására (szoftveres aktiválására), illetve a megszakítások prioritásának beállítására szolgálnak.

3. táblázat: Az NVIC modul regiszterei
Cím
Regiszternév
A regiszter szerepe
0xE000_E100
NVIC_ISER
Megszakítás engedélyezés (Set-enable)
0xE000_E180 NVIC_ICER
Megszakítás tiltás (Clear-enable)
0xE000_E200 NVIC_ISPR Megszakítás kérelem beállítás (Set-pending)
0xE000_E2800 NVIC_ICPR Megszakítás kérelem törlés (Clear-pending)
0xE000_E400 -
0xE000_E41F
NVIC_IPR0 -
NVIC_IPR7
Megszakítások prioritása (Interrupt Priority Registers)
NVIC_ISER, NVIC_ICER, NVIC_ISPR, NVIC_ICPR: a megfelelő bit ’1’-be állítása végzi el a kért feladatot. Minden megszakításhoz egy-egy bit tartozik. Az érintett bit sorszáma az IRQn sorszámmal egyezik meg.

NVIC_IPRx:  egy-egy 32 bites regiszter négy bájtban négy megszakítás prioritását állítja be. Bájtonként csak a legfelső két bit van használatban. A kisebb szám nagyobb prioritást jelent. Az alábbi ábra szemlélteti a prioritást szabályozó bitek elhelyezkedését.


4. ábra: Az NVIC_IPRx regiszterek bitkiosztása

CMSIS támogatás NVIC használatához

A CMSIS Core modul az alábbi támogatói függvényeket nyújtja a megszakítások kezeléséhez. A dupla aláhúzással kezdődőek ún. intrinsic függvények.

A core_cmFunc.h állományban
A core_cm0plus.h állományban
Fentieken kívül a projektjeinkbe beépülő startup_MKL25Z4.s állomány tartalmazza a megszakítási vektorok táblázatát, definiálja a megszakításokat kiszolgáló függvények neveit és a hozzájuk rendelt alapértelmezett kiszolgáló rutint. Ezek [WEAK] („gyenge”) definíciók, tehát a felhasználói programokban a megszakítást kiszolgáló függvények azonos névvel fölüldefiniálhatók!

Megszakítás GPIO port kivezetésekkel

A GPIO portok segítségével külső jeleket vezethetünk az NVIC bemeneteire, s a jelszint megváltozásával kapcsolatos eseményekhez megszakításokat rendelhetünk. Az MKL25Z128VLK4 mikrovezérlő esetében csak az A és a D porthoz tartozó kivezetések használhatók programmegszakítások keltésére. Megszakítást kérhetünk a bemeneten észlelt felfutó élre, lefutó élre, mindkét élre, alacsony, illetve magas szintre.

Az érintett regiszterek:
Minden portkivezetéshez tartozik egy PORTx_PCRn speciális funkciójú regiszter, amelynek segítségével az adott kivezetés konfigurálható. A PORTx_PCRn regiszterek bitkiosztása az alábbi ábrán látható. A regiszterek magasabb helyiértékű fele a portkivezetések külső megszakítási forrásként, vagy DMA átvitel indítására történő  használatával kapcsolatos, az alacsonyabb helyiértékű 16 bit  pedig a  funkció kiválasztásával (MUX bitcsoport),  illetve a kivezetés fizikai tulajdonságainak konfigurálásával kapcsolatosak.


5. ábra: A PORTx_PCRn regiszterek bitkiosztása

Az egyes bitek, vagy bitcsoport szerepe az alábbi táblázatban található:

4. táblázat: A PORTx_PCRn portvezérlő regiszter bitjeinek szerepe:
Bit
Funkció leírása
ISF
megszakítási esemény jelzése (0: nincs megszakítási kérelem, 1: megszakításra vár)
DMA átvitel után automatikusan törlődik, megszakítás esetén nekünk kell törölni.
IRQC
Megszakítási mód vezérlése:
  0000 Megszakítás/DMA kérelem letiltva.
  0001 DMA kérelem felfutó élre.
  0010 DMA kérelem felfutó élre.
  0011 DMA kérelem bármely élre
  1000 Megszakítás alacsony szintre.
  1001 Megszakítás felfutó élre.
  1010 Megszakítás lefutó élre.
  1011 Megszakítás bármely élre.
  1100 Megszakítás magas szintre
Az itt fel nem sorolt bitkombinációk nem használhatók (fenntartott)
PS
Pull-up select: Belső fel- vagy lehúzás kiválasztása (0: lehúzás, 1: felhúzás)
PE
Pull-up enable: Belső fel- vagy lehúzás engedélyezése (0: tilt, 1: enged)
SRE
Slew rate enable: felfutási sebesség korlátozásának engedélyezése (0: tilt, 1: enged)
PFE
Passive filter enable: passzív szűrő engedélyezés (0: tilt, 1: enged)
DSE
Drive Strength Enable: a kimeneti erősségének beállítása (0: alacsony, 1: magas)
MUX
Funkcióválasztás (0: analóg, 1: GPIO, 2 - 7: Alt2 - Alt7 választása)
Az adott portkivezetéshez tartozó PORTx_PCRn regiszter MUX bitcsoportjával – mint egy fokozatkapcsolóval – választhatjuk ki, hogy az adott kivezetés melyik funkciót szolgálja ki.

Program6_1: megszakítás nyomógombbal

A PTA12 vagy a PTA13 kivezetések bármelyikének földre húzásakor a zöld LED háromszor felvillan.  A főprogram a piros LED-et villogtatja, amíg megszakításra vár. A tankönyvi mintapéldát módosítottuk: az eredeti programban szereplő PA1, PTA2 helyett a PTA12, PTA13 kivezetéseket használjuk. Korrigálni kellett az időzítéseket is, mivel a CMSIS alapértelmezett CPU frekvenciája esetünkben 20.97 MHz a könyvben szereplő 41 MHz helyett.

A programmegszakítás engedélyezésének lépései:
A PORTA megszakítások kiszolgálása:
Hardver követelmények:
1. lista: A Program6_1/main.c program listája
/* Program6_1: programmegszakítás nyomógombbal
*
* A PTA12 vagy a PTA13 kivezetések valamelyikének
* földre húzásakor a zöld LED háromszor felvillan.
* A főprogram a piros LED-et villogtatja, amíg megszakításra vár.
*
* A program forrása: Mazidi et al., Freescale ARM Cortex-M Embedded Programming
* http://www.microdigitaled.com/ARM/Freescale_ARM/Code/Freescale_ARM_codes.htm
* Módosítások: A CPU frekvencia nálunk 20.97 MHz a könyvben szereplő 41 MHz helyett,
* Mi a PTA12, PTA13 kivezetéseket használjuk az eredeti PA1, PTA2 helyett.
*/

#include "MKL25Z4.h"

void delayMs(int n);

int main(void) {
__disable_irq(); // Megszakítások globális tiltása
//--- LED-ek konfigurálása ----
SIM->SCGC5 |= 0x400; // PORTB engedélyezése (LED-ek)
PORTB->PCR[18] = 0x100; // PTB18 legyen GPIO módban
PORTB->PCR[19] = 0x100; // PTB19 legyen GPIO módban
PTB->PDDR |= 0xC0000; // PTB18 és PTB19 legyen kimenet
PTB->PDOR |= 0xC0000; // LED-ek lekapcsolása
//--- PTA12 konfigurálása -----
SIM->SCGC5 |= 0x200; // PORTA engedélyezése (nyomógomb bemenetek)
PORTA->PCR[12] |= 0x00100; // PTA12 legyen GPIO
PORTA->PCR[12] |= 0x00003; // PTA12 belső felhúzás engedélyezése
PTA->PDDR &= ~0x1000; // PTA12 legyen bemenet
PORTA->PCR[12] &= ~0xF0000; // Megszakítási beállítások törlése
PORTA->PCR[12] |= 0xA0000; // Megszakítás aktiválás lefutó élre
//--- PTA13 konfigurálása -----
PORTA->PCR[13] |= 0x00100; // PTA13 legyen GPIO
PORTA->PCR[13] |= 0x00003; // PTA13 belső felhúzás engedélyezése
PTA->PDDR &= ~0x2000; // PTA13 legyen bemenet
PORTA->PCR[13] &= ~0xF0000; // Megszakítási beállítások törlése
PORTA->PCR[13] |= 0xA0000; // Megszakítás aktiválás lefutó élre

NVIC->ISER[0] |= 0x40000000;// INT30 engedélyezése (ISER[0] bit30)
__enable_irq(); // Megszakítások globális engedélyezése

//--- LED villogtatás ---------
while(1) {
PTB->PTOR = 0x40000; // Piros LED állapotváltás
delayMs(500);
}
}

//----------------------------------------------------------
// A PTA12-re, vagy PTA13-ra kötött nyomógombok aktiválják
//----------------------------------------------------------
void PORTA_IRQHandler(void) {
int i;
for (i = 0; i < 3; i++) { // Zöld LED felvillantása háromszor
PTB->PDOR &= ~0x80000; // Zöld LED be
delayMs(500);
PTB->PDOR |= 0x80000; // Zöld LED ki
delayMs(500);
}
PORTA->ISFR |= 0x00003000; // Megszakításjelző bitek törlése
}

//--------------------------------------------------------------
// Késleltető függvény alapértelmezett órajelhez (20.97152 MHz)
//--------------------------------------------------------------
void delayMs(int n) {
int i, j;
for(i = 0 ; i < n; i++)
for (j = 0; j < 3500; j++);
}

Program6_2: megszakítás nyomógombokkal

A PTA12 bemenet földre húzásakor a zöld LED villan fel háromszor, a  PTA13 bemenet földre húzásakor pedig a kék LED. A főprogram a piros LED-et villogtatja, amíg megszakításra vár.

Ez a program csak abban tér el az előző Program6_1 mintapéldától, hogy a  két megszakítást (PTA12 vagy PTA13) megkülönböztetjük egymástól, s PTA13  lehúzásakor a kék LED-et villogtatjuk a korábbi zöld helyett.

Hardver követelmények:
2. lista: A Program6_2/main.c program listája
/* Program6_2: programmegszakítás nyomógombokkal
*
* A PTA12 bemenet földre húzásakor a zöld LED háromszor felvillan.
* A PTA13 bemenet földre húzásakor a kék LED háromszor felvillan.
* A főprogram a piros LED-et villogtatja, amíg megszakításra vár.
*
* Ez a program csak abban tér el a Program6_1 mintapéldától, hogy a
* két megszakítást (PTA12 vagy PTA13) megkülönböztetjük és a PTA13
* lehúzásakor a kék LED-et villogtatjuk a zöld helyett.
*
* A program forrása: Mazidi et al., Freescale ARM Cortex-M Embedded Programming
* http://www.microdigitaled.com/ARM/Freescale_ARM/Code/Freescale_ARM_codes.htm
* Módosítások: A CPU frekvencia nálunk 20.97 MHz a könyvben szereplő 41 MHz helyett,
* Mi a PTA12, PTA13 kivezetéseket használjuk az eredeti PA1, PTA2 helyett.
*/

#include "MKL25Z4.h"

void delayMs(int n);

int main(void) {
__disable_irq(); // Megszakítások globális tiltása
//--- LED-ek konfigurálása ----
SIM->SCGC5 |= 0x400; // PORTB engedélyezése
SIM->SCGC5 |= 0x1000; // PORTD engedélyezése
PORTB->PCR[18] = 0x100; // PTB18 legyen GPIO módban
PORTB->PCR[19] = 0x100; // PTB19 legyen GPIO módban
PTB->PDDR |= 0xC0000; // PTB18 és PTB19 legyen kimenet
PTB->PDOR |= 0xC0000; // Piros és zöld LED-ek lekapcsolása
PORTD->PCR[1] = 0x100; // PTD1 legyen GPIO módban
PTD->PDDR |= 0x02; // PTD1 legyen kimenet
PTD->PDOR |= 0x02; // Kék LED lekapcsolása
//--- PTA12 konfigurálása -----
SIM->SCGC5 |= 0x200; // PORTA engedélyezése (nyomógomb bemenetek)
PORTA->PCR[12] |= 0x00100; // PTA12 legyen GPIO
PORTA->PCR[12] |= 0x00003; // PTA12 belső felhúzás engedélyezése
PTA->PDDR &= ~0x1000; // PTA12 legyen bemenet
PORTA->PCR[12] &= ~0xF0000; // Megszakítási beállítások törlése
PORTA->PCR[12] |= 0xA0000; // Megszakítás aktiválás lefutó élre
//--- PTA13 konfigurálása -----
PORTA->PCR[13] |= 0x00100; // PTA13 legyen GPIO
PORTA->PCR[13] |= 0x00003; // PTA13 belső felhúzás engedélyezése
PTA->PDDR &= ~0x2000; // PTA13 legyen bemenet
PORTA->PCR[13] &= ~0xF0000; // Megszakítási beállítások törlése
PORTA->PCR[13] |= 0xA0000; // Megszakítás aktiválás lefutó élre

NVIC->ISER[0] |= 0x40000000;// INT30 engedélyezése (ISER[0] bit30)
__enable_irq(); // Megszakítások globális engedélyezése

//--- LED villogtatás ---------
while(1) {
PTB->PTOR = 0x40000; // Piros LED állapotváltás
delayMs(500);
}
}

//----------------------------------------------------------
// A PTA12-re, vagy PTA13-ra kötött nyomógombok aktiválják
//----------------------------------------------------------
void PORTA_IRQHandler(void) {
int i;
while (PORTA->ISFR & 0x00003000) {
if(PORTA->ISFR & 0x00001000) { // PTA12 megszakítás?
for (i = 0; i < 3; i++) { // Zöld LED felvillantása háromszor
PTB->PDOR &= ~0x80000; // Zöld LED be
delayMs(500);
PTB->PDOR |= 0x80000; // Zöld LED ki
delayMs(500);
}
PORTA->ISFR |= 0x00001000; // Megszakításjelző bit törlése
}

if(PORTA->ISFR & 0x00002000) { // PTA13 megszakítás?
for (i = 0; i < 3; i++) { // Kék LED felvillantása háromszor
PTD->PDOR &= ~0x02; // Kék LED be
delayMs(500);
PTD->PDOR |= 0x02; // Kék LED ki
delayMs(500);
}
PORTA->ISFR |= 0x00002000; // Megszakításjelző bit törlése
}
}
}

//--------------------------------------------------------------
// Késleltető függvény alapértelmezett órajelhez (20.97152 MHz)
//--------------------------------------------------------------
void delayMs(int n) {
int i, j;
for(i = 0 ; i < n; i++)
for (j = 0; j < 3500; j++);
}

Program6_3: megszakítás PORTD használatával

Ebben a programban a PTD4 bemenet földre húzásával keltünk megszakításokat, s a bemenetet mindkét élre érzékenyítjük. Minden megszakításkor átbillentjük a kék LED állapotát. A főprogram megszakításra várakozva semmit sem csinál (üres végtelen ciklusban fut).

A PTD4 programmegszakítás engedélyezésének lépései:
A PORTD megszakítások kiszolgálása:
Hardver követelmények:
Megjegyzés: A program nem kezeli a nyomógomb pergéséből eredő fals megszakítások hatását (egy gombnyomásra több állapotváltozás is bekövetkezhet).

3. lista: A Program6_3/main.c program listája
/* Program6_3: A kék LED átbillentése PTD4 minden állapotváltásakor
*
* A PTD4 bemenet állapotváltási megszakítását le- és felfutó élre
* is engedélyezzük. A megszakítást kiszolgáló függvényben a kék LED
* állapotát ellenkezőjére billentjük.
*
* A program forrása: Mazidi et al., Freescale ARM Cortex-M Embedded Programming
* http://www.microdigitaled.com/ARM/Freescale_ARM/Code/Freescale_ARM_codes.htm
*/

#include "MKL25Z4.h"

int main(void) {
__disable_irq(); // Megszakítások globális tiltása
//--- A kék LED konfigurálása -----------
SIM->SCGC5 |= 0x1000; // PORTD engedélyezése
PORTD->PCR[1] = 0x100; // PTD1 legyen GPIO módban
PTD->PDDR |= 0x02; // PTD1 kimenet legyen
PTD->PDOR |= 0x02; // Kék LED lekapcsolása

//--- PTD4 megszakítás konfigurálása ----
PORTD->PCR[4] |= 0x00100; // PTD4 legyen GPIO módban
PORTD->PCR[4] |= 0x00003; // Felhúzás engedélyezése
PTD->PDDR &= ~0x0010; // PTD4 legyen bemenet
PORTD->PCR[4] &= ~0xF0000; // Megszakítási beállítások törlése
PORTD->PCR[4] |= 0xB0000; // Mindkét élre legyen érzékeny

NVIC->ISER[0] |= 0x80000000;// INT31 engedélyezése (ISER[0] bit31) */
__enable_irq(); // Megszakítások globális engedélyezése

while(1) { // Nincs mit tenni!
}
}

//--------------------------------------
// A PTD4-re kötött nyomógomb aktiválja
//--------------------------------------
void PORTD_IRQHandler(void) {
if (PORTD->ISFR & 0x00000010) {
PTD->PTOR = 0x0002; // PTD1 (kék LED) átbillentése
PORTD->ISFR = 0x0010; // A megszakításjelző bit törlése
}
}

UART soros porti megszakítások

A 4. fejezetben lekérdezéses (polling) módban használtuk az UART0 soros portot. Most megmutatjuk, hogyan használhatjuk programmegszakításos módban. Az UART0 megszakítások engedélyezésénél az UART0_CR2 vezérlő regiszter felső négy bitje, és az IRQ12-es megszakítási vektor játszik szerepet, a megszakítás prioritása pedig az NVIC_IPR3 regiszterben állítható be.

Az UARTx_CR2 vezérlő regiszterek bitkiosztása az alábbi ábrán látható.


6. ábra: Az UARTx_CR2 vezérlő regiszterek bitkiosztása (x = 0,1,2 lehet)

Az egyes bitek szerepe:
UARTx portokhoz tartozó megszakítási vektorok:
Megjegyzés: Az UARTx portok minden megszakítási eseménye ugyanazt a megszakítási vektort aktiválja, ezért a megszakítást kiszolgáló rutinban az állapotjelző bitek vizsgálatával kell eldönteni, hogy a vevő, vagy az adó okozott megszakítást, esetleg valamilyen hiba lépett fel.

Program6_4: UART0 karakterek fogadása megszakításban

Ez a program a korábban bemutatott Program4_2 mintapélda módosított változata, amelyben a FRDM-KL25Z kártya UART0 Rx bemenetén fogadott karakterek legalsó három bitjével az RGB LED színkomponenseinek ki- illetve bekapcsolt állapotát vezéreltük. A módosítás lényege az, hogy most az UART0 Rx vétel kiszolgálása a megszakítási szintre került át.

Az UART0 vevő megszakításainak engedélyezése roppant egyszerű:
Fenti lépéseknek megfelelően az alábbi három sort kell beírni, illetve módosítani a korábbi Program4_2 mintapélda forráskódjában (az UART_init() függvény végén).
 UART0->C2 = 0x24;            // Adatfogadás és megszakítás engedélyezése
NVIC->ISER[0] |= 0x00001000; // INT12 engedélyezése (ISER[0] bit12)
__enable_irq(); // Megszakítások globális engedélyezése
Az UART0 megszakítások kiszolgálásához UART0_IRQHandler() névvel kell függvényt definiálnunk. A beérkezett karakter kiolvasásával (UART0->D regiszter olvasása) a megszakítási kérelem automatikusan törlődik.

4. lista: A Program6_4/main.c program listája
/* Program6_4
*
* Ez a Program 4_2 mintapélda módosított változata, melyben
* az UART0 vétel a megszakítást kiszolgáló függvénybe került át.
*
* Karakter fogadása terminál emulátorból (PUTTy.exe,
* Termite, TeraTerm stb.) OpenSDA által biztosított virtuális
* soros porton keresztül, a FRDM-KL25Z kártya UART0 Rx
* bemenetén. A vett karakter legalsó bitjeivel az RGB LED
* állapotát vezéreljük. Az RGB LED inicializálása és vezérlése
* hasonlóan történik, mint Program2_7-ben.
*
* Használjunk valamilyen terminál emulátor programot (PUTTy.exe,
* Termite, TeraTerm stb.) a karakter kiküldéséhez! A kiküldött
* karaktert ebben a programban a FRDM kártya nem tükrözi vissza.
* Ügyeljünk rá, hogy a terminál emulátor ne fűzzön a kiküldött
* karakterhez sorvége jelzést! Például a Termite program
* Settings menüjében "Append nothing" beállítás kell.
* Beállítás: 9600 bps, 8N1, adatfolyam vezérlés nélkül.
*
* Alapértelmezetten SystemInit() 20.97 MHz FLL órajelet állít be.
* BDH=0, BDL=0x88, és OSR=0x0F beállításokkal 9600 Baud lesz a sebesség.
*
* A program forrása: Mazidi et al., Freescale ARM Cortex-M Embedded Programming
* http://www.microdigitaled.com/ARM/Freescale_ARM/Code/Freescale_ARM_codes.htm
*/

#include <MKL25Z4.h>

void UART0_init(void); // UART0 Rx bemenet inicializálás
void LED_init(void); // RGB LED inicializálás
void LED_set(int value); // RGB LED színbeállítás

int main (void) {
UART0_init(); // UART0 Rx bemenet inicializálása
LED_init(); // RGB LED inicializálása
while (1) {
// Az UART0 vétel a megszakításba került át ...
}
}


//------------------------------------
// UART0 megszakítás kiszolgálása
//------------------------------------
void UART0_IRQHandler(void) {
char c;
c = UART0->D; // Beolvassuk a vett karaktert
LED_set(c); // és beállítjuk a LED-et
}

/* UART0 Rx inicializálása 9600 Baud átviteli sebességre
* Baudrate = UART0 clock freq / [OSR+1] / BDH:BDL
* Esetünkben 20.97 MHz / [15 + 1] / 0x00:0x88 = 9637 bps,
* ami elfogadható eltérés a névlegestől.
*/
void UART0_init(void) {
__disable_irq(); // Megszakítások globális tiltása
SIM->SCGC4 |= 0x0400; // UART0 periféria engedélyezése
SIM->SOPT2 |= 0x04000000; // MCGFLLCLK választása UART0 baudrate órajelnek
UART0->C2 = 0; // UART0 letiltása a konfigurálás időtartamára
UART0->BDH = 0x00; // Baudrate = 9600 bps (bit8 - bit12)
UART0->BDL = 0x88; // Baudrate = 9600 bps (bit0 - bit7)
UART0->C4 = 0x0F; // Túlmintavételezési arány = 16
UART0->C1 = 0x00; // 8-bites adatformátum
UART0->C2 = 0x24; // Adatfogadás és megszakítás engedélyezése
NVIC->ISER[0] |= 0x00001000; // INT12 engedélyezése (ISER[0] bit12)
__enable_irq(); // Megszakítások globális engedélyezése
SIM->SCGC5 |= 0x0200; // PORTA engedélyezése
PORTA->PCR[1] = 0x0200; // PTA1 legyen UART0_Rx üzemmódban
}

/* RGB LED inicializálása Program2_7 alapján */
void LED_init(void) {
SIM->SCGC5 |= 0x400; // Port B engedélyezése
SIM->SCGC5 |= 0x1000; // Port D engedélyezése
PORTB->PCR[18] = 0x100; // PTB18 legyen GPIO (MUX = 1)
PTB->PDDR |= 0x40000; // PTB18 legyen kimenet (bit18)
PTB->PSOR = 0x40000; // Piros LED lekapcsolása (negatív logika!)
PORTB->PCR[19] = 0x100; // PTB19 legyen GPIO (MUX = 1)
PTB->PDDR |= 0x80000; // PTB19 legyen kimenet (bit19=1)
PTB->PSOR = 0x80000; // Zöld LED lekapcsolása (negatív logika!)
PORTD->PCR[1] = 0x100; // PTD1 legyen GPIO (MUX = 1)
PTD->PDDR |= 0x02; // PTD1 legyen GPIO (MUX = 1)
PTD->PSOR = 0x02; // Kék LED lekapcsolása (negatív logika!)
}

// LED-ek ki/bekapcsolása a kapott adat bitjeinek megfelelően
void LED_set(int value) {
if (value & 1) // A 0. bit a piros LED-et vezérli
PTB->PCOR = 0x40000; // Piros LED be
else
PTB->PSOR = 0x40000; // Piros LED ki

if (value & 2) // Az 1. bit a zöld LED-et vezérli
PTB->PCOR = 0x80000; // Zöld LED be
else
PTB->PSOR = 0x80000; // Zöld LED ki

if (value & 4) // A 2. bit a kék LED-et vezérli
PTD->PCOR = 0x02; // Kék LED be
else
PTD->PSOR = 0x02; // Kék LED ki
}


Megszakítások keltése időzítőkkel

Az "Időzítők, számlálók" c. fejezetben lekérdezéses módban használtuk a TPMx általános célú időzítőket. Megszakításos módban azonban hatékonyabban használhatjuk ezeket - egyidejűleg akár több időzítőt is használhatunk.


7. ábra: A TPMx_SC állapotjelző és vezérlő regiszter bitkiosztása

A TPMx általános célú időzítők megszakításának engedélyezéséhez a TPMx_SC állapotjelző és vezérlő regiszter TOIE (Time Out Interrupt Enable) megszakítást engedélyező bitet kell '1'-be állítani. Ennek hatására túlcsorduláskor (amikor a TOF jelzőbit bebillen) megszakítás keletkezik. Amint a megszakítási vektorok táblázatában (1. táblázat) láthatjuk, a TPM0, TPM1 és TPM2 időzítőkhöz rendre az IRQ17, IRQ18, IRQ19 sorszámú megszakítás tartozik, ennek megfelelően az engedélyezésük az NVIC-ISR[0] regiszter 17 .. 19. bitjeinek '1'-be állításával történik.
NVIC->ISER[0] |= 0x0020000;  //IRQ17 engedélyezése ISER[0]) 17. bitje)
NVIC->ISER[0] |= 0x0040000; //IRQ18 engedélyezése ISER[0]) 18. bitje)
NVIC->ISER[0] |= 0x0080000; //IRQ19 engedélyezése ISER[0]) 19. bitje)

Program6_5: időzítőkkel keltett megszakítások

Ebben a programban a főprogram a kék LED-et villogtatja folyamatosan, A TPM0 megszakítások a piros LED állapotát billentik át minden megszakításkor, a TPM1 megszakítások pedig a zöld LED állapotát váltogatják. Mindegyik LED más-más gyakorisággal villog, így  végeredményben a nyolc alapszínt látjuk váltakozni.  Ez a program tulajdonképpen a Program5_5 és Program5_6 mintapéldák összevonásának és  programmegszakítással kiegészített változatának tekinthető.

A programban szokás szerint az alapértelmezett 20.97 MHz-es CPU órajelet használjuk. A megfelelő hosszúságú időzítések érdekében a maximális (128-szoros) előosztási arányt állítjuk be (PS bitcsoport = 7). A 0xFFFF modulo-val beállított 16 bites TPM0 számláló így 65536 * 128 / 20 970 000 Hz = 0,4 s időközönként csordul túl és okoz megszakítást, melynek kiszolgálásához TPM0_IRQHandler() névvel kell függvényt definiálnunk.

A TPM1 számláló modulo regiszterét fele akkora értékre (0x7FFF) állítjuk be, így kétszer gyakrabban, kb. 0,2 másodpercenként okoz megszakítást.

A főprogramban a kék LED állapotát 1,5 másodpercenként billentjük át, így ez villog leglassabb ütemben.
 
5. lista: A Program6_5/main.c program listája
/* Program6_5: a piros és zöld LED-ek villogtatása TPM0 és TPM1 megszakításban
*
* A rendszer órajel alapértelmezetten 20.97 MHz,
* amit TPM0 előosztója 20.97 MHz/128 = 163.828 kHz-re leoszt.
* A 16 bites TPM0 max. modulo esetén 65536/163.828 = 400 ms
* körüli késleltetést ad, programunkban ennyi idő telik el
* két TPM0 megszakítás között. Minden TPM0 megszakításban
* átbillentjük a piros LED állapotát.
*
* TPM1-et úgy konfiguráljuk, hogy kétszer gyakrabban (200 ms
* időközönként) adjon megszakítást, mint TPM0. Minden TPM1
* megszakításban átbillentjük a zöld LED állapotát.
*
* A főprogram vételen ciklusában a kék LED állapotát billegtetjük
* 1500 ms időközönként, szoftveres késleltetéssel.
*
* A program forrása: Mazidi et al., Freescale ARM Cortex-M Embedded Programming
* http://www.microdigitaled.com/ARM/Freescale_ARM/Code/Freescale_ARM_codes.htm
*/

#include <MKL25Z4.H>
void delayMs(int n);

int main (void) {
__disable_irq(); // Megszakítások globális tiltása

SIM->SCGC5 |= 0x400; // Port B engedélyezése
SIM->SCGC5 |= 0x1000; // Port D engedélyezése
PORTB->PCR[18] = 0x100; // PTB18 legyen GPIO (MUX = 1)
PTB->PDDR |= 0x40000; // PTB18 legyen kimenet (bit18)
PORTB->PCR[19] = 0x100; // PTB19 legyen GPIO (MUX = 1)
PTB->PDDR |= 0x80000; // PTB19 legyen kimenet (bit19=1)
PORTD->PCR[1] = 0x100; // PTD1 legyen GPIO (MUX = 1)
PTD->PDDR |= 0x02; // PTD1 legyen GPIO (MUX = 1)

SIM->SOPT2 |= 0x01000000; // MCGFLLCLK legyen a közös TPM órajel

SIM->SCGC6 |= 0x01000000; // TPM0 engedélyezése
TPM0->SC = 0; // Konfigurálás idejére letiltjuk
TPM0->SC = 0x07; // Előosztó /128 legyen
TPM0->MOD = 0xFFFF; // Maximális modulo érték
TPM0->SC |= 0x80; // TOF túlcsordulásjelző törlése
TPM0->SC |= 0x40; // Megszakítás engedélyezése
TPM0->SC |= 0x08; // Számlálás engedélyezése
NVIC->ISER[0] |= 0x00020000; // IRQ17 engedélyezése (ISER[0] bit17)

SIM->SCGC6 |= 0x02000000; // TPM1 engedélyezése
TPM1->SC = 0; // Konfigurálás idejére letiltjuk
TPM1->SC = 0x07; // Előosztó /128 legyen
TPM1->MOD = 0x7FFF; // Modulo a maximum fele legyen!
TPM1->SC |= 0x40; // Megszakítás engedélyezése
TPM1->SC |= 0x08; // Számlálás engedélyezése
NVIC->ISER[0] |= 0x00040000; // IRQ18 engedélyezése (ISER[0] bit18)

__enable_irq(); // Megszakítások globális engedélyezése

while (1) {
PTD->PTOR = 0x02; // A kék LED állapotának átbillentése
delayMs(1500);
}
}

//---------------------------------
// TPM0 megszakítások kiszolgálása
//---------------------------------
void TPM0_IRQHandler(void) {
PTB->PTOR = 0x40000; // A piros LED átbillentése
TPM0->SC |= 0x80; // A túlcsordulásjelző bit törlése
}

//---------------------------------
// TPM1 megszakítások kiszolgálása
//---------------------------------
void TPM1_IRQHandler(void) {
PTB->PTOR = 0x80000; // A zöld LED állapotának átbillentése
TPM1->SC |= 0x80; // A túlcsordulásjelző bit törlése
}

//--------------------------------------------------------------
// Késleltető függvény alapértelmezett órajelhez (20.97152 MHz)
//--------------------------------------------------------------
void delayMs(int n) {
int i, j;
for(i = 0 ; i < n; i++)
for (j = 0; j < 3500; j++);
}

SysTick megszakítások

A SysTick időzítő is jó lehetőséget kínál programmegszakítások keltésére. A 24 bites számláló vagy a CPU órajelét, vagy  annak 16-tal leosztott értékét számlálja. A számlálás az újratöltési értéktől visszafelé, nulláig tart. Alulcsorduláskor újratöltődik a számláló értéke, s '1'-be áll a COUNTFLAG, s ha megszakítás engedélyezett, akkor megszakítás történik (15. vektor, melynek IRQ száma -1.


8. ábra: A SysTick időzítő blokkvázlata

A Systick vezérlő regiszterének (SYST_CSR, vagy CMSIS elnevezéssel SysTick->CTRL) bitkiosztása a 9. ábrán látható. A Cortex-M0+ Devices Generic User Guide dokumentumból átvett ábrán TICKINT felel meg annak a megszakítást engedélyező bitnek, amit a 8. ábrán INTEN névvel tüntettünk fel.


9. ábra: A SYST_CSR (SysTick->CTRL) vezérlő regiszter bitkiosztása

Az egyes bitek szerepe a következő:

Program6_6: SysTick megszakítások

Az alábbi programban a SysTick megszakítások segítségével 1 másodpercenként átbillentjük a piros LED állapotát. Az alapértelmezett 20,97 MHz-es órajelet a 16-szoros előosztón keresztül vezetjük a számlálóra, hogy a 24 bites számlálóval elő tudjuk állítani a kívánt hosszúságú időzítést. Az 1s időzítéshez az újratöltési regiszterbe a a CPU frekvencia 16-tal leosztott értékét eggyel csökkentve írjuk be (n-1-től nulláig tartó számlálás n ciklust vesz igénybe).
 
6. lista: A Program6_6/main.c program listája
/* Program6_6: A piros LED villogtatása SysTick megszakításban
*
* A SysTick időzítőt úgy konfiguráljuk, hogy 1 Hz gyakorisággal
* adjon megszakítást. Ehhez a /16 osztású alternatív órajelet kell
* választanunk.
*
* A program forrása: Mazidi et al., Freescale ARM Cortex-M Embedded Programming
* http://www.microdigitaled.com/ARM/Freescale_ARM/Code/Freescale_ARM_codes.htm
* Megjegyzés: Az alapértelmezett CPU frekvencia esetünkben 20.97 MHz
* a könyvben szereplő 41 MHz helyett!
*/

#include <MKL25Z4.H>

int main (void) {
__disable_irq(); // A megszakítások globális tiltása
SIM->SCGC5 |= 0x400; // PORTB engedélyezése
PORTB->PCR[18] = 0x100; // PTB18 GPIO módba
PTB->PDDR |= 0x40000; // PTB18 kimenet legyen

SysTick->LOAD = 20970000/16-1; // Újratöltéshez: 1s alatti óraütések száma - 1
SysTick->VAL = 0; // Kezdéshez alaphelyzetbe állunk
SysTick->CTRL = 3; // megszakítás engedélyezés, alternatív órajel
__enable_irq(); // Megszakítások globális engedélyezése

while(1) { } // Megszakításra várunk
}

void SysTick_Handler(void) {
PTB->PTOR = 0x40000; // A piros LED átbillentése
}

A megszakítások prioritásának beállítása

A megszakítási rendszer tulajdonságai között említettük, hogy az ARM Cortex-M mikrovezérlők megszakítási rendszere prioritáskezelésre és többszörös megszakításra is lehetőséget ad:
Az első 16 megszakítási vektorhoz tartozó kivételek közül néhánynak kötött (hardveresen meghatározott) a prioritása. A RESET prioritása -3, az NMI prioritás -2, a Hard Fault hibajelző megszakításé pedig -1. A többi kivétel és periféria megszakítás prioritása 4 fokozatban állítható (0, 1, 2, 3), alapértelmezetten nulla. Az alacsonyabb számhoz magasabb prioritás tartozik, így a rögzített prioritású első három kivétel mindig elsőbbséget élvez bármelyik szabályozható prioritású kivétel és megszakítással szemben.

A változtatható prioritású kivételek (SVC, PENDSV, SYSTICK) prioritását a Rendszervezérlő Blokk (System Control Block) SCB->SHP[0] és SCB->SHP[1] regisztereiben állítható be. Ennek leírása az ARM Cortex-M0+ Devices Generic User Guide 4.3.8 alfejezetében található.

A periféria megszakítások prioritása az NVIC_IPRx (CMSIS elnevezéssel NVIC->IPR[x]) regiszterekben adható meg (ahol x = 0 .. 7). Az NVIC_IPRx bitkiosztása az ARM Cortex-M0+ Devices Generic User Guide 4.2.5 alfejezetében található.

Például, ha a TPM2 megszakítás prioritását 3-ra szeretnénk beállítani, először meg kell határoznunk, hogy melyik NVIC_IPRx regiszter tartalmát kell módosítanunk. Mivel egy NVIC_IPRx regiszter 4 db megszakítási vektor prioritását szabályozza (minden megszakításhoz egy bájt van rendelve), a megszakítás IRQn sorszámát el kell osztani 4-gyel. TPM2-höz a 19-es IRQn szám tartozik, így az érintett regiszter NVIC_IPR4 lesz (19/4 = 4). Ezen belül az érintett bájt sorszáma a fenti osztás maradéka, azaz 3 lesz. Ezen bájton belül a legfelső két bitbe kell írni a prioritást, azaz 6 bináris helyiértékkel balra tolva. Végeredményben így írhatjuk:
NVIC->IP[IRQn/4] |= PRIO << (8 * (IRQn % 4) + 6);
ahol esetünkben IRQn = 19, PRIO pedig 3 (a beállítani kívánt prioritás, amely 0 .. 3 közötti érték lehet).

Az alábbi mintapéldában mi a kényelmesebb utat fogjuk járni: a megszakítások engedélyezésére és prioritásuk beállítására a CMSIS Core csomag által biztosított API függvényeket használjuk. A fenti példánál maradva:
#define PRIOTPM2 3U                    // TPM2 megszakítás prioritása    
NVIC_SetPriority(TPM2_IRQn,PRIOTPM2); // TPM2 megszakítás prioritásának beállítása
NVIC_EnableIRQ(TPM2_IRQn); // TPM2 megszakítás engedélyezése
Az NVIC_SetPriority() és az NVIC_EnableIRQ() inline függvények a CMSIS Core csomag core_cm0plus.h állományában vannak definiálva, a TPM2_IRQn makrót pedig az MKL25Z4.h fejléc állomány definiálja.

Program6_7: Egymásba ágyazott megszakítások vizsgálata

Az alábbi programban két, különböző prioritású megszakítást használunk a TPM1 és TPM2 időzítőkkel:
A megszakításokban viszonylag hosszú szoftveres késleltetéseket használunk. A valós alkalmazásoknál természetesen kerülendő mindenfajta késleltetés és időhúzás a megszakításokban, most azonban ez teszi számunkra szabad szemmel észlehetővé, hogy melyik megszakítás blokkolja a másikat.

A kísérletet két menetben tudjuk lefolytatni:
7. lista: A Program6_7/main.c program listája
/* Program6_7: Egymásba ágyazott megszakítások vizsgálata

* Timer1 1 Hz-es gyakorisággal kelt megszakítást.
* A megszakítást kiszolgáló függvényben a piros LED-et bekapcsoljuk,
* majd 360 ms késleltetés után kikapcsoljuk.
*
* Timer2 10 Hz gyakorisággal kelt megszakítást.
* A megszakítást kiszolgáló függvényben a kék LED-et bekapcsoljuk,
* majd 20 ms késleltetés után kikapcsoljuk. A kék LED-del együtt
* a PTD2 kimenetet is billegtetjük, oszcilloszkópos vizsgálathoz.
*
* Ha Timer1 megszakítása magasabb prioritást élvez, akkor Timer2
* megszakításait blokkolja Timer1 megszakítást kiszolgáló függvénye.
* Ez abból látszik, hogy a kék LED nem villog, amikor a piros LED világít.
*
* Ha viszont Timer2 megszakítása kap magasabb prioritást, akkor
* Timer1 megszakítást kiszolgáló függvényét szakítják meg
* Timer2 megszakításai, ekkor a kék LED folyamatosan villog.
*
* A program forrása: Mazidi et al., Freescale ARM Cortex-M Embedded Programming
* http://www.microdigitaled.com/ARM/Freescale_ARM/Code/Freescale_ARM_codes.htm
*
* Megjegyzések:
* - Az eredeti programban talált hibákat és keveredést kijavítottuk.
* - A 32 kHz-es oszcillátor használatához #define CLOCK_SETUP 0
* beállítás kell a system_MKL25Z.h állományban.
*/

#include "MKL25Z4.h"

void Timer1_init(void);
void Timer2_init(void);
void delayMs(int n);

int main (void) {
__disable_irq();
__disable_irq(); // Megszakítások globális tiltása

SIM->SCGC5 |= 0x400; // Port B engedélyezése
SIM->SCGC5 |= 0x1000; // Port D engedélyezése
PORTB->PCR[18] = 0x100; // PTB18 legyen GPIO (MUX = 1)
PTB->PDDR |= 0x40000; // PTB18 legyen kimenet (bit18)
PTB->PSOR = 0x40000; // Piros LED ki
PORTD->PCR[1] = 0x100; // PTD1 legyen GPIO (MUX = 1)
PTD->PDDR |= 0x02; // PTD1 legyen GPIO (MUX = 1)
PORTD->PCR[2] = 0x100; // PTD2 is legyen GPIO módban
PTD->PDDR |= 0x04; // PTD2 is legyen kimenet
PTD->PSOR = 0x06; // Kék LED és PTD2 ki
Timer1_init();
Timer2_init();
__enable_irq(); // Megszakítások globális engedélyezése

while(1) { // Megszakításra várunk
}
}

//---------------------------------
// TPM1 megszakítások kiszolgálása
//---------------------------------
void TPM1_IRQHandler(void) {
PTB->PCOR = 0x40000; // Piros LED be (active LOW
delayMs(350);
PTB->PSOR = 0x40000; // Piros LED ki
TPM1->SC |= 0x80; // Túlcsordulásjelző bit törlése
}

//---------------------------------
// TPM2 megszakítások kiszolgálása
//---------------------------------
void TPM2_IRQHandler(void) {
PTD->PCOR = 0x06; // Kék LED és PTD2 be (active LOW)
delayMs(50);
PTD->PSOR = 0x06; // Kék LED és PTD2 ki
TPM2->SC |= 0x80; // Túlcsordulásjelző bit törlése
}

// TPM1 és TPM2 megszakításainak prioritása 0 és 3 közötti szám lehet.
// A kisebb szám nagyobb prioritást jelent
#define PRIOTPM1 2U // TPM1 megszakítás prioritása
#define PRIOTPM2 3U // TPM2 megszakítás prioritása

//---------------------------------
// TPM1 inicializálása
//---------------------------------
void Timer1_init(void)
{
SIM->SCGC6 |= 0x02000000; // TPM1 engedélyezése
SIM->SOPT2 |= 0x03000000; // MCGIRCLK legyen a közös TPM órajel

TPM1->SC = 0; // Konfiguráláshoz letiltjuk
TPM1->MOD = 32767; // 1s alatti óraütések száma - 1
TPM1->SC |= 0x80; // TúlcsordulásjelzőTúlcsordulásjelzo bit törlése
TPM1->SC |= 0x48; // Számlálás indítása, megszakítás engedélyezése

NVIC_SetPriority(TPM1_IRQn, PRIOTPM1); // TPM1 megszakítás prioritásának beállítása
NVIC_EnableIRQ(TPM1_IRQn); // TPM1 megszakítások engedélyezése
}

//---------------------------------
// TPM2 inicializálása
//---------------------------------
void Timer2_init(void) {
SIM->SCGC6 |= 0x04000000; // TPM2 engedélyezése
TPM2->SC = 0; // Konfiguráláshoz letiltjuk
TPM2->MOD = 3276; // modulo = 32768/10 -1
TPM2->SC |= 0x80; // Túlcsordulásjelző bit törlése
TPM2->SC |= 0x48; // Számlálás indítása, megszakítás engedélyezése

NVIC_SetPriority(TPM2_IRQn, PRIOTPM2); // TPM2 megszakítás prioritásának beállítása
NVIC_EnableIRQ(TPM2_IRQn); // TPM1 megszakítások engedélyezése
}

//--------------------------------------------------------------
// Késleltető függvény alapértelmezett órajelhez (20.97152 MHz)
//--------------------------------------------------------------
void delayMs(int n) {
int i, j;
for(i = 0 ; i < n; i++)
for (j = 0; j < 3500; j++);
}