Arduino Rover per Firmata & Xbox One Gamepad steuern
Komponenten und Materialien
| Microsoft Xbox Controller + Wireless Adapter für Windows | 1 |
| SparkFun Bluetooth Modem BlueSMiRF Silver (oder HC-05/HC-06) | 1 |
| Arduino UNO | 1 |
Apps und Online-Dienste
- Microsoft Windows 10 IoT Core (Zwischenlayer zwischen Firmata-Sketch und Xbox One Controller)
Über dieses Projekt
Vor einigen Monaten kaufte ich einen kleinen Rover (gesteuert von Arduino Uno) zum Schnäppchenpreis. Der Bausatz war sehr vollständig: Chassis, 2 Räder, 2 DC-Getriebemotoren, UNO R3, L298N Dual H-Bridge Motortreiber und weitere Komponenten. Auch Ultraschallsensor, Servo und Arduino Sensor Shield 5 waren dabei – ein echtes Schnäppchen.
Der Rover ist für autonomen Betrieb gedacht. Meine Idee: Xbox One Controller + Firmata-Protokoll für manuelle Steuerung. Und es funktioniert hervorragend!
Bausatz zusammenbauen
Der Aufbau ist nicht schwer. Zwar war die Anleitung auf Chinesisch, aber alles wirkt logisch. Nur eine Grundplatte – manche Komponenten oben, manche unten (jetzt Motortreiber).
Zwei verschiedene Batteriehalter wurden hinzugefügt. Beim Programmieren stellte sich heraus: Separate Stromversorgung nötig, damit die Motoren laufen.
Motoren anschließen – Pin-Belegung Motortreiber
Das Herz der Hardware ist diesmal nicht der Arduino, sondern der Motortreiber L298N. Er steuert Geschwindigkeit und Richtung beider Motoren per Signalen vom Arduino.
Pin-Belegung L298N:
- Plus + DC Motor 1
- Minus – DC Motor 1
- Power IN: 9V von separatem Batteriehalter
- Common Ground: mit separatem Batteriehalter UND Arduino verbunden
- Power Out: 5V (nicht genutzt – Arduino hat eigene Stromversorgung)
- ENA → Arduino D10 (PWM-fähig, weiß)
- IN1 → Arduino D9 (grau)
- IN2 → Arduino D8 (lila)
- IN3 → Arduino D7 (blau)
- IN4 → Arduino D6 (grün)
- ENB → Arduino D5 (PWM-fähig, gelb)
- Plus + DC Motor 2
- Minus – DC Motor 2
- Jumper neben 2&3: NICHT entfernen (Eingangsspannung ≤12V, bis 35V möglich)
Hinweis: Controller und Arduino haben getrennte Stromversorgungen. Für gemeinsamen Betrieb: Common Ground herstellen (siehe Punkt 4).
Motoren anschließen – Pin-Belegung Arduino
Arduino-Anschluss ist simpel: Ground und 5V der Stromversorgung an Arduino. Sechs Leitungen (ENA, IN1-4, ENB) an Pins D10 bis D5.
Pins 7&8 (bzw. 9&10 für anderen Motor) sind normale GPIOs für Motorrichtung. Beide LOW → Motor stoppt. Einer HIGH, anderer LOW → eine Richtung. Umgekehrt → andere Richtung.
Aber: Nur Pins setzen bewirkt nichts! Die Magie kommt von Pins 5 und 10 – PWM-fähig (gekennzeichnet mit ~). Werte 0–255 → Motor sehr langsam bis Höchstgeschwindigkeit.
Bluetooth anschließen
Bluetooth-Modul: RX/TX gekreuzt anschließen, 5V und Ground vom Arduino.
Firmata-Sketch auf Arduino
Der Standard-Firmata-Sketch „StandardFirmata“ reicht völlig! Aus Arduino IDE Beispiele laden, hochladen (evtl. TX/RX vorher abziehen).
Hinweis: Wegen schlechter Bluetooth-Qualität Baudrate im Sketch fest auf 9600 gesetzt.
Testen der Verbindung
Warum Firmata? Weil es einfach ist. Wie einfach? Sehr. Sogar ohne Programmierung: „Windows Remote Arduino Experience“ App (Store, auch Windows 10 Mobile).
- Mit bereits gepaartem HC-06 Bluetooth-Modul verbinden
- PWM-Seite: Digital Pin 5 aktivieren, Wert z. B. 128 setzen
- Achtung: Nächster Schritt lässt Motor laufen! Helm auf, Räder frei.
- Digital-Seite: Digital Pin 6 umschalten
- Wenn alles verbunden: Ein Motor dreht!
- Andere Geschwindigkeiten testen (höherer/niedrigerer PWM) oder Richtung ändern (Pin 6 LOW, Pin 7 HIGH)
- Gleiches für Pin 10 (PWM) und Pins 9, 8
- Beide Räder laufen → Jetzt coden…
Die UWP-App als Vermittler
Wir brauchen eine UWP-App zwischen Xbox One Controller und Rover. Ich bloggte bereits über Xbox One Controller – diesmal kombinieren wir das mit dem Rover.
Interface: Zwei Buttons, ein Textblock. Aufgaben: 1. Per Firmata über Bluetooth mit Arduino verbinden. 2. Nach Verbindung: Endlosschleife für Controller-Eingaben → Befehle umsetzen.
Gewünschte Steuerung: Klassische Panzersteuerung mit zwei Sticks. Nur die beiden Thumbsticks der Xbox One abfragen. Vorwärts/rückwärts → entsprechender Motor vorwärts/rückwärts.
Wichtig: Bluetooth-Fähigkeit in UWP-App hinzufügen. NuGet-Paket für Firmata installieren.
XAML-Snippet im MainForm-Grid für Buttons (siehe Code-Bereich). Code-Behind in zwei Teile: 1. Bluetooth+Firmata-Verbindung. 2. Xbox Controller abhören.
In der Schleife: Prüfen ob Thumbstick vorwärts/rückwärts/Stopp. Dann PWM-Wert bestimmen. Interessant: PWM-Schreiben in Firmata = Analogwert auf digitalem Port schreiben (keine eigene PWM-Methode in Arduino-Klasse).
Zwei Methoden ArduinoDigitalWrite und ArduinoAnalogWrite verhindern doppeltes Senden gleicher Werte – würde Kommunikation verstopfen und Arduino-Performance verschlechtern (bei Summer ohne Duplikat-Filter extrem schlechte Performance).
Bei niedrigen PWM-Pulsen „quitscht“ der Motor mit charakteristischem Ton – normales Verhalten. So fand ich raus: Zwei Stromquellen nötig. Beim ersten Mal dachte ich, Controller sei defekt. Nichts passierte. Bis ich einen Motor abzog → der andere quitschte.
Fazit
So funktioniert's: Thumbsticks steuern beide Motoren unabhängig per PWM + Richtung. Blinklicht nicht nur für Effekt – Sicherheit! Wenn Konstruktionen sich bewegen: Du bist schlau, das Ding nicht.
Code
XAML Steuerelemente für Kommunikationsstart
<StackPanel Orientation="Vertical">
<Button Name="btnStart" Click="btnStart_Click" FontSize="50" Width="400" Content="Start" />
<Button Name="btnController" Content="Controller" FontSize="50" Width="400" IsEnabled="False" Click="btnController_Click" />
<TextBlock Name="tbRead" FontSize="20" Foreground="Gray" />
</StackPanel>
Code-Behind der UWP App MainForm (C#)
public sealed partial class MainPage : Page {
private BluetoothSerial _bluetooth;
private RemoteDevice _arduino;
private UwpFirmata _firmata = null;
private Gamepad _Gamepad = null;
private double _SpeedLimit = 0.75; // Max PWM 255 * 0.75
private double _ActionPoint = 0.15; // Schwellenwert für Stopp
private byte _LeftForward = 6, _LeftBackward = 7, _LeftValue = 5;
private byte _RightForward = 9, _RightBackward = 8, _RightValue = 10;
private Dictionary<byte, PinState> _CurrentPinStates = new Dictionary<byte, PinState>();
private Dictionary<byte, ushort> _CurrentSpeedValues = new Dictionary<byte, ushort>();
public MainPage() { this.InitializeComponent(); }
private void btnStart_Click(object sender, RoutedEventArgs e) {
btnStart.IsEnabled = false;
Gamepad.GamepadAdded += Gamepad_GamepadAdded;
Gamepad.GamepadRemoved += Gamepad_GamepadRemoved;
_bluetooth = new BluetoothSerial("HC-06");
_bluetooth.ConnectionLost += BluetoothConnectionLost;
_bluetooth.ConnectionFailed += BluetoothConnectionFailed;
_bluetooth.ConnectionEstablished += OnConnectionEstablished;
_firmata = new UwpFirmata();
_arduino = new RemoteDevice(_firmata);
_firmata.begin(_bluetooth);
_bluetooth.begin(9600, SerialConfig.SERIAL_8N1);
_arduino.DeviceReady += ArduinoDeviceReady;
}
// ... (Event-Handler für ConnectionLost, ConnectionFailed, ArduinoDeviceReady, OnConnectionEstablished, DisableEnableButtons, Gamepad_GamepadRemoved, Gamepad_GamepadAdded)
private async void btnController_Click(object sender, RoutedEventArgs e) {
DisableEnableButtons(false);
while (true) {
await Task.Delay(TimeSpan.FromMilliseconds(3));
if (_Gamepad == null) continue;
var reading = _Gamepad.GetCurrentReading();
// Linker Stick → linker Motor
if (Math.Abs(reading.LeftThumbstickY) < _ActionPoint) {
ArduinoDigitalWrite(_LeftForward, PinState.LOW);
ArduinoDigitalWrite(_LeftBackward, PinState.LOW);
} else if (reading.LeftThumbstickY >= _ActionPoint) {
ArduinoDigitalWrite(_LeftForward, PinState.HIGH);
ArduinoDigitalWrite(_LeftBackward, PinState.LOW);
ArduinoAnalogWrite(_LeftValue, (ushort)(255 * Math.Abs(reading.LeftThumbstickY) * _SpeedLimit));
} else {
ArduinoDigitalWrite(_LeftForward, PinState.LOW);
ArduinoDigitalWrite(_LeftBackward, PinState.HIGH);
ArduinoAnalogWrite(_LeftValue, (ushort)(255 * Math.Abs(reading.LeftThumbstickY) * _SpeedLimit));
}
// Rechter Stick → rechter Motor (analog)
if (Math.Abs(reading.RightThumbstickY) < _ActionPoint) {
ArduinoDigitalWrite(_RightForward, PinState.LOW);
ArduinoDigitalWrite(_RightBackward, PinState.LOW);
} else if (reading.RightThumbstickY >= _ActionPoint) {
ArduinoDigitalWrite(_RightForward, PinState.HIGH);
ArduinoDigitalWrite(_RightBackward, PinState.LOW);
ArduinoAnalogWrite(_RightValue, (ushort)(255 * Math.Abs(reading.RightThumbstickY) * _SpeedLimit));
} else {
ArduinoDigitalWrite(_RightForward, PinState.LOW);
ArduinoDigitalWrite(_RightBackward, PinState.HIGH);
ArduinoAnalogWrite(_RightValue, (ushort)(255 * Math.Abs(reading.RightThumbstickY) * _SpeedLimit));
}
}
}
private void ArduinoDigitalWrite(byte port, PinState state) {
var exists = _CurrentPinStates.ContainsKey(port);
if (!exists || _CurrentPinStates[port] != state) {
_CurrentPinStates[port] = state;
_arduino.digitalWrite(port, state);
}
}
private void ArduinoAnalogWrite(byte port, ushort value) {
var exists = _CurrentSpeedValues.ContainsKey(port);
if (!exists || _CurrentSpeedValues[port] != value) {
_CurrentSpeedValues[port] = value;
_arduino.analogWrite(port, value);
}
}
}
Herstellungsprozess
- RFID-Anwesenheitssystem mit Arduino & Python – Daten direkt in phpMyAdmin (XAMPP) speichern
- Universelle Android-Fernbedienung mit Arduino, 1Sheeld & IR-Steuerung
- Intelligentes Voltmeter mit Arduino & Smartphone bauen – Einfaches DIY-Projekt
- Fernsteuerung eines Roboterarms per IoT: Eigenbau mit Arduino MKR1000
- Frequenz & Tastverhältnis präzise messen mit Arduino UNO
- Echtzeit-Entfernungsmessung mit Arduino Sonar & Processing-Visualisierung
- Stufenlose LED-Helligkeitssteuerung mit Bolt IoT und Arduino UNO
- Intelligenter Arduino-Roboterarm bauen: Einfaches DIY-Tutorial
- TV per Alexa steuern mit Arduino IoT Cloud – Schritt-für-Schritt
- Kompaktes FM-Radio mit Arduino Nano und RDA5807M selbst bauen