18.3 Periféria-regiszter absztrakció C struktúrával

18.3.1 Motiváció

Az előző alfejezetben (GPIO közvetlen vezérlése: bitbanging) nyers memóriacímekkel és hexadecimális offsetekkel vezéreltük a GPIO-t. Ez a gép szintjén pontos, de a forráskód szintjén törékeny: egy nagyobb firmware-ben több tíz perifériaregiszter varázsszámként való karbantartása átláthatatlan és hibára hajlamos. Ha egy új perifériát kell kezelni, vagy a regiszterkészlet bővül, a nyers címek sora olvashatatlanná válik.

A mérnöki megoldás a periféria-struktúra: a regisztereket egy C struct mezőiként írjuk le, a struktúra kezdetét egy fix báziscímre illesztjük, és a továbbiakban nevesített mezőkkel érünk el mindent. Ez az absztrakció nem változtatja meg a generált gépi kódot – ugyanaz a báziscím+offset minta marad –, de a forráskód olvashatóvá, típusellenőrzötté és statikusan verifikálhatóvá válik. Ebben az alfejezetben felépítjük ezt az absztrakciót, és megmutatjuk az assembly-szintű megfelelőjét.

18.3.2 Definíció és működés

Periféria-struktúra (peripheral register struct): olyan C struktúratípus, amelynek mezői egy periféria regisztereit reprezentálják, sorrendben és méretben a hardver regisztertérképének megfelelően, és amelyet egy fordítási idejű konstans báziscímre castolunk.

A definíció három pillére:

  1. Mezők mint regiszterek. Minden mező egy regisztert jelöl. A mező típusa a regiszter szélességét tükrözze: 32 bites regiszterhez uint32_t, 16 biteshez uint16_t, 8 biteshez uint8_t. A gyakorlatban a legtöbb perifériaregiszter 32 bites, ezért a uint32_t a leggyakoribb választás.

  2. Sorrend és offset. A struktúra mezőinek sorrendje adja a regiszterek offset-sorrendjét. Az első mező a báziscím + 0, a második a báziscím + az első mező mérete (igazítási kitöltéssel), és így tovább. Ez pontosan a Struktúrák, igazítás és mezőelérés alfejezetben tanult mezőoffset-számítás: mezőcím = báziscím + mezőoffset.

  3. Fix báziscímre castolás. A struktúra típusát egy #define makróval kötjük egy abszolút címhez, például ((cmsdk_gpio_t *)0x40010000UL). Az egész számból pointerré castolás C-ben implementációdefiniált, de a beágyazott programozás bevett mintája: a memóriába leképzett regisztereket pontosan így érünk el.

A assembly-szintű megfelelő változatlan. A GPIO0->OUTENSET = érték; C kifejezésből a fordító STR utasítást készít, ahol a bázisregiszter a 0x40010000, az offset pedig a mező pozíciója. A a024-ben tanult összefüggés tehát közvetlenül érvényes: a struktúra-alapú elérés ugyanazt a báziscím+offset mintázatot adja, mint a nyers című változat.

18.3.2.1 A volatile szerepe

A perifériaregiszterek elérése volatile (illékony) minősítést igényel. A volatile kulcsszó azt mondja a fordítónak, hogy az adott objektum értéke a program akaratán kívül is megváltozhat, ezért:

E nélkül egy -O2 szinten optimalizáló fordító a látszólag hatástalan írásokat eltávolíthatja, vagy a read-modify-write sorozatot átrendezheti. A volatile azonban nem biztosít atomitást és nem zár ki versenyhelyzetet: a GPIO0->DATAOUT |= (1u << pin); továbbra is három gépi lépés (olvas, módosít, ír), amely megszakítás közben megszakítható. A GPIO közvetlen vezérlése: bitbanging RMW-mintája itt is érvényes.

A volatile-t két szinten lehet elhelyezni:

Mindkettő helyes. A mezőnkénti változat előnye, hogy a típus önmagában dokumentálja a szándékot, és később a pointer típusát nem kell módosítani. Hátránya, hogy minden meződeklarációt el kell látni a kulcsszóval. A könyv a mezőnkénti változatot használja.

