Arduino Trimmrad 2: Erweitert um Arm-Spoiler-Schalter
Umsetzung eines Trimmrad-Controllers für Flugsimulatoren mit einem Arduino
In der Reihe Arduino Trimmrad zur Einstellung der Flugzeug-Trimmung (Teil 1 bis 5) habe ich mittels eines Arduino-Boards ein Eingabegerät für Flugsimulatoren gebaut und programmiert. Ursprünglich war nur ein einfaches Trimmrad geplant. Daraus wurde dann ein umfangreicheres Projekt, in welchem ein Controller mit vielen frei belegbaren Knöpfen, Schaltern und Rotary Encoder.
Ich habe dann eine stark vereinfachte Version eines Trimm-Controllers auf Basis eines Arduio-Borads gebaut. Dieses Gerät beschränkt sich auf die zentrale Funktion, ist daher kompakter und der Quelltext ist einfacher.
Arm Spoiler
Für die Steuerung von Airbus-Flugzeugen sind die Controller TCA Captain Pack X Airbus Edition von Thrustmaster sehr praktisch, da sie wichtige für Airbus typische Steuerelemente nachbilden. Dem Hebel zur Steuerung der Spoiler fehlt jedoch eine wichtige Funktion. Im realen Flugzeug kann der Hebel nicht nur nach vorne und hinten bewegt werden, sondern auch nach oben gezogen oder herunter gedrückt werden. Zieht man den heben nach oben, werden die Spoiler scharf geschaltet (armed), so dass sie in bestimmten Situationen automatisch ausgefahren werden. Die Arm-Spoiler Funktion fehlt jedoch beim Nachbau von Thrustmaster. Eine entsprechende Funktion habe ich ergänzt und dabei das bereits vorhandene Trimmrad auf Arduino-Basis genutzt. Hierfür habe ich lediglich einen weiteren Schalter angeschlossen und den Quelltext um wenige Zeilen ergänzt.
In der ersten Variante war ein Druck-Schalter verbaut. Die Bedienung entspricht der Handhabung im echten Flugzeug. Durch Hochziehen des Hebels werden die Spoiler scharfgestellt, durch herunterdrücken wird die Automatik-Funktion der Spoiler deaktiviert. Der Druck-Schalter konnte jedoch mit der relativ spartanisch zusammengebastelten Konstruktion nicht zuverlässig betätigt werden.
Bei einem zweiten Prototypen habe ich den unzuverlässigen Schalter durch einen Kipphebel ersetzt. Die Funktionsweise entspricht damit nicht mehr der Funktionalität im echten Flugzeug, für einen Simulator und meine Zwecke genügt die Lösung aber vorerst. Der Schalter ist ganz primitiv und wenig ästhetisch mit Panzertape an den Hebel geklebt. Kippt man den Schalter nach oben, wird das Ziehen am hebel simuliert. Kippt man den Schalter nanch unten, wird das Drücken des Schalters nachgeahmt. Die Konstruktion sieht bescheiden aus, funktioniert aber und kann natürlich jederzeit durch eine bessere Konstruktion verbessert werden! Hierfür wäre ein 3D-Drucker sicher ein hilfreiches Werzeugt, um die entsprechenden Bauteile herzustellen.
Zu beachten ist, dass der gewählte Kipphebel drei Strom-Anschlüsse hat. Die auf der Litze am mittleren Anschluss anliegende Spannung wird durch den Hebel auf einen der beiden anderen Anschlüsse geleitet, so dass theoretisch zwei Buttons simuliert werden können. Dies ist in diesem Anwednugnsfall aber nicht nötig. Es genügt der Status eines einzigen Buttons: gedrückt oder nicht gedrückt. Abhängig von der gewählten Verdrahtung muss gegebenenfalls die Einstellung im Simulator angepasst werden, so dass die die Spoiler tatsächlich scharfgeschaltet werden, wenn der Hebel oben ist und deaktiviert, wenn der Hebel unten ist. Andernfalls verhät man ein exakt vertauschtes Verhalten.
Im Flugzeug wird der nachfolgend gezeigte Hebel mit dem Schalter bedient.
Benötigte Komponenten
Quelltext
Zunächst (für eine bessere Übersicht) nur der Quelltext, der den zusätzlichen Button für das scharfschalten der Spoiler betrifft.
/* * Arduino HID Trim Wheel 2 * * Author Jan Wischniowski <jan.wischniowski@metanox.de> * URL https://www.metanox.de/ * Version 1.1.0 * License GNU GENERAL PUBLIC LICENSE Version 3 */ #include "Arduino.h" #include "HID-Project.h" int button2 = 0; // Vorgesehen, um Controller Thrustmaster Airbus Spoiler zu erweitern. Der Schalter kann genutzt werden, um Spoiler scharf zu schalten (Arm Spoiler). void setup() { Gamepad.begin(); // Setup Serial Monitor. Serial.begin(9600); } void loop() { // Read Pin 8 (Button 2). button2 = digitalRead(pin8); if (button2 == LOW) { Gamepad.press(2); } else { Gamepad.release(2); } Gamepad.write(); // Put in a slight delay to help debounce the reading. delay(1); }
Und hier noch einmal der gesamte Quelltext.
/* * Arduino HID Trim Wheel 2 * * Author Jan Wischniowski <jan.wischniowski@metanox.de> * URL https://www.metanox.de/ * Version 1.1.0 * License GNU GENERAL PUBLIC LICENSE Version 3 */ /* * Strg + Shift + P -> Advanced Options in Arduino IDE 2.0! */ // Source https://lastminuteengineers.com/rotary-encoder-arduino-tutorial/ How Rotary Encoder Works and Interface It with Arduino #include "Arduino.h" #include "HID-Project.h" const int ledBlue = 2; const int ledGreen = 3; const int ledRed = 4; const int rotaryClock = 5; // Pin 5 to CLK (clock) on rotary encoder. const int rotaryData = 6; // Pin 6 to DT (data) on rotary encoder. const int rotarySwitch = 7; // Pin 7 to SW (switch) on rotary encoder. const int pin8 = 8; int rotaryXAxis; // Result between [-32768 .. +32767]. float rotaryXAxisNormalized; // Result between [-100.0 .. +100.0] (percent). int rotaryCounter = 0; int rotaryClockCurrentState; int rotaryClockLastState; int rotaryButton; unsigned long rotaryButtonLastPress = 0; int rotaryButtonCurrentState = 0; String rotaryCurrentDirection = ""; int button2 = 0; // Vorgesehen, um Controller Thrustmaster Airbus Spoiler zu erweitern. Der Schlater kann genutzt werden, um die Spoiler scharf zu schalten (Arm Spoiler). void setup() { // Set encoder pins as inputs. pinMode(rotaryClock, INPUT); pinMode(rotaryData, INPUT); pinMode(rotarySwitch, INPUT_PULLUP); pinMode(ledRed, OUTPUT); pinMode(ledGreen, OUTPUT); pinMode(ledBlue, OUTPUT); pinMode(pin8, INPUT_PULLUP); // Read the initial state of CLK. rotaryClockLastState = digitalRead(rotaryClock); // Reset digitalWrite(ledRed, LOW); digitalWrite(ledGreen, LOW); digitalWrite(ledBlue, LOW); Gamepad.begin(); // Setup Serial Monitor. Serial.begin(9600); } void loop() { // Read Rotary. // Read the button state. rotaryButton = digitalRead(rotarySwitch); //If we detect LOW signal, button is pressed. if (rotaryButton == LOW) { // If 50ms have passed since last LOW pulse, it means that the button has been pressed, released and pressed again. if (millis() - rotaryButtonLastPress > 50) { rotaryCounter = 0; rotaryXAxis = 0; rotaryXAxisNormalized = 0; rotaryButtonCurrentState = 1; Serial.println("Button pressed!"); Gamepad.press(1); Gamepad.rxAxis(rotaryXAxis); digitalWrite(ledRed, LOW); digitalWrite(ledGreen, LOW); digitalWrite(ledBlue, HIGH); Serial.print("Direction: "); Serial.print(rotaryCurrentDirection); Serial.print(" | "); Serial.print("rotaryCounter: "); Serial.print(rotaryCounter); Serial.print(" | "); Serial.print("rotaryXAxis: "); Serial.print(rotaryXAxis); Serial.print(" | "); Serial.print("rotaryXAxisNormalized: "); Serial.println(rotaryXAxisNormalized); } // Remember last button press event. rotaryButtonLastPress = millis(); } else { if (rotaryButtonCurrentState == 1) { rotaryButtonCurrentState = 0; digitalWrite(ledRed, LOW); digitalWrite(ledGreen, LOW); digitalWrite(ledBlue, LOW); Serial.println("Button released!"); Gamepad.release(1); } } // Read the current state of CLK. rotaryClockCurrentState = digitalRead(rotaryClock); // If last and current state of CLK are different, then pulse occurred. // React to only 1 state change to avoid double count. if (rotaryClockCurrentState != rotaryClockLastState && rotaryClockCurrentState == 1) { // If the rotaryData state is different than the CLK state then the encoder is rotating CCW so decrement. if (digitalRead(rotaryData) != rotaryClockCurrentState) { rotaryCounter --; rotaryCurrentDirection = "CW"; } else { // Encoder is rotating CW so increment. rotaryCounter ++; rotaryCurrentDirection = "CCW"; } if (200<=rotaryCounter) { rotaryCounter = 200; } if (-200>=rotaryCounter) { rotaryCounter = -200; } rotaryXAxis = (int)((float)rotaryCounter * 163.835); // Welche Schrittweite wäre nötig, um 0,5-Prozent-Schritte mit jedem Dreh-Intervall zu erlangen? -> 32767/200 = 163,835. if (32767<=rotaryXAxis) { rotaryXAxis = 32767; } if (-32768>=rotaryXAxis) { rotaryXAxis = -32768; } rotaryXAxisNormalized = (((float)rotaryXAxis/32768) * 100.0); Serial.print("Direction: "); Serial.print(rotaryCurrentDirection); Serial.print(" | "); Serial.print("rotaryCounter: "); Serial.print(rotaryCounter); Serial.print(" | "); Serial.print("rotaryXAxis: "); Serial.print(rotaryXAxis); Serial.print(" | "); Serial.print("rotaryXAxisNormalized: "); Serial.println(rotaryXAxisNormalized); Gamepad.rxAxis(rotaryXAxis); if (rotaryCounter<0) { digitalWrite(ledRed, HIGH); digitalWrite(ledGreen, LOW); digitalWrite(ledBlue, LOW); } else if (rotaryCounter>0) { digitalWrite(ledRed, LOW); digitalWrite(ledGreen, HIGH); digitalWrite(ledBlue, LOW); } else { digitalWrite(ledRed, LOW); digitalWrite(ledGreen, LOW); digitalWrite(ledBlue, LOW); } } // Remember last CLK state. rotaryClockLastState = rotaryClockCurrentState; button2 = digitalRead(pin8); // Read Pin 8 (Button 2). if (button2 == LOW) { Gamepad.press(2); } else { Gamepad.release(2); } Gamepad.write(); // Put in a slight delay to help debounce the reading. delay(1); }
Einstellungen im Microsoft FlightSimulator 2020
Im Menü zum Trimmrad ist der Button zwei enstsprechend zu belegen und auch festzulegen, welches Verhalten bei losgelassenem (ON RELEASE) und bei gedrücktem (ON PRESS) Schalter gewünscht ist.