RC-Heli über MX-12 mit X-Box Controller steuern

Hallo,

ich möchte meinen RC-Modellhelikopter über meine Fernsteuerung (Graupner MX-12) mit meinem X-Box Controller steuern können.


|X-Box Controller| --USB--> |PC| --COM--> |Arduino Uno Rev.3| --PPM--> |MX-12| --Funk--> |RC-Heli|


Die Steuerung mit dem X-Box Controller funktioniert sogar teilweise schon aber leider sehr Zeitverzögert (ca. 0,5 sec). :~ Der X-Box Controller ist per USB an meinem PC angeschlossen. Ein Vb.net Programm liest die Achsenpositionen des Controllers aus und sende diese an den Arduino Uno per serieller Schnittstelle. Der Arduino Uno erzeugt dann ein PPM-Signal (Puls-Pausen-Modulation), welches von der Fernsteuerung verarbeitet werden kann. Dieses PPM-Signal wird über die Lehrer-Schüler-Schnittstelle der Fernsteuerung übermittelt. Die Fernsteuerung steuert dann den RC-Heli.

Hier mein Vb.net Code zum Auslesen und Senden der X-Box Controller Achsen:

Imports System.IO.Ports     'COM-Port
Imports System

Public Class Form1

    Dim JInfo As JOYINFO
    Public Shared indata As String

    Public s As New SerialPort
    Private Declare Function joyGetPos Lib "winmm.dll" (ByVal uJoyID As Integer, ByRef pji As JOYINFO) As Integer

    Private Structure JOYINFO

        Dim X As Integer
        Dim Y As Integer
        Dim Z As Integer
        Dim Buttons As Integer

    End Structure

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        s.Close()
        s.PortName = "com3"                         'will need to change to your port number
        s.BaudRate = 9600
        s.DataBits = 8
        s.Parity = Parity.None
        s.StopBits = StopBits.One
        s.Handshake = Handshake.None
        s.Encoding = System.Text.Encoding.Default   'very important!
        s.Open()
        s.RtsEnable = True

        AddHandler s.DataReceived, AddressOf DataReceviedHandler

    End Sub

    Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
        Dim x, y, z, b As Integer

        joyGetPos(0, JInfo)      'Joystick 0

        b = Str$(JInfo.Buttons)

        x = Math.Round(Str$(JInfo.X) / 65525 * 255, 0)
        y = Math.Round(Str$(JInfo.Y) / 65525 * 255, 0)
        z = Math.Round(Str$(JInfo.Z) / 65525 * 255, 0)

        TextBox1.Text = b
        TextBox2.Text = x
        TextBox3.Text = y
        TextBox4.Text = z

        s.Write(Chr(x))
        's.Write(Chr(y))
        's.Write(Chr(z))

    End Sub

    Private Shared Sub DataReceviedHandler(ByVal sender As Object, ByVal e As SerialDataReceivedEventArgs)
        Dim sp As SerialPort = CType(sender, SerialPort)
        indata = sp.ReadLine

    End Sub
End Class

Hier mein Arduino-Code zur Erzeugung des PPM-Signals:

int pin = 3;  // PPM INPUT PIN
unsigned int duration1 = 1000;
unsigned int duration2 = 1000;
unsigned int duration3 = 1000;

void setup()
{
  Serial.begin(9600);
  pinMode(pin, OUTPUT);
}

void loop()
{        
digitalWrite(3, LOW);
delayMicroseconds(350);

digitalWrite(3, HIGH);
delayMicroseconds(1000);
digitalWrite(3, LOW);
delayMicroseconds(400);

digitalWrite(3, HIGH);
delayMicroseconds(duration1);
digitalWrite(3, LOW);
delayMicroseconds(400);

digitalWrite(3, HIGH);
delayMicroseconds(1000);
digitalWrite(3, LOW);
delayMicroseconds(400);

digitalWrite(3, HIGH);
delayMicroseconds(1000);
digitalWrite(3, LOW);
delayMicroseconds(400);

digitalWrite(3, HIGH);
delayMicroseconds(1000);
digitalWrite(3, LOW);
delayMicroseconds(400);

digitalWrite(3, HIGH);
delayMicroseconds(1000);
digitalWrite(3, LOW);
delayMicroseconds(400);

digitalWrite(3, HIGH);
delayMicroseconds(20000 - (400*7) - (1000*5) - duration1);

} 

