Problemlösungen

Arduino Trimmrad zur Einstellung der Flugzeug-Trimmung (Teil 4)

Grundlagen


Arduino Mikrocontroller mit Ein- und Ausgabekomponenten
In dieser Artikel-Serie ging es ursprünglich um den Nachbau eines Trimmrades für Flugsimulatoren auf Basis eines Arduino-Controllers. In Kombination mit PCF8574-Erweiterungsplatinen ermögliht der Kontroller den Anschluss von sehr viel mehr Drehreglern, Knöpfen und Leuchtdioden. Deshalb ist geplant, das Projekt auszuweiten und zusätzliche Bedienelemente zu integrieren.

Die Grundlagen können in vorhergehenden Artikeln nachgelesen werden.

Arduino Trimmrad zur Einstellung der Flugzeug-Trimmung (Teil 1)

Arduino Trimmrad zur Einstellung der Flugzeug-Trimmung (Teil 2)

Arduino Trimmrad zur Einstellung der Flugzeug-Trimmung (Teil 3)

Quelltext überarbeitet und Grundlage für viel mehr Funktionalität geschaffen


Ein oder zwei Trimmräder zur Steuerung von Flugsimulatoren sind nicht schlecht, aber es wäre natürlich schön, wenn noch weitere Funktionen gesteuert werden können: Parkbremse (Parkin Break), Fahrwerk (Gear), Treibstorrmischung (Mixture), Propelleranstellwinkel (Pitch), Störklappen (Spoiler). Außerdem sind ein paar Knöpfe sinnvoll, die mit Menüs des Flugsimulators verknüpft werden können, so dass die wichtigsten Menüs schnell aufgerufen und geschlossen werden können.

Arduino Mikrocontroller mit Ein- und Ausgabekomponenten

PCF8574-Erweiterungsplatinen

Die Anzahl der Pins auf einem Arduino ist natürlich stark limitiert. Also ist eine Erweiterung notwendig. Hierfür können sehr komfortabel PCF8574-Erweiterungsplatinen verwendet werden. Solche gibt es beispielsweise bei Amazon: DollaTek 5Pcs PCF8574 IO Erweiterungsplatine I/O-Expander I2C-Bus Auswertungs-Entwicklungs-Modul

Diese Platinen kommunizieren mit dem Arduino über den I²C-Bus (Inter-Integrated Circuit) und können kaskadiert werden. Wenn mehrere PCF8574-Erweiterungsplatinen gleichzeitig genutzt werden, dann muss für jeden eine separate Adresse über die gelben Jumper auf dem Board eingestellt werdden. Ein Board mit der Jumper-Stellung 000 (Standard bei Auslieferung) hat die Adresse 0x20 und kann beispielsweise mit

PCF8574 pcf8574_0(0x20);

eingebunden werden. Ein Board mit der Jumper-Stellung 001 hat die Adresse 0x21 und kann beispielsweise mit

PCF8574 pcf8574_1(0x21);
eingebunden werden. Die möglichen Kombinationen lauten:

000 -> 0x20
001 -> 0x21
010 -> 0x22
011 -> 0x23
100 -> 0x24
101 -> 0x25
110 -> 0x26
111 -> 0x27

I²C-Scan


Jedes Erweiterungs-Board enthält 8 reguläre Pins und einen Interrupt-Pin. Dadurch lassen sich bis zu 64 Pins hinzufügen. Neben dem PCF8574 gibt es die Variante PCF8574A, die den Adressraum erweitert, so dass statt 8 insgesamt 16 Erweiterungsplatinen angeschlossen werden können und dadurch bis zu 128 zusätzliche Pins genutzt werden können.

Zum aktuellen Zeitpunkt sind für Erweiterungs-Boards vorgesehen, von denen jedoch mangels Kabel bislang nur vier PCF8574-Platinen angeschlossen sind. Dementsprechend sind im folgenden I²C-Scan auch nur vier Adressen als “gefunden” markiert.

I2C-Scan