18.3.2.2 Előny és kockázat

Előny: olvashatóság és karbantarthatóság. A GPIO0->OUTENSET név önmagát dokumentálja; a 0x40010010 cím nem. A struktúra egy helyen írja le a periféria teljes regisztertérképét, és a mezőnevekhez kötött szemantika csökkenti a téves offset használatát. A fordító típusellenőrzést végez a mezőkön, a _Static_assert + offsetof párosával pedig a struktúra elrendezése fordítási időben ellenőrizhető. Ez az ellenőrzés azonban csak azt igazolja, hogy a struktúra azt a táblázatot írja le, amelyet mi az assertekbe beírtunk. Ha a táblázat maga hibás, a struktúra és az assertek együtt, egymással egyezően hibásak. A hardvertérkép forrása a gyártói leírás; azt a struktúra nem helyettesíti, legfeljebb pontosan átveszi.

Kockázat: a struktúra elrendezése és a tényleges hardver-layout eltérhet. Ha a mezőtípusok nem egyeznek a regiszterek szélességével, a fordító kitöltést (padding) szúrhat be, és a további mezők offsetje elcsúszik. Például ha az első mező uint8_t, a második uint32_t, akkor a második a 4. bájton kezdődik a várt 1. helyett. Ezért kötelező a struktúra méretének és kulcsmező-offsetjeinek statikus ellenőrzése, és a regiszterekhez mindig a hardver szélességének megfelelő típust kell választani. A __attribute__((packed)) nem javasolt: igazítatlan hozzáférést eredményezhet, ami Armv7-M-en HardFault-ot válthat ki (kivéve, ha a hardver kifejezetten támogatja az igazítatlan elérést, ez Cortex-M3-on nem általános).

További kockázat, hogy a struktúra-alapú elérés csak akkor azonos a nyers cíművel, ha a báziscím és az offsetek pontosan egyeznek. Ezt a generált assembly összehasonlításával lehet igazolni.

18.3.3 Kidolgozott példa

Munkakönyvtár: ~/arm-labor/cm/a074/ — 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/a074
cd ~/arm-labor/cm/a074

A GPIO-műveletek implementált CMSDK GPIO perifériát és valódi bemenetet feltételeznek. A QEMU 8.2.2 mps2-an385 modelljében a GPIO nincs implementálva; ott a fizikai be-/kimeneti próba SKIP, és a regiszterek írása nem igazolja a lábak működését.

Tekintsük az MPS2-AN385 CMSDK GPIO regiszterelrendezését. A GPIO0 báziscíme a dokumentált memóriatérképen 0x40010000. A regisztertérkép az Arm Cortex-M System Design Kit műszaki leírásának 3-8. táblázatából (3.5.2) származik:

Offset Név Szerep
0x000 DATA olvasás: a lábak tényleges szintje; írás: kimeneti adat
0x004 DATAOUT kimeneti adatregiszter
0x008–0x00C — fenntartott (két szó)
0x010 OUTENSET kimenet engedélyezése (set)
0x014 OUTENCLR kimenet engedélyezése (clear)
0x018 ALTFUNCSET alternatív funkció engedélyezése (set)
0x01C ALTFUNCCLR alternatív funkció engedélyezése (clear)
0x020 INTENSET megszakítás engedélyezése (set)
0x024 INTENCLR megszakítás tiltása (clear)
0x028 INTTYPESET élvezérlés (set)
0x02C INTTYPECLR szintvezérlés (clear)
0x030 INTPOLSET polaritás: felfutó él / magas szint (set)
0x034 INTPOLCLR polaritás: lefutó él / alacsony szint (clear)
0x038 INTSTATUS / INTCLEAR olvasás: függő megszakítások; írás: W1C nyugtázás

