FirmataとXbox OneコントローラーでArduinoローバーをシームレス操作
必要なパーツと材料
| パーツ | 数量 | 備考 |
|---|---|---|
| Xbox Oneコントローラー + Windows用ワイヤレスアダプター | 1 | PC接続用 |
| Bluetoothモジュール(HC-05/HC-06またはBlueSMiRF Silver) | 1 | ArduinoとPC間通信用 |
| Arduino UNO | 1 | |
| ローバーキット(シャーシ、ホイール、DCギアモーター×2、L298Nドライバー、9V電池ボックス等) | 1 | |
| 単3電池ボックス 6本用 | 1 | モーター用独立電源 |
使用ソフトウェア
- Windows 10 IoT Core(UWPアプリとして動作)
プロジェクト概要
自律動作用キットとして購入したローバーを、Xbox Oneコントローラーで手動操作できるように改造。Firmataプロトコルを使用することで、Arduino側には標準のStandardFirmataスケッチを書き込むだけで済み、制御ロジックはPC側のUWPアプリに集約しています。
ハードウェア組み立て
モータードライバー(L298N)接続
L298Nは2チャネルHブリッジで、各モーターの正転/逆転とPWM速度制御が可能。電源はモーター用(9V)とArduino用を分離し、GNDのみ共通化。
| L298Nピン | Arduinoピン | 機能 |
|---|---|---|
| ENA | D10 (PWM) | 左モーター速度 |
| IN1 | D9 | 左モーター方向 |
| IN2 | D8 | 左モーター方向 |
| IN3 | D7 | 右モーター方向 |
| IN4 | D6 | 右モーター方向 |
| ENB | D5 (PWM) | 右モーター速度 |
方向制御:IN1/IN2(またはIN3/IN4)の一方をHIGH、他方をLOWで正転/逆転。両方LOWで停止。PWM値(0-255)で速度制御。
Bluetooth接続
HC-06のRX/TXをArduinoのTX/RXにクロス接続、5VとGNDを供給。
Arduino側:StandardFirmata
Arduino IDEの例「StandardFirmata」を書き込むだけ。Bluetoothモジュールの品質によりボーレートを9600に下げています(スケッチ内でハードコード変更)。
動作確認:Windows Remote Arduino Experienceアプリ
プログラミング不要で動作確認可能。ストアアプリでBluetooth接続後、PWMページでD5/D10に値を設定、DigitalページでD6-D9を操作するとモーターが回転します。
UWPアプリ:コントローラーとFirmataの仲介役
XAMLで「Start」「Controller」ボタンとステータステキストを配置。C#コードビハインドで以下を実装:
- Bluetooth経由でFirmata接続確立
- Xbox Oneコントローラーの左右サムスティックY軸を監視
- デッドゾーン(±0.15)を設け、前倒しで前進、後ろ倒しで後進
- PWM値 = スティック傾き × 255 × 0.75(速度制限)
- 重複コマンド送信防止で通信効率向上
ソースコード
XAML(MainPage.xaml)
<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>
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, _leftPwm = 5;
private byte _rightForward = 9, _rightBackward = 8, _rightPwm = 10;
// 重複送信防止用キャッシュ
private Dictionary<byte, PinState> _pinStates = new Dictionary<byte, PinState>();
private Dictionary<byte, ushort> _speedValues = 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 void OnConnectionEstablished() {
Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => {
btnController.IsEnabled = true;
// 全モーター停止で初期化
ArduinoDigitalWrite(_leftForward, PinState.LOW);
ArduinoDigitalWrite(_leftBackward, PinState.LOW);
ArduinoDigitalWrite(_rightForward, PinState.LOW);
ArduinoDigitalWrite(_rightBackward, PinState.LOW);
});
}
private async void btnController_Click(object sender, RoutedEventArgs e) {
btnController.IsEnabled = false; btnStart.IsEnabled = false;
while (true) {
await Task.Delay(TimeSpan.FromMilliseconds(3));
if (_gamepad == null) continue;
var reading = _gamepad.GetCurrentReading();
// 左モーター制御
ControlMotor(reading.LeftThumbstickY, _leftForward, _leftBackward, _leftPwm);
// 右モーター制御
ControlMotor(reading.RightThumbstickY, _rightForward, _rightBackward, _rightPwm);
}
}
private void ControlMotor(double stickY, byte forwardPin, byte backwardPin, byte pwmPin) {
if (Math.Abs(stickY) < _actionPoint) {
ArduinoDigitalWrite(forwardPin, PinState.LOW);
ArduinoDigitalWrite(backwardPin, PinState.LOW);
ArduinoAnalogWrite(pwmPin, 0);
} else if (stickY >= _actionPoint) { // 前進
ArduinoDigitalWrite(forwardPin, PinState.HIGH);
ArduinoDigitalWrite(backwardPin, PinState.LOW);
ArduinoAnalogWrite(pwmPin, (ushort)(255 * Math.Abs(stickY) * _speedLimit));
} else { // 後進
ArduinoDigitalWrite(forwardPin, PinState.LOW);
ArduinoDigitalWrite(backwardPin, PinState.HIGH);
ArduinoAnalogWrite(pwmPin, (ushort)(255 * Math.Abs(stickY) * _speedLimit));
}
}
private void ArduinoDigitalWrite(byte port, PinState state) {
if (!_pinStates.ContainsKey(port) || _pinStates[port] != state) {
_pinStates[port] = state;
_arduino.digitalWrite(port, state);
}
}
private void ArduinoAnalogWrite(byte port, ushort value) {
if (!_speedValues.ContainsKey(port) || _speedValues[port] != value) {
_speedValues[port] = value;
_arduino.analogWrite(port, value);
}
}
// ... イベントハンドラ類省略 ...
}
ポイント
- FirmataでArduinoコード不要、PC側でロジック完結
- タンクコントロール(左右独立操作)で直感的操作
- モーター用独立電源必須(共通GND忘れずに)
- フラッシングライトは安全対策として推奨
製造プロセス
- Python連携で信頼性向上: Arduino RFID勤怠管理システム
- Arduinoと1Sheeldで作る万能Androidリモコン──IR制御で家電を一括操作
- Arduinoとスマホで作るスマート電圧計 – 簡単DIYプロジェクト
- IoTで遠隔操作する自作ロボットアーム:Arduino MKR1000とSimulink、ThingSpeak活用
- Arduino UNOで周波数とデューティサイクルを高精度測定
- Arduino超音波センサーとProcessingで作るリアルタイム距離測定ソナー
- Bolt IoTとArduino UNOでLED輝度をシームレス制御
- スマートなArduino制御ロボットアームの製作:簡単DIYチュートリアル
- Arduino IoT CloudとAlexaでテレビを音声操作:ステップバイステップガイド
- Arduino NanoとRDA5807Mで作るコンパクトFMラジオ
- スマホで家庭照明をシームレス制御 - 1Sheeld活用