Sander van de Velde
Published © GPL3+

Control Arduino Rover using Firmata and Xbox One Controller

My idea was to use both an Xbox One Controller and the Firmata protocol to let children drive a little trike rover. It works very well!

IntermediateFull instructions provided4 hours13,943
Control Arduino Rover using Firmata and Xbox One Controller

Things used in this project

Hardware components

Microsoft Xbox Controller and Wireless Adapter for Windows
This controller can be added to a Windows PC using the adapter
×1
SparkFun Bluetooth Modem - BlueSMiRF Silver
SparkFun Bluetooth Modem - BlueSMiRF Silver
Or an identical (HC-05, HC-06) Bluetooth module
×1
Arduino UNO & Genuino UNO
Arduino UNO & Genuino UNO
×1

Software apps and online services

Windows 10 IoT Core
Microsoft Windows 10 IoT Core
This app is the intermediate between the standard Firmata sketch and the Xbox One controller

Story

Read more

Code

Xaml controls to start the communication

snippets
Paste this into the main form of your new UWP app
<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 of the the UWP app mainform

C#
This code connects to the rover using Firmata and passes Xbox One Controller commands
public sealed partial class MainPage : Page
{
  private BluetoothSerial _bluetooth;
  private RemoteDevice _arduino;

  private UwpFirmata _firmata = null;
  private Gamepad _Gamepad = null;
  // We never supply a PWM of more then 255 * 0,75
  private double _SpeedLimit = 0.75;
  // values below this absolte value stop the rover
  private double _ActionPoint = 0.15;

  private byte _LeftForward = 6;
  private byte _LeftBackward = 7;
  private byte _LeftValue = 5;
  private byte _RightForward = 9;
  private byte _RightBackward = 8;
  private byte _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 BluetoothConnectionLost(string message)
  {
    await Dispatcher.RunAsync(
      CoreDispatcherPriority.Normal, () =>
      {
        tbRead.Text = "ConnectionLost" + message;
        btnStart.IsEnabled = true;
      });
  }

  private async void BluetoothConnectionFailed(string message)
  {
    await Dispatcher.RunAsync(
      CoreDispatcherPriority.Normal, () =>
      {
        tbRead.Text = "ConnectionFailed" + message;
        btnStart.IsEnabled = true;
      });
  }

  private async void ArduinoDeviceReady()
  {
    await Dispatcher.RunAsync(
      CoreDispatcherPriority.Normal, () =>
      {
        tbRead.Text = "Device Ready";
      });
  }

  private void OnConnectionEstablished()
  {
    var action = Dispatcher.RunAsync(
      CoreDispatcherPriority.Normal, 
      new DispatchedHandler(() =>
      {
        DisableEnableButtons(true);

        ArduinoDigitalWrite(_LeftForward, PinState.LOW);
        ArduinoDigitalWrite(_LeftBackward, PinState.LOW);
        ArduinoDigitalWrite(_RightBackward, PinState.LOW);
        ArduinoDigitalWrite(_RightForward, PinState.LOW);
      }));
  }

  private void DisableEnableButtons(bool enabled)
  {
    // Disable all buttons. Otherwise 
    //  the 'A' will press the one focussed.
    btnController.IsEnabled = enabled;
    btnStart.IsEnabled = enabled;
  }

  private async void Gamepad_GamepadRemoved(
                                   object sender, Gamepad e)
  {
    _Gamepad = null;

    await Dispatcher.RunAsync(
      CoreDispatcherPriority.Normal, () =>
      {
        tbRead.Text = "Controller removed";
      });
  }
  
  private async void Gamepad_GamepadAdded(
                                   object sender, Gamepad e)
  {
    _Gamepad = e;

    await Dispatcher.RunAsync(
      CoreDispatcherPriority.Normal, () =>
      {
        tbRead.Text = "Controller added";
      });
  }

  private async void btnController_Click(object sender, 
                                          RoutedEventArgs e)
  {
    DisableEnableButtons(false);

    while (true)
    {
      await Task.Delay(TimeSpan.FromMilliseconds(3));

      if (_Gamepad == null)
      {
        continue;
      }

      // Get the current state
      var reading = _Gamepad.GetCurrentReading();

      if (Math.Abs(reading.LeftThumbstickY) < _ActionPoint)
      {
        ArduinoDigitalWrite(_LeftForward, PinState.LOW);
        ArduinoDigitalWrite(_LeftBackward, PinState.LOW);
        sldrLeftSpeed.Value = 0;
      }
      else if (reading.LeftThumbstickY >= _ActionPoint)
      {
        ArduinoDigitalWrite(_LeftForward, PinState.HIGH);
        ArduinoDigitalWrite(_LeftBackward, PinState.LOW);
        sldrLeftSpeed.Value = 255 * 
               Math.Abs(reading.LeftThumbstickY) * _SpeedLimit;
      }
      else if (reading.LeftThumbstickY <= -_ActionPoint)
      {
        ArduinoDigitalWrite(_LeftForward, PinState.LOW);
        ArduinoDigitalWrite(_LeftBackward, PinState.HIGH);
        sldrLeftSpeed.Value = 255 * 
               Math.Abs(reading.LeftThumbstickY) * _SpeedLimit;
      }

      if (Math.Abs(reading.RightThumbstickY) < _ActionPoint)
      {
        ArduinoDigitalWrite(_RightForward, PinState.LOW);
        ArduinoDigitalWrite(_RightBackward, PinState.LOW);
        sldrRightSpeed.Value = 0;
      }
      else if (reading.RightThumbstickY >= _ActionPoint)
      {
        ArduinoDigitalWrite(_RightForward, PinState.HIGH);
        ArduinoDigitalWrite(_RightBackward, PinState.LOW);
        sldrRightSpeed.Value = 255 * 
          Math.Abs(reading.RightThumbstickY) * _SpeedLimit;
      }
      else if (reading.RightThumbstickY <= -_ActionPoint)
      {
        ArduinoDigitalWrite(_RightForward, PinState.LOW);
        ArduinoDigitalWrite(_RightBackward, PinState.HIGH);
        sldrRightSpeed.Value = 255 * 
          Math.Abs(reading.RightThumbstickY) * _SpeedLimit;
      }
    }
  }

  private void ArduinoDigitalWrite(byte port, PinState state)
  {
    // ignore duplicate commands
    var exists = _CurrentPinStates.ContainsKey(port);

    if (!exists
           || _CurrentPinStates[port] != state)
    {
      _CurrentPinStates[port] = state;
      _arduino.digitalWrite(port, state);
    }
  }
  
  private void ArduinoAnalogWrite(byte port, ushort value)
  {
    // ignore duplicate commands
    var exists = _CurrentSpeedValues.ContainsKey(port);

    if (!exists
          || _CurrentSpeedValues[port] != value)
    {
      _CurrentSpeedValues[port] = value;
      _arduino.analogWrite(port, value);
    }
  }
}

Credits

Sander van de Velde

Sander van de Velde

1 project • 11 followers
I started as a IT consultant in 1993. I like getting my hands dirty with software innovations and I try to implement these in my daily work

Comments