Ez a táblázat a II. rész egyetlen forrása a GPIO-hoz: a lab-cmsdk-gpio.h fejléc ebből készül, az a073 assembly-konstansai ugyanezek, és a II. záróprojekt is ezt a fejlécet másolja át, nem saját struktúrát ír. A fenntartott szavakat RESERVED0[2] tömb tölti ki, így az OUTENSET a 0x010-re kerül. A 0x038-on egy union fejezi ki, hogy ugyanazon a címen az olvasás INTSTATUS, az írás INTCLEAR (a const tag miatt az INTSTATUS-ba írni fordítási hiba). A struktúra a 0x038-ig tartó részleges térképet írja le, ezért a teljes mérete 0x03C. A fejléc a CMSDK-modellt írja le; nem állítja, hogy a QEMU 8.2.2 AN385 GPIO-eszközt valósít meg. A nyugtázás a megfelelő maszkot írja az INTSTATUS/INTCLEAR közös címére; a pin tartománya (pin < 16) a függvények rögzített előfeltétele:

/* lab-cmsdk-gpio.h — CMSDK AHB GPIO regisztertérkép (részleges, 0x000–0x038)
 * Forrás: Arm Cortex-M System Design Kit TRM, 3.5.2, 3-8. táblázat.
 * Ez a fejléc a II. rész EGYETLEN forrása a GPIO-offsetekhez; az a073
 * assembly-konstansai és a záróprojekt (a078) is ebből származnak.
 * Az offsetof-ellenőrzés csak azt igazolja, hogy a struktúra a fenti
 * táblázatot írja le; a hardver térképét nem bizonyítja. */
#ifndef LAB_CMSDK_GPIO_H
#define LAB_CMSDK_GPIO_H
#include <stddef.h>
#include <stdint.h>

typedef struct {
    volatile uint32_t DATA;          /* 0x000 RW  bemeneti szint olvasás / kimenet írás */
    volatile uint32_t DATAOUT;       /* 0x004 RW  kimeneti adatregiszter               */
    uint32_t          RESERVED0[2];  /* 0x008–0x00C fenntartott                         */
    volatile uint32_t OUTENSET;      /* 0x010 RW  1 → a láb kimenet                     */
    volatile uint32_t OUTENCLR;      /* 0x014 RW  1 → a láb bemenet                     */
    volatile uint32_t ALTFUNCSET;    /* 0x018 RW  1 → alternatív funkció                */
    volatile uint32_t ALTFUNCCLR;    /* 0x01C RW  1 → alternatív funkció ki             */
    volatile uint32_t INTENSET;      /* 0x020 RW  1 → megszakítás engedélyezve          */
    volatile uint32_t INTENCLR;      /* 0x024 RW  1 → megszakítás tiltva (NEM nyugtáz)  */
    volatile uint32_t INTTYPESET;    /* 0x028 RW  1 → élvezérelt                        */
    volatile uint32_t INTTYPECLR;    /* 0x02C RW  1 → szintvezérelt                     */
    volatile uint32_t INTPOLSET;     /* 0x030 RW  1 → felfutó él / magas szint          */
    volatile uint32_t INTPOLCLR;     /* 0x034 RW  1 → lefutó él / alacsony szint        */
    union {                          /* 0x038 ugyanaz a cím:                            */
        volatile const uint32_t INTSTATUS;  /*   olvasás: függő megszakítások          */
        volatile uint32_t       INTCLEAR;   /*   írás: 1 → az eseményjelző törlése (W1C) */
    };
} cmsdk_gpio_t;

_Static_assert(offsetof(cmsdk_gpio_t, DATA)       == 0x000, "DATA");
_Static_assert(offsetof(cmsdk_gpio_t, DATAOUT)    == 0x004, "DATAOUT");
_Static_assert(offsetof(cmsdk_gpio_t, OUTENSET)   == 0x010, "OUTENSET");
_Static_assert(offsetof(cmsdk_gpio_t, OUTENCLR)   == 0x014, "OUTENCLR");
_Static_assert(offsetof(cmsdk_gpio_t, ALTFUNCSET) == 0x018, "ALTFUNCSET");
_Static_assert(offsetof(cmsdk_gpio_t, ALTFUNCCLR) == 0x01C, "ALTFUNCCLR");
_Static_assert(offsetof(cmsdk_gpio_t, INTENSET)   == 0x020, "INTENSET");
_Static_assert(offsetof(cmsdk_gpio_t, INTENCLR)   == 0x024, "INTENCLR");
_Static_assert(offsetof(cmsdk_gpio_t, INTTYPESET) == 0x028, "INTTYPESET");
_Static_assert(offsetof(cmsdk_gpio_t, INTTYPECLR) == 0x02C, "INTTYPECLR");
_Static_assert(offsetof(cmsdk_gpio_t, INTPOLSET)  == 0x030, "INTPOLSET");
_Static_assert(offsetof(cmsdk_gpio_t, INTPOLCLR)  == 0x034, "INTPOLCLR");
_Static_assert(offsetof(cmsdk_gpio_t, INTSTATUS)  == 0x038, "INTSTATUS");
_Static_assert(offsetof(cmsdk_gpio_t, INTCLEAR)   == 0x038, "INTCLEAR");
_Static_assert(sizeof(cmsdk_gpio_t)               == 0x03C, "sizeof");

