A beágyazott rendszerek többségében a soros kommunikáció az egyik
legalapvetőbb perifériakezelési feladat. Egy szenzoradatokat fogadó,
parancsokat értelmező vagy egyszerűen csak konzolüzeneteket naplózó
firmware gyakran UART-on keresztül kommunikál a külvilággal. Ha a vételt
pollinggal valósítjuk meg, a CPU aktívan pörög, és nem tud aludni vagy
más feladatot végezni. Ezzel szemben az interrupt-vezérelt vétel
lehetővé teszi, hogy a CPU mélyalvásban (WFI) vagy más
kritikus feladat végrehajtásában legyen, amíg a periféria jelzi, hogy új
bájt érkezett. Ez a minta nem csak UART-ra, hanem szinte minden bemeneti
perifériára (SPI, I2C, ADC) adaptálható.
Ebben az alfejezetben egy minimális, de teljes UART drivert építünk
fel a QEMU mps2-an385 machine CMSDK APB UART perifériájára.
A fókusz a vételi irányon van: a bejövő karaktereket megszakításban
fogadjuk, és egy körkörös pufferbe (ring buffer) helyezzük. A küldést
egyszerű pollingos putc-vel oldjuk meg, hogy a példa ne
zsúfolódjon el. Az itt bemutatott regiszterkezelés és ISR-szerződés
közvetlenül használható más ARM Cortex-M alapú driver fejlesztésénél
is.
A UART (Universal Asynchronous Receiver/Transmitter)
egy olyan memóriába leképzett periféria, amely párhuzamos adatokat soros
formában továbbít, illetve soros adatokat párhuzamosít. A CMSDK APB UART
egy egyszerű, ARM által definiált periféria, amelyet a QEMU
mps2-an385 modellje is implementál. A regisztertérképe öt
32 bites regiszterből áll, amelyek a periféria báziscíméhez képest fix
offszeteken érhetők el. Az alábbi táblázat a feltételezett címezést
mutatja; a pontos báziscím és offszetek a QEMU machine-definíciójából
ellenőrizendők.
| Regiszter | Offszet | Írás | Olvasás |
|---|---|---|---|
DATA |
0x000 | Küldendő bájt | Vett bájt |
STATE |
0x004 | — | Állapotbitek: TXFULL, RXFULL, TXOVERRUN, RXOVERRUN |
CTRL |
0x008 | Vezérlés: TXEN, RXEN, TXIE, RXIE | Vezérlés visszaolvasása |
INTSTATUS |
0x00C | Write-1-to-clear: a megfelelő bitre írt 1 törli a jelzőt | Aktív megszakítások: TXINT, RXINT |
BAUDDIV |
0x010 | Baud osztó értéke | Baud osztó visszaolvasása |
A CTRL regiszter bitjei a következők: - bit 0:
TXEN — adó engedélyezése - bit 1: RXEN — vevő
engedélyezése - bit 2: TXIE — adási megszakítás
engedélyezése - bit 3: RXIE — vételi megszakítás
engedélyezése
A STATE regiszter bitjei: - bit 0: TXFULL —
adó puffer megtelt - bit 1: RXFULL — vevő puffer megtelt
(van olvasható adat) - bit 2: TXOVERRUN — adási
túlcsordulás - bit 3: RXOVERRUN — vételi túlcsordulás
Az INTSTATUS regiszter bitjei: - bit 0:
TXINT — adási megszakítás aktív - bit 1: RXINT
— vételi megszakítás aktív
Fontos szerződés: a vételi megszakítás
(RXINT) akkor válik aktívvá, ha a RXFULL bit
beáll, és a RXIE engedélyezve van. A DATA
regiszter olvasása törli a STATE.RXFULL bitet — ez
megakadályozza, hogy új RX-megszakítás keletkezzen —, de
nem törli az INTSTATUS.RXINT jelzőt, amely
a hardverben attól a pillanattól önálló, “latched” (rögzített)
állapotként él. A már megállapított RXINT-et külön,
write-1-to-clear móddal kell törölni: az
INTSTATUS megfelelő bitjébe 1-et kell írni. Az
INTSTATUS puszta olvasása a jelzőt nem törli. Ha a
DATA kiolvasása után elmarad az INTSTATUS-ba
írt törlés, az NVIC a megszakítást továbbra is pendingnek látja, és a
vezérlés az ISR visszatérése után — vagy akár azonnal, mielőtt
visszatérne — újra belép ugyanabba az ISR-be: megszakítási
vihart okozva, amely a CPU-t teljesen lefoglalja. Hasonlóképpen
az adási megszakítás (TXINT) akkor aktív, ha a
TXFULL bit törölve van (azaz a puffer fogad új bájtot) és a
TXIE engedélyezve van. Fontos, hogy ennek a fordítottja NEM
igaz: a DATA regiszter írása a puffert feltölti,
és ettől a TXFULL jellemzően beáll, nem
törlődik — a TXFULL törlését a hardver tényleges
adatküldése végzi, amikor a puffer a soros vonalra kiürül, függetlenül
attól, hogy a szoftver mikor írt utoljára a DATA-ba. A
TXINT jelzőt ugyanúgy az INTSTATUS megfelelő
bitjébe írt 1-gyel kell törölni, mint az RXINT-et.
A körkörös puffer (ring buffer) egy olyan
adatszerkezet, amely fix méretű tömbben tárolja az elemeket, és két
indexszel (head, tail) jelöli a következő
írási és olvasási pozíciót. Az egyik indexet a termelő (itt az ISR), a
másikat a fogyasztó (itt a fő ciklus) módosítja. A puffer akkor üres, ha
head == tail, és akkor tele, ha
(head + 1) % méret == tail. A méretet érdemes 2 hatványára
választani, hogy a modulo helyett bitmaszkolást használhassunk.
A BAUDDIV regiszter a periféria bemeneti órajelét osztja
le a kívánt baud rátára. A pontos összefüggés a CMSDK APB UART
specifikációjában található; tipikusan a következő alakú:
ahol
a UART periféria számára biztosított rendszeróra frekvenciája. A QEMU
mps2-an385 esetében ez az érték a machine konfigurációjától
függ, és a pontos frekvencia a QEMU forráskódjából vagy az MPS2 AN385
dokumentációból ellenőrizendő. A QEMU CMSDK UART modellje a baud rátát
baudrate = UARTCLK / BAUDDIV alakban számítja — nincs
-1 eltolás a képletben. A példában 25 MHz-es UARTCLK-t
feltételezünk, ami 115200 baud esetén
BAUDDIV = 25000000 / 115200 ≈ 217 értéket ad. Mivel a QEMU
nem valós idejű emulációt végez, a baud rate pontossága a konzol
bemenetére nincs hatással, de a regisztert a periféria inicializálásához
be kell állítani.
Munkakönyvtár: ~/arm-labor/cm/a076/ —
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/a076
cd ~/arm-labor/cm/a076Az alábbi példa egy teljes, fordítható UART driver vázát mutatja. A
kód C nyelvű, mert a regiszterelérések és a ring buffer kezelése így
olvashatóbb; az assembly szerepe a vektortábla és a startup kód, amelyet
a korábbi fejezetekből ismerünk. A futtatáshoz szükséges teljes
fájllista: lab-mps2.ld, lab-a076-startup.s (a
kanonikus startup, az alábbi bővítéssel), lab-uart.h,
lab-uart.c, lab-main.c és az ellenőrző
lab-check-vectors.py.
A linker script és a startup a II. rész kanonikus párja (Linker script alapjai, Startup
kód), a vektortábla ellenőrzője pedig a Vektortábla alfejezet
lab-check-vectors.py-ja. A linker script változatlan; a
startup másolatának vektortábláját egyetlen, gépileg alkalmazható
lépéssel bővítjük az UART0 RX (IRQ0) bejegyzésével:
cp ~/arm-labor/cm/a065/lab-mps2.ld .
cp ~/arm-labor/cm/a066/lab-startup.s lab-a076-startup.s
cp ~/arm-labor/cm/a064/lab-check-vectors.py .
cat > lab-a076-irq.inc <<'EOF'
/* 16-tól: külső IRQ-k; UART0 RX = IRQ0 = 16. index */
.word UART0_Handler /* 0x40: IRQ0 (UART0 RX) */
EOF
sed -i '/\.word SysTick_Handler/r lab-a076-irq.inc' lab-a076-startup.s # a SysTick sor után
# gyenge alias: kezelő nélkül is linkelhető
echo ' lab_default_handler UART0_Handler' >> lab-a076-startup.s
# ellenőrzés: a bővített tábla vége
sed -n '/\.word SysTick_Handler/,/^$/p' lab-a076-startup.sA sed mintája csak a vektortábla
.word SysTick_Handler sorára illeszkedik, a fájl végén lévő
lab_default_handler SysTick_Handler aliasra nem. A fel nem
használt IRQ-helyek a kanonikus Default_Handler-re
mutatnak. Az echo sor a kanonikus
lab_default_handler makróval a fájl végére egy
gyenge, Default_Handler-re mutató aliast
fűz a használt kezelőhöz is. A C-fájl erős definíciója ezt felülírja, de
ha egy build a kezelőt nem tartalmazza, a KEEP-pel
megtartott vektortábla hivatkozása akkor sem marad feloldatlan. Ezek
valódi táblaszavak, nem megjegyzések, mert csak így
kerül a használt kezelő a megfelelő indexre
().
// lab-uart.h
#ifndef LAB_UART_H
#define LAB_UART_H
#include <stdint.h>
#define UART0_BASE 0x40004000UL
#define UART0_IRQn 0 // AN385: UART0 RX = IRQ0, TX = IRQ1 (lásd a072/a079)
typedef struct {
volatile uint32_t DATA;
volatile uint32_t STATE;
volatile uint32_t CTRL;
volatile uint32_t INTSTATUS;
volatile uint32_t BAUDDIV;
} CMSDK_UART_TypeDef;
#define UART0 ((CMSDK_UART_TypeDef *) UART0_BASE)
#define UART_CTRL_TXEN (1U << 0)
#define UART_CTRL_RXEN (1U << 1)
#define UART_CTRL_TXIE (1U << 2)
#define UART_CTRL_RXIE (1U << 3)
#define UART_STATE_TXFULL (1U << 0)
#define UART_STATE_RXFULL (1U << 1)
#define UART_INTSTATUS_TXINT (1U << 0)
#define UART_INTSTATUS_RXINT (1U << 1)
void uart_init(uint32_t uartclk, uint32_t baudrate);
void uart_putc(char c);
int uart_getc(void);
int uart_available(void);
#endif// lab-uart.c
#include "lab-uart.h"
#define RX_BUF_SIZE 64 // 2 hatványa
static volatile char rx_buf[RX_BUF_SIZE];
static volatile uint32_t rx_head = 0; // csak az ISR írja
static volatile uint32_t rx_tail = 0; // csak a fő ciklus írja
void uart_init(uint32_t uartclk, uint32_t baudrate) {
UART0->CTRL = 0; // kikapcsolás
UART0->BAUDDIV = uartclk / baudrate; // baudrate = UARTCLK / BAUDDIV
UART0->CTRL = UART_CTRL_TXEN | UART_CTRL_RXEN | UART_CTRL_RXIE;
}
void uart_putc(char c) {
while (UART0->STATE & UART_STATE_TXFULL) {
// vár, amíg az adó puffer kiürül
}
UART0->DATA = (uint32_t)(uint8_t)c;
}
int uart_getc(void) {
if (rx_head == rx_tail) {
return -1;
}
uint8_t c = (uint8_t)rx_buf[rx_tail];
rx_tail = (rx_tail + 1) & (RX_BUF_SIZE - 1);
return (int)c;
}
int uart_available(void) {
return (rx_head != rx_tail);
}
void UART0_Handler(void) {
uint32_t status = UART0->INTSTATUS;
if (status & UART_INTSTATUS_RXINT) {
// write-1-to-clear: torli az INTSTATUS.RXINT-et
UART0->INTSTATUS = UART_INTSTATUS_RXINT;
char c = (char)(UART0->DATA & 0xFF); // olvasás törli a STATE.RXFULL-t
uint32_t next = (rx_head + 1) & (RX_BUF_SIZE - 1);
if (next != rx_tail) {
rx_buf[rx_head] = c;
rx_head = next;
}
// else: túlcsordulás, a bájt elveszik
}
if (status & UART_INTSTATUS_TXINT) {
UART0->INTSTATUS = UART_INTSTATUS_TXINT; // write-1-to-clear
// Adási megszakítás kezelése; itt nem használjuk
}
}Az alábbi ábra a körpuffer négy jellemző állapotát mutatja — üres,
részben telt, körbefordult, tele. A szemléltetéshez 8 szeletet
használunk a tényleges 64 helyett, hogy az ábra olvasható maradjon; a
head/tail logika (és a telítettség feltétele)
azonban azonos a fenti rx_buf-éval.
// lab-main.c
#include "lab-uart.h"
static void nvic_enable_irq(int irqn) {
volatile uint32_t *nvic_iser = (volatile uint32_t *)0xE000E100;
nvic_iser[irqn / 32] = (1U << (irqn % 32));
}
int main(void) {
uart_init(25000000, 115200); // feltételezett UARTCLK
nvic_enable_irq(UART0_IRQn);
const char *msg = "UART ready\r\n";
for (const char *p = msg; *p; ++p) {
uart_putc(*p);
}
while (1) {
int c = uart_getc();
if (c >= 0) {
uart_putc((char)c); // echo
}
}
}arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -ffreestanding -nostdlib -O1 -T lab-mps2.ld \
-o lab-uart.elf lab-a076-startup.s lab-uart.c lab-main.c
python3 lab-check-vectors.py lab-uart.elf 1=Reset_Handler 16=UART0_Handler
qemu-system-arm -machine mps2-an385 -cpu cortex-m3 -nographic -kernel lab-uart.elfA lab-a076-startup.s vektortáblájában a
UART0_Handler az IRQ0 helyén (16. index, 0x40)
áll; ezt a lab-check-vectors.py utolsó sora gépileg
igazolja. A -nographic kapcsoló a QEMU soros konzolját a
standard bemenetre/kimenetre köti. A várt kimenet: a konzolon megjelenik
az UART ready üzenet, majd a beírt karakterek visszaíródnak
(echo). Mivel a QEMU emuláció nem valós idejű, a beírt karakterek
megjelenési sorrendje és késleltetése eltérhet a fizikai hardveren
tapasztalttól; ez nem hiba, hanem az emuláció jellemzője.
| Tünet | Hipotézis | Adatgyűjtés | Javítás |
|---|---|---|---|
| Az első karakter után több nem érkezik | A DATA-t nem olvassuk ki, ezért a
STATE.RXFULL 1 marad, és a periféria a további bájtokat nem
fogadja (overrun) |
GDB-vel az ISR után nézzük meg a STATE értékét: ha
RXFULL 1, ez a hiba |
Az ISR-ben mindenképpen olvassuk ki a DATA regisztert,
még akkor is, ha a bájtot eldobjuk |
| Az ISR azonnal, folytonosan újra belép (megszakítási vihar) | A DATA olvasása csak a STATE.RXFULL-t
törli; az INTSTATUS.RXINT jelző „latched”, write-1-to-clear
nélkül megmarad |
GDB-vel az ISR-ből visszatérés után nézzük meg az
INTSTATUS értékét: ha RXINT még 1, ez a
hiba |
Írjunk explicit 1-et az INTSTATUS megfelelő bitjébe az
ISR-ben, a DATA kiolvasása előtt, ahogy a
driver teszi |
| Gyorsan egymás után érkező bájtoknál a vétel elakad, egy bájt beragad | A nyugtázás a DATA olvasása után áll:
a két művelet között érkező új bájt RXINT-jét a késői W1C
letörli, a bájt RXFULL = 1 mellett, megszakítás nélkül
marad a regiszterben |
STATE és INTSTATUS az elakadáskor:
RXFULL = 1, RXINT = 0 |
A sorrend cseréje: előbb W1C, utána DATA-olvasás |
| A beírt karakterek egyáltalán nem jelennek meg | A vételi megszakítás nincs engedélyezve (RXIE
hiányzik), vagy az NVIC-ben nem engedélyeztük az IRQ-t |
Ellenőrizzük a CTRL és az NVIC ISER
regiszterét GDB-vel |
Állítsuk be a RXIE bitet, és hívjuk meg az
NVIC_EnableIRQ-ot |
| A ring buffer tartalma „elavult”, a fő ciklus nem látja a friss adatot | A rx_head vagy rx_tail változó nem
volatile, a fordító optimalizálja |
Kapcsoljuk ki az optimalizálást (-O0), és nézzük meg,
hogy megjelenik-e a hiba |
Tegyük volatile-lá a ring buffer indexeit és a
puffert |
| A beírt karakterek minden második esetben jelennek meg | Az ISR-ben a DATA regisztert kétszer olvassuk (pl. a
STATE ellenőrzése után is), vagy a rx_head
növelése hibás |
Szúrjunk be egy számlálót az ISR-be, és figyeljük a
rx_head értékét |
Egyszer olvassuk a DATA-t, és a rx_head
növelését egyetlen atomi műveletsorként végezzük |
| A QEMU nem indítja el a firmware-t, vagy hibás címen áll meg | A linker script memóriatérképe nem egyezik a QEMU machine memory mapjével | Ellenőrizzük a readelf -S lab-uart.elf kimenetét és a
QEMU -d kapcsolójával a betöltési címeket |
Igazítsuk a linker scriptet az MPS2 AN385 flash/RAM tartományához |
A sorrend a perifériától függő eseményprotokoll
része, nem általános szabály. A CMSDK UART-nál az RXINT a
bájt érkezésekor beáll, és csak W1C törli; a DATA olvasása
az RXFULL-t törli, és ezzel engedi a következő bájt
fogadását. Ha a driver előbb nyugtáz, majd olvas, egy közben érkező bájt
újra beállítja az RXINT-et, és új megszakítást kér.
Fordított sorrendnél a DATA olvasása után, de a W1C előtt
érkező bájt jelzését a W1C letörli: a bájt bent ragad, és mivel
RXFULL = 1, további bájt sem érkezik. A helyettesítő
eszközlánccal (tools/cm-run.py, sűrűn érkező
ABCDEFGH bemenettel) ez kimutatható. A driver sorrendjével
mind a nyolc bájt visszaíródott. A felcserélt sorrenddel az
A után a vétel elakadt; ritkán érkező bájtoknál a két
változat nem különbözött. A timer más eset: ott nincs kiolvasandó adat,
a törlésnek csak a visszatérés előtt kell megtörténnie (Megszakítás-vezérelt I/O).
Magyarázó feladat: Ismertesse a CMSDK UART
STATE és INTSTATUS regisztereinek bitjeit!
Magyarázza el, hogyan törlődik az RX és a TX megszakítás, és miért nem
elegendő az INTSTATUS regiszter olvasása a megszakítás
törléséhez!
Implementációs feladat: Egészítse ki a ring
buffert egy volatile uint32_t rx_overflow számlálóval,
amely minden elveszett bájt esetén növekszik! Készítsen egy
uart_get_overflow(void) függvényt, amely visszaadja a
túlcsordulások számát! Ügyeljen arra, hogy a számláló írása az ISR-ben,
olvasása a fő ciklusban történik, és a 32 bites írás/olvasás atomi a
Cortex-M3-on.
Hibakeresési/mérési feladat: A programban a
beírt karakterek csak minden harmadik esetben jelennek meg. A gyanú
szerint az ISR-ben a rx_head növelése és a
rx_buf írása között megszakítás érkezik, vagy a
rx_tail olvasása nem atomi. Analizálja a kódot, készítsen
ellenpróbát, és javítsa a hibát! Mérésként hasonlítsa össze a javított
és a hibás változat viselkedését QEMU-ban, és dokumentálja a
különbséget!
Megoldás: A STATE regiszter bit 0 a
TXFULL, bit 1 az RXFULL. Az
INTSTATUS bit 0 a TXINT, bit 1 az
RXINT. Az RX megszakítás akkor aktív, ha
RXFULL && RXIE. A DATA regiszter
olvasása törli a STATE.RXFULL-t, ami megakadályozza, hogy
új RX-megszakítás keletkezzen — de az
INTSTATUS.RXINT jelző ettől önállóan, “latched” módon
megmarad, mert az INTSTATUS puszta
olvasása csak visszajelzi az állapotot, nem módosítja azt. A
már bekövetkezett RXINT-et (és TXINT-et) ezért
külön, write-1-to-clear móddal kell törölni: 1-et kell
írni az INTSTATUS megfelelő bitjébe. A TX megszakítás akkor
aktív, ha a STATE.TXFULL törölve van (a puffer fogad új
bájtot) és TXIE engedélyezett; a TXFULL-t a
hardver tényleges adatküldése törli, nem a DATA-ba írás — a
DATA írása éppen ellenkezőleg, feltölti a puffert, és ettől
a TXFULL jellemzően beáll. A TXINT jelzőt
emellett ugyanúgy explicit kell törölni az INTSTATUS-ba írt
1-gyel. Ha ez elmarad, az NVIC a megszakítást pendingnek látja, és az
ISR azonnal újra belép — megszakítási vihart okozva.
Megoldás:
// lab-uart.c kiegészítés
static volatile uint32_t rx_overflow = 0;
void UART0_Handler(void) {
uint32_t status = UART0->INTSTATUS;
if (status & UART_INTSTATUS_RXINT) {
UART0->INTSTATUS = UART_INTSTATUS_RXINT; // write-1-to-clear
char c = (char)(UART0->DATA & 0xFF);
uint32_t next = (rx_head + 1) & (RX_BUF_SIZE - 1);
if (next != rx_tail) {
rx_buf[rx_head] = c;
rx_head = next;
} else {
rx_overflow++;
}
}
// ...
}
uint32_t uart_get_overflow(void) {
return rx_overflow;
}A rx_overflow 32 bites, aligned változó; a Cortex-M3
egyetlen LDR/STR utasítással éri el, ezért az
írás és olvasás atomi. Nem kell kritikus szakasz.
rx_head növelése és a
rx_buf[rx_head] írása között egy másik megszakítás is
lefut, amely szintén módosítja a rx_head-et, vagy a fő
ciklus rx_tail olvasása nem atomi. Mivel a Cortex-M3-on a
32 bites aligned olvasás/írás atomi, a rx_head és
rx_tail külön kontextusban történő módosítása önmagában nem
okoz versenyhelyzetet. A hiba forrása inkább az lehet, hogy az ISR-ben a
DATA regisztert nem egyszer olvassuk, vagy a ring buffer
mérete nem 2 hatványa, és a % operátor lassú, illetve a
rx_head növelése nem maszkolással történik. Az ellenpróba:
adjunk ki egy GPIO-t vagy egy számlálót az ISR elején és végén, és
figyeljük, hogy az ISR futása alatt történik-e másik megszakítás. A
javítás: a rx_head növelését és a puffer írását egyetlen,
megszakításmentes szakaszban végezzük, vagy biztosítsuk, hogy a ring
buffer műveletek atomiak legyenek. Mivel az ISR-ben vagyunk, a
megszakítások engedélyezése nem szükséges, de ha más, magasabb
prioritású megszakítás is módosítja a puffert, akkor kritikus szakasz
kell. A mért különbség: a javított változatban a karakterek minden
esetben megjelennek, a hibásban csak minden harmadik.Az UART driver inicializálása a CTRL,
BAUDDIV és INTSTATUS regiszterek
konfigurációjából áll. A vételi megszakítás az ISR-ben két lépésben
törlődik: a DATA regiszter olvasása a
STATE.RXFULL bitet törli, a INTSTATUS
megfelelő bitjébe írt 1 (write-1-to-clear) pedig a tényleges
megszakításjelzőt — mindkettő szükséges, különben megszakítási vihar
keletkezik. A karaktert ezután egy körkörös pufferbe írjuk. A ring
buffer head és tail indexét különböző
kontextusban módosítjuk, így elkerülhető a versenyhelyzet. A küldés
polling-alapú egyszerűsítéssel történik. A QEMU mps2-an385
machine-en a periféria címei és IRQ számai ellenőrizendők.
Továbbhasznált definíciók: - UART:
univerzális aszinkron adó-vevő, memóriába leképzett periféria. -
ring buffer: fix méretű körkörös puffer
head és tail indexszel. -
ISR: hardveresen meghívott megszakításkezelő rutin,
amely az eseményt törli és feldolgozza.
Az alábbiak dokumentációs kiindulópontok (forrásállapot: nem megnyitott, lásd az Olvasói útmutató jelöléseit):
mps2-an385 machine és a CMSDK perifériák
memóriatérképe; a -nographic és -kernel
kapcsolók viselkedése.DATA,
STATE, CTRL, INTSTATUS,
BAUDDIV regiszterek bitmezői és a baud osztó képlete. A
pontos dokumentumazonosító és a link a munkafolyamat során nem került
ellenőrzésre.A laborkönyvtár összeállítását a helyettesítő eszközlánccal (Clang +
ld.lld) ellenőriztük: egy üres könyvtárban, kizárólag a Vektortábla, a Linker script
alapjai, a Startup kód és ennek az
alfejezetnek a fájljaiból, a fenti
cp/cat/sed lépésekkel a labor
fordul és linkelődik, és a lab-check-vectors.py a Reset- és
a használt IRQ-bejegyzést is a várt szimbólumra mutatónak találja. A
tools/cm-run.py UART0-modelljében
(--uart-stdin) a program kiírta az UART ready
sort, és a bemenetet visszaírta. Ellenőrzési hiányok: A
QEMU mps2-an385 machine-en a UART0 báziscíme, az IRQ
számok, a BAUDDIV pontos képlete és az UARTCLK frekvenciája
ebben a folyamatban nem lett visszaellenőrizve. Ezek a QEMU
forráskódjából (hw/char/cmsdk-apb-uart.c, hw/arm/mps2.c) vagy az MPS2
AN385 dokumentációból ellenőrizendők. A cikkben szereplő 0x40004000 és 0
IRQ szám feltételezett értékek; a hallgatónak a labor során a tényleges
QEMU-verzióhoz kell igazítania.