Head tracking auf Arduino Basis

Meier111

User
Ich bastel mir so etwas auch gerade mit nem Pro Mini, ner MPU 6050 und nem Gy271 Kompass.
Man rechnet anhand der Gyro Daten den Kreisel gerade und hat somit schon seine kompensierten Werte.
Das ganze in ein PPM Signal gepackt und als L/S in die Funke rein.
Ich kann ne Schritt für Schritt Anleitung schreiben wenn gewünscht?
Mit Quellcode?
Würde mich interessieren.
Und ich würde es eventuell nachbauen.
 

Cynob

User
Ist bei mir ja auch noch im Aufbau.
Ich druck gerade Teile für nen Servo Gimbal für nen doch schon betagten DJI F450 mit "getunter" Naza-M Light, Die Brille ist so ne Betafpv V03 und der Sender wird/ist ne Graupner Hott MX20. Die Videoübertragung kommt von so ner 26€ Cam und nem stinknormalen 5,8Ghz Analog Transmitter. Also wirklich Känguruh Style (Große Sprünge mit leerem Beutel). Ziel des Spiels ist das ich das Teil irgendwo in die Luft stellen kann und mich umschauen...
Um das zu bewerkstelligen braucht man nen Gyroskop welches einem die Lage im Raum darstellt ( in meinem Fall ne ranzige uralte MPU6050) und einen Kompass (wo ich den billigsten GY271 gekauft hab dens gibt).
Beide kann man über I2C ansprechen womit man nur insgesamt vier Leitungen ( +/-/SDA/SCL) für alle benötigt. Somit kann man das Geraffel recht kompakt bauen.
Nun liest man über die Wire lib die Daten für den Gyro und den Kompass aus und errechnet daraus die Werte für den Azimut und den Roll Winkel.
Die füttere ich gerade in einen Kalman Filter (auch ne lib) welcher mir die Werte schön glättet.
Dann teste ich da noch ne PID Regelung wobei ich noch nicht ganz durchgestiegen bin bei den möglichen Einstellungen.
Das "nullen" oder die Sicht auf Center setzen wird auf Tastendruck oder Befehl ausgelöst indem einfach die nächsten 100 "Lesungen" der ermittelten Daten zusammengefasst und als neue Mitte angenommen werden.
Die geglätteten Werte werden dann über nen PPM Encoder (welcher den Timer1 des Arduinos benutzt) zu nem 8 Kanal PPM Signal moduliert welches über Pin 10 des Arduinos ausgegeben wird - und in der MX-20 über nen aktiven Schülerswitch auch auf dem Servomonitor angezeigt und somit übertragen werden.
Das ist so jetzt Stand der Dinge...
 
Kann man (Amateur vielleicht nicht) ein Headtracking über eine 360° Kamera realisieren, sowas wie Insta360, und nur einen Ausschnitt übertragen? Dann bräuchte man keine Servos für Pan/Tilt.

Patrick
 

Cynob

User
Man hatt ja die Winkel von der Mitte - müsste man nur noch herausfinden wie man aus der 360° Cam Bildausschnitte jeweils von - bis in den Achsenwinkel herausbekommt.
 

Cynob

User
So ich bin fast fertig mit der ersten Version des Howtos.
Ich hab jetzt in Geany mit Codebeispielen schon 360 Zeilen gefüllt (Bin mal gespannt was in nem Texteditor mit genormter Zeilenbreite rauskommt).
Zuerst kommt der technische Aufbau mit Bildern. Danach erklär ich Stück für Stück wie man den Code zusammenzimmert.
Ich muss noch ein wenig über das Zentrieren des Headtrackers zur Gimbalausrichtung schreiben. Wenns gut läuft dürft ich heute abend oder morgen damit fertig werden.
 
Sehr schön beschrieben, wenn ich Zeit finde werde ich mal in der Bastelkiste schauen ob ich alles da habe (glaube schon). Bin gespannt.
Tolle Arbeit die Du da gemacht hast.

Gruß
Manfred
 