#define CMSDK_GPIO0_BASE 0x40010000UL      /* MPS2-AN385 */
#define CMSDK_GPIO1_BASE 0x40011000UL
#define CMSDK_GPIO_PINS  16u             /* lábak száma portonként: pin < 16 előfeltétel */
#define CMSDK_GPIO0 ((cmsdk_gpio_t *)CMSDK_GPIO0_BASE)
#define CMSDK_GPIO1 ((cmsdk_gpio_t *)CMSDK_GPIO1_BASE)

/* Platformtámogatás: a QEMU 8.2.2 mps2-an385 gépen a GPIO nincs implementálva.
   QEMU-buildnél LAB_HAVE_GPIO = 0, és a GPIO-próbák SKIP-et jelentenek. */
#ifndef LAB_HAVE_GPIO
#define LAB_HAVE_GPIO 0
#endif

#endif

A struktúra-alapú és a nyers című elérés összevetése, a megszakításkezelő függvényekkel együtt:

/* lab-gpio.c — struktúra-alapú és nyers című elérés, megszakítás-kezelés
 * Előfeltétel minden függvénynél: pin < CMSDK_GPIO_PINS (16). A függvények ezt
 * nem ellenőrzik; nagyobb pin érték más láb bitjét vagy (pin >= 32 esetén) C-ben
 * meghatározatlan eltolást adna. */
#include "lab-cmsdk-gpio.h"

void gpio_set_output(uint32_t pin)        { CMSDK_GPIO0->OUTENSET = 1u << pin; }

#define GPIO0_OUTENSET (*(volatile uint32_t *)(CMSDK_GPIO0_BASE + 0x010))
void gpio_set_output_raw(uint32_t pin)    { GPIO0_OUTENSET = 1u << pin; }

void gpio_write(uint32_t pin, int value)
{
    if (value) CMSDK_GPIO0->DATAOUT |= 1u << pin;
    else       CMSDK_GPIO0->DATAOUT &= ~(1u << pin);
}

void gpio_enable_interrupt(uint32_t pin)  { CMSDK_GPIO0->INTENSET = 1u << pin; }
void gpio_disable_interrupt(uint32_t pin) { CMSDK_GPIO0->INTENCLR = 1u << pin; }  /* tiltás */
/* nyugtázás, W1C */
void gpio_clear_interrupt(uint32_t pin)   { CMSDK_GPIO0->INTCLEAR = 1u << pin; }
uint32_t gpio_pending(void)               { return CMSDK_GPIO0->INTSTATUS; }

A gpio_set_output és a gpio_set_output_raw azonos assembly-re kell forduljon. Ezt -O2 mellett a arm-none-eabi-gcc -S kimenetén vagy az objektfájl disassemblyjén ellenőrizzük:

arm-none-eabi-gcc -std=c11 -mcpu=cortex-m3 -mthumb -O2 -c lab-gpio.c -o lab-gpio.o
arm-none-eabi-objdump -d lab-gpio.o

Kimenet a helyettesítő eszközlánccal (Clang -O2 -fomit-frame-pointer, disassembly: tools/thumb-dump.py; a GNU arm-none-eabi-gcc a bázist jellemzően literal poolból tölti, a regiszterkiosztás eltérhet):

00000000 <gpio_set_output>:
       0:   2101        movs    r1, #1
       2:   fa01 f000   lsl.w   r0, r1, r0
       6:   2110        movs    r1, #0x10
       8:   f2c4 0101   movt    r1, #0x4001      @ r1 = 0x40010010 = OUTENSET
       c:   6008        str r0, [r1]
       e:   4770        bx  lr

