Kommunikation

Obwohl ein autonomes System gefordert ist, wird eine Funkkommunikation benötigt, da mindestens Positionsdaten bzw. Befehle für Aktoren zwischen Zeppelin und Bodenstation (Navigationssystem) übertragen werden müssen. Außerdem ergibt sich eine enorme Erleichterung hinsichtlich Debugging und on-the-fly-changes von Flugparametern.

Inhaltsverzeichnis[Anzeigen]

1. Einleitung

Zuerst hatten wir ein robustes paketbasiertes Protokoll geplant. Der schnelleren Entwicklung halber verzichteten wir jedoch auf dessen Implementierung und benutzen ein einfacheres Nachrichtensystem. Zum Zweck der Dokumentation sollen beide Systeme beschrieben werden.

Die Kommunikation wird über XBee Pros mit verschiedenen Adaptern realisiert. Die XBees sind so konfiguriert, dass sie nur miteinander reden. Der Ausgang ist jeweils die serielle Schnittstelle, was die Ansteuerung sowohl bei PC als auch Arduino stark vereinfacht (Das war auch der Hauptentscheidungsgrund für XBees statt nordic-Modulen).

2. Einfaches Protokoll

Die Kommunikation läuft über die serielle Schnittstelle bei 19.2 kBd, 8N1. Die XBees sorgen dabei stressfrei dafür, dass keine tatsächliche Kabelverbindung benötigt wird.

2.1. Arduino → PC

Der Arduino sendet wie gewohnt Debug-Ausgaben und Statusmeldungen über den seriellen Port (so wie "Kompassoffset gespeichert.") mit Serial.print().

Diese Ausgaben können am PC mit einem "cat /dev/ttyUSB0" für den menschlichen Beobachter sichtbar gemacht werden. Sie sind aber aufgrund der Autonomität nicht sicherheitsnotwendig!

2.2. PC → Arduino

Während das Kameranavi ständig Positionsdaten mit seiner Boost::Asio Library sendet, kann man manuelle Kommandos mit einem "echo [Kommando] > /dev/ttyUSB0" senden.

In der Arduino-Software wird in jeder Schleife überprüft, ob der Serial-Buffer nicht leer ist (Serial.available()). Danach warten wir einige Millisekunden, um sicherzustellen, dass alle Zeichen empfangen wurden. Als nächstes wird der empfangene String mit der sscanf-Funktion 'zerlegt' und in einigen switch-cases ausgewertet.

2.3. Verfügbare Kommandos

Im Moment sind folgende Kommandotypen verfügbar (keine Leerzeichen!).

Liste der Parameter und Kommandos

p,[x],[y],[z/d] - Postionsdaten mit x und y Komponente und z Komponente oder Drehung d
n - Kompassdrehungsoffset einspeichern
w - Wegpunkt an aktueller Position setzen
d - Abwurfpunkt an aktueller Position setzen
r - Arduino hardware reset (per transistor nach /Reset)
g - Flugmodus ein/ausschalten
o - Liste der Wegpunkte ausgeben
l - Liste aktueller Reglerparamter ausgeben
s,[num],[val] - Reglerparameter #num auf wert val setzen

Ansicht am PC-Bildschirm

3. Paketbasiertes Protokoll

Das folgende (nicht implementierte) paketbasierte Protokoll bietet eine gewisse Robustheit, Flexibilität und Erweiterbarkeit bei geringem Implementationsaufwand.

3.1. Ablauf

Zuerst wird im Sender wird das Paket gebaut. Dazu muss ein uint8_t Array einer gewissen Größe alloziert werden oder unter C++ besser gleich ein std::vector<uint8_t> verwendet werden. In diesem Array bzw Vector wird ein FrameFlag als erstes Element gesetzt, in die Folgeelemente werden die entsprechenden Bytes platziert.
Vor dem setzen des vorletzen Bytes, der Checksumme, muss diese berechnet werden (siehe unten).
Als letztes Element wird wieder ein FrameFlag gesetzt.
Nun muss das Array gestuffed werden (siehe unten). Die FrameFlags müssen dabei unberücksichtigt bleiben, dürfen also nicht escaped werden.

Das Paket wird gesendet. Der Empfänger registriert die Ankunft eines neuen Pakets mit dem ersten FrameFlag und wartet auf die Ankunft des abschließenden FrameFlags. Die empfangenen Zeichen müssen unstuffed werden. Danach kann die im Paket enthaltene Information dekodiert und interpretiert werden.

3.2. Paketaufbau

Jedes übertragene Paket besitzt folgenden Aufbau:

FrameFlag | Counter | Command | Length | Data0 | Data1 | ... | DataN | Checksum | FrameFlag

Jeder Abschnitt ist ein Byte lang. Die Bedeutungen sind folgende:

FrameFlag ist ein reserviertes Byte, das sowohl Anfang als auch Ende eines Pakets markiert. Durch Stuffing kommt dieses Byte sonst nicht vor. Das Byte kann zB 0x00 (NUL) oder 0x0A (LF) sein.