void serialEvent(){
duration1 = map(Serial.read(), 0, 255, 700, 1500);
}

Habt ihr eine Idee wie ich die oben beschriebene Zeitverzögerung minimieren kann?

Danke vorab für eure Ideen und schönen Gruß :),

Christoph

Ich dachte die PPM impulse von Fernsteuerungen dauern von 1000 bis 2000µS und nicht von 700 bis 1500µS.
Der Arduino-Sketch verzögert gar nichts, da er keine Eingabe von der seriellen Schnittstelle bekommt und nur konstante Werte sendet.
Vieleicht solltest Du den richtigen Setch uns geben.
Grüße Uwe

Uwe der bekommt doch was von der Seriellen Schnittstelle und die Manipuliert nur den Wert Duration1. Ich weiss nur nicht wie das Aussieht mit serial.Event wenn er inmitten eines Delays hängt unterbricht er beartbeitet die Daten und arbeitet dann weiter, oder ignoriert er das bis zu nächsten durchlauf?

Zu uwefed: Ich hab das PPM-Signal der Fernsteuerung mit einem anderen Arduino-Sketch ausgelesen und es demnach reproduziert. Ich werde aber morgen mal probieren ob es auch mit einer HIGH-Signalverzögerung von 1 - 2 ms funktioniert.
Zu volvodani: Das oben aufgeführten Arduino-Sketch mit der "Serial.available" Methode in der Loop() Routine führt zum gleichen Problem. :~

if (Serial.available() > 0) {

        }

Grüße

Christoph

