Added mkdocs skript with corresponding build instruction in the README

This commit is contained in:
tims
2025-10-15 12:16:58 +02:00
parent 95b0db1977
commit 3b52bd9e15
86 changed files with 1647 additions and 0 deletions

9
README
View File

@@ -45,4 +45,13 @@ idaCom.git
|--- out/ /* Generated compiler output, is removed during "make clean" */
The skript is managed via mkdocs and you can build and access it as follows:
- Open a Terminal and navigate to the lab_sdes_student folder
- Use the command [mkdocs serve] to build the skript and make it accessible
-> hint: you can open mkdocs as a background-process with the [&] command, however it is advisable to open it in a seperate Terminal(-Tab)
- once the build is done there should be an information output showing an URL (IP-Adress) mkdocs is serving it on
-> you can then either open the skript by clicking it in the terminal or open it in a browser manually

View File

@@ -0,0 +1,4 @@
{
"cSpell.enabled": true,
"cSpell.language": "en,de-DE"
}

View File

@@ -0,0 +1,265 @@
# Aufgabe 6 - Task Scheduling
## 10.1. Tracing
### 10.1.1. Funktionsdefinition
Unter Tracing bezeichnet man das Aufzeichnen des Programmablaufs, um diesen dann
später zur Analyse von Fehlern zu nutzen. Die Analyse kann nach der Ausführung des Programms stattfinden. Auch Lese- und Schreibzugriffe auf Variablen können aufgezeichnet
werden. Abstürze, bei denen die Ursache in einem Speicherüberlauf oder NullpointerExceptions vermutet wird, können damit gelöst werden. Tracing kann auf verschiedene
Arten realisiert werden, die jeweils eigene Vor- und Nachteile mit sich bringen.
(Hardware-)Tracing lässt sich aber auch einsetzen, um den zeitlichen Ablauf des Programms zu analysieren und so zum Beispiel Statistiken über die Worst-Case Execution
Time (WCET) anzustellen.
***Software-Trace***
Das untersuchte Programm wird so verändert, dass es die benötigten Informationen selbst
erzeugt. Dazu werden die gesammelten Daten in Variablen in den Zielgerät-RAM geschrieben und später vom Debugger ausgelesen. Vorteile dieser Variante sind, dass die
Daten in beliebigem Umfang und beliebig genau bereitgestellt werden können. Gleichzeitig ist allerdings zu bedenken, dass die Hardware, auf der das Programm läuft, nun
auch die Datensammlung bewerkstelligen muss. Die logische Konsequenz ist die Verringerung der Geschwindigkeit der Ausführung und ein erhöhter Speicherbedarf. Auf Systemen, auf denen kaum Leistung und Speicher zur Verfügung stehen, kann dies zu Problemen führen. Außerdem ergibt sich aus dieser Variante des Tracings ein hoher Einfluss
auf das Zeitverhalten des Systems. Bestehende Fehler können, während der Ausführung
mit Software Tracing, anders auftreten als ohne Software-Tracing.<br>
Als Beispiel soll hier ein Programm dienen, welches durch einen externen Interrupt beeinflusst wird: Die Anwendung reagiert auf einen Interrupt und stürzt im betrachteten
Fall ab. Dieser Absturz findet immer genau dann statt, wenn der Interrupt aktiviert wird,
während sich das Programm in Methode XY befindet. Der Versuch diesen Fehler mit Hilfe
von Software-Tracing zu lösen, verändert die Laufzeit des Programms so, dass der Interrupt nun zu einem anderen Zeitpunkt im Programm auftritt. Das Programm stürzt nun
während des Debuggens nicht mehr ab. Jede dem Debuggen dienende Veränderung ändert das Zeitverhalten.
![Screenshot](img/jpg/PowerTrace-II.jpg)<br>
Abbildung 10.1.: Lauterbach Trace Debugger PowerTrace-II
***Offchip-Trace***
Im Gegensatz zum Software-Tracing kommt diese Methode des Tracings nicht ohne externe Trace-Hardware aus (siehe Abbildung 10.2). Sollen Informationen zum Zustand des
Systems zur Laufzeit aufgenommen werden, werden diese am Prozessor des Zielgerätes
abgenommen. Die übliche Methode bei Mikroprozessoren ist das Auslesen des Adressbusses zum Speicher und einiger Steuersignale. Mithilfe dieser Daten kann der Programmablauf rekonstruiert werden. Bei modernen Chips sind CPU Kerne, Haupt- und Massenspeicher, Cache und Peripherie in einem Gehäuse integriert. Das macht es unmöglich den
Speicherbus abzugreifen. Um diese Systeme trotzdem noch mit Trace-Debugging nutzen
zu können, werden sogenannte Trace-Interfaces bereitgestellt. Auf ihnen wird in komprimierter Form der Programmfluss übertragen. Es handelt sich dabei meist um ein 4,
8 oder 16 Bit breiten Bus, über den mit Frequenzen bis 400 MHz Daten übertragen werden. Die bereitgestellten Informationen liegen so vor, wie sie auch in der CPU vorliegen
würden, dass heißt es werden auch Speicherzugriffe aufgezeichnet. Es muss sich nicht um
etwaige fehlende Informationen über Lese-, aber vor allem Schreibzugriffe auf den Cache
gekümmert werden.
***Onchip-Trace***
Diese Methode des Tracings kommt ohne externen Trace-Speicher aus. Es gibt CPUs mit
einem Trace-Speicher, der in das System integriert ist (siehe Abbildung 10.3). Auf diesem werden ähnlich der Methode des Offchip-Tracings die benötigten Daten gespeichert
und können nach Beenden des Programms ausgelesen werden. Vorteil gegenüber dem
Software-Tracing ist, dass keine Änderungen am Programm vorgenommen werden müssen. Im Vergleich zum Offchip-Tracing wird zwar kein externer Trace-Speicher benötigt,
allerdings ist der interne Trace-Speicher aus Kostengründen und Platzgründen sehr klein
gehalten. Um diesen Nachteil zumindest teilweise zu kompensieren, gibt es häufig die
Möglichkeit das Programm zu stoppen und einen Interrupt auszulösen, wenn der interne Trace-Speicher voll ist. Dann können die Daten auf die Debugplattform übertragen
und das Programm weiter ausgeführt werden. Der Nachteil dieser Variante ist das benötigte Starten und Stoppen und der damit verbundenen Eingriff in das Zeitverhalten des
Systems.
![Screenshot](img/png/trace-offchip.png) <br> Abbildung 10.3.: Funktionsweise Onchip Tracing
### 10.1.2. Art der Anwendung, Nutzung des Werkzeugs
Die Anwendungsmöglichkeiten von Tracing sind vielfältig. Oft ist es nötig, die Ausführungszeit einer Methode zu kennen. Auch ist es nützlich die Register und Variablen ohne
Unterbrechung des Programms auslesen zu können. Wenn ein Programm abstürzt, macht
es Tracing möglich genau nachzuvollziehen, welche Funktionsaufrufe mit welchen Werten vor dem Absturz getätigt wurden. Programmkomponenten, die sich mit den vorherigen Debugwerkzeugen nur schwer oder gar nicht analysieren ließen, können nun auf
ihre Auswirkungen auf den Programmfluss überprüft werden. Dazu gehört zum Beispiel
erhöhte Interruptlast oder Unterbrechung durch höherpriore Tasks. Fehleranalyse von
Fehlern zur Laufzeit und die Analyse von Kommunikation über Busse wird einfacher.
In dieser Aufgabe sollen sie Trace-Points nutzen, um den Wechsel von einer Funktion zur
anderen darzustellen. Diese Tracepoints können zum Beispiel ein Taskset visualisieren,
indem sie zu Beginn und am Ende eines Tasks gesetzt werden.
![Screenshot](img/png/trace-onchip/)
### 10.1.3. Grenzen und Nachteile
Trotz dessen, dass die beiden in Kapitel [10.2](10_aufgabe6.md#102-lauterbach-wissen) vorgestellten Trace-Methoden von den Nachteilen bezüglich des Heisenbergeffekts beim SoftwareTracing nicht betroffen sind, haben
sie Nachteile. Dazu gehört der sehr hohe Kaufpreis solcher Systeme, der sich im niedrigen fünfstelligem Bereich bewegt. Außerdem ist die Unterstützung von Tracing unter
den Entwicklungsboards wesentlich weniger weit verbreitet, als das bei JTAG-Debugging
der Fall ist. Das Interface benötigt aufgrund der hohen Datenraten bei der Übertragung
viele Pins. Unterschiede zwischen den verschiedenen Trace-Methoden lassen sich in der
Tabelle 10.1 finden.
![Screenshot](img/png/tabelle-trace-vergleich.png) <br> Tabelle 10.1.: Vergleich von Software-, Onchip- und Offchip- Trace
Diese Aufgabe soll Ihnen Methoden vermitteln, mit denen Sie die Auslastung des Systems beurteilen und optimieren können. Grundstein für die Überlegungen dieser Aufgabe ist ein System mit mehreren Tasks. Es stellt sich nun die Frage, wann welcher Task
CPU-Zeit in Anspruch nehmen darf. In der Aufgabe lernen Sie verschiede Scheduling
Algorithmen kennen und analysieren sie auf die Auswirkungen auf das Task-Set.
***Alle benötigten Informationen zur Bearbeitung bekommen Sie aus den beigefügten Folien der
Übung und Vorlesung zur Lehrveranstaltung Rechnerstrukturen 2. In den Vorlesungsfolien sind aus Kapitel 5, Folien 29 bis 59 relevant. Kapitel 9 und 10 der Übung helfen beim
Anwenden der Verfahren. Sie finden das benötigte Material in Ihrem Repository unter
RS2_Unterlagen.***
## 10.2. Lauterbach-Wissen
### 10.2.1. Tracing Points
Das Aufzeichnen des gesamten Programmflusses ist oftmals nicht notwendig und meistens eher störend beim Fehler finden, da die wichtigen Details in der Menge an Informationen untergehen. Daher ist es möglich das Tracing erst bei Bedarf zu aktivieren. Um
das Handling zu erleichtern, ist die Syntax bei Lauterbach zwischen Break-Points und
Tracing-Points sehr ähnlich (vgl. [9.1.1](9_aufgabe5.md#911-bedienung-des-lauterbach-debuggers)):
```
break.set <function_name> /program /TraceEnable
```
TraceEnable aktiviert das Tracing für einen kurzen Moment beim eintreten der Bedingung oder des Events. Es erzeugt so zu sagen ein Snapshot. Dies ermöglicht es auch den
Zugriff auf eine Variable zu tracen und den Rest des Systems auszublenden:
```
var .break.set <variable_name> /<access> /TraceEnable
```
Leider stehen hardware-bedingt nur 4 TraceEnable-Points zur Verfügung. Daher greift
man auf Trace-On/-Off-Points zurück. Beim Erreichen eines Trace-On-Points wird das
Tracing aktiviert und wieder beendet beim Erreichen eines Trace-Off-Points.
```
break.set <function_name_1> /program /TraceON
break.set <function_name_2> /program /TraceOFF
```
Das Setzen von Tracepoints ist auch über die GUI möglich z.B. indem man im Sourcecode ein Rechtsklick macht und Breakpoints->TraceEnable/-On/-Off auswählt.<br>
Ebenso wie bei den Breakpoints ist die Verwendung des symbol-Befehls möglich [[tra](references.md), S.286]
![Screenshot](img/jpg/lauterbach_taskset_statistic_example.png)<br>
Abbildung 10.4.: Verteilung dargestellt<br>
Das Diagramm zeigt die Dauer zwischen OSQPost und OS_EventTaskRdy.
### 10.2.2. Darstellung
Die Darstellung der aufgezeichneten Daten übernimmt die Trace32 Software und kann
angezeigt werden mit (Abbildung 10.5):
```
trace.chart
```
Alternativ kann über die GUI gearbeitet werden mit Trace->Chart->Symbols.
Je nach Dauer und Menge der Daten kann die Darstellung etwas dauern.
Zudem kann Lauterbach die Verteilung bestimmter Werte in einer Statistik darstellen.
Der folgende Befehl erzeugt ein Diagramm, dass die Verteilung der Dauer zwischen den
beiden angegebenen Methoden darstellt (Abbildung 10.4).
```
Trace.STATistic.AddressDURation <function_name_1> <function_name_2>
```
Falls man erneut Tracen möchte, sollte man die gespeicherten Daten zurücksetzen mit:
```
Trace.Init
```
![Screenshot](img/jpg/lauterbach_taskset_chart_example.png)<br>
Abbildung 10.5.: Trace im Chart dargestellt<br>
Ein langer und großer Trace, der zwar viele Daten enthält. Aber die wesentlichen Punkte
sind nicht sofort ersichtlich.
## 10.3 Aufgabenteil
Bearbeiten Sie das Arbeitsblatt und notieren Sie sich Ergebnisse und Vorgehensweisen.
Beantworten Sie außerdem folgende Fragen:
- Was ist der Unterschied zwischen Analyse und Simulation?
- Welche Arten der Taskaktivierung gibt es?
- Erklären Sie die Begriffe Periode und Jitter
- Was ist Präemption im Bezug auf Scheduling?
- Was sind die Unterschiede zwischen TDMA und Round Robin?
- Was ist RMS?
***Hinweis: Nutzen sie für die Berechnungen das entsprechende Material aus der Vorlesung
Rechnerstrukturen 2.***
![Screenshot](img/png/TaskSet-Tabelle.png)
***Aufgabe 1:***
Vergeben Sie die Prioritäten der Tasks nach RMS. **Hinweis** 0 ist die höchste Priorität.
***Aufgabe 2:***
Berechnen Sie die maximale Last, wenn alle Tasks auf demselben Prozessorkern ausgeführt werden. Was fällt Ihnen auf, wenn Sie die berechnete Last mit der Auslastungsschranke aus dem Theorem von Liu/Layland zu RMS vergleichen?
***Aufgabe 3:***
Berechnen Sie für das gegebene Taskset die **BCRT** sowohl für SPP, als auch für SPNP Scheduling.
**Hinweis:** Alle Tasks werden synchron zum Zeitpunkt t=0 aktiviert. Überlegen Sie sich die Eigenschaften des Tasksets. Hierfür wird keine Formel benötigt!
***Aufgabe 4:***
Ermitteln Sie für das gegebene Taskset die **WCRT**, sowohl für SPP als auch SPNP **zeichnerisch**
***Aufgabe 5:***
Ermitteln Sie für **Task 4** die **WCRT** für SPP und SPNP **rechnerisch**.
**Hinweis:** Nutzen Sie für die Berechnung die Formel für die WCRT, welche Sie aus der Vorlesung kennen.
***Aufgabe 6:***
Nehmen Sie an, dass für das Taskset implizite Deadlines gelten, d.h. Deadline = Periode. Ist das Taskset unter diesen Voraussetzungen "scheduable"?
***Aufgabe 7:***
Angenommen die Aktivierung von **Task 1** hat einen **Jitter von 0,5ms**. Wie wirkt sich dies bei **SPP** Scheduling auf die BCRT und WCRT von **Task 2** und **Task 4** aus?
## 10.4. Aufgabenteil 2
Kompilieren Sie nun die Aufgabe 6 und laden diese auf das Board. In dieser Aufgabe wird
ein Tasksetsimulator genutzt, indem sich mehrere Tasks nach dem P-J-D Modell konfigurieren lassen. Tragen Sie zunächst die korrekten Prioritäten der Tasks in der Datei
src/gt_tasks.c ein.
Untersuchen Sie zunächst mit Hilfe der Tracingtechniken des Lauterbachs die Kalibrierung des Tasksetsimulators. Der Simulator nutzt eine Schleife in der Funktion
```
__burn_wcet(CET,GT_CPU_OS_TASK_OFFSET,GT_CPU_CYCLE_SCALE);
```
um die BCET ≤ CET ≤ WCET zu simulieren. Das heißt der Task hat effektiv keine
Funktion sondern verbraucht nur die Rechenzeit CET. Die CET wird dabei für jede Aktivierung zufällig zwischen der BCET und WCET gewählt. Um die Ausführungszeiten
des Simulators auf den Prozessor anzupassen müssen bestimmte Parameter korrekt gesetzt werden, da ansonsten die CET nicht den Sollwerten entsprechen. Ihre Aufgabe ist
es zunächst diese Parameter so zu konfigurieren, dass die Ergebnisse den Sollwerten entsprechen. Die Parameter unterteilen sich in einen Scale-Wert und einen Offset. Der Scale-Parameter garantiert die korrekte Ausführungszeit langer Delays. Der Offset muss korrekt
gesetzt werden, damit kurze Delays genau genug für eine Simulation des Taskssets sind.
Zu Beginn der Main-Funktion wird eine Methode zum Kalibrieren der Parameter aufgerufen. Der Kommentar zu der Methode erklärt die Funtionsweise. Messen Sie die Ausführungszeiten der von dem Taskset genutzen Funktion in der Methode GT_calibrate
indem Sie Tracepunkte nutzen. Die CET wird dabei exponentiell von 0,1ms auf 51,2ms
erhöht. Die Zeiten zwischen den Trace-Events können sie in der trace.list Darstellung ablesen. Nutzen Sie zunächst weiter das Onchip-Tracing (zc706_onchip_trace.cmm), da bei der Kalibrierung ja nur wenige Tracedaten generiert werden.
Erstellen Sie sich eine Tabelle mit Soll- und Ist-Werten für die einzelnen Schleifen-Durchläufe in der Kalibrierungsfunktion. Nun können Sie das Verhältnis zwischen Soll- und Ist-Werten ausrechnen und in einem Diagram darstellen lassen. Stellen Sie die Parameter so ein, dass 0, 96 ≤ IST/SOLL ≤ 1, 0 gilt.
**Tipp:** Passen Sie ihr .cmm Skript für Aufgabe 6 an, um wiederkehrende Arbeitsschritte
wie das Setzen von Break-/ Tracepunkten zu automatisieren!
Ist die Kalibrierung geglückt, sollen Sie das Taskset aus der theorethischen Aufgabe in
den Simulator übertragen und visualisieren. Der Taskssetsimulator erlaubt es, mehrere
Tasks nach dem Periode-Jitter Modell zu konfigurieren. Diese sind in der Datei APP/Aufgabe6/src/gt_tasks.c bereits mit den BCET und WCET Parametern aus dem Aufgabenblatt
definiert.
Nach der Bearbeitung des Arbeitsblattes und der Kalibrierung des Simulators sollen Sie
nun untersuchen, wie sich das vorher berechnete Zeitverhalten auf einem realen System
verhält. Nutzen Sie im Folgenden OffChip-Tracing, indem Sie die zc706_offchip_trace.cmm
ausführen.
Vergleichen Sie die zc706_offchip_trace.cmm und zc706_onchip_trace.cmm. Wo liegen die
Unterschiede? Tipp: Tools wie meld oder vimdiff erleichtern die Arbeit.
In der Datei finden sich auch die folgenden Methoden:
```
GT_TaskActivationHook
GT_TaskStartHook
GT_TaskEndHook
GT_TaskSwHook
```
Mit ihnen lassen sich Aktionen auslösen, wenn ein Task bereit ist, gestartet, gescheduled
oder beendet wird.
- Erweitern Sie diese Methoden so, dass sie unterscheiden können, welcher Task aktiviert bzw. beendet wurde.
- Benutzen Sie zu erst Trace-Enable Points, um das Scheduling eines einzelnen Tasks
aufzuzeichnen (Activated, Scheduled, Not-Scheduled, Finished).
- Benutzen Sie anschließend Trace-On/-Off Points, um das Verhalten aller Tasks zu
untersuchen.
- Erstellen Sie eine Statistik über die Verteilung der WCET und der WCRT jedes
Tasks.
- Vergessen Sie nicht ihre Ergebnisse zu dokumentieren z.B. mit Screenshots oder als
Text-Export.

View File

@@ -0,0 +1,121 @@
# 11. Aufgabe 7 - Integration
## 11.1. Aufgabenteil 1
In dieser Aufgabe sollen die zuvor erstellten Programmodule zusammengesetzt werden.
Es soll ein balancierender Roboter entstehen. Erstellen Sie auf Basis des in Aufgabe 6
genutzten Taskset-Simulators ein Taskset, dass ihre Tasks periodisch ausführt. Denken
Sie daran ihre Init-Funktionen einzutragen. Nachfolgend werden die Anforderungen an
die bisherigen Programme aufgelistet:
### 11.1.1. PID
Eingänge:
- Aktueller Winkel der IMU
- Sample Time
Funktion
- PID Wert erstellen
Ausgänge
- PID Wert zwischen -1000 und 1000
**Hinweis:** Der PID Regler mittelt für die ersten 10 Sekunden nach Start den Nullpunkt zur Kalibrierung
### 11.1.2. IMU
Funktion
- IMU initialisieren
- Werte über I2C auslesen
Ausgänge
- IMU Winkel
### 11.1.3. Motortreiber
Eingänge
- PID Wert
Funktion
- Ansteuern der Richtung der Motoren für vorwärts und rückwärts
- Regeln der Frequenz der Steps: Frequenz: min. 800us/step, max 100us/step
## 11.2 Aufgabenteil 2
Sie finden sich nun in einem Szenario der Wirtschaft: Ihr Vorgesetzter schränkt ihre Ressourcen auf dem genutzten Steuergerät ein, weil diese anderweitig genutzt werden. Verändern Sie dazu in der Datei gt_tasks.c das Struct GT_AllTasks wie folgt und fügen Sie die
Zeile
```
{50, 0, 0, {3, 5}};
```
Quellcode 11.1: Ergänzung Task Timing Struct
hinzu. Außerdem sollen Sie in dem Struct GT_Tasks die Zeile
```
{ GT_TASK_DEF, GT_ACT_INT, 4, 29, NULL, NULL, NULL, GT_RUNABLE_NULL,
GT_INTERNAL_NULL, (void *)&GT_AllTasks[4]},
```
Quellcode 11.2: Ergänzung Task Struct
hinzufügen. Achten Sie darauf die Konstante GT_NUM_OF_TASKS anzupassen. Ihre Aufgabe ist es nun, die Funktionalität des Roboters wiederherzustellen. Untersuchen Sie dafür, wo im Programm CPU Zeit eingespart werden kann.
Um die in diesem Bereich genutzte CPU-Zeit zu reduzieren bietet es sich an die Kommunikation mit der inertialen Messeinheit zu verändern. Die Hardwareeinheit des Xynq-
7000 macht es möglich nach jeder abgeschlossenen Kommunikation einen Interrupt auszulösen. Wir können die Kontrolle somit während des Senden an andere Tasks abgeben.
Orientieren Sie sich für das Erstellen und Verknüpfen des Interrupts an dem des Timers.
Suchen Sie nach geeigneten Funktionen vergleichbar zu denen, die bei der Einrichtung
des Timer-Interrupts genutzt wurden. Grundsätzlich sollten Sie wie folgt vorgehen:
- Erstellen Sie einen Interrupt und suchen Sie die korrekte Interrupt-ID heraus, geben Sie als Interrupt-Handler *MasterInterruptHandler* an.
- Die I2C Instanz hat einen Statushandler der angegeben werden kann, er bietet sich an um die Semaphore zu pushen
- Die Funktionen XIicPs_MasterSendPolled und XIicPs_MasterRcvPolled haben passende Gegenstücke zur Nutzung mit Interrupts der I2C Hardware. Nach dem Aufrufen
der Funktion muss auf die Fertigstellung gewartet werden. Dies wird über den Semaphore realisiert.
Zunächst muss eine Interrupt-Service-Routine (ISR) erstellt und im Setup dem Iic-StatusHandler übergeben werden.<br>
Erstellen Sie zudem ein globales Semaphor. Nutzen Sie hierfür die Funktion
```
OS_SemCreate(...);
```
**Tipp:** Überlegen Sie sich was ein sinnvoller initial-Wert für das Semaphor in diesen Anwendungsfall ist.
Wir erstellen einen Interrupt welcher ausgelöst wird, wenn die I2C Kommunikation abgeschlossen ist. Die Interrupt ID ist XPS_I2C1_INT_ID. Dazu können die folgenden Methoden genutzt werden.
```
UCOS_IntVectSet(...);
UCOS_IntSrcEn(...);
```
Zuletzt müssen Sie noch die I2C Kommunikation überarbeiten. Während der Übertragung soll dem System nun erlaubt werden andere Tasks zu schedulen. Überlegen Sie hierfür wann das von Ihnen erstelle Semaphor dafür Ressourcen freigeben und wann wieder blockieren soll.
<br> Hierfür können folgende Methoden genutzt werden:
```
OSSemPend(...);
OSSemPost(...);
```
<br>
**Hinweis:** Sie müssen zudem die Funktionen
```
XIicPs_MasterSendPolled(...);
// und
XIicPs_MasterRecvPolled(...);
```
durch folgende Funktionen ersetzen:
```
XIicPs_MasterSend(...);
XIicPs_MasterRecv(...);
```

View File

@@ -0,0 +1,34 @@
# 1. Einleitung
## 1.1 Motivation
Im Programmieralltag wird man immer wieder damit konfrontiert, dass Software nicht einwandfrei funktioniert. Neben der Tatsache, dass es bei bei größeren Projekten schnell unmöglich wird den Fehler nur durch Analyse des Quellcodes zu finden, kommt es vor allem in eingebetteten Systemen vor, dass nicht immer Fehler in der Programmiersyntax oder -Logik vorliegen. Dabei geht es um Fehler, die nicht beim Kompilieren oder Linken des Programms auftreten, sondern erst zur Laufzeit der Anwendung. Um ein Programm zur Laufzeit zu debuggen gibt es mehrere Möglichkeiten. Dieses Praktikum wird Ihnen die klassischen Debug-Varianten darlegen und an Beispielen nachvollziehen lassen um diese Fehler effizient zu finden und zu korrigieren.
Dazu werden zunächst die Grundlagen des Programmbaus vom C-Code zum Maschinencode und zur Makefile behandelt, um später auftretende Fehler richtig einordnen und beheben zu können. Außerdem wird ein Grundverständnis von Betriebssystemen für ein- gebettete Systeme vermittelt.
Im nächsten Teil des Praktikums werden Ihnen Grundlagen des Software Debuggings
von eingebetteten Systemen dargelegt und praktisch auf die entsprechende Probleme angewandt. Dabei werden Sie lernen, welche Vor- und Nachteile die einzelnen Debugmethoden haben und wann es sinnvoll ist, welche Debugmethode zu verwenden. Die Arten
des Debuggings, die in diesem Praktikum behandelt werden beinhalten Printf Debugging, Debugging via Programmablauf-Counter/Single-Stepping, die Nutzung von Break-
/ Trace- und Watchpoints, die Nutzung direkten Speicherzugriffs zur Laufzeit des Programms und Möglichkeiten und Anwendung des Tracing. Die Aufgaben fördern das Verständnis zur Arbeitsweise eines Betriebssystems vor allem in Bezug auf Tasks und deren
Zustände, Scheduling und das Wissen um das Zeitverhalten in Echtzeitsystemen. Im Laufe
der Veranstaltung wird auf Inter-Core Kommunikation in Multicore-Systemen eingegangen.
## 1.2 Lehrnziel
Die Studierenden kennen am Ende des Praktikums die klassischen Varianten des Software
Debuggings von eingebetteten Systemen. Sie können mit Software Debugging Verfahren
wie zum Beispiel JTAG Debugging umgehen, kennen sich mit der Lauterbach Debugumgebung aus und wissen, welche Möglichkeiten sowie Vor- und Nachteile die jeweiligen
Debugmethoden mit sich bringen. Sie sind in der Lage Probleme zu beurteilen und die
am besten geeigneten Methoden des Debuggings auf diese anzuwenden.
## 1.3 Anwendungsfall
Das Praktikum vermittelt seinen Lehrinhalt an einem Anwendungsfall. Es soll die Software für einen sich selbst balancierenden zweirädrigen Roboter entwickelt werden, der
nach dem Vorbild eines Segways funktioniert. Der Roboter soll selbstständig sein Gleichgewicht halten und seine Position über eine Eingabe durch Fahrbewegung verändern
können. Die dafür benötigte Hardware ist bereits vorhanden und wird den Studierenden
zur Verfügung gestellt. Dazu sollen zu den Praktikumsterminen einzelne Komponenten
entwickelt, beziehungsweise zum Teil vorgegebene Software mit Lauterbach-Debuggern
debuggt werden. Die Roboterhardware wird über ein Zynq-7000 Board angesteuert. Die
Teilnehmer müssen eine inertiale Messeinheit auswerten und die ausgewerteten Daten
in Echtzeit verarbeiten. Die daraus berechnete benötigte Lagekorrektur des Roboters wird
an die Motortreiber übermittelt. Hintergrund dieses Anwendungsfalls ist die gegebene
Nähe zum Auslesen von Sensorik, Echtzeitverarbeitung von Daten, sowie Parallelen zur
Automobilindustrie im Bereich der Multiprozessor-Datenverarbeitung im späteren Verlauf des Praktikums.

View File

@@ -0,0 +1,16 @@
# 2. Hardware und Aufbau
## 2.1 Hardware
Der Aufbau und die Zusammenhänge der Hardwarekomponenten sind in Diagramm 2.1
dargestellt. Der Debugger Lauterbach PowerDebug Pro mit der Erweiterung PowerTrace-II ist ein leistungsfähiges Debugging-System. Besonderheiten sind unter anderem die
eingebauten Features zum Debugging von Echtzeitbetriebssystemen und die umfangreichen Trace Funktionen. Genauere Informationen zum Lauterbach Debugger finden sich
in der Quelle [[lau]](references.md).
![Screenshot](img/png/versuchsaufbau.png) Abbildung 2.1.: Komponenten und Zusammenhänge des Versuchsaufbaus im Praktikum
Das Xilinx ZC706 Evaluation Kit ist auf das Entwickeln mit dem SoC Zynq 7000 optimiert. Das SoC ist mit einem Dual Core ARM Cortex A9 Prozessor ausgestattet. Außerdem
findet sich ein FPGA in dem System, welcher eng mit dem Hauptprozessor zusammen arbeitet, um etwa die Ansteuerung der GPIOs flexibler zu gestalten (siehe Abbildung 2.2).
Genauere Informationen zum Xilinx Zynq-7000 SoC ZC706 Evaluation Kit finden sich in
der Quelle [[zc7, Xilinx ZC706]](references.md).
![Screenshot](img/jpg/zynq7000plc.jpg) Abbildung 2.2.: Nutzung des Programmable Logic Controllers im Zynq7000 SoC

View File

@@ -0,0 +1,58 @@
# 3. Grundlagenwissen
## 3.1. Echtzeitsysteme
Ein Echtzeitsystem wird nicht durch seine Schnelligkeit definiert, sondern durch das Einhalten von Zeitschranken und damit verbundene Rechtzeitigkeit. Das Ergebnis der Berechnung eines Echtzeitsystems ist nur dann brauchbar, wenn es rechtzeitig vorliegt. Echtzeitanforderungen können in harte und weiche Echtzeitanforderungen unterschieden werden. Entsprechend Ihrer Betitelung sind harte Anforderungen unter allen Umständen einzuhalten, da sonst das Ergebnis des Systems als nicht brauchbar gilt. Als Beispiel hierfür gilt die Auslösung des Airbags im Auto. Weiche Echtzeitanforderungen hingegen werden durch die bei ihrer Verletzung verursachten Kosten definiert. Je stärker die vorgegebene Zeitschranke verletzt wird, desto höher fallen die damit verbundenen Kosten in Form
von Rechenzeit oder materiellem Verlust aus. Die Eigenschaft der Gleichzeitigkeit impliziert die korrekte Verarbeitung paralleler Arbeitsabläufe. Als Beispiel kann die Regelung der Lage des im Praktikum verwendeten Roboters gesehen werden, der Bewegungssensordaten und Motorsteuerung gleichzeitig bearbeiten muss. Diese Aufgaben werden in Form
von Tasks umgesetzt. Wichtig ist dabei, dass ein höherpriorer Prozess einen niederprioren Prozess verdrängen kann (preemptiv), und dass das System nicht überlastet ist. So wird Vorhersagbarkeit garantiert.
## 3.2. Debugging
Es gibt nicht nur eine bestimmte Methode des Debuggings, sondern mehrere Möglichkeiten. Sie unterscheiden sich in ihrer Leistungsfähigkeit, dem Anwendungsgebiet, den
Kosten, den Anforderungen an die Hardware und auch in der Pinanzahl. Außerdem gibt
es große Unterschiede in ihrem Einfluss auf das Zeitverhalten des Debugger (zu debuggendes System). Die einfachste Möglichkeit einen Einblick in ein laufendes Programm zu
bekommen, ist die Ausgabe von Variablen zur Laufzeit über eine serielle Schnittstelle. Das
Printf Debugging ist simpel und reicht für einfache kleine Programme ohne Taskstruktur und ohne strikte Anforderungen an Echtzeitfähigkeit aus. Die nächst-mächtigere Methode des Debuggings stellt JTAG-Debugging dar. Es ermöglicht die Steuerung des Programmablaufs via Single-Stepping, also das kontrollierte Anhalten des Programms und
das Setzen von Halte-, Verfolgungs- und Überwachungspunkten. Außerdem ermöglicht es
den vollständigen Lese- und Schreibzugriff auf den Speicher des Programms. Mit diesen
Werkzeugen lassen sich einfach Fehler finden und beheben. Problematisch wird es dann,
wenn unvorhergesehene Fehler zur Laufzeit auftreten, deren Ursprung und Wirkung im
Code entweder zeitlich oder programmierhierarchisch weit auseinander liegen. Die bisher bekannten Debugmethoden können ihre Möglichkeiten aufgrund für den Menschen
schwierig erkennbarer Zusammenhänge nicht ausspielen, was die Fehlersuche sehr erschwert. Außerdem ist es auch hier nahezu unmöglich, das Zeitverhalten des Programms
umfangreich zu analysieren. An dieser Stelle hilft es Trace-Tools als Debugmethode zu
verwenden. Zusätzlich zur Steuerung der CPU wird nun der Programmablauf teilweise
oder vollständig aufgezeichnet. Aus diesem kann dann wahlweise auf Assemblerebene
bis hin zum Ablauf in der Hochsprache nachvollzogen werden in welcher Reihenfolge
das Programm ablief. Diese Daten helfen zu rekonstruieren wo das Programm anders
als gewünscht ablief und ermöglichen es den Stand des Programms vor dem Absturz zu
betrachten und damit auch eine mögliche Fehlerquelle zurück zuschließen.
## 3.2.1 Debugging-Prozess
Um Programme zu debuggen, sollte zunächst definiert sein, was ein Fehler ist und wie
an ihn herangegangen wird. Der Begriff Fehler ist im Deutschen mehrdeutig belegt und
lässt sich mit Hilfe der englischen Begriffe differenzierter betrachten (siehe Abbildung
3.1). Ein Error ist ein nicht korrekt programmierter Code oder eine falsch implementierte
Nutzeranforderung. Dieser Error im Programmcode kann einen Fault auslösen. Es kann
sich zum Beispiel um einen Speicherüberlauf handeln. Der Fault muss nicht nach außen
sichtbar sein. Das extern zu beobachtende Fehlverhalten des Systems wird Failure genannt.
Ist ein Failure erkannt worden, muss die Stelle im Quellcode gefunden werden, die den
Fault auslöst und den Error beschreibt. Die Ursache des Fault muss analysiert und korrigiert werden. Sollten mehrere Fehlverhalten gleichzeitig auftreten, deren Symptome sich
überlagern, kann es vorkommen, dass die Betrachtung eines einzelnen nicht funktionierenden Programmteils alleine nicht zur Lösung des Problems führt. An dieser Stelle kann
viel Zeit gespart werden, indem nicht sofort versucht wird herauszufinden was nicht rich-
tig arbeitet, sondern was überhaupt funktioniert. Je konsequenter das Programm in modularisierter Form geschrieben wurde, desto einfacher und schneller geht dieser Schritt
vonstatten. Es ist möglich Eingabedaten in ihrer Größe oder in ihrem Inhalt zu variieren,
um ihren Einfluss auf Fehler zu untersuchen. Es sollten Hypothesen zur Fehlerursache
aufgestellt und das Programm systematisch darauf getestet werden. Sofern es sich nicht
um triviale Fehler handelt, ist es sinnvoll, das Wissen um die Ursache des Fault festzuhalten, um den Hergang des Problems sowie seine Lösung zu dokumentieren.
## 3.2.2 Grundlagen des Debuggens eingebetteter Systeme
Debugging in der Softwareentwicklung für Desktopanwendungen ist wesentlich zugänglicher und komfortabler als im Kontext der eingebetteten Systeme. So programmieren
die meisten Anwendungsentwickler auf den Systemen, auf denen die von ihnen entwickelte Software später auch ausgeführt wird. Die Steuerung des Programmablaufs, das
Betrachten von Variablen und andere Debugwerkzeuge sind ohne große Umwege nutzbar. Um eingebettete Systeme zu programmieren, kommt oft die Technik des Cross-Compilings und des Cross-Debuggings zum Einsatz. Es wird also nicht auf der Zielhardware
kompiliert und debuggt, sondern auf einem Anwendungscomputer. Das zu programmierende System hat eventuell nicht ausreichend leistungsfähige Hardware, um ein Anwenderbetriebssystem samt Entwicklungsumgebung auszuführen oder es ist schlicht nicht
gewünscht das System zu beeinflussen. Auch die Architektur der Systeme unterscheidet
sich für gewöhnlich. Diese Trennung von Entwicklungsplattform und Laufzeitplattform
erschwert die Beobachtbarkeit und Steuerbarkeit eingebetteter Systeme und verkompliziert somit deren Debugging. Dennoch ist die Anwendung von Debugging notwendig,
weil es unter Umständen zu Fehlern kommt, die erst während der Laufzeit des Systems
auftreten, deren Entstehung aus dem Code nicht ersichtlich ist oder das Projekt zu umfangreich ist.
![Screenshot](img/png/error-fault-failure.png) Abbildung 3.1.: Entwicklung eines Fehlers: Error->Fault->Failure

View File

@@ -0,0 +1,90 @@
# 4. Coding Guidelines
## 4.1. Regeln
Im folgenden finden Sie einige Regeln wie der C-Code in diesem Praktikum formatiert
sein muss. Sauber strukturierter Quellcode trägt entscheident dazu bei, dass Fehler entweder von vorneherin vermieden werden oder beim Debugging leichter eingrenzbar sind.
## 4.1.1. Modul- und Funktionsnamen
Ein Modul (z.B. eine C-Datei mit Funktionen die als Gruppe eine Teilfunktion erfüllen)
hat einen prägnanten Namen. Dieser wird sowohl im Dateinamen verwendet, wie auch als
präfix für alle Funktionen und Variablen. Beispiel: Die Datei imu.c soll soll alle Funktionen zum auslesen der IMU enthalten. Diese heißen dann entsprechend imu_init(),
imu_readGyro() usw. und nutzen globale Variablen wie imu_config
## 4.1.2. Interne Funktionen und Variablen
Als Grundsatz gilt: Alles was nicht für andere Module als Interface nötig ist wird mit static
und einem Unterstrich als Präfix versehen. Somit ist es nur innerhalb dieser einen C-Datei
bekannt und es wird klar wie die Schnittstellen nach außen aussehen.
```
/ Dies ist eine interne variable /
static int32_t _internalTimerCount;
/ Dies ist eine interne Funktion /
static void _imu_handleError(int err);
```
Quellcode 4.1: Deklaration von static Variablen und Funktionen
## 4.1.3. Kommentare
Jede Funktion bekommt einen Kommentar im Doxygen-Stil, der den Inhalt der Funktion
sowie ihre Parameter und Rückgabewerte beschreibt.
```
//
/
This function initiates an interruptdriven receive in master mode.
It sets the transfer size register so the slave can send data to us.
The rest of the work is managed by interrupt handler.
@param InstancePtr is a pointer to the XIicPs instance .
@param MsgPtr is the pointer to the receive buffer .
@param ByteCount is the number of bytes to be received.
@param SlaveAddr is the address of the slave we are receiving from.
@return None.
@note This receive routine is for interruptdriven transfer only.
/
void XIicPs_MasterRecv(XIicPs InstancePtr, u8 MsgPtr, s32 ByteCount,u16 SlaveAddr){
...
}
```
Quellcode 4.2: Beispielhaftes Doxygen Kommentar aus der Xilinx Libary
## 4.1.4. Aufbau der C-Datei
Jede C-Datei hat einen klaren Aufbau der sich in die Bereiche “Includes”, “Inputs”, “Outputs”, “interne Variablen”, “interne Konstanten”, “interne Funktions-Prototypen” und die
eigentliche Implementierung aufteilt. So ist klar ersichtlich wo man eingreifen muss um
z.B. konstante Parameter zu verändern.
```
/ Includes /
#include " .... h"
/ module input variables /
extern int16_t accData [3];
/ module output variables /
int32_t pidValue=0;
/ module internal variables /
static float _pid_errorSumAngle=0;
/ module internal constatns /
const int16_t ACC_MIN[3] = {10, 20, 30};
/ internal function prototypes /
static int16_t _pid_map(int16_t x, int16_t in_min, int16_t in_max, int16_t out_min, int16_t out_max);
...
/ functions /
void pid_init (void) {
...
}
static int16_t _pid_map(int16_t x, int16_t in_min, int16_t in_max, int16_t out_min, int16_t out_max){
...
}
```
Quellcode 4.3: Aufbau einer C-Datei

View File

@@ -0,0 +1,170 @@
# 5. Aufgabe 1 - Makefile
Die in dem Skript stehenden Aufgaben dienen der Anweisung was programmiert werden soll. Zusätzlich zu den Programmieraufgaben halten Sie Ihre Ergebnisse in dem vorgefertigten Dokument doku.odt fest. Dieses finden Sie im Repository, welches Sie im
Folgenden für alle Aufgaben verwenden. Die Dokumentation dient als Hilfestellung für
Sie in den Kolloquien, um die Schritte Ihrer Lösung erklären zu können.
## 5.1. Wissen
### 5.1.1. Toolchain
Jedes in Hochsprache geschriebenes Programm muss in Maschinencode übersetzt werden. Die Toolchain ist eine Aneinanderreihung von Routinen zur Erstellung eines ausführbaren Programms aus dem Code einer Hochsprache wie zum Beispiel der Sprache C.
Damit der Code unserer Hochsprache auf unserem Zielsystem ausgeführt werden kann,
muss er zuerst in die dem Zielsystem entsprechende Maschinensprache übersetzt werden. Der Compiler unserer Toolchain übersetzt den von uns geschriebenen Code aus der
Hochsprache in Assembler-Code. Dieser Code hängt bereits von dem verwendeten Ziel
ab. Je nach Befehlssatz des Zielarchitektur (ARM, AVR, x86, ...) sieht der Assemblercode
verschieden aus. Der vom Compiler erstellte Assembly-Code wird vom Assembler in Maschinencode umgewandelt. Als Basis für das weitere Verständnis zur Toolchain kann das
Dokument How a Compiler Works [[RS14](references.md), Kapitel 2, S. 2] genutzt werden. In dem Diagramm
5.1 können zum Verständnis die Arbeitsschritte der
Toolchain eingetragen werden. Nutzen Sie dafür das zur Verfügung gestellte .odt Dokument.
### 5.1.2 Makefile
Das Programm Make wird verwendet, um den Buildprozess zu automatisieren. Dafür
liest Make die Makefile aus und gibt die entsprechenden Anweisungen an die Toolchain
weiter (z.b. Compiler, Linker, ...). Dies ist besonders bei größeren Projekten hilfreich, da
der Buildprozess aus vielen Einzelanweisungen bestehen kann. Außerdem können unterschiedliche Build-Konfigurationen und Targets benutzt werden. Eine Erklärung von
Makefiles bietet die Website [[mak](references.md)]. Schauen Sie sich den Inhalt an und verinnerlichen Sie den Sinn und die Arbeitsweise von Makefiles.
![Screenshot](img/jpg/toolchain-leer.png) Abbildung 5.1.: Toolchain Diagramm zum Ausfüllen und zur Vorbereitung auf das Prekolloquium
![Screenshot](img/png/makefile.png) Abbildung 5.2.: Beispiel zum Aufbau einer Makefile
### 5.1.3 Printf-Debugging
#### Funktionsdefinition
Eine intuitive und einfache Methode des Debuggens ist die Ausgabe von Werten oder
Nachrichten über eine serielle Schnittstelle. Zum Lesen solcher Nachrichten auf der Entwicklungsplattform ist es meist nötig, einen Seriell-zu-USB Adapter einzusetzen. Diese
sind günstig und weit verbreitet. Als Software dient ein serieller Monitor.
#### Anwendung
Möchte man die Implementierung einer Berechnung überprüfen, so kann dies über einen
Printf-Befehl getan werden, der das Ergebnis ausgibt. Auch eignet sich diese Debugging-Methode gut als Indikator, ob bestimmte Stellen im Programmcode erreicht werden. Besonders hilfreich ist diese Methode zum Überprüfen von Variablenwerten in Schleifendurchläufen. Auf die gleiche Art und Weise können falsche Übergabe- oder Rückgabeparameter erkannt werden.
#### Grenzen
Der Rahmen in dem Printf-Debugging zum Erfolg führt, ist stark begrenzt. Es bietet keinerlei Hilfe bei Problemen, die mit Speicherallozierung oder Interrupts zu tun haben.
Printf nutzt die langsame serielle Schnittelle und verändert das Zeitverhalten des Programmes stark. Das kann dazu führen, dass sich Fehler in einem auf Echtzeitfähigkeit
ausgelegten System anders verhalten, wenn eine Printf-Anweisung in den Code eingefügt
wurde. Es ist dadurch nicht möglich zeitkritische Anwendungen zu debuggen. Als Beispiel
dafür gilt die Kommunikation über Bussysteme. Dazu kommt der Aufwand und die Dauer des wiederkehrenden Build-Prozesses, da nach dem Verschieben der Printf-Anweisung
an eine andere Stelle im Programm, das Programm erneut kompiliert werden muss. Vor
allem bei großen Projekten bedeutet dies lange Wartezeiten und ist nicht praktikabel.
## 5.2. Pre-Kolloquium
Für das Kolloquium sollte klar sein, wie die Toolchain funktioniert und welche Werkzeuge in den Programmbau involviert sind. Als Visualisierung sollen Sie das Diagramm zur
Toolchain ausfüllen. Machen Sie sich mit den einzelnen Schritten vertraut. Die Regeln,
nach denen das Programm gebaut wird, finden sich in dem Makefile. Sie sollten erklären
können welche Teile im Makefile welche Funktion erfüllen. In der folgenden Aufgabe
werden Sie unterschiedliche Fehler im Code aber auch im gegebenen Makefile finden
müssen. Die Fehler im Makefile sollten Sie durch genaue Analyse des Makefiles bereits
zum Teil finden können. Die Datei toolchain.odt enthält das Diagramm aus dem Skript,
welches den exemplarischen Ablauf einer Toolchain zeigt. Füllen Sie die Lücken der Dateien und Tools und erklären Sie was in welchem Schritt passiert. Machen Sie sich zudem
mit den Linux Befehlen find und grep vertraut.
## 5.3. Aufgabenstellung
### 5.3.1. Toolchain
Die erste Aufgabe formt den Einstieg in die Programmierung des Praktikumsboards. Es
wird sich mit der Ordnerstruktur und der Entwicklungsumgebung vertraut gemacht und
die Struktur des Gits verstanden. Es soll das erste Programm gebaut und geflasht sowie der
Umgang mit Makefiles geübt werden. Außerdem soll die Funktionsweise der Toolchain
verinnerlicht werden.
Erstellen Sie zunächst einen Fork des Repository von
```
https://git.ida.ing.tu-bs.de/IDA_Lehre/lab_sdes_student
```
Dazu clonen Sie zunächst Ihren Fork mit dem angezeigten Link aus dem Webinterface in
Ihren lokalen Ordner.
Zusätzlich sollten Sie einen neuen Branch in folgender Form anlegen:
```
git checkout -b praktikum
```
Es kommt vor, dass der Betreuer im Praktikumsverlauf Änderungen am Master vorneh-
men muss, dies führt häufig zu Konflikten. Durch den Branch werden diese vermieden.
Sie arbeiten in ihrem Fork auf dem angelegten Branch.
In dem bereitgestellten git Repository finden sich alle für die Aufgabe benötigten Dateien. In dem Ordner src/APP befinden sich der Quellcode der Aufgaben des Praktikums.
Der Ordner src/APP/Aufgabe1/ps7/core0/ enthält den Quellcode der Aufgabe 1 für
den die Architektur ps7 und den Kern 0. In ihm finden sich weitere Unterordner. In
Aufgabe1/ps7/core0/src findet man alle .c-Dateien außer der main.c.
Nach dem Klonen des Gits ist die Entwicklungsumgebung einzurichten:
- Terminal in Verzeichnis der Wahl (außer innerhalb des Repository) öffnen
- Eine Integrated Development Environment (IDE) bzw. Texteditor Ihrer Wahl starten
(z.B. VSCode)
- Falls gewünscht, kann das Makefile-Projekt eingerichtet werden, um den Buildprozess aus der IDE heraus zu starten
Ihre Aufgabe ist es, das Programm zu kompilieren und in die Laufzeitumgebung zu
laden. Dabei auftretende Fehler sind zu beheben und der **Arbeitsvorgang zu dokumentieren**. Zunächst ist es sinnvoll sich den Programmcode der Datei main.c anzusehen und zu
verstehen. Um das Programm zu kompilieren gehen sie in den Repositoryordner, öffnen
ein Terminal und führen den Befehl make aus. Es ist nötig dem Programm make mitzu-
teilen, für welche Aufgabe und Architektur das Programm kompiliert werden soll. Die
Syntax des Aufrufs ist folgende:
```
make ARCH=ps7 APP=Aufgabe1 CORE=0
```
Falls Fehler auftreten, analysieren Sie diese und versuchen Sie ihre Gründe herauszufinden. Es lohnt sich die Ausgabe genauer anzusehen und mit dem Wissen über die
Toolchain und Makefile zu verknüpfen. So kann der Ursprung des Fehlers schnell eingegrenzt werden. Nutzen sie die Ausgabe des Kompiliervorgangs und lösen Sie die auftretenden Fehler, um den Build-Prozess zu ermöglichen.
Tipp: Alle von der Toolchain benötigten Abhängigkeiten finden sich in dem Ordner
Aufgabe1/ps7/core0/build. Suchen Sie auch in dem Makefile nach Fehlern und achten
Sie auf Groß- und Kleinschreibung!
Der Ordner Aufgabe1/ps7/core0/cfg enthält die Header-Dateien und der Ordner
linker das Linkerscript.
Wichtig ist außerdem der Aufgabe1/ps7/core0/build Ordner. In den Dateien config.mk,
includes.mk und sources.mk festgelegt, welche Pfade beim Build-Prozess überhaupt
berücksichtigt und welche Compilerflags gesetzt werden. Neue Dateien in bisher nicht
inkludierten Pfaden müssen in den entsprechenden Dateien eingetragen werden. In dem
Ordner out finden sich geordnet nach den Aufgaben, Architektur und Core die entsprechenden Zielpfade für die aus dem Build-Prozess entstehenden Objekt-Dateien.
Immer wenn eine neue Terminal-Session gestartet wird müssen zunächst einige Umgebungsvariablen gesetzt werden, damit die benötigten Tools gefunden werden. Dazu
dienen hier die “setup-lm” Befehle, die die PATH-Variable um die gewünschten Tool-
Verzeichnisse erweitern und bei Bedarf Lizenzserver zu setzen.
```
setuplm lauterbach r_2020_09
setuplm gcc gccarmnoneeabi72018q2
```
Der Ordner Debug/ps7 im Oberverzeichnis enthält das Skript "start_amp_session.sh",
welches die Debug-Umgebung lädt. Zur Anwendung des Skripts wird ein Terminal in dem
Ordner ps7 geöffnet und der Befehl
```
./ start_amp_session.sh lauterbach[Lauterbachnummer]
```
ausgeführt. Die [Lauterbachnummer] muss angepasst werden und entspricht der Gruppennummer.
**Achtung:** Es kommt vor, dass die vorherige Lauterbach-Session auf dem Debugger nicht
ordnungsgemäß beendet wurde oder der Lauterbach von einem anderen Nutzer belegt
war. Dann wirft der Befehl zunächst den Fehler: Selected device already in use by... Dann bitte einmal prüfen ob wirklich der richtige Lauterbach angesprochen ist und nicht der einer
anderen Gruppe. Ein erneutes Ausführen dauert länger, sollte dann aber erfolgreich sein.
Nach dem erfolgreichen Kompilieren muss das Programm auf das Board geflasht werden. Dafür starten wir die Lauterbach Software ”Trace 32” wie oben beschrieben. Für jede
Aufgabe gibt es im Ordner Debug/ps7/ einen entsprechenden Unterordner, welcher ein
Lauterbach-Skript enthält. Diese kann über die Befehlszeile in Trace32 ausgeführt werden:
```
do Aufgabe1/zc706_onchip_trace.cmm
```
Generell arbeitet Trace32 vollständig skript-basiert und jedes GUI Kommando kann auch
in einem Skript eingesetzt werden. Dies können Sie sich für spätere Aufgaben merken um
wiederkehrende Befehle zu automatisieren.
Das zu Aufgabe 1 gehörige Skript sorgt dafür, dass das Board durch die Software geflasht
wird, sich aber keine weiteren Fenster in der Software öffnen. Ziel dieser Aufgabe ist das
Debuggen über printf. Nachdem Sie das den Befehl zum Flashen im richtigen Verzeichnis
ausgeführt haben, sollte sich ein Fenster ähnlich der Abbildung 5.3 öffnen. Das Programm
wird nun durch einen Klick auf “Go” gestartet. Sie können dieses Fenster nun ignorieren
und in einer freien Konsole die Verbindung zur Ausgabe des Boards aufbauen:
```
telnet idaser2net 800X
```
Der Port hängt von Ihrer Gruppennummer und dem verwendeten Lauterbach ab. Erset-
zen Sie das X durch die Nummer ihres Lauterbach-Debuggers. Das Board sollte Ihnen
jetzt jede Sekunde ein “ILL BE BACK” ausgeben.
Bei Überprüfung der Arbeitsergebnisse sollten Sie auftretende Fehler dem Linker oder
dem Compiler zuordnen können.
![Screenshot](img/jpg/lauterbach-aufgabe1-1.png) Abbildung 5.3.: Lauterbachumgebung zum Flashen des Boards in Aufgabe 1

View File

@@ -0,0 +1,89 @@
# 6. Aufgabe 2 - Erstellung von Tasks
## 6.1. Wissen
### 6.1.1. Tasks
Ein Task ist für gewöhnlich eine Endlosschleife einer Funktion, die von dem Scheduler
der CPU zugeteilt wird. Es können mehrere Tasks gleichzeitig laufen, die dann entsprechend ihrer Priorität CPU-Zeit zugeteilt bekommen. In µC/OS-II wird ein Task mit der
Methode
```
INT8U OSTaskCreate (void (task)(void pd), void pdata, OS_STK ptos, INT8U prio)
```
erstellt. Der Task benötigt eine Priorität, die gleichzeitig auch seine Identifikation darstellt. Kleinere Zahlen bedeuten eine höhere Priorität. Mit dem Funktionsaufruf von
```
UCOSStartup (CPU_FNCT_PTR initial_func)
```
wird unter anderem der Scheduler und somit das Multitasking gestartet. Ein Task be-
nötigt seinen eigenen Stack mit der entsprechenden Stack-Größe. Dieser sollte statisch
alloziert werden.
Empfohlene Literatur:
- µC/OS-II Micrium Documentation[[mic](references.md), µC/OS-II Quick Reference]
- Micrium: Inter Process Communication via Message Queues [[wik](references.md), Message Queues QuickStart Guide, S. 9]
- Technical Reference Manual : Zynq-7000 ZC-706 im Repository Ordner
## 6.2. Aufgabenstellung
### 6.2.1. Teil1
Das in der ersten Aufgabe programmierte Programm soll nun als Task ausgeführt werden. Zusätzlich sollen in einem weiteren Task Fibonaccizahlen berechnet und ausgegeben
werden.
- Das Programm weist zahlreiche Fehler auf, die es zu debuggen gilt
![Screenshot](img/png/lauterbach-aufgabe2-1.png) <br> Abbildung 6.1.: Menü zum Inspizieren von Betriebssystemfunktionen
![Screenshot](img/png/lauterbach-aufgabe2-2.png) <br> Abbildung 6.2.: Fenster zum Inspizieren der Stackausnutzung
- Eine sinnvolle Hilfe stellt die UCOS-2 Dokumentation zu dem Thema Task Management dar (siehe oben: ”Empfohlene Literatur”)
- Tipp: Die UART Schnittstelle ist fehlerhaft konfiguriert, die gewünschte Frequenz sollte auf 50000000 eingestellt sein, die erste der beiden UART Instanzen ausgewählt
sein. (siehe TRM Zynq 7000)
Dokumentieren Sie die gefundenen Fehler.
Die Lauterbach Umgebung bietet mehrere Möglichkeiten das Multitasking in UCOS
zu überwachen (siehe Abbildung 6.1):
- Überwachung der Tasks und ihrer Prioritäten (Beispiel siehe Abbildung 6.2)
- Überwachung von Stackgrößen, auch die der Tasks (Beispiel siehe Abbildung 6.3.: Diese Überwachung basiert auf der Überprüfung auf Nullen im Stack.)
![Screenshot](img/png/lauterbach-aufgabe2-3.png) <br> Abbildung 6.3.: Fenster zum Inspizieren von laufenden Tasks, ihren Prioritäten und IDs
## 6.3. Post-Kolloquium
- Welche Bedeutung hat UCOSStartup()?<br>
(Tipp: Suchen Sie in Eclipse nach der Funktion (Strg+H) und machen Sie sich mit
dem Inhalt vertraut. Ist der Aufruf dieser Funktion für die korrekte Funktion des
Programms notwendig?)
- Wie ist der Task Control Block aufgebaut? (Siehe Quellcode 6.1)
- Was ist eine Message Queue und warum wird sie genutzt?
- Welche Vorteile hat eine Message Queue?
- Über welche Parameter wird die UART Schnittstelle konfiguriert?
- In Aufgabe 1 haben Sie sich eingehend mit der Toolchain beschäftigt. Schauen sie
sich nun einmal die Dateien out/Aufgabe2_ps7_core0.lst und out/Aufgabe2_ps7_core0.map
an. Was steht in diesen Dateien und welche Informationen könnten Sie hier heraus
ziehen?
```
typedef struct os_tcb {
OS_STK *OSTCBStkPtr;
void *OSTCBExtPtr;
OS_STK *OSTCBStkBottom;
INT32U OSTCBStkSize;
INT16U OSTCBOpt;
INT16U OSTCBId;
struct os_tcb *OSTCBNext;
struct os_tcb *OSTCBPrev;
OS_FLAGS OSTCBFlagsRdy;
INT8U OSTCBStat;
INT8U OSTCBPrio;
} OS_TCB;
```
Quellcode 6.1: Ausschnitt aus dem Task Control Blocks in µC/OS-II

View File

@@ -0,0 +1,273 @@
# 7. Aufgabe 3 - Motorsteuerung
## 7.1. Wissen
### 7.1.1. JTAG-Debugging
**Funktionsdefinition**
Der Joint Test Action Group (JTAG)-Debugger ermöglicht es, Eingriffe in den Programmablauf vorzunehmen. Außerdem unterstützt er den Entwickler dabei, den Programmzustand zu inspizieren. Dazu lässt sich der Speicher auslesen und die daraus gewonnenen
Informationen werden ausgewertet und zu Analysezwecken aufbereitet. Plattformen, die
Multitasking unterstützen, bieten Übersichten zu laufenden Tasks. Damit wird das Überwachen der Nebenläufigkeit vereinfacht. Fortgeschrittene Debuggerprogramme bieten
die Möglichkeit die Interprozesskommunikation, zum Beispiel Semaphoren und Nachrichten, auszuwerten. Der Standard in dem Bereich des Debuggings für eingebettete Systeme ist der GNU-Debugger (GDB), welcher Teil der GNU Compiler Collection (GCC)
ist. Das JTAG-Interface hat den Zweck ein Verfahren zu ermöglichen, mit dem Schaltungen getestet werden können, während sie sich verlötet auf der Leiterplatte befinden
[[jta](references.md)]. JTAG-kompatible Systeme haben im Normalbetrieb abgetrennte Komponenten, die
erst dann aktiviert werden, wenn das JTAG-Interface genutzt werden soll. Technisch gesehen ist die Schnittstelle als Schieberegister verwirklicht. Das Zielsystem ist über das
JTAG-Interface mit der Debugginghardware verbunden. Die Kommunikation zwischen
der Entwicklungsplattform auf dem PC und der Debuggingplattform findet über USB
statt (Abbildung 7.1).
#### Quelltextansicht
Im Gegensatz zu Assembler-Debugging kann der Code via High Level Language (HLL)-
Debugging in der Quelltextansicht inspiziert werden (siehe Abbildung 7.2). Der Programmablaufzähler wird eingeblendet und es lässt sich nachvollziehen, an welcher Stelle im
Quelltext sich das Programm gerade befindet. Diese Möglichkeiten bieten sich, weil der
Compiler beim Erzeugen der Executable and Linking Format (elf)-Datei Debuginformationen hinzufügt. Diese werden von dem Debugwerkzeug interpretiert und die Assembler-Instruktionen werden den Zeilen im Quelltext zugeordnet.
![Screenshot](img/png/jtag-connection.png) <br> Abbildung 7.1.: Cross Debugging via Joint Test Action Group (JTAG)-Debugging
![Screenshot](img/jpg/lauterbach-aufgabe3-3.png) <br> Abbildung 7.2.: Quelltextansicht in der Lauterbach Umgebung
#### (Single-)Stepping
Das Programm kann in Einzelschritten ausgeführt werden. Dabei können entweder die
Schritte der Hochsprache oder der Assembler-Ebene einzeln ausgeführt werden. Außerdem ist es möglich, die aktuelle Methode zu Ende laufen zu lassen oder in die auszuführende Unterroutine hineinzuspringen, beziehungsweise erst bei deren Rückkehr zu
stoppen. Diese Möglichkeiten ergeben sich, wie auch die Quelltextansicht, aus den vom
Compiler der elf-Datei hinzugefügten Debuginformationen.
#### Starten und Stoppen des Programmablaufs
Das Programm kann angehalten werden. Dies kann hilfreich sein, wenn man an bestimmten Stellen im Programm Variablen auslesen möchte. Das manuelle Stoppen des Programmflusses ist allerdings sehr ungenau. Daher sollten für das gezielte Anhalten des
Programms Breakpoints genutzt werden.
#### Haltepunkt (Breakpoint)
Das Setzen eines Breakpoints beschreibt die Auswahl einer Stelle im Programmfluss, an
der die Ausführung des Programms gestoppt wird, bevor der markierte Befehl ausgeführt
wird. Aus [[Gra](references.md), S. 115], Vorgehen beim Debuggen mit Breakpoints:
- Aufstellen einer These über die mögliche Position des Defekts
- Setzen eines Haltepunkts vor der vermuteten Position
- Annäherung mit Hilfe von Breakpoints / Stepping, dabei: Überprüfung des Programmzustands
- These falsch / Korrigieren des Defekts
Es wird zwischen Software und Hardware Breakpoints unterschieden [arm]. Erstere werden temporär in den RAM des Zielsystems geschrieben und ersetzen bis zum Eintritt des
Breakpoints die ursprüngliche Instruktion. Diese wird durch eine Breakpoint-Instruktion
überschrieben und die CPU geht bei der Ausführung in einen Debugstatus. Hardware
Breakpoints werden durch das Überprüfen des Instruction Fetch von einer spezifischen
Speicheradresse aus umgesetzt (siehe Abbildung 7.5). Im Gegensatz zu Software Breakpoints können Hardware Breakpoints auch auf Befehle aus dem ROM angewendet werden. Sollte eine Memory Management Unit (MMU) Adressbereiche neu zuordnen, so kann
es zum Überschreiben von Software Breakpoints kommen.
Das Setzen eines Breakpoints erfolgt über einen Doppelklick neben die Programmzeile
(siehe Abbildung 7.3).
![Screenshot](img/jpg/lauterbach-aufgabe3-1.png) <br> Abbildung 7.3.: Setzen eines Breakpoints in Zeile 28.
![Screenshot](img/jpg/lauterbach-aufgabe3-2.png) <br> Abbildung 7.4.: Breakpoint-Übersichtsfenster in der Lauterbach Umgebung
#### Überwachungspunkt (Watchpoint)
Ein Watchpoint überwacht eine gewünschte Variable und hält die Ausführung des Programms an, wenn diese verändert werden. Die Möglichkeiten der Überwachung hängen
von dem genutzten Debugprogramm ab. Möglich ist zum Beispiel die Überprüfung auf
einen Wertebereich oder auf Lese/Schreibzugriffe auf eine Variable. Nicht überwachen lassen sich alle Datenströme, die an der CPU vorbei laufen. Sollten Speicherbereiche zum
Beispiel durch Direct Memory Access (DMA) verändert werden, so kann dies nicht mit
Watchpoints an der CPU detektiert werden. Die gesetzten Break- und Watchpoints erscheinen im Übersichtsfenster, wo auch ihr Typ näher spezifiziert ist.
![Screenshot](img/png/jtag-breakpoint.png) <br> Abbildung 7.5.: Hardware Breakpoint Realisierung
![Screenshot](img/jpg/lauterbach-aufgabe3-5.png) <br> Abbildung 7.6.: Watch-Fenster Breakpoint Realisierung
#### Speicherzugriff
Der Speicherzugriff ermöglicht das Auslesen des Speichers. Die Daten können in verschiedenen Formatierungen angezeigt werden. Es ist möglich den Inhalt direkt als ASCII
String, hexadezimal, dezimal und binär darzustellen.
#### Watch Fenster
Mit Hilfe der Debuginformationen aus der elf-Datei können die, aus dem Speicher gelesenen, Daten im Watch Fenster geordnet und entsprechend der zugehörigen Datenstrukturen dargestellt werden (siehe Abbildung 7.6). Es ist möglich, sich die Daten in Arrayform
oder in anderen Formatierungen anzeigen zu lassen. Konstanten werden von dem Debugger nicht aufgelöst.
#### Auswertung des Call Stacks
Die im Call Stack enthaltenen Daten lassen sich auswerten und eine Aufrufliste daraus
rekonstruieren (siehe Abbildung 7.7). Außerdem werden beim Verlassen einer Unterroutine die lokalen Variablen auf den Call Stack gelegt und lassen sich von vielen Debuggern
auswerten.
![Screenshot](img/jpg/lauterbach-aufgabe3-4.png) <br> Abbildung 7.7.: Watch-Fenster Breakpoint Realisierung
### 7.1.2. Beispiel: Schreiboperation auf Variablen überprüfen
Wenn es zu nicht nachvollziehbaren Änderungen von Variablen kommt ist, ist es sinnvoll
diese mit Watchpoints zu überwachen. Es ist möglich, dass die Variable durch einen fehlerhaften Schreibvorgang einer anderen Variable beeinflusst wird. Ein Beispiel, wie es zu
solch einer Situation kommen kann, ist in dem Quellcode 7.1 zu betrachten. Die Tabelle
7.1 zeigt, dass der in der globalen Variable a gespeicherte Wert durch die Schreiboperation
auf numbers[4] überschrieben wurde. Das Ergebnis der Addition in Zeile 25 des Quellcodes
7.1 ist somit falsch.
```
/
zur Veranschaulichung:
alle Variablen global und im gleichen Speichersegment
/
uint8_t numbers[4];
uint8_t a = 10;
uint8_t b = 15;
uint8_t result ;
void main (void) {
/
forSchleife mit Fehler in Abbruchbedingung
Schreiboperation auf numbers[4]
> fehlerhafte Daten in Speicherbereich der Variable a
/
for ( uint8_t i = 0; i <= 4; i ++) {
numbers[i] = i ;
}
/ falsches Ergebnis durch fehlerhafte Daten in Variable a /
result = a + b;
}
```
Quellcode 7.1: Beispielcode zum Überschreiben von Speicherbereichen und dadurch entstehende
Folgefehler
![Screenshot](img/png/array-overflow.png) <br> Tabelle 7.1.: Visualisierung des Speicherinhalts bei Ausführung des Programms 7.1
### 7.1.3. Grenzen des JTAG-Debuggings
JTAG-Debugging eignet sich nur dann, wenn das System zur Erfassung des Fehlers auch
pausiert werden kann. Sobald eine Analyse des Systems ausgeführt wird, wird das Zeitverhalten stark verändert, weil das System angehalten werden muss. Fehler, die auf Zeitverhalten beruhen, lassen sich damit nur schwer untersuchen. Dazu gehören auch Interrupts
oder Unterbrechungen durch höherpriore Tasks. Es ist außerdem nicht möglich, im Programmablauf zurück zu gehen und sich den Hergang des Fehlers genau anzusehen. Dafür
bedarf es der Aufzeichnung des Programmflusses.
### 7.1.4. Schrittmotoren
Schrittmotoren können schrittweise und somit sehr genau gesteuert werden. In unserem
Anwendungsfall ist die schrittweise Ansteuerung allerdings nicht wichtig. Es ist interessant, in welcher Frequenz die Schritte ausgelöst werden, denn damit wird die Geschwindigkeit des Motors geregelt. Um die Schrittmotoren einfacher ansteuern zu können, werden Schrittmotortreiber genutzt. Das Datenblatt zu dem Treiber A4988 findet sich dabei
in der Quelle [[All](references.md)]. Für jeden Schritt, den der Motor machen soll, muss ein Puls an den
Eingang des Schrittmotortreibers gesendet werden. Schauen Sie sich im Datenblatt zu
dem Zynq-7000 [[Xil18](references.md)] das Kapitel zu dem Thema Triple Timer Counter an. Hier finden sich
Informationen, wie man diese Pulse erstellen könnte, ohne dass man sich in der Software
um das Zählen direkt kümmern müsste. Die Drehrichtung wird von einem Signal über
GPIOs gesetzt.
## 7.2. Pre-Kolloquium
Versuche Sie herauszufinden, wie man die Motoren mit Hilfe der Timer mit verschiedenen Geschwindigkeiten ansteuert. Halten Sie Ihre Ergebnisse zunächst schriftlich fest.
Anregungen:
- Welche Vorteile bieten Timer gegenüber dem Zählen in Software?
- Ist es möglich Signale auf GPIOs zu geben, wie kann davon Nutzen gemacht werden?
Können diese Signale auch von Timern erzeugt werden?
- Schauen Sie sich die verschiedenen Zählmodi (Interval Mode, Overflow Mode) an: Wann
startet der Timer wieder bei 0?
- Finden Sie die Bedeutung von Match Value und Interval Length heraus
- Wie lang muss der Puls sein? Schauen Sie sich das Datenblatt des Motortreibers an.
- Wie können Sie in der Lauterbach Skriptsprache Breakpoints generieren, laden und
speichern?
- Wie hoch ist die Clockfrequenz des Timers? Was wäre ein passender Prescaler um
c.a. 1µs pro Timer-Tick zu generieren?
- Was macht die Funktion XTtcPs_CalcIntervalFromFreq? Ist es sinnvoll diese Funktion hier anzuwenden?
## 7.3. Aufgabe
Schreiben Sie die Software für das Ansteuern der Motortreiber. Es soll auf einen PID Wert
zwischen -1000 und 1000 reagiert werden und die Motorleistung, sowie Drehrichtung entsprechend geregelt werden. Die Motoren drehen bis zu einer Pulsfrequenz von c.a. 10kHz
flüssig. Als Mindestfrequenz sollten c.a. 1kHz gesetzt werden.
- Configs:
- Ein Timer Tick entspricht c.a Mikrosekunde
- Nutzen Sie die Timer TTC0_0 und TTC0_1
- Die GPIO-Pinnummern für die Richtungseinstellung sind 54 und 55
- Simulieren Sie einen PID-Regler indem:
- Sie einen globale Variable pidValue in main.c anlegen
- Sie In der Methode InitDoneCallback eine Schleife erstellen, die die globale Variable in ihrem Wertebereich hoch zählt.
- Tipp: Denken Sie daran, dass Sie die Variable nicht unendlich schnell hoch zählen.
- Initialisieren Sie einen Timer in der ttc_timer.c:
- Erstellen Sie dafür eine init-Methode
- Timer Instanz erstellen (global)
```
XTtcPs (...)
```
- Erstellen Sie eine Konfigurationsinstanz für den Timer
```
XTtcPs_Config (...)
```
- Die Konfigurationsinstanz des Timers füllen dazu die folgende Methode nutzen
```
XTtcPs_LookupConfig(XPAR_PS7_TTC_0_DEVICE_ID);
```
- Den Timer initialisieren
```
XTtcPs_CfgInitialize (...)
```
- Modus des Timers setzen (Tipp: Welcher Timermodus soll gewählt werden? Wann soll ein HIGH/LOW am Ausgang erzeugt werden? Welche Option wird benötigt, um den Match Value zu nutzen?)
```
XTtcPs_SetOptions (...) ;
```
- Prescaler setzen
```
XTtcPs_SetPrescaler (...)
```
- Match Value erstellen und setzen
```
XTtcPs_SetMatchValue(...)
```
- Intervalllänge erstellen und setzen
```
XTtcPs_SetInterval (...)
```
- Schreiben Sie Methoden zum Starten und Stoppen der soeben erstellten Timer (Hinweise finden sich in der Datei xttcps.h)
- GPIO Initialisierung für das Festlegen der Drehrichtung des Motors
- Erstellen Sie eine Konfigurationsinstanz für GPIOs
```
XGpioPs_Config (...)
```
- Implementieren Sie die Funktion
```
timer_gpio_Init ()
```
- Füllen Sie die Konfigurationsinstanz (ähnlich wie bei der Erstellung des Timers)
- Initialisieren Sie die GPIO-Instanz (Tipp: XPAR_PS7_GPIO_0_DEVICE_ID)
- Definieren Sie die Richtung der Pins mit
```
XGpioPs_SetDirectionPin(gpioInstanz, Pinnummer, 1);
XGpioPs_SetOutputEnablePin(gpioInstanz, Pinnummer, 1);
```
- Erstellen Sie eine Funktion motor_Set_Moving_Direction, die die Drehrichtung der Schrittmotoren in Abhängigkeit des PID Values bestimmt. Nutzen Sie dabei die Methode
```
XGpioPs_WritePin()
```
- Erstellen Sie eine Funktion timer_Set_Interval_Length, die die Interval-Länge sowie
das Match-Value eines Timers setzt.
- Schreiben sie eine Task-Funktion timer_Task, die die Drehrichtung der Schrittmotoren sowie deren Geschwindigkeit in Abhängigkeit des pidValue setzt. Diese soll
die Timer stoppen, die GPIOs korrekt setzen, die Intervall-Längen aus dem pidValue berechnen und dann die Timer wieder starten. Erstellen Sie in ihrer main.c
einen Task, der zunächst die timer_Init() Funktion aufruft und dann alle 2ms die
timer_Task() Funktion.
- Da ihre Methode in Abhängigkeit zum PID Wert steht, und dieser in der main.c simuliert wird, bietet es sich an die globale Variable pidValue mit extern in ihre Datei einzubinden.
- Überlegen Sie sich welche Konsequenzen nebenläufiger Schreib- oder Lesezugriff auf eine Variable haben kann und wie Sie diese Effekte verhindern können.
- Tipp: Nutzen Sie Critical Sections beim Zugriff auf die globale pidValue Variable.
## 7.4. Post-Kolloquium
- Zeichnen Sie mit der Funktion ”iprobe.timing” die Pulse auf die Sie mit dem Timer generieren.
- Wie können in Trace32 Breakpoints erstellt werden? Welchen Einfluss haben Breakpoints auf das Zeitverhalten eines Systems?
- Was ist im Bereich der Programmierung eine sogenannte "Atomare Operation" und wie hängen diese mit Critical Sections zusammen?

View File

@@ -0,0 +1,260 @@
# Aufgabe 4 - IMU
Nachdem in der vorherigen Aufgabe das Thema JTAG bereits angerissen wurde, soll es
auch in Aufgabe 4 behandelt werden. Es wird eine weitere Komponente des Anwendungsfalls debuggt. Zusätzlich zum JTAG-Debugging wollen wir Ihnen einige Funktionen des
Lauterbach-Debuggers näher bringen.
Damit der Roboter jederzeit seinen Winkel zur Horizontalen kennt, benötigt er eine
echtzeitfähige Lagemessung. Der Treiber für die Ansteuerung der inertialen Messeinheit
über I2 C wird mit Hilfe des JTAG-Debuggings und der Nutzung des Lauterbach Logik
Analysators in einen fehlerfreien Zustand gebracht. Die Werte des Sensors werden den
anderen Programmmodulen über globale Variablen zur Verfügung gestellt.
Die Aufgabe ist es, ein Programm zu schreiben, dass die aktuellen Werte des Gyroskops
und des Beschleunigungssensors ausliest und in zwei globalen Variablen speichert. Maßgeblich für die Funktion des Moduls ist die korrekte Initialisierung des I2 C Busses, die
richtige Konfiguration der inertialen Messeinheit und das korrekte Umwandeln der Daten. Bei dieser Aufgabe wird neben dem JTAG-Debugging unterstützend ein Logik Analysator genutzt. Die Studierenden sollen mit Hilfe des Lauterbach Logic Analysers die
übertragenen Daten auf dem I2 C Bus einsehen und die gesendeten Informationen extrahieren. Zur Kontrolle kann die von Lauterbach zur Verfügung gestellte automatische
Protokollanalyse des Logik Analysators genutzt werden.
## 8.1. Wissen
### 8.1.1. Inertiale Messeinheit und Sensorfusion
Die MPU9250 von InvenSense bietet eine 9-Achsen Messeinheit mit Accelerometer, Gyroskop und Magnetometer. Die MPU9250 wird auch als Bewegungs- und Lagesensor in
Smartphones genutzt. Die aus der Sensoreinheit ausgelesenen Werte müssen für die weitere Verwendung bearbeitet werden. Dabei ist es sinnvoll sich auf die Kippachse nach
vorne und hinten zu konzentrieren. Um die Lage richtig einschätzen zu können, benötigt
man zwei Messwerte von dem Sensor. Die Beschleunigung sowie die Winkelgeschwindigkeit. Um einen möglichst fehlerfreien Wert zu bekommen, müssen die Messwerte gefiltert
werden. Die Probleme, die sich dabei auftun, sind folgende: Der Beschleunigungssensor
ist sehr anfällig für Rauschen, also für kurzfristige Fehler. Dafür kann er die Winkelmessung nicht relativ, sondern absolut ausführen. Die Winkelgeschwindigkeit wird sehr
genau gemessen und kaum von äußeren Einflüssen gestört. Der aus dem Gyroskop resultierende Winkel unterliegt durch die Integration der Messwerte einem gewissen Drift. Es
ist nötig diese Sensordaten zu filtern und zu kombinieren, um die Schwächen der beiden
einzelnen Methoden damit auszugleichen.
Neben der in Aufgabe 3 erlernten Arbeitsweise mit JTAG-Debugging, ist Grundlagen-
wissen über die Funktionsweise von I2 C nötig. Dazu können Sie sich [[i2c](references.md)] ansehen. Es
sollte bekannt sein, wie ein I2 C Gerät angesprochen wird, welche Pins dafür nötig sind
und wie Register im Zielgerät gelesen oder geschrieben werden. Für die Analyse mit dem
Lauterbach Logic Analyser finden sich in [[Lau14](references.md), S. 48-49] Informationen zur automatischen Protokollanalyse von I2 C. Weitere Informationen sind im Datenblatt [[Inva](references.md)] und der
Register Map [[Invb](references.md)] der inertialen Messeinheit zu finden. Sie sollten über Wissen zu inertialen Messeinheiten verfügen und sich darüber im Klaren sein, warum eine Sensorfusion nötig ist und wie man sie realisiert. Informationen dazu können Sie aus der folgenden
Quelle beziehen [[HiB](references.md)].
### 8.1.2. Visualisierung der Daten
Der Lauterbach-Debugger und die Trace32-Software bietet eine Vielzahl verschiedener
Funktionen. Beim Arbeiten mit Sensoren, die mit hoher Frequenz mehrere Messwerte
produzieren, kann es sehr hilfreich sein die Daten zu visualisieren.
Eine IMU ist ein solcher Sensor. Bei mehreren Achsen und wenigen zehn Hertz Messfrequenz ist die Ausgabe mit printf nahezu unbrauchbar, da der Entwickler kaum mit dem
Lesen der Werte hinterher kommt. Ganz abgesehen von dem großen Einfluss von printf
auf das Zeitverhalten.
Es bietet sich daher an die Daten graphisch in einem Koordinatensystem darzustellen.
Der Lauterbach ermöglicht es auf Variablen zuzugreifen und diese zu visualisieren. Dazu
speichert man in die Messwerte in seinem Programm über eine gewisse Zeit in einem
Array ab. Anschließend hält man sein Programm durch den Debugger an.
Zum einen kann man nach dem Array in dem “Symbol.browse”-Fenster suchen, die
Variable zur Watch-List hinzufügen und sich die Werte textlich anzeigen lassen.
Zum anderen kann man das Array plotten, indem man in die Lauterbach-Befehlszeile
folgendes eingibt:
```
var.DRAW <NAME_OF_ARRAY>
```
Der Befehl ermöglicht es auch mehrere Arrays in ein Fenster zu plotten, um Zusammenhänge zwischen Daten besser zu verstehen:
```
var.DRAW <NAME_OF_ARRAY_1> <NAME_OF_ARRAY_2>
```
## 8.2. Pre-Kolloquium
- Wie liest man aus einem I2 C Gerät?
- Welche Register aus der IMU sind interessant für uns? Schauen Sie sich das Registerdatenblatt der IMU an und suchen Sie Register, die für unsere Anwendung
interessant sind
- Wie werden rauschende Signale geglättet?
- Was wollen wir aus der IMU lesen?
- Müssen wir die IMU erst aufwecken?
- Wie konfiguriert man Gyroskop und Accelerometer so, dass beim Gyroskop Dps =
500 ist, ACC Skala = 4g ?
- Mit welcher Formel berechnet man den Winkel aus ACC und Gyroskop-Daten? Wo
ist der Unterschied zwischen beiden? Wo spielt die Sampling-Zeit mit hinein?
- Werten Sie den gegebenen I2 C Datenstrom (siehe 8.1) aus und geben Sie Adresse und
Inhalt der Nachricht wieder, handelt es sich um Lesen oder um Schreiben? (Tipp:
Abgebildet ist nur der Datenstrom ausgehend vom Master)
- Warum benötigt man zur sicheren Winkelberechnung eine Sensorfusion aus den
Daten von ACC und Gyroskop? Warum reicht ein Sensor hier nicht aus? (Machen
Sie sich mit dem Komplementärfilter vertraut)
![Screenshot](img/jpg/i2c.jpg) <br> Abbildung 8.1. I2 C Datenstrom zum Auswerten
## 8.3. Aufgabe
In dieser Aufgabe sollen Sie folgenden Ablauf schrittweise implementieren:
- I2 C initialisieren
- IMU initialisieren
- IMU-Task starten
- Periodisch IMU-Daten abfragen
- Winkel aus Accelerometer- und Gyro-Daten berechnen
- Berechneten Winkel an PID Filter propagieren
### IMU-Task erstellen
Die imu.c soll zwei Methoden nach außen bereitstellten. Diese lauten:
```
int mpu9250_Imu_Init(void pdata); // Initialize
int mpu9250_CalculateAngle(void pdata); // Task function
```
Erstellen Sie in der main.c einen neuen 5-Millisekunden-Task und starten Sie ihn so,
wie in Aufgabe 2 gelernt. Dieser soll das IMU Modul zunächst initialisieren und dann
in der while(1) Schleife die Berechnung des Winkels periodisch aufrufen. Füllen Sie in
der weiteren Aufgabe diese beiden Methoden mit allem was zur Initialisierung und zur
Berechnung benötigt wird aus.
### I2 C initialisieren
Erstellen Sie in der imu.c eine Methode zur Initialisierung der I2 C Instanz:
```
static uint8_t mpu9250_Iic_Init();
```
- Zunächst die globale Variable für die I2 C Instanz erstellen
```
static XIicPs Iic ;
```
- Zur I2 C Initialisierung: Config Struct erstellen
```
XIicPs_Config Config;
```
- Config-Struct mit XIicPs Config füllen, (Tipp: Informationen zu Übergabeparametern finden sich in der imu.h)
```
Config = XIicPs_LookupConfig(...) ;
```
- I2 C initialisieren und Status abfragen, übergeben Sie der Methode die benötigten
Parameter (Tipp: Basisaddresse des I2 C findet sich auch in dem Config-Struct)
```
XIicPs_CfgInitialize (...) ;
```
- Zur Sicherheit einen Self-Test machen und das Ergebnis abfragen (in die Methode
gucken, um den Rückgabewert interpretieren zu können)
```
XIicPs_SelfTest (...) ;
```
- I2 C Clockrate setzen, suchen Sie in der imu.h nach Übergabeparametern. Achtung:
Die Clockrate soll 100kHz betragen.
```
XIicPs_SetSClk (...) ;
```
Das Nutzen der I2 C Schnittstelle erfolgt über zwei Methoden zum Senden und Empfangen von Daten aus den Registern des MPU9250 Sensors. Erstellen und implementieren
Sie die beiden benötigten Methoden in der Datei imu.c.
```
static uint8_t mpu9250_Write_Reg(uint8_t iic_address, uint8_t data)
static int8_t mpu9250_Read_Data(uint8_t iic_address, uint8_t length, u8 RecvBuffer [])
```
Die benötigte Schnittstelle des I2 C Treibers von Xilinx sind die Methoden
```
XIicPs_MasterSendPolled()
```
und
```
XIicPs_MasterRecvPolled()
```
Schauen Sie sich die Methoden an und identifizieren Sie die benötigten Übergabepara-
meter. Implementieren Sie auch hier eine Fehlerüberprüfung.
### IMU initialisieren
Nach der erfolgreichen Initialisierung der I2 C Schnittstelle ist es notwendig die inertiale
Messeinheit zu initialisieren. Nutzen Sie dafür die zuvor implementierten Send- und Recv
Methoden, um die Register der IMU wie gewünscht zu konfigurieren. Die benötigten
Register haben Sie im Präkolloquium ausgearbeitet. Zu überprüfen, ob die Initialisierung
erfolgreich war, ist nicht zwingend notwendig, hilft im Zweifel aber Fehler zu finden. Hier
kann der Status der IMU einmal abgefragt werden.
### IMU-Daten abfragen
Nach dem Initialisieren der IMU sollen Sie nun eine Methode zum Auslesen der Daten
von Accelerometer und Gyroskop erstellen. Lesen Sie die Daten der IMU an einem Stück
aus und vermeiden Sie schnell aufeinander folgende Lesevorgänge. Es lassen sich alle
Register mit den benötigten Rohwerten mit einem Lesevorgang auslesen.
Falls beim Auslesen Fehler auftreten, ist es sinnvoll den Status des Sensors auszulesen.
Vergleichen Sie dazu den Inhalt des Statusregisters mit dem angegebenen Sollwert. Gehen Sie systematisch alle Fehlerquellen durch, die die Kommunikation mit dem Sensor
unterbinden könnten. Einen Fehler in der Hardware können Sie ausschließen.<br>
Wenden Sie das Wissen aus 8.1.2 an, um die Daten von Accelerometer und Gyroskop mit
der Trace32-Software zu plotten. Schalten Sie den Lowpass Filter vom Accelerometer an
und aus und vergleichen Sie die Qualität der Messwerte.
Nutzen Sie den Lauterbach Logik Analysator (Untermenü Probe->Timing) um die I2 C
Übertragung zu inspizieren. Verifizieren Sie die Rohwerte durch den Übungsleiter.
***Tipp: Überlegen Sie genau welche Sensorachsen Sie für die Winkelberechnung im weiteren Verlauf der Aufgabe benötigen und welche nicht. Vergleichen Sie hierzu die Grafik
zur Ausrichtung der Achsen im Datenblatt der IMU mit der Lage auf dem Roboter im
Versuchsaufbau.***
### Winkel berechnen
Außerdem müssen die Accelerometerdaten skaliert werden. Für die Winkelberechnung
ist es nötig, dass das Accelerometer die Erdbeschleunigung auf jeder der drei Achsen
gleich misst (von Werk aus nicht der Fall). Finden Sie zunächst die Maximal- und Minimalwerte für jede Achse bezüglich der Erdbeschleunigung heraus. Dies können Sie
tun, indem Sie den Roboter langsam um jede Achse drehen dabei jede Messung mit den
_imu_accMaxData und _imu_accMinData Arrays vergleichen und diese ggf. aktualisieren.
Notieren Sie sich diese Messwerte und nutzen Sie sie im Folgenden als Konstanten für die
Grenzen des ACC Wertebereiches. Mappen Sie anschließend die Daten der einzelnen Achsen des Accelerometers auf einen konstanten Wertebereich, anhand dieser Messdaten. (z.B. InMin = INT16_MIN ; inMax = INT16_MAX)<br>
***Tipp: Schauen Sie sich hierfür typische Map-Funktionen an und mappen Sie linear!***
Die Gyroskopdaten müssen mit einem
passenden Offset kalibiert werden. Beachten Sie außerdem den entsprechenden sensitivity scale factor (Stichwort: GYRO_FS_SEL) beim Ermitteln der Daten des Gyroskop.<br>
***Tipp: Schauen Sie hierfür nicht nur in die Register Map, sondern auch in das entsprechende Datenblatt.***
Im Folgenden sollen die gemessenen Werte in eine Winkelangabe transformiert werden. Berechnen Sie zwei Winkel: Der erste soll auf Basis der Accelerometerdaten (Stichwort: atan2()) berechnet werden, der zweite auf Basis der Gyrodaten (Stichwort: Winkelgeschwindigkeit -> Winkel). Überprüfen Sie die beiden Winkel auf ihre Plausibilität. Zusätzlich benötigen Sie noch die Winkeländerung pro Zeitschritt.<br>
***Tipp: In unserem Aufbau zeigen die Winkel des Accelometers und des Gyroskops in die jeweils gegenüberliegende Richtung!***
Benutzen Sie die oben genannten Winkel und implementieren Sie einen Komplementärfilter, der die beiden Winkel zu einem fusioniert. Dieser sollte wie in etwa so aussehen:
```
currentAngle = 0.98(previousAngle + gyroAnglePerTimeStep) + 0.02accAngle;
```
In Aufgabe 7 wird Ihnen ein PID-Regler vorgegeben werden, damit die Motoren des
Roboters adäquat auf den aktuellen Winkel reagieren. Die Kommunikation mit dem PID-
Regler-Task läuft über eine globale Variable.
Tipps:
- Achten Sie auf das Einfügen der Datei in dem richtigen Ordner.
- Schauen Sie sich die IMU an und interpretieren Sie die Achsausrichtung, um herauszufinden welche Achsen für die Berechnung der Winkel aus Accelerometer- und
Gyroskopdaten nötig sind.
- Was macht die Funktion atan2()? Zu welcher Library gehört sie? Es gibt eine Besonderheit die zur Verwendung der Library erfüllt sein muss, welche ist es?
- Wie erstellt man aus dem Gyroskopwert dem aktuellen Winkel? Führen Sie für die
Sample Time eine Konstante ein, die später an die echte Ausführungsperiode an-
gepasst werden kann
## 8.4. Post-Kolloquium
Bereiten Sie sich auf das Kolloquium vor, indem Sie sich die Arbeitsergebnisse und gefundenen Fehler gut dokumentieren. Verstehen Sie, was Sie programmiert haben.
- Sie haben für die I2 C Kommunikation die Funktion XIicPs_MasterSendPolled() und XIicPs_MasterRecvPolled() verwendet. Wo ist der Unterschied zu den Funktionen XIicPs_MasterSend() und XIicPs_MasterRecv()?
- Nennen Sie neben I2 C noch weitere simple Busprotokolle, welche in Eingebetteten-Systemen verbreitet sind. Wie unterscheiden diese sich?
- Mit dieser Aufgabe haben Sie erstmals ein Echtzeitverhalten implementiert. Wo liegt dies, welche Zeitschranken müssen eingehalten werden und was könnte passieren wenn das diese nicht eingehalten werden sollten?
- Wodurch könnte dieses Zeitverhalten verletzt werden?

View File

@@ -0,0 +1,199 @@
# Aufgabe 5 - JTAG-Debugging
## 9.1. Wissen
In dieser Aufgabe soll das Wissen in Bezug auf betriebssystemeigene Funktionen erweitert
werden. Zur Erinnerung um im Zweifelsfall den richtigen Pfad zu finden: Der Zynq-7000
arbeitet mit Cortex A9 Cores. Wir nutzen die GNU Toolchain.
Alle Ergebnisse dieser Aufgabe werden im Post-Kolloquium besprochen.
### 9.1.1. Bedienung des Lauterbach-Debuggers
Die Trace32-Software enthält viele Komfort-Funktionen, die das Debugging aber auch die
Bedienung der Software erleichtern sollen.
***Window***
Die vielen Fenster können schnell unübersichtlich werden und überfordern Einsteiger
häufig. Daher können die Fenster automatisch angeordnet werden:
Window->Cascade oder Window->Tile
Anpassungen die während des Betriebs an den Fenstern vorgenommen wurden sind,
können in verschiedenen Konfigurationen gespeichert und geladen werden:
Window->Store Windows to... oder Window->Load Windows from...
Weitere Fenster können über die Lauterbach-Befehlszeile geöffnet werden, indem die
Caption des Fensters eingegeben wird. Viele der unter der Befehlszeile gelisteten Befehle
sind auch Fenster und sind je nach Anwendungszweck unterschiedlich nützlich.
So kann sich der Befehlsverlauf mit
```
History
```
angezeigt werden lassen und eine Übersicht aller Tasks findet sich mit
```
Task.task
```
Diese ist insbesondere dann hilfreich, wenn man sich mit Kontext-Wechsel beschäftigt.
Dies setzt aber voraus, dass die Tasks benannt wurden sind. Dazu benutzt man:
```
OSTaskNameSet(TASK_PRIORITY, TASK_NAME, &ERROR_CODE)
```
***Break- und Watchpoints***
Wie schon bei den Fenster-Einstellungen kann Trace32 auch Breakpoints permanent speichern, sodass eine Debug-Session unterbrochen und am nächsten Arbeitstag fortgesetzt
werden kann. Das Speichern erreicht man mit:
```
STORE <filename>.cmm BREAK
```
Zum Laden benutzt man:
```
do <filename>.cmm
```
Um Watchpoints auf eine Variable zu setzen gibt es den Befehl
```
Var.Break.Set <variable_name>; /<access> /VarCONDition <condition>
```
- <variable_name> ist mit der Variable zu ersetzen, die man überwachen möchte
- <access> ist der Zugriff: ReadWrite, Read, Write
- <condition> ist eine Bedingung in C-Style; zum Beispiel: (a == 3). Kann aber auch weggelassen werden, falls bei jedem Zugriff getriggered werden soll.
Damit lassen sich auch schwer erreichbare Stellen debuggen. So kann man in den
Kontext-Wechsel eines bestimmten Tasks springen, indem man auf OSPrioHighRdy triggered und als Bedingung die Priorität des aktuellen Tasks angibt:
```
Var.Break.Set OSPrioHighRdy; /Write /VarCONDition (OSPrioCur==<priority>)
```
Auch interessant ist es am Ende einer Funktion anzuhalten, zum Beispiel dann wenn
der Rücksprung nicht mehr funktioniert.
```
break. set Symbol.end(<function_name>)
```
***Ergebnisse speichern***
Die Software besitzt auch Funktionen, die die Dokumentation der Ergebnisse erleichtert.
So können Screenshots des übergeordneten Parent-Window gemacht werden:
Window->Screenshot to file...
Genauso wie Screenshots eines einzelner, untergeordneter Fensters gemacht werden
können. Dazu klickt man in die obere, linke Ecke des Fensters auf sein Icon und wählt
Window Screenshot to file...
In diesem Menü kann der Fenster-Inhalt auch mit To Clipboard in Textform gespeichert
werden.
## 9.2. Aufgabe
- Ziel soll es zunächst sein, die Methode
```
UCOSStartup()
```
zu untersuchen.
- Warum wird diese Methode noch vor dem Code aus der Main aufgerufen?
- Welchen Sinn hat diese Methode?
Was macht die Methode:
```
CPU_Init()
```
Welche Aufgabe hat die Funktion:
```
Mem_Init()
```
- Muss diese Funktion aufgerufen werden?
Betrachten Sie die Funktion:
```
OSInit ()
```
- Was ist in diesem Kontext ein Hook?
- Warum werden die Methoden:
```
OS_InitMisc()
OS_InitRdyList()
OS_InitTCBList()
```
benötigt? Was würde passieren, würden sie nicht ausgeführt werden.
- inwiefern wird in dieser Methode Multitasking vorbereitet?
- Was ist ein idle-Task und wofür wird er benötigt? Wo wird er erstellt? Welche Priorität sollte er haben?
- Was ist ein Startup-Task? Was passiert dort?
- Erklären Sie den Sinn der Methode
```
OSStart ()
```
- Was passiert in der Funktion
```
OSTaskCreateExt()
```
- Was macht die Funktion
```
OSTaskStackInit()
```
- Was macht folgende Methode
```
OS_TCBInit()
```
- Wie funktioniert die Methode
```
OStimeDly()
```
- Untersuchen Sie die Methoden, warum wird eine kritische Sektion betreten?
```
OS_Sched() und OS_SchedNew()
```
- Nachdem der Task erstellt wurde muss das Betriebssystem auch mit dem neuen Task arbeiten.
```
OSStartHighRdy()
```
Wofür sind in dieser Funktion die letzten 3 Anweisungen zuständig?
Warum ist ein Ausrufezeichen in manchen Befehlen? Was macht das Zirkumflex
hinter dem Befehl? <br>
Tipp: Unser System arbeitet auf einer ARM-Cortex-A
- Was passiert bei einem Kontextswitch? Schreiben Sie die Arbeitsschritte auf. Finden
Sie heraus in welcher Methode der Kontextswitch ausgeführt wird. Welche wesentlichen Schritte werden abgearbeitet?

Binary file not shown.

After

Width:  |  Height:  |  Size: 669 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 534 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

View File

@@ -0,0 +1,4 @@
# 1. Software Debugging in Eingebetteten Echtzeitsystemen
![Screenshot](img/jpg/IMG_0397.JPG)
Laurenz Borchers, Kai-Björn Gemlau, Sebastian Abel, Tim Smektala

View File

@@ -0,0 +1,35 @@
[[lau](2_hardwareUndAufbau.md#21-hardware)] Feature Overview Lauterbach Debugger. Verfügbar online unter https://www.lauterbach.com/frames.html?home.html; abgerufen am 10.10.2019.
[[zc7](2_hardwareUndAufbau.md#21-hardware)] Feature Overview Xilinx ZC706.
Verfügbar online unter https://www.xilinx.com/products/boards-and-kits/ek-z7-zc706-g.html; abgerufen
am 10.10.2019.
[[rs14](5_aufgabe1.md#511-toolchain)] Universität Mannheim Robert Schieler. Building and using a cross development tool chain. 2014. Verfügbar online unter ftp://gcc.gnu.org/pub/gcc/summit/2003/BuildingandUsingaCrossDevelopmentToolChain.pdf; abgerufen am 22.4.2019.
[[mak](5_aufgabe1.md#512-makefile)] C-HowTo: Makefile. Verfügbar online unter http://www.c-howto.de/tutorial/makefiles/; abgerufen am 22.4.2019.
[[mic](6_aufgabe2.md#611-tasks)] Micrium Documentation. Verfügbar online unter https://micrium.atlassian.net/wiki/spaces/osiidoc/overview; abgerufen am 05.05.2022.
[[wik](6_aufgabe2.md#611-tasks)] Erstellung von Message Queues.
Verfügbar online unter https://wiki.eecs.yorku.ca/course_archive/2016-17/W/4352/_media/an1005_inter-process_communication_.pdf; abgerufen am 21.5.2019.
[[jta](7_aufgabe3.md#711-jtag-debugging)] Corelis JTAG Interface and Boundary-Scan Educational Resources. Verfügbar online unter https://www.corelis.com/education/tutorials/jtag-tutorial/what-is-jtag/; abgerufen am 22.4.2019.
[[Gra](7_aufgabe3.md#haltepunkt-breakpoint)] Philipp Graf. Verfügbar online unter https://pdfs.semanticscholar.org/0aa7/b91e99d9749adbdca55286c31c0c990fe849.pdf; abgerufen am
22.4.2019
[[All](7_aufgabe3.md#714-schrittmotoren)] Allegro. A4988, DMOS Microstepping Driver. Verfügbar online unter https://www.pololu.com/file/0J450/a4988_DMOS_microstepping_driver_with_translator.pdf; abgerufen am 22.4.2019.
[[Xil18](7_aufgabe3.md#714-schrittmotoren)] Xilings. Zynq-7000 SoC, Technical Reference Manual, 2018.
[[i2c](8_aufgabe4.md#811-inertiale-messeinheit-und-sensorfusion)] I2C Communication. Verfügbar online unter https://www.totalphase.com/support/articles/200349156-I2C-Background; abgerufen am 22.4.2019.
[[Lau14](8_aufgabe4.md#811-inertiale-messeinheit-und-sensorfusion)] Lauterbach. Lauterbach iprobe users guide. 2014. Verfügbar online
unter http://www2.lauterbach.com/pdf/iprobe_user.pdf; abgerufen am
22.4.2019.
[[Inva](8_aufgabe4.md#811-inertiale-messeinheit-und-sensorfusion)] Invensense. MPU9250 Datenblatt. Verfügbar online unter http://www.invensense.com/wp-content/uploads/2015/02/PS-MPU-9250A-01-v1.1.pdf; abgerufen am 22.4.2019.
[[Invb](8_aufgabe4.md#811-inertiale-messeinheit-und-sensorfusion)] Invensense. MPU9250 Register Map. Verfügbar online unter http://www.invensense.com/wp-content/uploads/2017/11/RM-MPU-9250A-00-v1.6.pdf; abgerufen am 22.4.2019.
[[tra](10_aufgabe6.md#1021-tracing-points)] General Function Reference. Verfügbar online unter https://www2.lauterbach.com/pdf/general_func.pdf; abgerufen am 25.05.2020.

View File

@@ -0,0 +1,20 @@
site_name: Sdes_Skript
nav:
- Home: index.md
- 1. Einleitung: 1_einleitung.md
- 2. Hardware und Aufbau: 2_hardwareUndAufbau.md
- 3. Grundlagenwissen: 3_grundlagenwissen.md
- 4. Coding Guidelines: 4_codingGuidelines.md
- 5. Aufgabe 1 - Makefile: 5_aufgabe1.md
- 6. Aufgabe 2 - Erstellung von Tasks: 6_aufgabe2.md
- 7. Aufgabe 3 - Motorsteuerung: 7_aufgabe3.md
- 8. Aufgabe 4 - IMU: 8_aufgabe4.md
- 9. Aufgabe 5 - JTAG-Debugging: 9_aufgabe5.md
- 10. Aufgabe 6 - Task Scheduling: 10_aufgabe6.md
- 11. Aufgabe 7 - Integration: 11_aufgabe7.md
- references: references.md
theme:
name: readthedocs