Es gibt verschiedene I²C-Scan, die leicht mit einer Internet-Suche nach “arduino i2c scanner” gefunden werden können. Beispielsweise hier: How to Scan I2C Address in Arduino

Manche Scanner liefern bei einem neueren Arduino keine Ergebnisse. Gegebenenfalls lässt sich das Problem lösen, indem nach der Anweisung “Serial.begin(9600);” mit “while (!Serial);” vor der Ausgabe gewartet wird, bis die Serialle Schnittstelle tatsächlich verfügbar ist. Andernfalls wird das Scan-Ergebnis geliefert, bevor die Text-Ausgabe verfügbar ist.

Serial.begin(9600);
 
// https://forum.arduino.cc/index.php?topic=171889.0
// While the serial stream is not open, do nothing.
while (!Serial);
 
Serial.println("I2CScanner ready!");
 

PCF8574_library

Um die PCF8574-Platinen komfortabel nutzen zu können, habe ich die Bibliothek xreef / PCF8574_library installiert. Diese bietet glücklicherweise auch eine Funktionalität, die Werte der verwendeten Rotary Encoder abzufragen. Dadurch bleibt der eigene Quelltext sehr viel übersichtlicher.

Aktueller Stand


Nachfolgend der aktuelle Quelltext des Projektes. Mir ist bewusst, dass man die Schaltung auf dem Foto nicht sehr gut erkennen kann, und die Interpretation des Quelltextes somit nur unzureichend möglich ist. Gegebenenfalls werde ich zumindes für das fertiggestellte Projekt einen Schaltungsplan erstellen.

/*
 * Arduino HID Trim Wheel
 *
 * Author	Jan Wischniowski
 * URL		https://www.metanox.de/
 * Version	0.8.2
 * License	GNU GENERAL PUBLIC LICENSE Version 3
*/
 
#include "Arduino.h"
#include "HID-Project.h"
#include "PCF8574.h"
 
// Initialize library
PCF8574 pcf8574_0(0x20);
PCF8574 pcf8574_1(0x21);
PCF8574 pcf8574_2(0x22);
PCF8574 pcf8574_3(0x23);
PCF8574 pcf8574_4(0x24);
 
// Arduino
const int arduino_led_1			= LED_BUILTIN;
 
// LED 0
const int led_0_red				= P0;
const int led_0_green			= P1;
const int led_0_blue			= P2;
 
// LED 1
const int led_1_red				= P0;
const int led_1_green			= P1;
const int led_1_blue			= P2;
 
// Rotary 0
const int rotary_0_switch		= P3;	// Pin 3 to SW (switch pin) on rotary encoder.
const int rotary_0_data			= P4;	// Pin 4 to DT (data) on rotary encoder.
const int rotary_0_clock		= P5;	// Pin 5 to CLK (clock) on rotary encoder.
volatile long rotary_0_value	= 1;	// Die Funktion readEncoderValue() liefert beim Start it 0 als ersten Wert -1. Also mit 1 Starten. Dann ist der erste Wert 0.
int rotary_0_x_axis				= 0;
bool rotary_0_button			= LOW;
bool rotary_0_changed			= false;
 
// Rotary 1
const int rotary_1_switch		= P3;	// Pin 3 to SW (switch pin) on rotary encoder.
const int rotary_1_data			= P4;	// Pin 4 to DT (data) on rotary encoder.
const int rotary_1_clock		= P5;	// Pin 5 to CLK (clock) on rotary encoder.
volatile long rotary_1_value	= 1;	// Die Funktion readEncoderValue() liefert beim Start it 0 als ersten Wert -1. Also mit 1 Starten. Dann ist der erste Wert 0.
int rotary_1_y_axis				= 0;
bool rotary_1_button			= LOW;
bool rotary_1_changed			= false;
 
