Programmegszakítások
A fejezet tartalma:- A programmegszakítás alapjai
- Az MKL25Z128VLK4 mikrovezérlő megszakítási rendszere
- Megszakítás GPIO port kivezetésekkel
- UART soros porti megszakítások
- Megszakítások keltése időzítőkkel
- SysTick megszakítások
- A megszakítások prioritásának beállítása
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:
- a CPU időt arra pazaroljuk, hogy lekérdezéseket hajtson végre
- tovább rontja a hatékonyságot, ha túl ritkán, vagy túl gyakran végezzük a lekérdezést.
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 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
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:
- 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ási
jelzőbit
bebillenése) történt. A legtöbb utasítás gyorsan, 1 órajel
ciklus alatt
befejeződik. Néhány utasítás azonban sok utasításciklusig
tart: Load
Multiple (LDM), Store Multiple (STM), Push, Pop, MULS
(némely CPU
esetében akár 32 ciklus is lehet). Az ilyen, sokciklusú
utasítás
befejezése késleltetné a megszakítások kiszolgálását, ezért
ha ilyen
utasítás végrehajtása van folyamatban, amikor megszakítási
kérelem
érkezik, akkor a CPU:
- Eldobja az utasítást
- Reagál a megszakításra
- Végrehajtja a megszakítást kiszolgáló eljárást
- Visszatér a megszakításból
- Újrakezdi az eldobott utasítást
- a veremtárban automatikusan elmentésre kerül a CPU néhány fontos regisztere a regiszterkészletből: R0..R3, R12, LR/R14 (Link regiszter), PC/R15 (programszámláló, azaz a visszatérési cím), valamint az xPSR állapotjelző bitek.
- a Link Regiszter (LR/R14) egy speciális értéket vesz fel (EXC_RETURN).
- IPSR tartalma is
frissül:
új értéke a kivétel/IRQ számát tartalmazza.
- a PC programszámlálóba a megszakítási vektorok táblázatából automatikusan betöltődik a beérkezett programmegszakítási kérelemhez tartozó előre meghatározott cím (amit "interrupt vektornak" nevezünk), ami azt eredményezi, hogy a program futása eltérül, az interrupt vektorból elővett címen folytatódik.
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 |
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_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
- __disable_irq() – megszakítások globális tiltása
- __enable_irq() – megszakítások globális engedélyezése
- NVIC_EnableIRQ(IRQn_Type IRQn) – adott megszakítási vektor engedélyezése
- NVIC_DisableIRQ(IRQn_Type IRQn) – adott megszakítási vektor tiltása
- NVIC_GetPendingIRQ(IRQn_Type IRQn) – IRQn megszakítás állapotának vizsgálata
- NVIC_SetPendingIRQ(IRQn_Type IRQn) – IRQn megszakítási kérelem aktiválása
- NVIC_ClearPendingIRQ(IRQn_Type IRQn) – IRQn megszakítási kérelem törlése
- NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority) – IRQn prioritásának beállítása
- NVIC_GetPriority(IRQn_Type IRQn) – IRQn prioritásának lekérdezése
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:
- PORTx_PCRn – Portkivezetés vezérlő regiszterek:
- portonként 32 db. regiszter
- mindegyik regiszter megfelel egy-egy kivezetésnek.
- PORTx_ISFR – Megszakításjelző bitek:
- portonként 1 db. regiszter
- Minden bit megfelel egy-egy kivezetésnek
- A bit ’1’-be áll, ha a megszakítási esemény bekövetkezett és kiszolgálásra vár
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) |
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:
- Konfigurálás előtt globálisan tiltsuk le a megszakításokat a __disable_irq() függvény meghívásával!
- Lefutó élre történő érzékenyítéshez a PORTA->PCR[12] és PORTA->PCR[13] portvezérlő regiszterek IRQC megszakításvezérlő bitcsoportjába 1010 bitkombinációt állítsunk be (0x000A 0000)!
- Az NVIC_ISER (CMSIS hivatkozással NVIC->ISER[0]) regiszter 30. bitjének '1'-be állításával engedélyezzük a PORTA-hoz tartozó IRQ30 megszakítást! Az A porthoz tartozó valamennyi bemenet ugyanezt a megszakítást aktiválja...
- Konfigurálás végén globálisan engedélyezzük a megszakításokat az __enable_irq() függvény meghívásával!
- Definiáljuk a megszakítást kiszolgáló függvényt PORTA_IRQHandler()
névvel (a megszakításokat kiszolgáló függvényeknek nem lehet
sem bemenő paramétere, sem visszatérési értéke)!
- Ha meg akarjuk különböztetni a portbemenetekhez tartozó eseményeket (lásd Program6_2), akkor vizsgáljuk meg a PORTA_ISFR (CMSIS hivatkozással PORTA->ISFR) regiszter bitjeit, hogy melyik bemenet okozta a megszakítást!
- Végezzük el a kívánt tevékenységet!
- Töröljük a megszakításjelző bitet a PORTA->ISFR regiszterben ('1"-be írás töröl)!
- FRDM-KL25Z kártya
- Kössünk két nyomógombot a PTA12, illetve PTA13 kivezetések és a közös pont (GND) közé! Külső felhúzás nem szükséges, a programban mindkét bemenetre bekapcsoljuk a belső felhúzást.
/* 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:
- FRDM-KL25Z kártya
- Kössünk két nyomógombot a PTA12, illetve PTA13 kivezetések és a közös pont (GND) közé! Külső felhúzás nem szükséges, a programban mindkét bemenetre bekapcsoljuk a belső felhúzást.
/* 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:
- Konfigurálás előtt globálisan tiltsuk le a megszakításokat a __disable_irq() függvény meghívásával!
- Le- és felfutó élre történő érzékenyítéshez a PORTD->PCR[4] portvezérlő regiszter IRQC megszakításvezérlő bitcsoportjába 1011 bitkombinációt állítsunk be (0x000B 0000)!
- Az NVIC_ISER (CMSIS hivatkozással NVIC->ISER[0]) regiszter 31. bitjének '1'-be állításával engedélyezzük a PORTD-hoz tartozó IRQ31 megszakítást! Az D porthoz tartozó valamennyi bemenet ugyanezt a megszakítást aktiválja...
- Konfigurálás végén globálisan engedélyezzük a megszakításokat az __enable_irq() függvény meghívásával!
- Definiáljuk a megszakítást kiszolgáló függvényt PORTD_IRQHandler() névvel (a megszakításokat kiszolgáló függvényeknek nem lehet sem bemenő paramétere, sem visszatérési értéke)!
- Ha meg akarjuk különböztetni az egyes portbemenetekhez tartozó eseményeket (lásd Program6_2), akkor vizsgáljuk meg a PORTD_ISFR (CMSIS hivatkozással PORTD->ISFR) regiszter bitjeit, hogy melyik bemenet okozta a megszakítást!
- Végezzük el a kívánt tevékenységet!
- Töröljük a megszakításjelző bitet a PORTD->ISFR regiszterben ('1"-be írás töröl)!
- FRDM-KL25Z kártya
- Kössünk egy nyomógombot a PTD4 kivezetés és a közös pont (GND) közé! Külső felhúzás nem szükséges, a programban mindkét bemenetre bekapcsoljuk a belső felhúzást.
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:
- Megszakítások engedélyezése
- TIE: Megszakítás ha a küldő adatregiszter üres
- TCIE: Megszakítás az átvitel végén
- RIE: Megszakítás ha van beérkezett adat
- ILIE: Megszakítás ha tétlen a vonal
- Modul engedélyezés
- TE: Adó (transmitter) engedélyezés
- RE: Vevő (receiver) engedélyezés
- Továbbiak:
- RWU: a vevőt standby módba teszi, felébresztés az előírt feltételek esetén
- SBK: Break karakter (csupa nulla) küldése
- INT12 - UART0
- INT13 - UART1
- INT14 - UART2
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ű:
- Az UART0_CR2 regiszter RIE bitjét '1'-be állítjuk (ennek hatására karakter érkezésekor az RDIF jelzőbit megszakítási kérelemként jelentkezik az NVIC megszakításvezérlőben).
- Engedélyezzük az UART0 porthoz tartozó IRQ12 megszakítási vektort.
- Globálisan engedélyezzük a megszakításokat.
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ő:
- COUNTFLAG - Eseményjelző, amely '1', ha a visszaszámlálás 0-ra futott. Kiolvasáskor automatikusan törlődik.
- CLKSOURCE - az órajel forrása (0: rendszer órajel/16, 1: a rendszer órajele)
- TICKINT- SysTick megszakítás engedélyezése (1: megszakítás kérése, amikor a visszaszámlálás 0-ra fut)
- ENABLE - a SysTick számláló bemenetének engedélyezése (0: nincs számlálás, 1: számlálás engedélyezve)
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 egyszerre beérkező megszakítási kérelmek közül mindig a magasabb prioritású érvényesül.
- Az azonos prioritású megszakítási kérelmek közül az alacsonyabb sorszámú megszakítási vektorhoz tartozó megszakításnak van elsőbbsége.
- A magasabb prioritású megszakítás az alacsonyabb prioritású megszakítás kiszolgálását is félbeszakíthatja.
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: - TPM1 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.
- TPM2 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, esetleges oszcilloszkópos vizsgálathoz.
A kísérletet két menetben tudjuk lefolytatni:
- Az első esetben TPM1 prioritása magasabb. Emiatt a 360 ms-os késleltetés alatt TPM2 megszakítási kérelmei nem érvényesülnek, így amíg a piros LED világít, a kék LED nem villog.
- A második esethez módosítsuk TPM2 prioritását! A #define PRIOTPM2 3U sor helyett írjuk ezt:
#define PRIOTPM2 1U
Fordítsuk le a módosított programot és futtassuk! Most - mivel TPM2 prioritása lett a magasabb - azt fogjuk tapasztalni, hogy a kék LED folyamatosan villog.
/* 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++);
}