00000010 <gpio_set_output_raw>:
      10:   2101        movs    r1, #1
      12:   fa01 f000   lsl.w   r0, r1, r0
      16:   2110        movs    r1, #0x10
      18:   f2c4 0101   movt    r1, #0x4001
      1c:   6008        str r0, [r1]
      1e:   4770        bx  lr
...
0000005c <gpio_clear_interrupt>:
      5c:   2101        movs    r1, #1
      5e:   fa01 f000   lsl.w   r0, r1, r0
      62:   2138        movs    r1, #0x38
      64:   f2c4 0101   movt    r1, #0x4001      @ r1 = 0x40010038 = INTCLEAR
      68:   6008        str r0, [r1]
      6a:   4770        bx  lr

A két első függvény gépi kódja azonos, és mindkettő a 0x40010010 címre ír. A fordító itt a teljes címet movs + movt párral rakja össze, ezért a 0x10 eltolás az alsó félszóban látszik. Egy literal poolból betöltött bázis esetén ugyanez str r0, [r1, #16] alakban jelenne meg; a báziscím+offset minta (a024) mindkét formában ugyanaz. A gpio_clear_interrupt a 0x40010038-ra, a gpio_disable_interrupt a 0x40010024-re ír; a kettő tehát két különböző művelet.

Ellenőrzési feltétel: a struktúra-alapú és a nyers változat disassemblyje egyezzen, és a célcím a gyártói táblázat szerinti legyen. A fordítás QEMU nélkül is ellenőrizhető. A regiszterek tényleges viselkedése viszont csak támogatott hardveren próbálható ki, mert a QEMU mps2-an385 gépen a GPIO nincs implementálva (lásd a támogatási mátrixot); ott a futó próba SKIP.

18.3.4 Hibák és ellenpróbák

Tünet Hipotézis Adatgyűjtés Javítás
Az írás „eltűnik”, a periféria nem reagál Hiányzó volatile; a fordító elhagyta az írást objdump -d: nincs str a várt helyen Minden regisztermező volatile
A második regiszter rossz címen íródik Padding a mezők között; rossz mezőtípus offsetof kiíratása vagy _Static_assert uint32_t minden 32 bites regiszterre; _Static_assert
A struktúra mérete nagyobb a vártnál Záró padding a struktúra igazítása miatt sizeof ellenőrzése Sorrend/mezőtípusok rendezése; _Static_assert(sizeof(...))
A castolt pointerrel más típusban is írunk ugyanoda Strict aliasing megsértése Fordító figyelmeztetései, viselkedésváltozás -O2-n Egyetlen hozzáférési típus használata
Igazítatlan elérés HardFault-ot okoz packed struktúra vagy uint8_t mező 32 bites regiszterre Fault-regiszterek (CFSR) vizsgálata Igazított, szélességhelyes mezők

Ellenpróba: vegyük ki a volatile-t a DATAOUT mezőből, és fordítsuk -O2-vel. Ha a gpio_write függvény két egymást követő írása összevonódik vagy eltűnik, a volatile hiánya igazolt. Hasonlóképpen cseréljük az OUTENSET mezőt uint8_t-re, vagy hagyjuk ki a RESERVED0[2] tömböt: a _Static_assert offset-hibát jelez. (A helyettesítő eszközlánccal kipróbálva egy szándékosan elrontott INTSTATUS == 0x030 assert a fordítást static assertion failed ... INTSTATUS hibával állította meg.)

18.3.5 Feladatok

  1. (magyarázó, alapszint — cél: megértés-ellenőrzés) Magyarázd meg, miért nem elegendő a struktúra pointerét volatile-nak jelölni, ha a mezők nem azok. Mi a különbség a volatile cmsdk_gpio_t *p és a cmsdk_gpio_t *volatile p között? Melyik védi a perifériaregiszterek elérését?

  2. (implementációs, alapszint — cél: rutinépítés) Írd meg a cmsdk_gpio_t struktúrát a 0x038-ig tartó teljes térképpel (a fenntartott szavakkal és az ALTFUNC párral együtt), _Static_assert ellenőrzésekkel minden offsetre és a méretre (0x03C). Készíts gpio_enable_interrupt, gpio_disable_interrupt és gpio_clear_interrupt függvényt, és indokold, melyik regiszterbe ír a tiltás és melyikbe a nyugtázás.

  3. (hibakeresési/mérési, középszint — cél: rutinépítés) Adott egy struktúra, amelyben a mezők uint8_t, uint16_t, uint32_t típusúak vegyesen. Írd ki az egyes mezők offsetof értékét egy rövid C programmal, és hasonlítsd össze a hardver regisztertérképével. Azonosítsd, hol keletkezik padding, és javítsd a struktúrát úgy, hogy a _Static_assert-ek átmenjenek. Ezután vesd össze a struktúra-alapú és a nyers című elérés generált assemblyjét arm-none-eabi-objdump -d-vel.

  4. (integráló, elmélyült — cél: fejezetek összekapcsolása: ISR írása: C és assembly vegyesen) Írj egy GPIO0_Handler ISR-t, amely a struktúra-alapú CMSDK_GPIO0->INTCLEAR-be írt maszkkal nyugtázza az eseményt (write-1-to-clear, ugyanaz a 0x038 cím, mint az INTSTATUS olvasása), majd egy volatile uint8_t g_gpio_pending megosztott jelzőt állít — pontosan a070 TIMER0_IRQHandler mintáját követve, csak itt a periféria-struktúrán keresztül, nem nyers címmel. Indokold a070 alapján, miért nem kell az ISR-ben explicit regisztermentés, és mi történne, ha a g_gpio_pending-ről lemaradna a volatile. Ellenőrizd objdump-pal, hogy a struktúra-alapú INTCLEAR-írás ugyanarra a str-mintára fordul (a 0x40010038 címre), mint egy nyers címmel írt megfelelője — ez a jelen fejezet saját ellenőrzési módszere, most az ISR kontextusában alkalmazva.

18.3.6 Megoldások

1. megoldás — Segítség. Gondold át külön, mit véd a volatile a mutatott oldalon, és mit a pointer oldalán — a kettő nem ugyanaz a tulajdonság.

1. megoldás — Teljes megoldás. A volatile cmsdk_gpio_t *p azt jelenti, hogy a mutatott objektum volatile: minden p->mezo elérés volatile lesz, függetlenül attól, hogy a meződeklaráció tartalmaz-e volatile-t. A cmsdk_gpio_t *volatile p ezzel szemben magát a pointerváltozót védi: a pointer értéke nem gyorsítótárazható, de a mutatott struktúra mezői nem lesznek volatile-ok, ha a mezők nem azok. A perifériaregiszterek védelméhez az első forma (vagy a mezőnkénti volatile) kell. A két forma kombinálható is, de a gyakorlatban a mezőnkénti volatile a legkevésbé félreérthető.

2. megoldás — Segítség. Indulj a gyártói táblázatból, ne a korábbi struktúrából: hol vannak fenntartott szavak, és mi van a 0x038-on olvasáskor, illetve íráskor?

2. megoldás — Teljes megoldás. A struktúra a fenti lab-cmsdk-gpio.h: RESERVED0[2] a 0x008–0x00C helyen, ALTFUNCSET/ALTFUNCCLR a 0x018/0x01C-n, a 0x038-on INTSTATUS/INTCLEAR union, sizeof == 0x03C. A három függvény:

void gpio_enable_interrupt(uint32_t pin)  { CMSDK_GPIO0->INTENSET = 1u << pin; }  /* 0x020 */
/* 0x024: tiltás */
void gpio_disable_interrupt(uint32_t pin) { CMSDK_GPIO0->INTENCLR = 1u << pin; }
/* 0x038: nyugtázás, W1C */
void gpio_clear_interrupt(uint32_t pin)   { CMSDK_GPIO0->INTCLEAR = 1u << pin; }

Az INTENCLR-be írás a megszakítást tiltja: a láb további eseményei nem kérnek megszakítást, de a már beállt eseményjelző megmarad. Az eseményjelzőt az INTCLEAR törli (write-1-to-clear), és utána a megszakítás engedélyezve marad. A korábbi kiadás gpio_clear_interrupt néven az INTENCLR-be írt. Ez nyugtázás helyett tiltott, ezért a függvénynek új neve (gpio_disable_interrupt) lett, a gpio_clear_interrupt pedig a 0x038-ra ír.

Ellenpróba (támogatott hardveren; QEMU-n SKIP): engedélyezett, élvezérelt pin mellett (1) egy él beállítja az INTSTATUS bitjét és lefut a kezelő; (2) a kezelő gpio_clear_interrupt-tal nyugtáz; (3) egy újabb él újraengedélyezés nélkül ismét megszakítást kér. Ha a (3) lépés elmarad, a nyugtázás valójában tiltás volt. Ha a kezelő a visszatérés után azonnal újra belép, a nyugtázás elmaradt.

3. megoldás. A vegyes típusú struktúra tipikusan így néz ki:

typedef struct {
    uint8_t  A;   /* 0x000 */
    uint32_t B;   /* 0x004 – padding 0x001..0x003 */
    uint16_t C;   /* 0x008 */
    uint8_t  D;   /* 0x00A */
    uint32_t E;   /* 0x00C */
} mixed_t;

Az offsetof értékek: A=0, B=4, C=8, D=10 (0x0A), E=12 (0x0C), sizeof=16 (0x10). Az A után 3 bájt padding van, D után 1 bájt, a struktúra végén 1 bájt. Ha a hardver regisztertérképe azt mondja, hogy B a 0x001 címen van, akkor ez a struktúra hibás. A javítás: minden 32 bites regiszterhez uint32_t, és a _Static_assert-ek felveszik a várt offseteket. A disassembly-ellenőrzés ezek után azt mutatja, hogy a struktúra-alapú STR [r0, #offset] és a nyers STR [r0, #offset] azonos kódot ad.

18.3.7 Rövid összefoglaló

A periféria-struktúra a memóriába leképzett regisztereket C struct mezőiként írja le, és egy fix báziscímre castolja. A mezőoffsetek adják a regisztercímeket, a báziscím+offset minta assembly szinten változatlan. A volatile mezők garantálják, hogy a fordító ne optimalizálja el a periféria-hozzáféréseket. Az olvashatóság előnye mellé a _Static_assert-ek és az offsetof adnak biztonságot a padding és a téves elrendezés ellen. A struktúra-alapú és a nyers című elérés generált kódja azonos kell legyen.

Továbbhasznált definíciók: - periféria-struktúra: C struktúratípus, amelynek mezői egy periféria regisztereit képezik le, fix báziscímre castolva. - volatile: minősítő, amely biztosítja, hogy a fordító minden hozzáférést ténylegesen végrehajtson, és ne rendezze át azokat. - mezőoffset: a struktúramező kezdőcíme a struktúra kezdetéhez képest; a regiszter címének kiszámításához használjuk.

18.3.8 Források és ellenőrzési hiányok

Az alábbiak dokumentációs kiindulópontok (forrásállapot: nem megnyitott, lásd az Olvasói útmutató jelöléseit):

Ellenőrzési hiányok: A regisztertérképet a lektor a CMSDK műszaki leírás 3-8. táblázatával vetette össze, és ennek alapján javítottuk (a korábbi kiadásból hiányzott a két fenntartott szó, és INENSET/INENCLR név szerepelt az ALTFUNC mezők helyén). A lab-cmsdk-gpio.h-t és a lab-gpio.c-t a helyettesítő eszközlánccal (Clang, -std=c11) lefordítottuk; az assertek átmentek, a közölt disassembly ebből a fordításból származik. A regiszterek viselkedése (nyugtázás, újbóli megszakításkérés) csak támogatott hardveren próbálható ki; ez ebben a munkafolyamatban nem történt meg. A _Static_assert offset-ellenőrzések használatához a fordító C11-támogatása szükséges; a projekt build-beállításait ennek megfelelően kell ellenőrizni. Az assembly-kimenetek -O2 szinten várhatók, de a konkrét regiszterkiosztás fordítóverziófüggő lehet.