PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : RP6 mit Raspberry Pi verbinden



a7x
12.04.2014, 15:00
Hallo!

Ich versuche gerade, eine Kommunikation zwischen dem RP6 und dem Raspberry Pi über das beim RP6 mitgelieferte USB-Interface (FTDI) herzustellen. Ziel des Ganzen ist es, den Roboter über einfache Kommandos vom PC aus zu steuern. Der PC soll die Kommandos per WLAN an den Pi schicken, dieser sendet sie über FTDI/UART weiter an den RP6. Erst einmal will ich jedoch nur testhalber vom RP6 Nachrichten zum Pi schicken. Auf dem RP6 verwende ich dazu folgenden Code:


#include "RP6RobotBaseLib.h"


int main(void)
{
initRobotBase();
powerON();
startStopwatch1();
while(1)
{
if (getStopwatch1() > 500)
{
setLEDs(0b111111);
}
if (getStopwatch1() > 1000)
{
setLEDs(0b000000);
setStopwatch1(0);
}
writeString_P("Hello, my name is RP6!\n");
}
return 0;
}



Auf dem Pi verwende ich die pylibftdi (http://pylibftdi.readthedocs.org/en/latest/) und dieses Skript:


from pylibftdi import Device

with Device(mode='t') as dev:
dev.baudrate = 38400
while True:
message = dev.readline()
print repr(message)



Das funktioniert grundsätzlich, nur werden manche Nachrichten nicht vollständig empfangen und bei manchen Durchläufen ist der Text völlig unleserlich (siehe Grafiken).
2797327974

Meine Frage: Wie kommt dieser Output zustande? Muss ich das Ganze noch irgendwie synchronisieren (Protokoll?)? Vielleicht hat das ja schon jemand hinbekommen und könnte mir ein paar Tipps geben, ich steh nämlich grad etwas auf der Leitung.

mfg

RolfD
14.04.2014, 18:46
Also bei älteren seriellen Verbindungen dieser Bauart benutzt man üblicherweise Hardwarehandshake oder wenn man das wie Du nicht hat, Softwarehandshake. Das Letztere nennt sich xon/xoff und ist googlebar... leider aber auch etwas aufwändiger und nur wirklich sinnvoll wenn man ein schlauen UART Baustein hat/nutzt, der das Protokol dafür selbst unterstützt.
Ich würde aber wohl auch eher versuchen, den PI per I2C an den RP6 an zu flanschen und erst mal mit langsamen Baudraten zu arbeiten, die zuverlässig syncronisiert werden können. Siehe dazu auch die Baudratenberechnung vom RP6 in Bezug auf die 8MHz Taktung.
Ein weiteres Problem steckt in den RP6 Libs bzw. in der Art wie die Software auf dem RP6 läuft.
Die originale RP6 Lib macht einige Funktionen über polling und hardwaits statt per reinem Interrupt und das kann bedeuten, das die CPU irgendwo in einer Wateschleife hängt wärend der UART voll läuft. Mit anderen Worten, die RP6 Lib ist nicht wirklich echtzeitfähig.
Für den normalen Gebrauch und ab und zu mal ein Zeichen fällt das kaum auf... aber wenn man einen IO Port wirklich mit Daten bombardiert, merkt man doch, das die Dinge nicht wirklich rund laufen.
Ich bastel für mich schon seid langem an einer Lösung mit freeRTOS für diese fehlende Echtzeitfähigkeit aber das ist bisher nichts was man vorführen oder veröffentlichen könnte.
Als Tip, der UART der Atmel CPU hat Fehlerflags, die man abfragen kann... bau dir in deine Lese und Schreibfunktionen mal eine Abfrage der Flags rein, dann kannst du auch sehen, das dort overruns passieren. Allerdings geb die Meldung dann nicht auf dem UART aus...
Gruß

SlyD
15.04.2014, 00:13
Hallo,

@a7x:
Probier erstmal nach jeder Zeile eine kurze Pause einzufügen (also die Ausgabe mit ner zweiten Stopwatch machen)
damit der Raspi(!) da überhaupt nachkommt mit dem Empfang. Fang mal mit 25ms an und teste dann andere Zeiten.
Als nächstes dann mal auf C umsteigen und nicht python scripte nutzen und statt es in nem Terminal (per Netzwerk oder im Fenster) ausgeben lassen mal in eine Datei oder einen großen Puffer schreiben (im RAM, nicht auf der SD Karte ;-) ).
EDIT: Du kannst statt einer kurzen Zeile natürlich auch mehr Text auf einmal ausgeben (wieviel musst Du ausprobieren, 5 Zeilen, 20 Zeilen... ), wichtig ist, dass da regelmäßig eine kurze Pause eingefügt wird.

