Problemlösungen

Arduino Trimmrad 2 zur Einstellung der Flugzeug-Trimmung

Erste Umsetzung


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.

Ein massiver “Overkill” für ein einfaches Trimmrad zur Steuerung eines (Höhen-)Trimmruders. Leider gibt es aber auch weiterhin kaum Trimmräder zu kaufen, zumindest nicht zu vernünftigen Preisen. Der Markt ist wie leergefegt. Aus diesem Grund habe ich nun 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. Zudem wird eine eine externe Bibliothek weiger benötigt.

Zweite Umsetzung


Arduino Mikrocontroller mit Ein- und Ausgabekomponenten


Benötigte Komponenten


Benötigt wird natürlich ein Arduino-Leonardo-Board. Es muss nicht zwingend ein Original-Arduino sein. Um die Weiterentwicklung zu unterstützen, kann man natürlich ein original Produkt erwerben. Bei einem solchen ist allerdings kein USB-Kabel dabei. Da ich ein solches benötigte, habe ich ein Board eines Fremdherstellers erworben. Wichtig ist, dass es sich beim Board um ein HID-Gerät (Human Interface Device) handelt, also etwa ein Leonardo-Board. HID ist eine Geräteklasse des USB-Standards für Computer, welche Geräte beschreibt, mit denen Benutzer direkt interagieren. Meist wird HID bei Geräten wie Tastatur, Maus, Joystick und Grafiktabletts verwendet.

Arduino Leonardo with Headers

oder

KEYESTUDIO Leonardo R3 Entwicklungsboard Mikrocontroller Board für Arduino mit USB-Kabel

Für die Steuerung wird ein Rotary-Encoder benötigt.

WayinTop 5 Stück Rotary Encoder Modul KY-040 360 Grad Drehgeber Drehwinkelgeber mit Druckknopf

Zur optischen Anzeige, ob eine Trimmung eingestellt ist und in welche Richtung, kann noch eine LED verbaut werden.

3 Stück KY-016 3 Farben RGB LED Sensor Modul für Arduino DIY Starter Kit

Zum Verbinden der einzelnen Komponenten können Jumper-Kabel verwendet werden.

AZDelivery 5 x Jumper Wire Kabel 3 x 40 STK. je 20 cm M2M/ F2M / F2F

Eingebaut sind die Komponenten in einer kleinen transparenten Box aus dem Baumarkt. Ein Gehäuse kann natürlich auch selbst gebaut werden. Dadurch kann eine Optik erzielt werden, die eher einem Flugzeug-Cockpit nachempfunden ist.

Quelltext


Der Quelltext basiert auf dem Tutorial How Rotary Encoder Works and Interface It with Arduino. In diesem Tutorial wird auch hervorragend erklärt, wie der Rotary Encoder fuktioniert.

Mit den im Quelltext gewählten Einstellungen wird mit jeder Drehung des Rotation Encoders um eine Rasterstufe die Trimmung um 0,5 Grad vergrößert oder verkleinert.
Um die HID-Funktionalität nutzen zu können, wird noch eine entsprechende Bibliothek benötigt. Ich habe die Bibliothek NicoHood / HID verwendet und hierfür die entpackten Dateien im Ordner “sketchbook/libraries” abgelegt.

Die LED ist mit den Pins 2, 3, 4 und einem Groud-Pin verbunden. Der Rotary Encoder ist mit den Pins 5, 6, 7, einem Groud-Pin und einem 5-Volt-Pin verbunden.

Die LED leuchtet bei positiver Trimmung grün und bei negativer Trimmung rot.

Der Rotary Encoder hat auch einen Button. Um diesen zu betätigen, muss der Dreh-Knopf gedrückt werden. Diese Funktion wird in diesem Projekt genutzt, um die Trimmung auf 0 zurückzusetzen. Um anzuzeigen, dass die Trimmung zurückgesetzt wird, leuchtet die LED beim Drücken des Knopfes blau und erlischt dann.

/*
 * Arduino HID Trim Wheel 2
 *
 * Author	Jan Wischniowski <jan.wischniowski@metanox.de>
 * URL		https://www.metanox.de/
 * Version	1.0.0
 * License	GNU GENERAL PUBLIC LICENSE Version 3
*/
 
/*
 * Strg + Shift + P -> Advanced Options in Arduino IDE 2.0!
*/
 
#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.
 
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		= "";
 
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);
 
	// 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 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 ist 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;
 
	Gamepad.write();
 
	// Put in a slight delay to help debounce the reading.
	delay(1);
}

Einstellungen im Microsoft FlightSimulator 2020


Arduino Mikrocontroller mit Ein- und Ausgabekomponenten