Bestuur een Arduino-rover naadloos met Firmata en een Xbox One-controller
Onderdelen en benodigdheden
| Microsoft Xbox One-controller + Wireless Adapter voor Windows | 1 |
| Bluetooth-module (HC-05, HC-06 of BlueSMiRF Silver) | 1 |
| Arduino UNO | 1 |
| Rover-kit (chassis, 2 wielen, 2 DC-gearmotoren, L298N motor-driver, 9V-batterijhouder, sensor shield) | 1 |
| Extra batterijhouder 6xAA (voor motoren) | 1 |
| Laptop met Windows 10 (Bluetooth-dongle, Visual Studio 2015+) | 1 |
Apps en online diensten
- Microsoft Windows 10 IoT Core – UWP-app als tussenstuk tussen Firmata en Xbox-controller
Over dit project
Ik kocht een goedkope rover-kit (bedoeld voor autonoom rijden met ultrasoon/servo). Maar ik wilde het handmatig besturen met een Xbox One-controller via het Firmata-protocol. En het werkt uitstekend!
Kit bouwen
De handleiding was in het Chinees, maar logisch opgebouwd. Eén basisplaat: componenten zowel boven als onder. Twee batterijhouders: één voor Arduino, één voor motoren (apart nodig, anders lopen motoren niet).
Motor-aansluiting – L298N pinlayout
De L298N dual H-brug stuurt snelheid en richting van beide motoren via signalen van de Arduino.
- Motor 1: + / –
- Power IN: 9V (losse batterijhouder)
- Gemeenschappelijke GND (Arduino + motor-batterij)
- ENA → Arduino D10 (PWM, wit)
- IN1 → D9 (grijs), IN2 → D8 (paars)
- IN3 → D7 (blauw), IN4 → D6 (groen)
- ENB → D5 (PWM, geel)
- Motor 2: + / –
- Jumper bij 12V-invoer niet verwijderen (we blijven onder 12V)
Belangrijk: Arduino en motor-driver hebben elk hun eigen voeding, maar GND moet gemeenschappelijk zijn.
Arduino-pinlayout
D5–D10 aangesloten op ENA, IN1–IN4, ENB. D5 en D10 zijn PWM-pins (getild ~) voor snelheidsregeling. D6–D9 zijn gewone GPIO voor richting:
- Beide LOW → motor stil
- Eén HIGH, één LOW → één richting
- Omgekeerd → andere richting
Zonder PWM-signaal (D5/D10) beweegt niets!
Bluetooth-aansluiting
HC-06: RX↔TX (kruisen), 5V en GND van Arduino.
Firmata-sketch op Arduino
Gewoon StandardFirmata uit de Arduino IDE-voorbeelden uploaden (TX/RX loskoppelen voor upload). Let op: mijn Bluetooth-module was slecht, dus baudrate in de sketch verlaagd naar 9600.
Testen zonder programmeren
Gebruik de Windows Remote Arduino Experience-app (Store, werkt ook op Windows 10 Mobile):
- Koppelen met HC-06
- PWM-pagina: pin 5 inschakelen, waarde 128
- Digital-pagina: pin 6 HIGH zetten → motor draait!
- Richting wisselen: pin 6 LOW, pin 7 HIGH
- Zelfde voor pin 10 (PWM) + pin 8/9 (richting motor 2)
De UWP-app als schakel
Een UWP-app (C#/XAML) luistert naar de Xbox-controller en vertaalt thumbstick-waarden naar Firmata-commando's.
Principes:
- Linker thumbstick Y-as → linker motor (vooruit/achteruit)
- Rechter thumbstick Y-as → rechter motor
- Dode zone (
_ActionPoint = 0.15) → motor stil - PWM-waarde = 255 × |thumbstick| ×
_SpeedLimit(0,75) - Firmata: PWM = analogWrite op digitale pin
- Duplicate-commando's filteren (performance)
Let op: Bij lage PWM-waarden "wierpen" motoren (normaal). Daarom twee voedingen!
Code
XAML-besturingselementen
<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 (C#) – MainPage.xaml.cs
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;
private double _ActionPoint = 0.15;
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;
}
private async void OnConnectionEstablished() {
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => {
DisableEnableButtons(true);
ArduinoDigitalWrite(_LeftForward, PinState.LOW);
ArduinoDigitalWrite(_LeftBackward, PinState.LOW);
ArduinoDigitalWrite(_RightBackward, PinState.LOW);
ArduinoDigitalWrite(_RightForward, PinState.LOW);
});
}
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 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 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) {
if (!_CurrentPinStates.ContainsKey(port) || _CurrentPinStates[port] != state) {
_CurrentPinStates[port] = state;
_arduino.digitalWrite(port, state);
}
}
private void ArduinoAnalogWrite(byte port, ushort value) {
if (!_CurrentSpeedValues.ContainsKey(port) || _CurrentSpeedValues[port] != value) {
_CurrentSpeedValues[port] = value;
_arduino.analogWrite(port, value);
}
}
// ... event handlers voor Bluetooth/Gamepad (zie origineel)
}
Productieproces
- Betrouwbaar Arduino RFID Aanwezigheidssysteem Aangedreven door Python
- Universele Android-afstandsbediening bouwen met Arduino, 1Sheeld en IR-besturing
- Een slimme voltmeter bouwen met Arduino en smartphone – Eenvoudig DIY-project
- Externe IoT-besturing van een zelfgebouwde robotarm
- Nauwkeurige Frequentie- en Duty Cycle-meting met Arduino UNO
- Realtime afstandsmeting met Arduino Sonar & Processing visualisatie
- Naadloze LED-dimmer met Bolt IoT en Arduino UNO
- Bouw een Slimme Arduino-gestuurde Robotarm: Eenvoudige DIY Handleiding
- Je TV bedienen met Alexa via Arduino IoT Cloud – stapsgewijze handleiding
- Bouw een compacte FM-radio met Arduino Nano en RDA5807M
- Naadloze Smartphone-besturing van Huisverlichting met 1Sheeld