Und natürlich kann man da ein kleines Protokoll selbst implementieren (egal was fürn UART) - muss nix wildes sein einfach nur den Empfang kurz quittieren und erst dann weitersenden (+Timeout +Repeat).




@Rolf:
a7x nutzt hier die andere Richtung: RP6 sendet, Raspi empfängt.
Das Senden ist in der RP6Lib natürlich blockierend (ist mit voller Absicht so und nicht interrupt basiert, sonst wäre es gerade für Anfänger völlig undurchsichtig wann welcher String gesendet wird - so wird die Ausgabe erst vollständig erledigt und dann gehts mit dem rest des Programms weiter), da geht auf der Seite nichts verloren sondern beim Raspi.

Der UART Empfang allerdings läuft in der RP6 Lib natürlich Interrupt basiert und nutzt einen größeren Ringpuffer.
Das läuft auch auch mit 500k Baud stabil (vielfach erfolgreich mit dem RP6 getestet - klar der Rest muss auch stimmen damit das gut klappt, die Lib kann aber nix dafür wenn der Anwender es falsch nutzt bzw. die CPU anderweitig zu stark auslastet). Egal - wird hier gar nicht genutzt, ist nur zusatzinfo.
Hier läufts ja sogar nur mit 38k Baud also viel langsamer als das was möglich ist...


MfG,
SlyD

RolfD
15.04.2014, 02:41
hm ok... der Einwand hat Hand und Fuß. Die Datenrichtung habe ich übersehen. Asche über mein Haupt. Aber beim senden von Daten vom RP6 auf den PC hatte ich auch schon solche Phänomene und ein PC mit BufferUART wie der 16550 steht bei 38,4 kbaud nicht im Verdacht Daten zu verlieren.

Das Argument mit Anfänger und blockierend... hm... Ok ich sehe ein das es für Anfänger zunächst "durchsichtiger" ist. Tatsächlich schränkt es aber auch massiv ein, vor allem wenn man viele Daten vom RP6 auf den PC sendet.



void writeChar(char ch)
{
while (!(UCSRA & (1<<UDRE)));
UDR = (uint8_t)ch;
}


Das Argument mit der Baudzahl ist übrigends interssant. Bedeutet es doch, daß je langsamer man die Baudzahl stellt, man sich um so mehr Probleme im Timing des Systems verschafft weil weniger Baud auch automatisch länger pollen von UDRE in UCSRA bedeutet. Wir reden bei 9600 baud gegen 38400 baud schon um den Faktor 4. Gibt man seitenweise Text wie im Beispiel aus, kommen da schon einige Sekündchen zusammen in denen die CPU nur UCSRA pollt und bestenfalls irqs bearbeitet.
Der bestechende Vorteil der Methode... man braucht sich kein Kopf über volle Buffer zu machen, die bei viel Text sogar mit irq-senden ein Warten auf Platz im Buffer erzwingen würde.
Spätestens da hatte ich damals beschlossen freeRTOS zu nutzen, da kann man die Warterei wenigstens anderen Tasks als Rechenzeit zuteilen.
Allerdings verkompliziert das widerum _etwas_ das Interrupthandling :)
Gruß

SlyD
15.04.2014, 11:41
Aber beim senden von Daten vom RP6 auf den PC hatte ich auch schon solche Phänomene und ein PC mit BufferUART wie der 16550 steht bei 38,4 kbaud nicht im Verdacht Daten zu verlieren.


Tatsächlich ein echter Serialport und am RP6 ein RS232 Levelshifter?
Oder doch nur per USB emuliert?
(die echten sind selten geworden)
Der FT232R hat z.B. "nur" 128 Byte Empfangspuffer und über USB gibts ja durchaus lange Latenzen bis in den 50ms Bereich...
Der 16550 hat übrigens nur 16 Bytes Puffer... ist aber natürlich direkter angebunden daher reicht das i.d.R.