Hallo,
vielen Dank für die ganzen Antworten,
jetzt ist mir das ganze schon klarer geworden. Wird wohl noch ein bisschen dauern bis ich das Projekt umsetzen kann,
aber im Sommer find ich hoffentlich Zeit dafür :).

mfG Christian
 
@Cynob
Hallo "ohne Namen" :)

ich hab mal die Komponenten zusammengelötet und deinen Quellcode zusammengebaut. Soweit erstmal alles Prima, vielen Dank dafür.
Ich hab mir mal die PPM Werte ausgeben lassen weil ich noch keinen Sender dran habe:

Serial.print("Roll=");
Serial.print(ppm_pos_azimuth);
Serial.print(" Nick=");
Serial.println(ppm_pos_nick);

Bei Nick scheint alles ok zu sein, wenn man aber bei Roll über 90 Grad dreht und danach wieder auf den Tisch legt hat Roll nicht mehr 1500 sondern bei mir z.B. 933.
Wenn ich mit "s" wieder abnulle geht es erstmal wieder. Da ich das gerade erst Zusammenkopiert habe bin ich noch nicht dazu gekommen nach dem "Fehler" zu suchen.

Die Frage ist eher: kannst Du das bei Dir nachvollziehen?

Gruß
Manfred


Nachtrag: ich glaube es liegt an meinem Aufbau, das ist zu wackelig alles. Werde die Tage mal ein Gewicht dran kleben damit es "gerader" liegt wenn ich das los lasse.
 
Zuletzt bearbeitet:
20240212_225654.jpg
 

Cynob

User
@Cynob
Hallo "ohne Namen" :)

ich hab mal die Komponenten zusammengelötet und deinen Quellcode zusammengebaut. Soweit erstmal alles Prima, vielen Dank dafür.
Ich hab mir mal die PPM Werte ausgeben lassen weil ich noch keinen Sender dran habe:

Serial.print("Roll=");
Serial.print(ppm_pos_azimuth);
Serial.print(" Nick=");
Serial.println(ppm_pos_nick);

Bei Nick scheint alles ok zu sein, wenn man aber bei Roll über 90 Grad dreht und danach wieder auf den Tisch legt hat Roll nicht mehr 1500 sondern bei mir z.B. 933.
Wenn ich mit "s" wieder abnulle geht es erstmal wieder. Da ich das gerade erst Zusammenkopiert habe bin ich noch nicht dazu gekommen nach dem "Fehler" zu suchen.

Die Frage ist eher: kannst Du das bei Dir nachvollziehen?

Gruß
Manfred


Nachtrag: ich glaube es liegt an meinem Aufbau, das ist zu wackelig alles. Werde die Tage mal ein Gewicht dran kleben damit es "gerader" liegt wenn ich das los lasse.
Nenn mich Simon :) (muss mal bei Gelegenheit mein Profil ausbauen)
Du hast den Code doch hoffentlich nicht eigenhändig zusammenkopiert oder doch?
Auf der Githubseite findest du links oben ne Schaltfläche "Code" welche dich auf die Haupseite führt: https://github.com/Cynobs-repo/poor_mans_fpv_headtracker
Dort hab ich die .ino Datei(en) auch hinterlegt welche du dir einfach runterladen kannst.

Zu dem Problem.... ich hab da wirklich einen Logikfehler gefunden. Ob der jetzt mit deiner "Wieder den Mittelpunkt erreichen" Symptomatik zusammenhängt kann ich grad nicht beurteilen.
In meinem Code ist beginnt der Spass bei Zeile 207 wo man die Winkel auf das PPM Signal mappt.
Dort steht ursprünglich:

C++:
    diff_Center_azimuth = filtered_azimuth - ViewCenter_azimuth;
    diff_Center_nick = filtered_nick - ViewCenter_nick;
    ppm_pos_azimuth = map(diff_Center_azimuth, -90, 90, ppm_lowerEnd_azimuth, ppm_upperEnd_azimuth);
    ppm_pos_nick = map(diff_Center_nick, -90, 90, ppm_lowerEnd_nick, ppm_upperEnd_nick);
    if(ppm_pos_azimuth > ppm_upperEnd_azimuth){ppm_pos_azimuth = ppm_upperEnd_azimuth;}
    if(ppm_pos_azimuth < ppm_lowerEnd_azimuth){ppm_pos_azimuth = ppm_lowerEnd_azimuth;}
    if(ppm_pos_nick > ppm_upperEnd_nick){ppm_pos_nick = ppm_upperEnd_nick;}
    if(ppm_pos_nick < ppm_lowerEnd_nick){ppm_pos_nick = ppm_lowerEnd_nick;}
    ppmEncoder.setChannel(6, ppm_pos_azimuth);
    ppmEncoder.setChannel(7, ppm_pos_nick);

