# 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.

Abbildung 7.1.: Cross Debugging via Joint Test Action Group (JTAG)-Debugging

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).

Abbildung 7.3.: Setzen eines Breakpoints in Zeile 28.

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.

Abbildung 7.5.: Hardware Breakpoint Realisierung

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.

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) {
/∗
∗∗ for−Schleife 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

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?