Das Argument mit Anfänger und blockierend... hm... Ok ich sehe ein das es für Anfänger zunächst "durchsichtiger" ist. Tatsächlich schränkt es aber auch massiv ein, vor allem wenn man viele Daten vom RP6 auf den PC sendet.


Wer möchte kann mit ähnlicher Methode wie beim Empfangen auch beim Senden vorgehen.
Wer soweit ist dass er das braucht, kann das auch selbst implementieren denke ich.



Wir reden bei 9600 baud gegen 38400 baud schon um den Faktor 4.


Deswegen verwendet der RP6 38400 Baud und nicht 9600 ;-)
38400 wurde gewählt, weils noch langsam genug ist dass das mit den meisten anderen Gerätschaften die man daran anschließen könnte problemlos läuft
(wie man an einigen Threads zu Funkmodulen sieht, ist aber selbst das oft noch zu hoch),
aber schnell genug dass größere Textausgaben nicht allzu lange brauchen - das wurde bei der Entwicklung getestet und für gut genug befunden.
Man kann wenn man mag mit eigener Software auch auf andere höhere Baudraten wechseln - wie es der Bootloader ja auch tut.
Das WLAN Modul läuft z.B. auch direkt mit 500k Baud.


MfG,
SlyD

a7x
25.04.2014, 19:19
Erst einmal danke für die Antworten. Das mit den Timeouts + Protokoll werde ich mal versuchen.

Mittlerweile hab ich es geschafft, eine Steuerung für den RP6 über den PC zu bauen. Ein Protokoll ist hier denke ich nicht unbedingt notwendig, da nur hier und da ein paar Parameter versendet werden. Im Grunde sendet der PC die Parameter (Bewegungsart, Richtung, Distanz, Speed) an den Pi, der leitet die Parameter dann weiter zum RP6, der den Befehl dann verarbeitet. Hier der Quellcode, falls es jemanden interessiert:

Auf dem PC wird eine normale HTML-Seite verwendet, über Ajax werden dann einfach die gewählten Parameter per HTTP GET Request an den Webserver auf dem Pi geschickt. Der Pi und der PC müssen sich nur im gleichen Netzwerk befinden, dann muss man noch die richtige IP-Adresse eintragen. Die Jsonp-Sache dient nur zu Debug-Zwecken, braucht man, um Daten auch wieder vom Pi an den PC zurückzuschicken.



<html>
<head>
<script src="jquery-1.10.2.min.js"></script>
<title>RP6 Steuerung </title>
</head>
<body>
<h1> RP6 Steuerung </h1>

<script>
function sendCommand()
{
var move = document.Formular.movement[document.Formular.movement.selectedIndex].value;
var dir = document.Formular.direction[document.Formular.direction.selectedIndex].value;
var dist = document.Formular.distance.value;
var speed_ = document.Formular.speed.value
$.ajax({
type: 'GET',
url: 'http://10.0.0.4',
dataType: 'jsonp',
jsonp: 'jsonp',
jsonpCallback : 'parseResponse',
crossDomain: true,
data: {movement: move, direction: dir, distance: dist, speed: speed_},
error: function (data){
$('#antwort').html("Befehl konnte nicht gesendet werden: " + data);
},
success: function (data){
$('#antwort').html("Folgender Befehl wurde erfolgreich gesendet:<br>" + "Bewegung: " + data.movement + "<br>Richtung: " + data.direction + "<br>Distanz/Grad: " + data.distance + "<br>Geschwindigkeit: " + data.speed);
}
});
}
</script>
<form name="Formular">
Bewegung:<select name="movement">
<option>Fahren</option>
<option>Rotieren</option>
</select><br>
Richtung:<select name="direction">
<option>Vor</option>
<option>Zurueck</option>
<option>Links</option>
<option>Rechts</option>
</select><br>
Distanz/Grad:<input name="distance"><br>
Geschwindigkeit:<input name="speed" value="100"><br>
<a href="javascript:sendCommand()">Senden!</a>
</form>

<div id="antwort">

</div>

</body>
</html>


Auf dem Pi verwende ich das Framework Flask für den Webserver und die pylibftdi für die Kommunikation zum USB-Interface. Das Script verarbeitet HTTP-Anfragen und sendet die Parameter durch einen Strichpunkt getrennt an den RP6 weiter.