void setup()
{
	unsigned long time_start = millis();
	const long timeout_serial = 1000; 
 
	Serial.begin(9600);
 
	// Warten bis der der Serial Stream geöffnet ist.
	// "while (!Serial)" endet bei einem USB-Reconnect oder einem Reset nicht. Aus diesem Grund soll die Schleife nach einem ausreichend langen Timeout abgebrochen werden.
	// https://forum.arduino.cc/index.php?topic=171889.0
	while (!Serial) { if (millis() - time_start >= timeout_serial) { break; } }
 
	Serial.println("SERIAL OK :)");
 
	pinMode(arduino_led_1, OUTPUT);
 
	pcf8574_0.pinMode(led_0_red, OUTPUT);
	pcf8574_0.pinMode(led_0_green, OUTPUT);
	pcf8574_0.pinMode(led_0_blue, OUTPUT);
 
	pcf8574_1.pinMode(led_1_red, OUTPUT);
	pcf8574_1.pinMode(led_1_green, OUTPUT);
	pcf8574_1.pinMode(led_1_blue, OUTPUT);
 
	pinMode(rotary_1_switch, INPUT_PULLUP);
	pinMode(rotary_1_clock, INPUT);
	pinMode(rotary_1_data, INPUT);
 
	pcf8574_0.pinMode(rotary_0_switch, INPUT);
	pcf8574_0.pinMode(rotary_0_clock, INPUT);
	pcf8574_0.pinMode(rotary_0_data, INPUT);
 
	pcf8574_1.pinMode(rotary_1_switch, INPUT);
	pcf8574_1.pinMode(rotary_1_clock, INPUT);
	pcf8574_1.pinMode(rotary_1_data, INPUT);
 
	pcf8574_0.encoder(rotary_0_data, rotary_0_clock); // Rotary 0 Pins
	pcf8574_0.pinMode(rotary_0_switch, INPUT_PULLUP); // Rotary 0 Button
	pcf8574_0.pinMode(P6, INPUT);
	pcf8574_0.pinMode(P7, INPUT_PULLUP);
 
	pcf8574_1.encoder(rotary_1_data, rotary_1_clock); // Rotary 1 Pins
	pcf8574_1.pinMode(rotary_1_switch, INPUT_PULLUP); // Rotary 1 Button
	pcf8574_1.pinMode(P6, INPUT);
	pcf8574_1.pinMode(P7, INPUT_PULLUP);
 
	// Sends a clean report to the host. This is important on any Arduino type.
	pcf8574_0.begin();
	pcf8574_1.begin();
	Gamepad.begin();
 
	if (pcf8574_0.begin())
	{
		Serial.println("PCF8574 OK :)");
	}
	else
	{
		Serial.println("PCF8574 KO :(");
	}
 
	pcf8574_0.digitalWrite(led_0_red, LOW);
	pcf8574_0.digitalWrite(led_0_green, LOW);
	pcf8574_0.digitalWrite(led_0_blue, LOW);
 
	pcf8574_1.digitalWrite(led_1_red, LOW);
	pcf8574_1.digitalWrite(led_1_green, LOW);
	pcf8574_1.digitalWrite(led_1_blue, LOW);
}
 
 
void loop()
{
	if (pcf8574_0.digitalRead(P6))
	{
		Serial.println("BUTTON PRSSED!");
	}
 
	rotary_0();
	rotary_1();
}
 
 
void rotary_0()
{
	rotary_0_changed = pcf8574_0.readEncoderValue(rotary_0_data, rotary_0_clock, &rotary_0_value);
 
	bool rotary_0_switch_value = pcf8574_0.digitalRead(rotary_0_switch);
	rotary_0_switch_value = !rotary_0_switch_value;
 
	if (rotary_0_switch_value!=rotary_0_button)
	{
		rotary_0_changed = true;
		rotary_0_button = rotary_0_switch_value;
 
		if (rotary_0_button)
		{
			digitalWrite(arduino_led_1, HIGH);
 
			// Button 1 gedrückt. X-Achse auf 0 zurücksetzen.
			Gamepad.press(1);
			Gamepad.write();
 
			rotary_0_value = rotary_0_x_axis = 0;
 
			pcf8574_0.digitalWrite(led_0_red, LOW);
			pcf8574_0.digitalWrite(led_0_green, LOW);
			pcf8574_0.digitalWrite(led_0_blue, HIGH);
		}
		else
		{
			digitalWrite(arduino_led_1, LOW);
 
			Gamepad.release(1);
			Gamepad.write();
 
			pcf8574_0.digitalWrite(led_0_red, LOW);
			pcf8574_0.digitalWrite(led_0_green, LOW);
			pcf8574_0.digitalWrite(led_0_blue, LOW);
		}
	}
 
	if (rotary_0_changed)
	{
		rotary_0_x_axis = rotary_0_value*500;
 
		if (0>rotary_0_x_axis)
		{
			pcf8574_0.digitalWrite(led_0_red, HIGH);
			pcf8574_0.digitalWrite(led_0_green, LOW);
		}
		else if (0<rotary_0_x_axis) { pcf8574_0.digitalWrite(led_0_red, LOW); pcf8574_0.digitalWrite(led_0_green, HIGH); } else { pcf8574_0.digitalWrite(led_0_red, LOW); pcf8574_0.digitalWrite(led_0_green, LOW); //pcf8574_0.digitalWrite(led_0_blue, LOW); } Serial.print("ENCODER 2 ENCODER VALUE: "); Serial.println(rotary_0_value); Serial.print("ENCODER 2 ENCODER BUTTON: "); Serial.println(rotary_0_button?"HIGH":"LOW"); Serial.print("ENCODER 2 VAR rotary_0_x_axis: "); Serial.println(rotary_0_x_axis); rotary_0_changed = false; Gamepad.rxAxis(rotary_0_x_axis); Gamepad.write(); } } void rotary_1() { rotary_1_changed = pcf8574_1.readEncoderValue(rotary_1_data, rotary_1_clock, &rotary_1_value); bool rotary_1_switch_value = pcf8574_1.digitalRead(rotary_1_switch); rotary_1_switch_value = !rotary_1_switch_value; if (rotary_1_switch_value!=rotary_1_button) { rotary_1_changed = true; rotary_1_button = rotary_1_switch_value; if (rotary_1_button) { digitalWrite(arduino_led_1, HIGH); // Button 2 gedrückt. Y-Achse auf 0 zurücksetzen. Gamepad.press(2); Gamepad.write(); rotary_1_value = rotary_1_y_axis = 0; //Serial.println ("Rotary 1 Button: Pressed"); pcf8574_1.digitalWrite(led_1_red, LOW); pcf8574_1.digitalWrite(led_1_green, LOW); pcf8574_1.digitalWrite(led_1_blue, HIGH); } else { digitalWrite(arduino_led_1, LOW); Gamepad.release(2); Gamepad.write(); pcf8574_1.digitalWrite(led_1_red, LOW); pcf8574_1.digitalWrite(led_1_green, LOW); pcf8574_1.digitalWrite(led_1_blue, LOW); } } if (rotary_1_changed) { rotary_1_y_axis = -rotary_1_value*500; if (0>rotary_1_y_axis)
		{
			pcf8574_1.digitalWrite(led_1_red, HIGH);
			pcf8574_1.digitalWrite(led_1_green, LOW);
		}
		else if (0<rotary_1_y_axis)
		{
			pcf8574_1.digitalWrite(led_1_red, LOW);
			pcf8574_1.digitalWrite(led_1_green, HIGH);
		}
		else
		{
			pcf8574_1.digitalWrite(led_1_red, LOW);
			pcf8574_1.digitalWrite(led_1_green, LOW);
			//pcf8574_1.digitalWrite(led_1_blue, LOW);
		}
 
		Serial.print("ENCODER 1 VALUE: ");
		Serial.println(rotary_1_value);
		Serial.print("ENCODER 1 BUTTON: ");
		Serial.println(rotary_1_button?"HIGH":"LOW");
		Serial.print("ENCODER 1 VAR rotary_1_y_axis: ");
		Serial.println(rotary_1_y_axis);
 
		rotary_1_changed = false;
 
		Gamepad.ryAxis(rotary_1_y_axis);
		Gamepad.write();
	}
}