Dort muss die Abfrage für die Endanschläge vor das mapping gesetzt werden.
Das sieht dann so aus:
C++:
    diff_Center_azimuth = filtered_azimuth - ViewCenter_azimuth;
    diff_Center_nick = filtered_nick - ViewCenter_nick;
    if(diff_Center_azimuth > 90){diff_Center_azimuth = 90;}
    if(diff_Center_azimuth < -90){diff_Center_azimuth = -90;}
    if(diff_Center_nick > 90){diff_Center_nick = 90;}
    if(diff_Center_nick < -90){diff_Center_nick = -90;}
    ppm_pos_azimuth = map(diff_Center_azimuth, -90, 90, ppm_lowerEnd_azimuth, ppm_upperEnd_azimuth);
    ppm_pos_nick = map(diff_Center_nick, -90, 90, ppm_lowerEnd_nick, ppm_upperEnd_nick);
    //if(ppm_pos_azimuth > ppm_upperEnd_azimuth){ppm_pos_azimuth = ppm_upperEnd_azimuth;}
    //if(ppm_pos_azimuth < ppm_lowerEnd_azimuth){ppm_pos_azimuth = ppm_lowerEnd_azimuth;}
    //if(ppm_pos_nick > ppm_upperEnd_nick){ppm_pos_nick = ppm_upperEnd_nick;}
    //if(ppm_pos_nick < ppm_lowerEnd_nick){ppm_pos_nick = ppm_lowerEnd_nick;}
    ppmEncoder.setChannel(6, ppm_pos_azimuth);
    ppmEncoder.setChannel(7, ppm_pos_nick);

Ich hab den Code in meinem Repo und in der Anleitung schon aktualisiert. ausserdem habe ich eine zweite Version mit hochgeladen welche einem zum testen jede Sekunde die gelesenen Werte ausgibt. Bitte im Code je nach Bedarf aus und einkommentieren.
Nebenbei wird noch angezeigt wie oft pro Sekunde die Loop durchläuft. Das wird für später interessant da die augenblickliche Konfiguration mit millis() arbeitet und das meines Wissens auch den Timer1 des Arduinos benutzt - welcher allerdings von unserem PPM Signalgenerator benötigt wird. Ich weiß jetzt mangels Oszilloskop nicht ob die mit millis() zeitgesteuerte serielle Ausgabe Einfluss auf unsere PPM Signalqualität hat. Später im Code will ich die aber einfach weghaben und mit den gezählten Durchläufen rechnen.
 
Hallo Simon,

tja, ich hab wirklich aus deiner Anleitung alles manuell zusammen kopiert :cool:.
Ich abe mir gerade den Spaß gemacht und mit einer alten Spektrum DX8 im Servomonitor das Servosignal (mit bisherigem Code)
anzusehen. Das funktioniert schon mal garnicht. Auf dem Oszilloskop sah das für mich erstmal OK aus. Das kann nun aber sein das man das Signal für Spektrum noch invertieren muss.

Ich werde mir aber mal deine neue Version ansehen.

Gruß
Manfred

Nachtrag: ich war wieder zu blöd, hätte nochmal in den Quellcode schauen sollen. Ist ja nicht Kanal 7 und 8 sondern anscheinend 6 und 7. Kommt drauf an auf das Nullbasiert ist oder nicht. Ich teste weiter.
 
Habe gerade Testprogramm gemacht und mit einer Spektrum DX9 probiert.
Bei 50% PPM hab ich auf den Kanalen schon Vollauschlag von 127%

