diff --git a/UE/u01/doc/img/msp_irqhandler.jpeg b/UE/u01/doc/img/msp_irqhandler.jpeg new file mode 100644 index 0000000..142cb5a Binary files /dev/null and b/UE/u01/doc/img/msp_irqhandler.jpeg differ diff --git a/UE/u01/doc/psp.PNG b/UE/u01/doc/img/psp_1.PNG similarity index 100% rename from UE/u01/doc/psp.PNG rename to UE/u01/doc/img/psp_1.PNG diff --git a/UE/u01/doc/img/psp_debug.jpeg b/UE/u01/doc/img/psp_debug.jpeg new file mode 100644 index 0000000..84aaca0 Binary files /dev/null and b/UE/u01/doc/img/psp_debug.jpeg differ diff --git a/UE/u01/doc/img/psp_superloop.jpeg b/UE/u01/doc/img/psp_superloop.jpeg new file mode 100644 index 0000000..9919a30 Binary files /dev/null and b/UE/u01/doc/img/psp_superloop.jpeg differ diff --git a/UE/u01/doc/u01_dokumentation.typ b/UE/u01/doc/u01_dokumentation.typ index 76cf0ac..47d171e 100644 --- a/UE/u01/doc/u01_dokumentation.typ +++ b/UE/u01/doc/u01_dokumentation.typ @@ -1,5 +1,5 @@ // Dokumentation Übung 1 RTO -// Kompilieren: typst compile u01_dokumentation.typ (im Ordner doc/) +// Kompilieren: typst compile --root .. u01_dokumentation.typ (im Ordner doc/; --root für den Quellcode aus ../src) #let todo(body) = box( fill: rgb("#ffe0e0"), inset: (x: 3pt, y: 2pt), radius: 2pt, @@ -35,7 +35,7 @@ #pagebreak(weak: true) = Überblick -*Programmstruktur:* Kooperative Superloop in `main.c`. Jeder Task ist eine Funktion `void TaskX(void)`, die pro Aufruf nur einen kurzen Arbeitsschritt erledigt und sofort zurückkehrt; der Zustand zwischen zwei Aufrufen liegt in `static`-Variablen. Gemeinsame Zeitbasis ist der SysTick-Interrupt mit 1 ms Periode. +*Programmstruktur:* Kooperative Superloop in `main.c`. Jeder Task ist eine Funktion, die pro Aufruf nur einen kurzen Arbeitsschritt erledigt und sofort zurückkehrt; der Zustand zwischen zwei Aufrufen liegt in `static`-Variablen. Gemeinsame Zeitbasis ist der SysTick-Interrupt mit 1 ms Periode. ```c while (1) @@ -83,7 +83,7 @@ Jedem Task und dem SysTick-Interrupt ist ein Ausgang von Port B zugeordnet (@tab caption: [Zuordnung der Debug-Pins], ) -`Debug_Init()` aktiviert den Takt für GPIOB und konfiguriert die sieben Pins als Push-Pull-Ausgänge (50 MHz, ohne Pull-up/-down). Die Tasks werden in `main.c` von außen eingerahmt, sodass der Task-Code selbst frei von Messcode bleibt. Nur der SysTick-Handler setzt seinen Pin intern: +`Debug_Init()` aktiviert den Takt für GPIOB und konfiguriert die sieben Pins als Push-Pull-Ausgänge. Die Tasks werden in `main.c` von außen eingerahmt, sodass der Task-Code selbst frei von Messcode bleibt. Nur der SysTick-Handler setzt seinen Pin intern: ```c void SysTick_Handler (void){ @@ -128,8 +128,8 @@ Die folgenden Messungen zeigen den Ausgangszustand der Vorlage: Die Tasks geben *Auswertung:* - Die Laufzeit der Tasks wird fast vollständig von der Textausgabe bestimmt. Aus Led (4 Zeichen, 1,889 ms), Poti (5 Zeichen, 2,362 ms) und Watch (6 Zeichen, 2,832 ms) ergibt sich übereinstimmend *≈ 0,472 ms pro Zeichen*. Ein Zeichen des 16×24-Fonts hat 384 Pixel, also ≈ 1,23 µs bzw. ≈ 59 Takte pro Pixel, da jedes Pixel per Software über GPIO-Zugriffe an das Display übertragen wird. -- Mandelbrot blockiert die Superloop für 16,16 s. Damit beträgt die Zykluszeit aller Tasks ≈ 16,2 s; keine der Zeitanforderungen aus Aufgabe B ist so erfüllbar. -- Der SysTick-Puls ist 0,956 µs breit. Davon sind 0,748 µs Messoverhead (@sec-overhead), die eigentliche Arbeit (`msTicks++`) dauert also nur ≈ 0,21 µs ≈ 10 Takte. Hinzu kommen Ein- und Austritt in den Interrupt (Cortex-M0: 16 Takte Latenz), die am Pin nicht sichtbar sind. Die CPU-Last durch den SysTick liegt damit unter 0,2 %. +- Mandelbrot blockiert die Superloop für 16,16 s. Damit beträgt die Zykluszeit aller Tasks ≈ 16,2 s; keine der Zeitanforderungen aus Aufgabe B ist so erfüllt. +- Der SysTick-Puls ist 0,956 µs breit. Davon sind 0,748 µs Messoverhead (@sec-overhead), die eigentliche Arbeit (`msTicks++`) dauert also nur ≈ 0,21 µs ≈ 10 Takte. Hinzu kommen Ein- und Austritt in den Interrupt (Cortex-M0: 16 Takte Latenz), die am Pin nicht sichtbar sind. == Overhead der Messroutine @@ -139,19 +139,7 @@ Die folgenden Messungen zeigen den Ausgangszustand der Vorlage: Die Tasks geben $ t_"Overhead" = 748 "ns" quad arrow.r.double quad 748 "ns" dot 48 "MHz" approx 36 "Takte" $ -Der Puls umfasst den Rücksprung aus `GPIO_WriteBit` und `Debug_Set`, den Aufruf von `Debug_Unset` (Parameter laden, `BL`, `PUSH`) und den Aufruf von `GPIO_WriteBit` bis zum eigentlichen Schreibzugriff auf das Portregister. Die Flash-Wartezyklen bei 48 MHz verlängern die Ausführung zusätzlich. Disassembly von `Debug_Set`: - -``` -Debug_Set: - PUSH {r7,lr} - MOV r1,r0 ; Pin-Maske - LDR r0,=0x48000400 ; GPIOB - MOVS r2,#1 ; Bit_SET - BL GPIO_WriteBit - POP {r7,pc} -``` - -*Bedeutung:* Jede gemessene Pulsbreite ist um ≈ 0,75 µs zu lang. Für Tasks im Millisekundenbereich ist das vernachlässigbar, für sehr kurze Tasks (z. B. Led mit 1,9 µs in Aufgabe B) dagegen nicht; dort gilt $t_"netto" approx t_"gemessen" - "0,75 µs"$. Der Overhead ließe sich auf wenige Takte reduzieren, indem `Debug_Set`/`Debug_Unset` als `static inline`-Funktionen direkt in die Register `GPIOB->BSRR` bzw. `GPIOB->BRR` schreiben. +*Bedeutung:* Jede gemessene Pulsbreite ist um ≈ 0,75 µs zu lang. Für Tasks im Millisekundenbereich ist das vernachlässigbar, für sehr kurze Tasks (z. B. Led mit 1,9 µs in Aufgabe B) dagegen nicht; dort gilt $t_"netto" approx t_"gemessen" - "0,75 µs"$. #pagebreak(weak: true) = Aufgabe B – Reaktionszeit in einer Superloop-Architektur @@ -166,13 +154,13 @@ uint32_t Tick_GetMs(void){ } ``` -Ein 32-Bit-Lesezugriff ist auf dem Cortex-M0 atomar. Zeitvergleiche erfolgen immer als vorzeichenlose Differenz (`Tick_GetMs() - start >= dauer`); diese bleibt auch beim Überlauf des Zählers nach ≈ 49,7 Tagen korrekt. Kein Task wartet aktiv auf eine Zeit, sondern prüft nur, ob sie abgelaufen ist. +Ein 32-Bit-Lesezugriff ist auf dem Cortex-M0 atomar. Zeitvergleiche erfolgen immer als vorzeichenlose Differenz (`Tick_GetMs() - start >= dauer`); diese bleibt auch beim Überlauf des Zählers nach ≈ 49,7 Tagen korrekt. == Implementierung der Tasks / Counter: Erhöht den Zähler bei jedem Aufruf um 1 und gibt ihn aus. -/ Key: Liest die drei Tasten ein (+Flankenerkennung), USER-Taste aendert die Farbe (Schwarz → Blau → Grün → Schwarz) und gilt für alle danach gezeichneten Ausgaben. +/ Key: Liest die drei Tasten ein (+Flankenerkennung), USER-Taste ändert die Farbe (Schwarz → Blau → Grün → Schwarz) und gilt für alle danach gezeichneten Ausgaben. ```c static BOOL Key_GetDebounced (KeyType key, uint32_t now) @@ -185,7 +173,7 @@ static BOOL Key_GetDebounced (KeyType key, uint32_t now) } ``` -/ Led: Lauflicht über Led3, Led4 und Led5 als Zustandsautomat. Bei jedem Aufruf wird nur geprüft, ob seit dem letzten Wechsel 100 ms vergangen sind. Da der Zeitpunkt mit `lastSwitch += LED_PERIOD_MS` fortgeschrieben wird (statt auf die aktuelle Zeit gesetzt), entsteht keine Drift: Einzelne Umschaltungen können sich um höchstens einen Schleifendurchlauf verspäten, die mittlere Periode bleibt exakt 100 ms. +/ Led: Lauflicht über Led3, Led4 und Led5 als Zustandsautomat. Bei jedem Aufruf wird nur geprüft, ob seit dem letzten Wechsel 100 ms vergangen sind. Einzelne Umschaltungen können sich um einen Schleifendurchlauf verspäten, die mittlere Periode bleibt exakt 100 ms. ```c else if (Tick_GetMs() - lastSwitch >= LED_PERIOD_MS) @@ -199,14 +187,14 @@ else if (Tick_GetMs() - lastSwitch >= LED_PERIOD_MS) / Poti: Liest den Potentiometer-Wert über den ADC (in mV) und gibt ihn mit fester Breite (`%4d`) aus, damit beim Kürzerwerden des Werts keine alten Ziffern stehen bleiben. -/ Watch: Berechnet die Betriebszeit aus `Tick_GetMs()` und gibt sie im Format mm:ss aus. Da die Zeit aus dem SysTick abgeleitet wird und nicht aus der Anzahl der Aufrufe, ist sie unabhängig von der Schleifendauer exakt. +/ Watch: Berechnet die Betriebszeit aus `Tick_GetMs()` und gibt sie im Format mm:ss aus. / Mandelbrot: Die Berechnung wurde zerlegt: Pro Aufruf werden Pixel berechnet, bis ein Zeitbudget von 5 ms verbraucht ist; die aktuelle Position (`curX`, `curY`) bleibt bis zum nächsten Aufruf erhalten. Nach dem letzten Pixel wird der Bildbereich zeilenweise (ebenfalls innerhalb des Zeitbudgets) gelöscht und das Bild neu gezeichnet. Dafür wurde der Display-Treiber um `Tft_ClearArea()` erweitert, das ein Rechteck in der Hintergrundfarbe füllt. ```c while (Tick_GetMs() - start < TIME_BUDGET_MS) { - if (clearing) { /* eine Bildzeile löschen, nach der letzten: clearing = FALSE */ } + if (clearing) { /* delete one row, after the last one: clearing = FALSE */ } else { MandelBrotPixel(curX, curY); @@ -221,7 +209,7 @@ while (Tick_GetMs() - start < TIME_BUDGET_MS) == Optimierung der Superloop -Nach der Zerlegung des Mandelbrot-Tasks betrug die Zykluszeit noch 26–27 ms. Ursache war die Textausgabe (≈ 0,47 ms pro Zeichen): In jedem Durchlauf wurden alle Beschriftungen und Werte neu gezeichnet, insgesamt rund 59 Zeichen. Folgende Maßnahmen wurden umgesetzt: +Nach der Zerlegung des Mandelbrot-Tasks betrug die Zykluszeit noch 26–27 ms. Ursache war, dass in jedem Durchlauf alle Beschriftungen und Werte neu gezeichnet wurden. + *Beschriftungen nur einmal zeichnen:* „Cnt“, „Key“, „Led“, „Watch“, „Poti“ und „ManBr“ werden nur beim ersten Aufruf des jeweiligen Tasks ausgegeben. + *Werte nur bei Änderung zeichnen:* Key bei Änderung eines entprellten Tastenzustands, Watch beim Sekundenwechsel, Poti bei einer Änderung von mehr als 10 mV gegenüber dem zuletzt angezeigten Wert (unterdrückt das Rauschen des ADC). Der Counter ändert sich bei jedem Aufruf und wird daher immer gezeichnet. @@ -289,8 +277,6 @@ Da Watch nur einmal pro Sekunde und die Mandelbrot-Beschriftung nur einmal pro B caption: [Vergleich mit den Zeitanforderungen], ) -Der Preis der Zerlegung ist eine längere Gesamtdauer für das Mandelbrot-Bild: Bei 16,16 s Rechenzeit und 5 ms pro Aufruf sind ≈ 3 230 Aufrufe nötig, bei 7,18 ms Zykluszeit dauert ein Bild damit rechnerisch ≈ 23 s statt 16 s. Dafür reagieren alle anderen Tasks innerhalb ihrer Grenzen. - #pagebreak(weak: true) = Aufgabe C – Process-Stack (PSP) und Main-Stack (MSP) @@ -319,9 +305,9 @@ int main(void) - `__ISB()` (Instruction Synchronization Barrier) stellt sicher, dass alle folgenden Befehle bereits mit dem neuen Stack-Pointer ausgeführt werden. - Im Handler-Modus (Interrupts) verwendet der Prozessor unabhängig von SPSEL immer den MSP. -*Warum eine eigene Funktion?* Der Compiler legt den Stackframe einer Funktion beim Eintritt an. In der ersten Version lagen Umschaltung und Superloop direkt in `main`: `main` reservierte beim Eintritt mit `SUB sp,sp,#8` einen Platz auf dem *MSP*, schaltete dann auf den PSP um und griff danach mit `[sp,#4]` auf diesen Platz zu, der nun über den PSP adressiert wurde. Das Ergebnis lag außerhalb von `processStack` im Ausgabepuffer von TaskCounter und wurde von diesem überschrieben. Im Debugger (@fig-psp-alt) ist das sichtbar: R4 enthält 0x30343432, also den Text „2440“ des Zählers, und wurde als Pin-Maske an `Debug_Set(led)` übergeben (ODR von GPIOB = 0x3432). In der korrigierten Version hat `main` keinen Stackframe mehr; der Stackframe von `Superloop` entsteht bereits auf dem PSP. `noinline` verhindert, dass der Compiler `Superloop` wieder in `main` einfügt. +Der Compiler legt den Stackframe einer Funktion beim Eintritt an. In der ersten Version lagen Umschaltung und Superloop direkt in `main`: `main` reservierte beim Eintritt mit `SUB sp,sp,#8` einen Platz auf dem *MSP*, schaltete dann auf den PSP um und griff danach mit `[sp,#4]` auf diesen Platz zu, der nun über den PSP adressiert wurde. Das Ergebnis lag außerhalb von `processStack` im Ausgabepuffer von TaskCounter und wurde von diesem überschrieben. Im Debugger (@fig-psp-alt) ist das sichtbar: R4 enthält 0x30343432, also den Text „2440“ des Zählers, und wurde als Pin-Maske an `Debug_Set(led)` übergeben (ODR von GPIOB = 0x3432). In der korrigierten Version hat `main` keinen Stackframe mehr; der Stackframe von `Superloop` entsteht bereits auf dem PSP. `noinline` verhindert, dass der Compiler `Superloop` wieder in `main` einfügt. -#figure(image("psp.PNG", width: 100%), caption: [Erste Version: Zugriff von `main` über den falschen Stack (R4 = 0x30343432, ODR = 0x3432)]) +#figure(image("img/psp_1.PNG", width: 100%), caption: [Erste Version: Zugriff von `main` über den falschen Stack (R4 = 0x30343432, ODR = 0x3432)]) == Stack-Layout @@ -346,7 +332,7 @@ Der Heap (512 B in der Startup-Datei) wird nicht verwendet und vom Linker entfer *Begründung der Aufteilung:* - *Trennung:* Der Stackbedarf der Interrupts muss nicht im Task-Stack eingeplant werden, und ein Überlauf des Task-Stacks beschädigt nicht den Stack der Interrupts. In einem späteren RTOS mit eigenem Stack pro Task muss die Interrupt-Reserve so nur einmal (im MSP) statt in jedem Task-Stack vorgehalten werden. - *MSP, 1 KB* (Vorgabe `Stack_Size EQU 0x400` der Startup-Datei): Vor der Umschaltung belegt der Startup-Code der C-Bibliothek 16 B (`PUSH {r2-r4,lr}` in `__scatterload`), deshalb steht der MSP in der Superloop auf 0x20000888. Danach verwendet ihn nur noch der SysTick-Handler: `PUSH {r4,lr}` (8 B), `Debug_Set` (8 B) und `GPIO_WriteBit`. Der Bedarf liegt deutlich unter 100 B. Da nur eine Interruptquelle ohne Verschachtelung aktiv ist, ist 1 KB großzügig und bietet Reserve für weitere Interrupts in den folgenden Übungen. -- *PSP, 1 KB:* Der gesamte Anwendungscode läuft auf dem PSP, darunter `snprintf` aus der C-Bibliothek und die Software-Gleitkommaroutinen für `double` im Mandelbrot-Task. Zusätzlich werden pro Interrupt 32 B für den Exception-Frame auf dem PSP abgelegt. #todo[Maximal genutzte PSP-Tiefe messen: Da `processStack` im mit 0 initialisierten `.bss` liegt, zeigt die niedrigste Adresse ungleich 0 im Memory-Fenster nach einigen Minuten Laufzeit den höchsten Füllstand.] +- *PSP, 1 KB:* Der gesamte Anwendungscode läuft auf dem PSP, darunter `snprintf` aus der C-Bibliothek und die Software-Gleitkommaroutinen für `double` im Mandelbrot-Task. Zusätzlich werden pro Interrupt 32 B für den Exception-Frame auf dem PSP abgelegt. - *Risiko:* Der Cortex-M0 besitzt keine MPU. Ein Überlauf des PSP würde unbemerkt die darunterliegenden Variablen (u. a. `msTicks`) überschreiben, ein Überlauf des MSP die Ausgabepuffer. Abhilfe wären Stacks am Ende des RAM mit freiem Abstand oder ein Füllmuster, das zur Laufzeit geprüft wird. == Ablauf bei einem Interrupt @@ -408,30 +394,36 @@ Die Hardware sichert nur die Register, die laut ARM-Aufrufkonvention (AAPCS) ein == Analyse im µVision-Debugger -#figure(fehlt("img/psp_superloop.png"), caption: [Thread-Modus in der Superloop]) +Für die Analyse wurden Breakpoints in `Debug_Set`, in `TaskCounter` und im `SysTick_Handler` gesetzt. Im Register-Fenster stehen unter „Banked“ MSP und PSP, R13 (SP) zeigt den gerade aktiven Stack-Pointer. Das Memory-Fenster zeigt den Stack byteweise in Dezimaldarstellung; Wörter liegen im Little-Endian-Format im Speicher. -In der Superloop (@fig-psp) läuft der Prozessor im Thread-Modus: R13 (SP) ist gleich dem PSP. Erwartet wird PSP = 0x20000470 (Startwert 0x20000478 minus 8 B Stackframe von `Superloop`) und MSP = 0x20000888. Das xPSR hat IPSR = 0 (Thread-Modus) und das Thumb-Bit gesetzt. +#figure(image("img/psp_debug.jpeg", width: 100%), caption: [Breakpoint in `Debug_Set`, aufgerufen aus `Superloop`: SP = PSP = 0x20000470]) -#todo[Projekt neu übersetzen, Screenshots in der Superloop, mit Breakpoint auf der ersten Anweisung von `SysTick_Handler` (Register- und Memory-Fenster auf den PSP) und nach der Rückkehr einfügen, Werte in die Tabelle eintragen und die Adressen in @tab-ram mit `listing/Apus.map` vergleichen.] +*Aufruf aus der Superloop (@fig-dbg-superloop):* `Superloop` ruft `Debug_Set(counter)` vor TaskCounter auf (R0 = 2 = Pin PB1). Der Prozessor läuft im Thread-Modus (xPSR = 0x21000000: IPSR = 0, Thumb-Bit gesetzt), R13 (SP) ist gleich dem PSP = 0x20000470. Das ist der Startwert 0x20000478 minus 8 B Stackframe von `Superloop` (`SUB sp,#8`). In diesem Stackframe liegt bei 0x20000474 der Wert 0x00001000 (Bytes 000 016 000 000), die Pin-Maske von `led` (PB12), die `Superloop` mit `STR r1,[sp,#4]` dort ablegt. Das ist genau der Platz, der in der ersten Version (@fig-psp-alt) außerhalb des Stacks lag. Direkt oberhalb des Stacks beginnt bei 0x20000478 der Ausgabepuffer von TaskCounter mit dem Text „1434“ (049 052 051 052), wie in @tab-ram angegeben. Der MSP steht auf 0x20000888. -#grid( - columns: 2, - gutter: 10pt, - [#figure(fehlt("img/isr_entry.png"), caption: [Im SysTick-Handler, nach dem Stacking])], - [#figure(fehlt("img/isr_return.png"), caption: [Nach der Rückkehr in die Superloop])], -) +#figure(image("img/psp_superloop.jpeg", width: 100%), caption: [Breakpoint in `TaskCounter` nach `PUSH {r4,lr}`: SP = PSP = 0x20000468]) + +*In einem Task (@fig-dbg-task):* TaskCounter hat mit `PUSH {r4,lr}` 8 B auf dem PSP abgelegt, der PSP ist auf 0x20000468 gesunken. Im Memory-Fenster liegen dort das gesicherte R4 = 0x00000002 und die Rücksprungadresse 0x0800086F (Bytes 111 008 000 008), die mit R14 übereinstimmt und hinter den Aufruf von TaskCounter in `Superloop` zeigt. Der MSP ist unverändert 0x20000888, der Anwendungscode verwendet nur den PSP. + +#figure(image("img/msp_irqhandler.jpeg", width: 100%), caption: [Breakpoint im `SysTick_Handler` nach `PUSH {r4,lr}`: SP = MSP = 0x20000880, LR = 0xFFFFFFFD]) + +*Im SysTick-Handler (@fig-dbg-isr):* Der Breakpoint liegt auf `Debug_Set(systick)`, also nach dem Prolog `PUSH {r4,lr}`. Der Prozessor ist im Handler-Modus (xPSR = 0x0100000F: IPSR = 15 = SysTick), R13 (SP) ist jetzt gleich dem MSP, und LR enthält 0xFFFFFFFD (Rückkehr in den Thread-Modus mit PSP, @tab-excret). +- *MSP:* Er ist nur um die 8 B des `PUSH` auf 0x20000880 gesunken. Im Memory-Fenster liegen dort das R4 des unterbrochenen Codes (0xFFFEE127) und der `EXC_RETURN`-Wert 0xFFFFFFFD (Bytes 253 255 255 255). Das Hardware-Stacking hat auf dem MSP nichts abgelegt. +- *PSP:* Er steht auf 0x20000440. Die Hardware hat den Exception-Frame (32 B, @tab-frame) bei 0x20000440–0x2000045F auf dem Stack des unterbrochenen Codes abgelegt; dieser lief also mit PSP = 0x20000460 (bzw. 0x20000464, falls zur Ausrichtung ein Füllwort eingefügt wurde). #figure( - table( - columns: (auto, 1fr, 1fr, auto), - table.header([*Register*], [*vor dem Interrupt*], [*im Handler (erwartet)*], [*gemessen*]), - [R13 (SP)], [= PSP], [= MSP], [], - [PSP], [$p$ (in der Superloop 0x20000470)], [$p - "0x20"$ (Exception-Frame)], [], - [MSP], [0x20000888], [0x20000888, nach `PUSH` 0x20000880], [], - [LR], [beliebig], [0xFFFFFFFD], [], - [xPSR (IPSR)], [0], [15 (SysTick)], [], - ), - caption: [Erwartete Registerwerte beim Eintritt in den SysTick-Handler], + { + set par(justify: false) + table( + columns: (auto, 1fr, 1fr, 1fr), + table.header([*Register*], [*Thread-Modus gemessen*], [*Handler erwartet*], [*Handler gemessen*]), + [R13 (SP)], [0x20000470 (= PSP)], [\= MSP], [0x20000880 (= MSP)], + [PSP], [0x20000470], [$p - "0x20"$ (Exception-Frame)], [0x20000440], + [MSP], [0x20000888], [0x20000880 (nach `PUSH`)], [0x20000880], + [LR], [0x0800086B (Rücksprung in `Superloop`)], [0xFFFFFFFD], [0xFFFFFFFD], + [xPSR], [0x21000000 (IPSR = 0)], [IPSR = 15 (SysTick)], [0x0100000F (IPSR = 15)], + ) + }, + caption: [Registerwerte im Thread-Modus (@fig-dbg-superloop) und im SysTick-Handler (@fig-dbg-isr); $p$: PSP des unterbrochenen Codes], ) == Funktion von PSP und MSP @@ -441,10 +433,31 @@ In der Superloop (@fig-psp) läuft der Prozessor im Thread-Modus: R13 (SP) ist g - *Vorteil der Trennung:* Interrupts haben einen eigenen, zuverlässig verfügbaren Stack, dessen Bedarf nur einmal eingeplant werden muss, und Anwendungs- und Systemspeicher sind sauber getrennt. #pagebreak(weak: true) -= Quellen +#set heading(numbering: "A.1") +#counter(heading).update(0) += Quellcode -- ARM: _Cortex-M0 Devices Generic User Guide_ (ARM DUI 0497A), Abschnitte „Stacks“, „Core registers“ (CONTROL-Register) und „Exception entry and return“. -- ARM: _Cortex-M0 Technical Reference Manual_ (ARM DDI 0432C), Exception-Modell und Interrupt-Latenz. -- STMicroelectronics: _RM0091 Reference Manual STM32F0x1/STM32F0x2/STM32F0x8_, Kapitel „Memory map“. -- STMicroelectronics: _Datenblatt STM32F072x8/xB_. -- J. Langer: _Übung 1 RTO_ (Angabe), 2026. +#[ + #show raw.where(block: true): set text(size: 7.5pt) + #show raw.where(block: true): it => block(fill: luma(245), inset: 8pt, radius: 3pt, width: 100%, grid( + columns: (1.8em, 1fr), + column-gutter: 0.8em, + row-gutter: 0.65em, + ..it.lines.map(l => (align(right, text(fill: luma(140), str(l.number))), l.body)).flatten(), + )) + + #for pfad in ( + "main.c", + "Debug.h", + "Debug.c", + "Tasks/TaskCounter.c", + "Tasks/TaskKey.c", + "Tasks/TaskLed.c", + "Tasks/TaskWatch.c", + "Tasks/TaskPoti.c", + "Tasks/TaskMandelbrot.c", + ) [ + == #raw("src/" + pfad) + #raw(read("../src/" + pfad).trim(at: end), lang: "c", block: true) + ] +]