Tegyük fel, hogy egy 1 kHz-es periodikus timer megszakítására épülő háttérfeladatot és egy UART RX megszakítást kell üzemeltetnünk ugyanazon a Cortex-M3-on. A periféria már konfigurált, a vektortábla megfelelő bejegyzése a kezelőre mutat (lásd Reset és boot-folyamat: vektortábla), a globális megszakítás-maszk nyitva van — mégsem történik semmi. A hiba oka szinte mindig ugyanaz: a periféria kér, de a kérést az NVIC (Nested Vectored Interrupt Controller) még nem engedélyezte, vagy a prioritás úgy lett beállítva, hogy a kérés örökre várakozik.
Ez a helyzet tipikus mérnöki feladatot ad: két független kaput kell
összehangolni. Az egyik kapu a periféria saját megszakítás-engedélyező
bitje (ez a periféria-vezérlő regisztereiben van), a másik az NVIC
engedélyező bitje. A kettő közül az NVIC az, amelyet assembly szinten,
közvetlen regiszterírással ismerni kell: hibakeresésnél nem elég egy
CMSIS-függvényt hívni, a regiszter visszaolvasásával kell bizonyítani,
hogy az engedélyezés és a prioritás tényleg beállt. Ráadásul a prioritás
beállítása bitmező-művelet egy 32 bites regiszterben — pontosan az a
minta, amelyet AArch64-en a BFI/UBFX kapcsán
már láttunk (Bitfield-utasítások: BFI/UBFX/SBFX és
csomagolt adatszerkezetek).
Definíció (NVIC-engedélyezés és prioritás). Egy külső IRQ akkor válik kiszolgálhatóvá, ha (a) az NVIC ISER regiszterének megfelelő bitje 1, (b) a globális maszk (PRIMASK) nyitva van, és (c) az adott kivétel prioritása nem alacsonyabb egy éppen futó, magasabb rendű kivételénél. A prioritásértéket az IPR megfelelő bájtja tárolja; kisebb szám magasabb rendet jelent (lásd A Cortex-M kivételmodell: prioritás és vektortábla-bejegyzések).
Az NVIC az Armv7-M architektúra része, és fix, architekturálisan rögzített címeken érhető el a System Control Space-ben (SCS). Az itt használt összes utasítás Thumb-2/Armv7-M ISA (a könyv II. része); az I. részben tanult A64 utasítások itt nem érvényesek, a két ISA egyetlen példában sem keverhető.
| Regiszter | Szó-cím | Indexelés | 1 írásának hatása | Visszaolvasáskor |
|---|---|---|---|---|
NVIC_ISER0–7 |
0xE000E100 + 4·i |
IRQ 32i … 32i+31 |
engedélyez | az engedélyezettség |
NVIC_ICER0–7 |
0xE000E180 + 4·i |
ugyanaz | letilt | az engedélyezettség (ugyanaz, mint az ISER) |
NVIC_ISPR0–7 |
0xE000E200 + 4·i |
ugyanaz | pending beállít | az aktuális pending állapot |
NVIC_ICPR0–7 |
0xE000E280 + 4·i |
ugyanaz | pending töröl | az aktuális pending állapot (ugyanaz, mint az
ISPR) |
NVIC_IABR0–7 |
0xE000E300 + 4·i |
ugyanaz | nincs hatása | aktív állapot |
NVIC_IPR bájtok |
0xE000E400 + n |
IRQ n |
prioritásbájt | prioritásbájt |
A táblázat két szabálya a gyakorlatban a legfontosabb. Egyrészt az
NVIC regiszterei írás-1-hatásúak: a 0 írása nem töröl
semmit, ezért ISER = (1 << b) és nem
ISER &= ~(1 << b) alakban használjuk őket.
Másrészt a párok ugyanazt az állapotot olvassák: az
ISER és az ICER az engedélyezettséget, az
ISPR és az ICPR az aktuális pending állapotot.
Az ICPR tehát nem „mindig 0-t” ad, hanem 1-et mutat minden
olyan biten, amely éppen függőben van. Emiatt a |= és
&= operátorok ezeken a regisztereken félrevezetők: egy
ICPR |= m a már függő összes megszakítás
pending bitjét is visszaírná, és ezzel törölné. Az ICER
1-es írása csak tilt, a pending bitet nem törli; arra az
ICPR való.
Az engedélyezés bitkiosztása egyszerű, mert az
ISER/ICER bitek IRQ-szám
szerint indexeltek:
A str.w r3, [r2, r1, lsl #2] alakú regiszter-offszetes
tárolás pontosan ezt a 4 · i bájt-offszetet állítja elő,
külön szorzás nélkül. Fontos következmény: a IRQn = 33
esetén a regiszter az ISER1 (0xE000E104), a
bit pedig az 1-es — nem a 33-as, mert a 32 bites szóhatáron túl új
regiszter kezdődik.
Ugyanez a séma érvényes az ISPR-re (pending beállítás)
és az ICPR-re (pending törlés). Az ISPR
különösen hasznos hibakeresésnél: egy periféria nélkül,
tisztán szoftverből pendíthetjük a megszakítást, és ellenőrizhetjük,
hogy a kezelő elindul-e. Az IABR ezzel szemben csak
olvasható: megmutatja, hogy az adott kivétel éppen aktív (fut vagy
futott, és még nem fejeződött be) — ez reentrancia-vizsgálatnál és a
kezelő „beszorulásának” kimutatásánál hasznos.
A prioritásbájtok a 0xE000E400-tól induló, 240 bájtos
tömbben vannak, és itt egy másik indexelés él, mint a
vektortáblában: a prioritásmező bájtindexe az IRQ-szám,
nem a kivételszám. Ennek oka, hogy a konfigurálható rendszerkivételek
(MemManage, BusFault, UsageFault, SVCall, Debug Monitor, PendSV és
SysTick) prioritása nem itt, hanem az SCB SHPR1–3
regisztereiben (0xE000ED18–0xE000ED20) található. A Reset
(−3), az NMI (−2) és a HardFault (−1) prioritása fix: ezeknek nincs
írható SHPR-mezőjük, és nem is vezethető le rájuk beírandó
prioritásérték (kivételmodell). Az
NVIC_SetPriority(SysTick_IRQn, …) CMSIS-hívás ezért nem is
az IPR-t írja, hanem az SHPR-t — a látszat
ellenére a két útvonal nem ugyanaz.
Egy IRQ prioritásmezőjének elhelyezése levezetve:
IPR szó négy prioritásmezőt
tartalmaz;0xE000E400 + 4·w;Például IRQ 33 esetén
,
a szó címe 0xE000E420,
,
tehát a mező a szó 15:8 bitjein van, a bájt címe pedig
0xE000E421.
A művelet maga tipikus read–modify–write bitmező-illesztés: maszkkal
töröljük a régi mezőt, majd a 8 bites új értéket a helyére tolva
OR-ral összefésüljük. A a034-ben tanult BFI
itt nem használható közvetlenül, mert a
BFI az lsb és a szélesség operandusát is
immediate-ként várja; futásidőben számolt mezőpozícióhoz a maszk+shift
minta marad. Ha viszont az IRQ-szám fordítási időben ismert (például egy
fix, beégetett konfigurációnál), a fordító jellemzően magától
bfi/ubfx szekvenciát állít elő — ezt a mintát
a Tipikus fordítói mintázatok felismerése
tárgyalja.
Két gyakorlati buktató tartozik ide. Először: a prioritásmező
nem azonos a logikai prioritásszámmal. A kivételmodell szerint négy dolgot kell külön
kezelni: a nyers prioritásbájtot, a megvalósított bitek számát
(CMSIS-ben __NVIC_PRIO_BITS, a könyv laborjában
LAB_NVIC_PRIO_BITS), az ebből adódó logikai prioritást, és
az AIRCR.PRIGROUP szerinti csoport- és alprioritást. A
CMSIS NVIC_SetPriority() a kapott logikai értéket balra
tolja
bittel. Ha mi nyers bájtot írunk, a hívó felelőssége a shiftelés.
Másodszor: a nem implementált alsó bitek olvasáskor 0-t adnak, ezért a
0x01 és 0x07 ugyanazt a prioritást jelentheti
egy 3 bitet megvalósító Cortex-M3-on. Ezt nem lehet kitalálni, csak
megmérni: írjunk 0xFF-et egy szabad IRQ prioritásmezőjébe,
olvassuk vissza, és a visszakapott minta megmutatja a megvalósított
biteket.
A megszakítás-vezérlő beállítása nem tetszőleges sorrendben biztonságos. A követendő minta:
nvic_disable_irq(n) — tisztítsuk a korábbi
állapotot;nvic_clear_pending(n) — ha a periféria már pendített, a
bit törlése megakadályozza a „száguldó” kezelőt;nvic_set_priority(n, prio);nvic_enable_irq(n);cpsie i, PRIMASK) — ez
külön mechanizmus, amelyet a ISR írása: C és
assembly vegyesen és a Megszakítás-latencia
mérése és korlátai tárgyal részletesen;A 3. lépés elhagyása a leggyakoribb „első próbálkozás” hiba: ha az engedélyezés előtt egy pending bit már be van állítva, a kezelő azonnal lefut — sokszor még azelőtt, hogy a szoftveres állapot, amire támaszkodik, inicializálódott volna.
Munkakönyvtár: ~/arm-labor/cm/a069/ —
az alfejezet minden laborfájlja ide kerül, és minden parancs innen
futtatandó (a könyvtárrendet a Labor-alapkörnyezet alfejezet írja le).
mkdir -p ~/arm-labor/cm/a069
cd ~/arm-labor/cm/a069A példa a fejezet laborkörnyezetének CMSDK APB timerét használja
mps2-an385 alatt. A periféria báziscíme és az IRQ-száma a
QEMU machine dokumentált memóriatérképéből ellenőrizendő, ezért a kód
szimbolikus konstansokat használ; az NVIC-címek architekturálisan
rögzítettek.
.syntax unified
.cpu cortex-m3
.thumb
.equ NVIC_ISER, 0xE000E100
.equ NVIC_ICER, 0xE000E180
.equ NVIC_ISPR, 0xE000E200
.equ NVIC_ICPR, 0xE000E280
.equ NVIC_IABR, 0xE000E300
.equ NVIC_IPR, 0xE000E400
.text
.thumb_func
.global nvic_enable_irq
@ r0 = IRQn (0..239)
nvic_enable_irq:
ubfx r1, r0, #5, #3 @ r1 = IRQn / 32 -> ISER index
ubfx r2, r0, #0, #5 @ r2 = IRQn % 32
movs r3, #1
lsls r3, r3, r2 @ maszk = 1 << (IRQn % 32)
movw r2, #(NVIC_ISER & 0xFFFF)
movt r2, #(NVIC_ISER >> 16)
str.w r3, [r2, r1, lsl #2] @ ISER[IRQn/32] = maszk
dsb
isb
bx lr
.thumb_func
.global nvic_disable_irq
@ r0 = IRQn ; ugyanaz a minta, csak NVIC_ICER bázissal
nvic_disable_irq:
ubfx r1, r0, #5, #3
ubfx r2, r0, #0, #5
movs r3, #1
lsls r3, r3, r2
movw r2, #(NVIC_ICER & 0xFFFF)
movt r2, #(NVIC_ICER >> 16)
str.w r3, [r2, r1, lsl #2]
dsb
isb
bx lr
.thumb_func
.global nvic_set_priority
@ r0 = IRQn, r1 = prioritásbájt (nyers IPR-bájt, nem logikai prioritásszám)
nvic_set_priority:
uxtb r1, r1 @ csak a 8 bites mező érdekes
ubfx r2, r0, #0, #2 @ r2 = IRQn % 4 -> bájt-sáv
lsls r2, r2, #3 @ bit-offszet = 8 * (IRQn % 4)
bic r0, r0, #3 @ r0 = IRQn & ~3 -> szó-offszet
movw r3, #(NVIC_IPR & 0xFFFF)
movt r3, #(NVIC_IPR >> 16)
add r3, r3, r0 @ r3 = IPR szó címe
ldr r0, [r3] @ régi szó
movs r12, #0xFF
lsls r12, r12, r2 @ maszk = 0xFF << bit-offszet
bic r0, r0, r12 @ mező törlése
lsls r12, r1, r2 @ új érték a helyére tolva
orrs r0, r0, r12 @ összefésülés
str r0, [r3] @ visszaírás
dsb
isb
bx lrA C oldali megfelelő és a teljes inicializálási sorrend:
#include <stdint.h>
#define NVIC_ISER(i) (*(volatile uint32_t *)(0xE000E100u + 4u * (i)))
#define NVIC_ICER(i) (*(volatile uint32_t *)(0xE000E180u + 4u * (i)))
#define NVIC_ICPR(i) (*(volatile uint32_t *)(0xE000E280u + 4u * (i)))
#define NVIC_IABR(i) (*(volatile const uint32_t *)(0xE000E300u + 4u * (i)))
#define NVIC_IPR_BYTE(irq) (*(volatile uint8_t *)(0xE000E400u + (uint32_t)(irq)))
#define IRQ_TIMER0 8u /* MPS2-AN385 Timer0 */
static void nvic_enable_irq(unsigned irq) { NVIC_ISER(irq / 32u) = 1u << (irq % 32u); }
static void nvic_disable_irq(unsigned irq) { NVIC_ICER(irq / 32u) = 1u << (irq % 32u); }
static void nvic_clear_pending(unsigned irq){ NVIC_ICPR(irq / 32u) = 1u << (irq % 32u); }
static void nvic_set_priority(unsigned irq, uint8_t raw_prio)
{
NVIC_IPR_BYTE(irq) = raw_prio; /* byte-index = IRQ-szám, nem kivételszám! */
}
void timer0_nvic_init(void)
{
nvic_disable_irq(IRQ_TIMER0);
nvic_clear_pending(IRQ_TIMER0); /* maradék pending törlése */
nvic_set_priority(IRQ_TIMER0, 0xC0u); /* nyers 0xC0: 3 bitnél logikai 6, 8 bitnél 192 */
nvic_enable_irq(IRQ_TIMER0); /* ISER bit beállítása */
__asm volatile ("dsb" ::: "memory");
__asm volatile ("isb" ::: "memory");
/* a globális maszk (PRIMASK) és a periféria engedélyezése külön lépés */
}Minden várt cím és bit ugyanabból a szimbolikus
IRQ-ból (IRQ_TIMER0 = 8) származik. Tiszta, csak
ezt az IRQ-t engedélyező állapotban:
| Ellenőrzés | Cím / művelet | Képlet | Várt érték (IRQ_TIMER0 = 8) |
|---|---|---|---|
| engedélyezettség | x/1xw 0xE000E100 |
ISER[8 / 32], bit 8 % 32 |
0x00000100 |
| prioritás bájt | x/1xb 0xE000E408 |
0xE000E400 + 8 |
0xC0 (a megvalósított felső bitek szerint
maszkolva) |
| prioritás szó | x/1xw 0xE000E408 |
0xE000E400 + 4·(8 / 4), sáv 8 % 4 = 0 |
alsó bájt 0xC0; a többi bájt a 9–11. IRQ
prioritása |
| pending | x/1xw 0xE000E280 |
ICPR[0], bit 8 |
a bit az aktuális pending állapot: 0, ha nincs függő kérés |
| aktív állapot | x/1xw 0xE000E300 |
IABR[0], bit 8 |
0, amíg a kezelő nem fut |
A pending regiszterek viselkedését egy rövid teszt igazolja, amely a
megszakítást közben tiltva tartja, így semmi nem fut
le: (1) tiltás és pending törlés; (2) ISPR-rel szoftveres
pending; (3) visszaolvasás az ISPR-ből és az
ICPR-ből (mindkettő 1-et mutat a biten); (4) törlés
ICPR-be írt 1-gyel; (5) újabb visszaolvasás (0). A teszt
ugyanebben a futásban a megvalósított prioritásbiteket is megméri:
0xFF-et ír a prioritásbájtba, és visszaolvassa. A kimenet
semihostingon megy, a startup és a linker script a kanonikus pár (a065, a066):
/* lab-a069-test.c — NVIC-állapot ellenőrzése egyetlen szimbolikus IRQ-ból.
* Minden cím és bit a LAB_IRQ-ból származik; a kimenet semihostingon megy. */
#include <stdint.h>
#define IRQ_TIMER0 8u /* MPS2-AN385 Timer0 */
#define LAB_IRQ IRQ_TIMER0
#define NVIC_ISER(i) (*(volatile uint32_t *)(0xE000E100u + 4u * (i)))
#define NVIC_ICER(i) (*(volatile uint32_t *)(0xE000E180u + 4u * (i)))
#define NVIC_ISPR(i) (*(volatile uint32_t *)(0xE000E200u + 4u * (i)))
#define NVIC_ICPR(i) (*(volatile uint32_t *)(0xE000E280u + 4u * (i)))
#define NVIC_IPR_ADDR(irq) (0xE000E400u + (uint32_t)(irq))
#define NVIC_IPR_BYTE(irq) (*(volatile uint8_t *)NVIC_IPR_ADDR(irq))
#define IRQ_WORD(irq) ((irq) / 32u)
#define IRQ_MASK(irq) (1u << ((irq) % 32u))
static void sh_call(uint32_t op, const void *arg)
{
register uint32_t r0 asm("r0") = op;
register const void *r1 asm("r1") = arg;
asm volatile ("bkpt 0xab" : "+r"(r0) : "r"(r1) : "memory");
}
static void put_hex(const char *label, uint32_t v)
{
char buf[48];
char *p = buf;
while (*label) *p++ = *label++;
*p++ = '0'; *p++ = 'x';
for (int sh = 28; sh >= 0; sh -= 4) *p++ = "0123456789abcdef"[(v >> sh) & 0xFu];
*p++ = '\n'; *p = '\0';
sh_call(0x04u, buf); /* SYS_WRITE0 */
}
static int fails;
static void expect(const char *label, uint32_t got, uint32_t want)
{
put_hex(label, got);
if (got != want) { fails++; put_hex(" HIBA, várt: ", want); }
}
void lab_nvic_done(void) { } /* GDB-töréspont: a konfiguráció itt már lefutott */
int main(void)
{
const uint32_t w = IRQ_WORD(LAB_IRQ), m = IRQ_MASK(LAB_IRQ);
__asm volatile ("cpsid i" ::: "memory"); /* a teszt alatt nincs kiszolgálás */
put_hex("IRQ maszk: ", m);
put_hex("IPR bájt címe: ", NVIC_IPR_ADDR(LAB_IRQ));
/* 1. kiindulás: tiltva, pending törölve */
NVIC_ICER(w) = m;
NVIC_ICPR(w) = m;
expect("ISER (tiltva): ", NVIC_ISER(w) & m, 0u);
/* 2. tiltott IRQ-n szoftveres pending: az ISPR és az ICPR is a pending állapotot olvassa */
NVIC_ISPR(w) = m;
expect("ISPR a beállítás után: ", NVIC_ISPR(w) & m, m);
expect("ICPR a beállítás után: ", NVIC_ICPR(w) & m, m);
/* 3. törlés write-1-to-clear módon, majd újabb visszaolvasás */
NVIC_ICPR(w) = m;
expect("ICPR a törlés után: ", NVIC_ICPR(w) & m, 0u);
/* 4. megvalósított prioritásbitek: 0xFF írása, visszaolvasás */
NVIC_IPR_BYTE(LAB_IRQ) = 0xFFu;
put_hex("IPR 0xFF visszaolvasva: ", NVIC_IPR_BYTE(LAB_IRQ));
/* 5. végleges konfiguráció: prioritás, majd engedélyezés */
NVIC_IPR_BYTE(LAB_IRQ) = 0xC0u;
NVIC_ISER(w) = m;
__asm volatile ("dsb\n isb" ::: "memory");
expect("ISER (engedélyezve): ", NVIC_ISER(w), m);
expect("IPR bájt: ", NVIC_IPR_BYTE(LAB_IRQ), 0xC0u);
lab_nvic_done();
sh_call(0x04u, fails ? "nvic FAIL\n" : "nvic ok\n");
sh_call(0x18u, (const void *)(fails ? 0x20023u : 0x20026u)); /* SYS_EXIT */
for (;;) { }
}cp ~/arm-labor/cm/a065/lab-mps2.ld ~/arm-labor/cm/a066/lab-startup.s .
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -ffreestanding -nostdlib -O1 -g \
-T lab-mps2.ld -o lab-a069.elf lab-startup.s lab-a069-test.c
qemu-system-arm -machine mps2-an385 -cpu cortex-m3 -nographic -semihosting \
-kernel lab-a069.elf; echo "kilépési állapot: $?"Kimenet a helyettesítő eszközlánccal (Clang + ld.lld) és
a tools/cm-run.py emulátorral, amely az NVIC regisztereit
az Armv7-M leírás szerint modellezi (a teszt a megszakítást végig tiltva
tartja, ezért kézbesítés itt nem történik), --prio-bits 3
beállítással:
IRQ maszk: 0x00000100
IPR bájt címe: 0xe000e408
ISER (tiltva): 0x00000000
ISPR a beállítás után: 0x00000100
ICPR a beállítás után: 0x00000100
ICPR a törlés után: 0x00000000
IPR 0xFF visszaolvasva: 0x000000e0
ISER (engedélyezve): 0x00000100
IPR bájt: 0x000000c0
nvic ok
kilépési állapot: 0
Az IPR 0xFF visszaolvasva sor az egyetlen, amely a
futtató környezettől függ: 3 megvalósított bitnél 0xe0, 8
bitnél 0xff (a modellt --prio-bits 8-cal
futtatva az utóbbi jött ki, minden más sor változatlan maradt). A QEMU
stable-8.2 mps2-an385 NVIC-modellje Armv7-M-en 8 bitet
valósít meg, ott 0xff várható; a fenti, 0xe0-t
mutató kimenet a 3 bites helyettesítő modellből származik. A sor mutatja
meg, hány prioritásbitet valósít meg a futtatókörnyezet. Ebből kell a
LAB_NVIC_PRIO_BITS-et rögzíteni: a megvalósított bitek
száma a visszaolvasott maszk egyeseinek száma, a legalacsonyabb
konfigurálható prioritás pedig maga a maszk (0xe0, illetve
0xff). A 0xC0 nyers érték mindkét esetben
érvényes, de csak a 3 bites esetben jelenti a második legalacsonyabb
szintet. A várt kimenetet tehát mindig a saját futtatás
visszaolvasásából, nem egy másik eszköz adataiból kell levezetni.
GDB-vel az állapotot csak a konfiguráció lefutása
után érdemes olvasni. A -S kapcsolóval
indított QEMU a reset után, az első utasítás előtt áll meg; ekkor az
NVIC még az indulás előtti állapotot mutatja, engedélyezett bitet még
nem láthatunk. A system emulátor GDB-szerverét a
-gdb tcp::PORT kapcsoló nyitja meg; a II. rész egységesen a
-S -gdb tcp::3333 alakot használja (a -s a
-gdb tcp::1234 rövidítése, az is működik, de akkor a
GDB-ben is az 1234-es port kell). A qemu-user -g 1234
kapcsolója a system emulátorba nem vehető át, ezért itt nem
használható:
qemu-system-arm -machine mps2-an385 -cpu cortex-m3 -nographic -semihosting \
-kernel lab-a069.elf -S -gdb tcp::3333
# másik terminálban:
gdb-multiarch lab-a069.elf -ex "target remote :3333" \
-ex "break lab_nvic_done" -ex "continue" \
-ex "x/1xw 0xE000E100" -ex "x/1xb 0xE000E408" -ex "x/1xw 0xE000E280"A lab_nvic_done töréspontnál a konfiguráció már
lefutott, ezért ott várható a fenti táblázat állapota. Ha a gdbstub
hibát ad az SCS-címek olvasásakor, a firmware saját visszaolvasása (a
teszt kimenete) a bizonyíték.
IPR szót,
és hasonlítsuk össze a várt IRQn mod 4 sávval. Egy
változtatás: írjunk a bájtcímre
(0xE000E400 + IRQn) strb-vel.
Ellenőrzés: a szó és a bájt ugyanazt tükrözi-e.ISPR-rel kézzel pendítsünk,
majd töröljük a pending bitet, és igazoljuk, hogy engedélyezett
állapotban sem fut le.ISER &= ~mask mintát
használtunk. Ellenpróba: az ISER nem
támogatja a 0-írású törlést; a helyes útvonal
ICER = mask.0xFF-et,
olvassuk vissza.IRQ 33. Határozza meg az
ISER regisztert és bitet, a maszkot, valamint az
IPR szó címét, bájt-sávját és bit-offszetjét. Indokolja,
miért nem a kivételszámot használjuk az IPR
indexelésénél.nvic_get_priority(irq)
függvényt Thumb-2 assemblyben, amely visszaadja egy IRQ
prioritásbájtját. Készítsen olyan változatot is, amely szóhosszú
olvasást és maszkolást használ a bájtolvasás helyett.breakpoint-ot a
timer kezelő első utasítására, és GDB-vel olvassa ki az
IABR-t a kezelő futása közben, majd a kezelő végén.
Magyarázza a különbséget, és mérje meg, mennyi idő telik el az
ISPR-alapú pendítés és a kezelő első utasítása között
(ciklusszámítással vagy SysTick-alapú időméréssel).1. exc = IRQ 33 + 16 = 49, de az
IPR-t az IRQ-szám indexeli: w = 33 / 4 = 8 →
szó cím 0xE000E400 + 32 = 0xE000E420;
l = 33 mod 4 = 1 → bit-offszet 8, azaz a szó
15:8 bitjei; bájt cím 0xE000E421. Az
ISER-nél i = 33 / 32 = 1 →
0xE000E104, bit 33 mod 32 = 1 → maszk
0x00000002. Azért az IRQ-szám az index, mert a
rendszer-kivételek prioritása az SCB SHPR1–3-ban van; az
IPR tömb csak a külső IRQ-kat fedi le.
2. A bájtolvasó változat kihasználja, hogy a bájtcím
éppen 0xE000E400 + IRQn:
.thumb_func
.global nvic_get_priority
nvic_get_priority:
movw r1, #(NVIC_IPR & 0xFFFF)
movt r1, #(NVIC_IPR >> 16)
add r1, r1, r0 @ r1 = 0xE000E400 + IRQn
ldrb r0, [r1] @ a prioritásbájt
bx lrA szóhosszú változat: w = IRQn & ~3 címről
ldr, majd lsrs r0, r0, r2 a
r2 = 8·(IRQn mod 4) shiftel, végül
and r0, r0, #0xFF (a 0xFF modified
immediate-ként kódolható). A szóhosszú változat akkor előnyös, ha az
implementáció a bájt-elérést megszorítja; a bájtváltozat viszont
pontosan azt a CMSIS-mintát tükrözi, amelyet a valós kódbázisok
használnak.
3. Az IABR a kezelő futása alatt 1-est
mutat a kérdéses biten (a kivétel aktív), a kezelő visszatérése után
0-t. Ez azért hasznos, mert megkülönbözteti a „pending, de még nem
futott” és az „aktív, éppen futó” állapotot. A latenciamérésnél az
ISPR-be írt pendítés és a kezelő első utasítása közötti
ciklusszám a belépési latencia felső korlátját adja; mivel
QEMU-emulációban nincs hardverhű ciklusszám, az eredmény csak
relatív/illusztratív, és ezt a mérési jegyzőkönyvben jelölni kell.
Az NVIC engedélyezését az ISER/ICER végzi
IRQ-szám szerinti bitenkénti indexeléssel, a prioritást pedig a
0xE000E400-tól induló bájttömb tárolja, ahol a bájtindex az
IRQ-szám (nem a kivételszám), a mezőpozíció pedig
.
Az ISPR/ICPR a pending állapotot állítja és
mindkettő vissza is olvassa, az IABR az
aktív állapotot kérdezi le. A biztonságos sorrend: letiltás, pending
törlés, prioritás, engedélyezés, majd a globális maszk és a periféria
engedélyezése.
Továbbhasznált definíciók: - írás-1-hatású
regiszter: olyan NVIC-regiszter, amelyben az 1-es írás
műveletet vált ki, a 0 írása pedig hatástalan. -
prioritásbájt: az IPR tömb
0xE000E400 + IRQn címén lévő 8 bites mező, amelynek kisebb
értéke magasabb rendet jelent. - aktív állapot: az
IABR által mutatott feltétel, amely szerint az adott
kivétel éppen kiszolgálás alatt áll.
Az alábbiak dokumentációs kiindulópontok (forrásállapot: nem megnyitott, lásd az Olvasói útmutató jelöléseit):
ISER/ICER/ISPR/ICPR/IABR/IPR
regisztereinek bitkiosztása, az írás-1-hatás szemantika és a
prioritásmezők elhelyezése.mps2-an385 machine, a CMSDK
perifériák báziscímei és IRQ-hozzárendelése.Ellenőrzési hiányok: (0) a
lab-a069-test.c tesztet és a könyv assembly NVIC-rutinjait
(nvic_enable_irq, nvic_disable_irq,
nvic_set_priority) a helyettesítő eszközlánccal
fordítottuk, és a tools/cm-run.py NVIC-regisztermodelljén
futtattuk. Ez a modell az Armv7-M regiszterszemantikát követi
(ISER/ICER az engedélyezettséget,
ISPR/ICPR a pending állapotot olvassa), de nem
a QEMU NVIC-je; a teszt a megszakítást tiltva tartja. A QEMU alatti
futtatás és a GDB-lépések a laborban ellenőrizendők (a közös táblázatot
lásd az a061 forrásjegyzetében). (1) a CMSDK APB
timer báziscíme és pontos IRQ-száma az mps2-an385
machine-en; (2) a mps2-an385 (QEMU) és a fizikai MPS2/STM32
által megvalósított prioritásbitek száma — ezt a fenti visszaolvasásos
próbával kell meghatározni; (3) a byte/halfword elérés támogatottsága az
egyes NVIC-regisztereknél; (4) a dsb/isb
kötelező volta az NVIC-engedélyezés után. Mindegyik pont a hallgató
laborjában, az adott toolchain-verzióval ellenőrizendő.