#include "PPMEncoder.h"
void setup() {
// put your setup code here, to run once:
ppmEncoder.begin(10);
}
void loop() {
// put your main code here, to run repeatedly:
ppmEncoder.setChannelPercent(0, 50);
ppmEncoder.setChannelPercent(1, 50);
ppmEncoder.setChannelPercent(2, 50);
ppmEncoder.setChannelPercent(3, 50);
ppmEncoder.setChannelPercent(4, 50);
ppmEncoder.setChannelPercent(5, 50);
ppmEncoder.setChannelPercent(6, 50);
ppmEncoder.setChannelPercent(7, 50);
}

Irgendwas stimmt da nicht mit der Library oder da fehlt eine Initialisierung oder ich bin gerade zu blöd. Arduino ist ein NANO

Ergänzung: setChannelPercent funktioniert mit meiner DX9 nicht, nur setChannel

void loop() {
// put your main code here, to run repeatedly:
ppmEncoder.setChannel(0, 1900);
ppmEncoder.setChannel(1, 1900);
ppmEncoder.setChannel(2, 1900);
ppmEncoder.setChannel(3, 1900);
ppmEncoder.setChannel(4, 1900);
ppmEncoder.setChannel(5, 1900);
ppmEncoder.setChannel(6, 1900);
ppmEncoder.setChannel(7, 1900);
delay(2000);
ppmEncoder.setChannel(0, 1100);
ppmEncoder.setChannel(1, 1100);
ppmEncoder.setChannel(2, 1100);
ppmEncoder.setChannel(3, 1100);
ppmEncoder.setChannel(4, 1100);
ppmEncoder.setChannel(5, 1100);
ppmEncoder.setChannel(6, 1100);
ppmEncoder.setChannel(7, 1100);
delay(2000);
}

1900ms entspricht 100% und 1100ms 0%
 
Zuletzt bearbeitet:

Cynob

User
snipp....

void loop() {
// put your main code here, to run repeatedly:
ppmEncoder.setChannel(0, 1900);
ppmEncoder.setChannel(1, 1900);
ppmEncoder.setChannel(2, 1900);
ppmEncoder.setChannel(3, 1900);
ppmEncoder.setChannel(4, 1900);
ppmEncoder.setChannel(5, 1900);
ppmEncoder.setChannel(6, 1900);
ppmEncoder.setChannel(7, 1900);
delay(2000);
ppmEncoder.setChannel(0, 1100);
ppmEncoder.setChannel(1, 1100);
ppmEncoder.setChannel(2, 1100);
ppmEncoder.setChannel(3, 1100);
ppmEncoder.setChannel(4, 1100);
ppmEncoder.setChannel(5, 1100);
ppmEncoder.setChannel(6, 1100);
ppmEncoder.setChannel(7, 1100);
delay(2000);
}

....snipp
Mich wundert eher das da überhaupt noch ein Signal herauskommt.
Du darfst in dem Code kein delay() verwenden da diese Funktion (und einige andere auch) auf den Timer1 zugreifen welchen wir aber für das PPM Signal benötigen. Mit den millis() mit welcher die seriellen Ausgaben in meinem zweiten Beispielcode getriggert werden ist eigentlich auch nicht sauber.
 
Mich wundert eher das da überhaupt noch ein Signal herauskommt.
Du darfst in dem Code kein delay() verwenden da diese Funktion (und einige andere auch) auf den Timer1 zugreifen welchen wir aber für das PPM Signal benötigen. Mit den millis() mit welcher die seriellen Ausgaben in meinem zweiten Beispielcode getriggert werden ist eigentlich auch nicht sauber.
Hallo,
da hast Du natürlich recht. Mir ging es dort aber nur darum herauszufinden bei welchen Werten ich die jeweiligen Kanal Endwerte sauber erreiche. Denn bei der DX9 funktionieren Prozentwerte überhaupt nicht. Da bin ich bei 50% schon weit übern Endausschlag hinaus. Bei meinen ersten Tests war kein delay drin.
Das ist erst rein gekommen als ich den Wechsel im Servomonitor sehen wollte.

Sorry für die Verwirrung.
 
Ansicht hell / dunkel umschalten
Oben Unten