from flask import Flask
from flask import request
from pylibftdi import Device
import time

app = Flask(__name__)

@app.route('/')
def send_command():
if 'direction' in request.args and 'movement' in request.args and 'distance' in request.args and 'speed' in request.args:
with Device(mode='b') as dev:
dev.baudrate = 38400
dev.write(request.args.get('movement') + ';' + request.args.get('direction') + ';' + request.args.get('distance') + ';' + request.args.get('speed') + '\n')
time.sleep(1)
answer = dev.readline()
print repr(answer)
return request.args.get('jsonp') + '({"text": "Kommando wurde erfolgreich gesendet",' + '"direction":"' + request.args.get('direction') + '","movement":"' + request.args.get('movement') + '","distance":"' + request.args.get('distance') + '","speed":"' + request.args.get('speed') + '"})';
else:
return 'Willkommen zur RP6-Steuerung!'
if __name__ == '__main__':
app.run(host='0.0.0.0', port=80, debug=True)



Auf dem RP6 habe ich zum Einlesen im Grunde das Beispielprogramm genommen. Die Parameter werden in ein Struct geschrieben, welches dann einer Funktion übergeben wird, die den Befehl ausführt.



#include "RP6RobotBaseLib.h"
#include <stdint.h>

#define MAX_SIZE 70 // maximum UART buffer size

typedef struct command
{
char movement_ [20];
char direction_ [20];
char distance_string_ [20];
char speed_string_ [20];
int16_t distance_; // also degrees for rotation
int16_t speed_;
} Command;

void executeCommand (Command* command)
{
uint8_t direction = 0;
if (strcmp(command->direction_,"Vor") == 0)
{
direction = FWD;
}
else if (strcmp(command->direction_,"Zurueck") == 0)
{
direction = BWD;
}
else if (strcmp(command->direction_,"Links") == 0)
{
direction = LEFT;
}
else if (strcmp(command->direction_,"Rechts") == 0)
{
direction = RIGHT;
}

//fortunately the functions move and rotate have the same signature
void (*movement_function) (uint8_t, uint8_t, uint16_t, uint8_t) = 0;

if (strcmp(command->movement_,"Fahren") == 0 && (direction == FWD || direction == BWD) && command->speed_ <= 200)
{
command->distance_ = DIST_CM(command->distance_);
movement_function = move;
setLEDs(0b111000);
}
else if (strcmp(command->movement_,"Rotieren") == 0 && (direction == LEFT || direction == RIGHT))
{
movement_function = rotate;
setLEDs(0b000111);
}

// execute the function, either move or rotate
if (movement_function)
{
movement_function(command->speed_,direction,command->distance_,true);
}
}

void readInput(void)
{
char receiveBuffer [MAX_SIZE+1];
clearReceptionBuffer();
uint8_t buffer_pos = 0;
while (1)
{
if(getBufferLength())
{
receiveBuffer[buffer_pos] = readChar();
if (receiveBuffer[buffer_pos] == '\n')
{
break;
}
buffer_pos++;
if (buffer_pos >= MAX_SIZE)
{
writeString_P("Too many characters entered!\n");
return;
}
}
}
buffer_pos++;
receiveBuffer[buffer_pos] = '\0';

// parse the receive buffer and store the values to the Command struct, parameters are separated by a ';'
Command command;
char* string = command.movement_;
uint16_t i = 0;
uint16_t k = 0;
while (1)
{
if (receiveBuffer[i] == ';') // delimiter was found
{
string[k] = '\0';
string += 20; //set the pointer to the next char* in the Command struct
k = 0;
i++;
continue;
}
if (receiveBuffer[i] == '\n')
{
string[k] = '\0';
break;
}
string[k] = receiveBuffer[i];
k++;
i++;
}

// convert string to integer
command.distance_ = atoi(command.distance_string_);
command.speed_ = atoi(command.speed_string_);

// debug: send the values back to the raspberry pi
writeString(command.direction_);
writeString(command.movement_);
writeInteger(command.speed_,DEC);
writeInteger(command.distance_,DEC);

executeCommand(&command);
}

int main(void)
{
initRobotBase();
powerON();

while(1)
{
readInput();
task_RP6System();
}
return 0;
}