Counter ist ein Byte, das vom Sender mit jedem gesendeten Paket inkrementiert wird. Dadurch kann erkannt werden, wenn ein Paket verloren gegangen ist.

Command bestimmt die Bedeutung der Datenbytes, siehe dazu Commands.

Length gibt die Anzahl der folgenden Datenbytes an.

Data0..N enthalten die eigentliche Paketinformation, die abhängig vom Command Byte interpretiert werden muss.

Checksum enthält die Checksumme des Pakets, wie unter Checksumme beschrieben. Nach korrekter Übertragung sollte ein XOR über alle Paketbytes immer 0x00 ergeben.

3.3. Stuffing

Byte-Stuffing wird benutzt, um den Wert des FrameFlags zu reservieren. Wenn Bytes mit dem Wert des FrameFlags im zu übertragenden Paket auftauchen, wird der Wert mit einem ControlByte escaped und verändert. Durch die Veränderung ist sichergestellt, dass kein Byte mit dem Wert des FrameFlag im Paket enthalten ist, außer dem Frame-Anfang und Ende selbst. Die Dekodierung wird so vereinfacht.

Nach der Übertragung und Erkennung des Pakets anhand der Frame Flags muss des Empfänger das Paket vor der Dekodierung und Interpretation noch destuffen.

Der folgende Beispielcode gibt aus:
01 02 00 FE FF 00 
01 02 1B FF FE FF 1B FF 
01 02 00 FE FF 00

#include <stdio.h>
#include <inttypes.h>

int main() {
// String, der stuffed und unstuffed werden soll
uint8_t string[] = { 0x01, 0x02, 0x00, 0xFE, 0xFF, 0x00 };
uint8_t stringLength = 6;

// Festlegung des zu escapenden Zeichens und der Escape-Sequenz
uint8_t frameByte = 0x00;
uint8_t escapeByte = 0x1B;

// Stuffing
uint8_t stuffedString[stringLength * 2];
uint8_t stuffIndex = 0;
for (uint8_t i = 0; i < stringLength; i++) {
	if (string[i] == frameByte || string[i] == escapeByte) {
		stuffedString[stuffIndex++] = escapeByte;
		stuffedString[stuffIndex++] = ~string[i];
	} else {
		stuffedString[stuffIndex++] = string[i];
	}
}

// Unstuffing
uint8_t unstuffedString[stringLength];
uint8_t unstuffIndex = 0;
for (uint8_t i = 0; i < stuffIndex; i++) {
	if (stuffedString[i] == escapeByte) {
		unstuffedString[unstuffIndex++] = ~stuffedString[++i];
	} else {
		unstuffedString[unstuffIndex++] = stuffedString[i];
	}
}

// Zur veranschaulichung ausgeben
for (uint8_t i = 0; i < stringLength; i++) {
	printf("%02X ", string[i]);
}
printf("\n");
for (uint8_t i = 0; i < stuffIndex; i++) {
	printf("%02X ", stuffedString[i]);
}
printf("\n");
for (uint8_t i = 0; i < unstuffIndex; i++) {
	printf("%02X ", unstuffedString[i]);
}
printf("\n");

return 0;
}

3.4. Commands

Mögliche Werte für das Command Byte wären zB:

0x01 - Debug String
Die Datenbytes repräsentieren ein char array mit 0 an letzter Stelle (= C-String), das am Empfänger ausgegeben werden sollte.

0x02 - Fehler
Datenbyte gibt eine Fehlernummer an.

0x03 - Position
Die Datenbytes sind 4 little-endian floats (je 4 Byte), Das erste float ist die X-Position, es folgt Y, Z und Genauigkeit.

0x04 - Setze Wert
Erstes Datenbyte gibt Wert-ID an, weitere den neuen Wert.
Wert-IDs: 

0x05 - Lese Wert
Das Datenbyte gibt die Wert-ID an

0x06 - Wert rückgabe
Erstes Byte gibt Wert-ID an, die folgenden 4 den Messwert als little-endian float

...

Wert-IDs:
0x00 Akkuspannung in Volt (readonly)
0x01 Winkel Motordrehservo (-100..100)
0x02 Motor Links (-100..100)
0x03 Motor Rechts (-100..100)
0x04 Ultraschall (readonly)
0x05 IMU...
...

3.5. Checksumme

Es werden alle Paketbytes bis auf FrameFlag an Anfang und Ende und Checksum mit einem 0x00 verXORt. Das Ergebnis wird im Checksum-Byte gespeichert.

uint8_t checksum = 0;
for (uint8_t i = 0; i < stringLength; i++)
	checksum ^= string[i];
string[stringLength] = checksum;

Wenn am Empfänger nach dem Destuffing alle Bytes (bis auf die FrameFlags, aber mit Checksum-Byte) wieder geXORt werden, muss 0 herauskommen, ansonsten liegt ein Übertragungsfehler vor.