@Deluxee
In dem Sketch den Du uns gegeben hast, wird die Funkton void serialEvent(){ nie angesprungen und somit sendet der Arduino nie die Daten des Controllers -> PC -> an die Fernsteuerung.
Grüße Uwe

Auszug aus der HardwareSerial.cpp

void serialEventRun(void)
{
#ifdef serialEvent_implemented
  if (Serial.available()) serialEvent();
#endif
#ifdef serialEvent1_implemented
  if (Serial1.available()) serialEvent1();
#endif
#ifdef serialEvent2_implemented
  if (Serial2.available()) serialEvent2();
#endif
#ifdef serialEvent3_implemented
  if (Serial3.available()) serialEvent3();
#endif
}

Und dann der aufruf aus der Main.h

#include <Arduino.h>

int main(void)
{
	init();

#if defined(USBCON)
	USB.attach();
#endif
	
	setup();
    
	for (;;) {
		loop();
		if (serialEventRun) serialEventRun();
	}
        
	return 0;
}

Das wird “nach der loop” ausgeführt, damit habe ich dann auch meine Antwort auf meine Frage :fearful:.
Was mir aber so nach dem durchschauen auffällt du verlierst ja 30ms auch im Ardu durch warten. Bist du dir sicher das es keinen “Zeitverlust” zwischen Controller und Ardu Eingabe

Ich muß doch mal ganz blöd Fragen warum das ganze so kompliziert gemacht wird?
Wozu überhaupt der PC dazwischen?
Warum wandelst Du nicht mit dem Arduino direkt das X-Box Protokoll in ein PPM-Summensignal mit entsprechender Kanalanzahl um.
Das wäre doch einfacher, sicherer und garantiert um etliches schneller.

LG Lena

Zu uwefed: Die "serialEventRun" Routine ist quasi ein Programm-Interrupt und braucht keinen Sprungbefehl.
Zu volvodani: Die Zeitverzögerung ist auf jeden Fall größer als 30 ms. Ich schätze es sind ca. 500 ms bis der Servo am RC-Heli auf eine Bewegung am X-Box Controller reagiert.
Zu Lena: Die indirekt Steuerung über den PC bietet mehr Möglichkeiten z.B. bezogen auf die Weiterverarbeitung oder Auswertung der Signale. Ich werde aber mal versuchen die Variable "duration1" über ein Poti direkt zu ändern.

Grüße,

Christoph

Das Problem mit der Verzögerung hab ich gelöst! :smiley: Problem war, dass der vb.net Timer nicht jede Millisekunde das Signal gesendet hat. Zudem musste ich die "if (Serial.available() > 0)" Methode benutzen, da ansonsten der Stellwert sich immer wieder auf 1000 zurückgesetzt hat. Zudem hab ich auch noch die Stellgrößen duration 2 und 3 übertragen!

Grüße

Christoph

Hier der arduino-Sketch:

int pin = 3;  // PPM INPUT PIN
unsigned int duration1 = 1000;
unsigned int duration2 = 1000;
unsigned int duration3 = 1000;
int lowduration = 350;

void setup()
{
  Serial.begin(115200);
  pinMode(pin, OUTPUT);
}

void loop()
{        
 if (Serial.available() > 0) {
   
   if (Serial.read() == 255){
        duration1 = map(Serial.read(), 0, 250, 700, 1500);
        duration2 = map(Serial.read(), 0, 250, 700, 1500);
        duration3 = map(Serial.read(), 0, 250, 700, 1500);
    }
  }
  
digitalWrite(3, LOW);
delayMicroseconds(lowduration);

digitalWrite(3, HIGH);
delayMicroseconds(duration3);
digitalWrite(3, LOW);
delayMicroseconds(lowduration);

digitalWrite(3, HIGH);
delayMicroseconds(duration1);
digitalWrite(3, LOW);
delayMicroseconds(lowduration);

digitalWrite(3, HIGH);
delayMicroseconds(duration2);
digitalWrite(3, LOW);
delayMicroseconds(lowduration);

digitalWrite(3, HIGH);
delayMicroseconds(1000);
digitalWrite(3, LOW);
delayMicroseconds(lowduration);

digitalWrite(3, HIGH);
delayMicroseconds(1000);
digitalWrite(3, LOW);
delayMicroseconds(lowduration);

digitalWrite(3, HIGH);
delayMicroseconds(1000);
digitalWrite(3, LOW);
delayMicroseconds(lowduration);

digitalWrite(3, HIGH);
delayMicroseconds(20000 - (lowduration*7) - (1000*3) - duration1 - duration2 - duration3);
}

Und hier der VB.code

Imports System.IO.Ports     'COM-Port
Imports System

Public Class Form1

    Dim JInfo As JOYINFO
    Public Shared indata As String

    Public s As New SerialPort
    Private Declare Function joyGetPos Lib "winmm.dll" (ByVal uJoyID As Integer, ByRef pji As JOYINFO) As Integer

    Private Structure JOYINFO

        Dim X As Integer
        Dim Y As Integer
        Dim Z As Integer
        Dim Buttons As Integer

    End Structure

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        s.Close()
        s.PortName = "com3"                         'will need to change to your port number
        s.BaudRate = 115200
        s.DataBits = 8
        s.Parity = Parity.None
        s.StopBits = StopBits.One
        s.Handshake = Handshake.None
        s.Encoding = System.Text.Encoding.Default   'very important!
        s.Open()
        s.RtsEnable = True

        AddHandler s.DataReceived, AddressOf DataReceviedHandler

    End Sub

    Private Shared Sub DataReceviedHandler(ByVal sender As Object, ByVal e As SerialDataReceivedEventArgs)
        Dim sp As SerialPort = CType(sender, SerialPort)
        indata = sp.ReadLine

    End Sub

    Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
        Dim x, y, z As Integer

        joyGetPos(0, JInfo)      'Joystick 0

        x = Math.Round(Str$(JInfo.X) / 65525 * 250, 0)
        y = Math.Round(Str$(JInfo.Y) / 65525 * 250, 0)
        z = Math.Round(Str$(JInfo.Z) / 65525 * 250, 0)


        TextBox1.Text = x + y + z

        TextBox2.Text = x
        TextBox3.Text = y
        TextBox4.Text = z
   
    End Sub

    Private Sub TextBox1_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TextBox1.TextChanged
        Dim x, y, z As Integer

        joyGetPos(0, JInfo)      'Joystick 0

        x = Math.Round(Str$(JInfo.X) / 65525 * 250, 0)
        y = Math.Round(Str$(JInfo.Y) / 65525 * 250, 0)
        z = Math.Round(Str$(JInfo.Z) / 65525 * 250, 0)

        s.Write(Chr(255) & Chr(x) & Chr(y) & Chr(z))

    End Sub

Hi,
ich weis der Topic ist schon älter aber hast du irgendwas noch gebaut um das Arduino PPM Signal in die MX 12 zu bekommen? Ich habe gerade den DIY Headtracker fertig und scheue mich noch das PPM Signal in die Funke zu bringen.

mfg
stefan