XIAO ESP32-S3 で Xbox Elite Controller 2 の入力をBluetooth受信する
会社で機械の内容しかやんないので基板さわりたくてやりました。学生時代に謎に苦労した覚えがあるので残しときますが正直全く困りませんでした antigravityくんのおかげです。
はじめに
Seeed Studio の XIAO ESP32-S3 と Xbox Elite Wireless Controller Series 2 を Bluetooth で接続して、スティックやボタンの入力を受け取る方法について書きます。学生の時はdualsence使ってましたが多分あんまり変わらないです。
BLEとBluetooth Classicの違い
簡単に言えばBluetooth Classicはシリアル通信がそのまま無線になった感じ、BLEは長期運用向けの機能とかいろいろ組み込まれてる代わりに使うのがだるい感じです。
XIAO ESP32-S3 は BLE(Bluetooth Low Energy)のみ対応 です。
一般的な Bluetooth(Bluetooth Classic / BR/EDR)は ハードウェアレベルで非対応 です。
Xbox Elite Controller 2 はデフォルトで Bluetooth Classicを使いますが、
ファームウェア v5.15 以降にアップデートすると BLE に対応 します。
コントローラのファームウェア更新
Xbox Elite 2 は出荷時の firmware では Bluetooth Classic のみ対応です。
BLE を有効にするには v5.15 以上 へのアップデートが必要です。
更新方法:
- Windows PC に 「Xboxアクセサリ」アプリ をインストール(Microsoft Store から無料)
- コントローラを USB ケーブルで PC に接続
- アプリを開いてファームウェアアップデートを実行
Arduino IDE のセットアップ
1. Bluepad32 ボード定義を追加
ファイル → 環境設定 → 追加のボードマネージャURL に以下を追記:
https://raw.githubusercontent.com/ricardoquesada/esp32-arduino-lib-builder/master/bluepad32_files/package_esp32_bluepad32_index.json
参考リンク: Bluepad32 公式ドキュメント
GitHub: https://github.com/ricardoquesada/bluepad32
2. ボードをインストール
ツール → ボード → ボードマネージャ で
「ESP32 + Bluepad32 by Ricardo Quesada」 を検索してインストール
3. ボードを選択
ツール → ボード → Bluepad32 ESP32-S3 Boards → XIAO_ESP32S3
コード全文
全部antigravityに書かせました。AI最高。
アドレスはスマホにコントローラを接続して見ました。
#include <Bluepad32.h>
#include <uni.h> // uni_bt_allowlist_* 関数の宣言に必要
// 接続対象コントローラの MAC アドレス(通常の表記順で指定)
// 例: 3C:FA:06:12:7D:77 → {0x3C, 0xFA, 0x06, 0x12, 0x7D, 0x77}
static const uint8_t TARGET_MAC_ADDR[6] = {0x3C, 0xFA, 0x06, 0x12, 0x7D, 0x77};
ControllerPtr myController = nullptr;
// ── コントローラ接続時コールバック ──────────────────
void onConnectedController(ControllerPtr ctl) {
if (myController == nullptr) {
ControllerProperties props = ctl->getProperties();
Serial.printf("✅ コントローラ接続!\n");
Serial.printf(" MAC: %02X:%02X:%02X:%02X:%02X:%02X\n",
props.btaddr[5], props.btaddr[4], props.btaddr[3],
props.btaddr[2], props.btaddr[1], props.btaddr[0]);
myController = ctl;
} else {
ctl->disconnect(); // 2台目は切断
}
}
// ── コントローラ切断時コールバック ──────────────────
void onDisconnectedController(ControllerPtr ctl) {
if (myController == ctl) {
Serial.println("❌ 切断されました。");
myController = nullptr;
}
}
// ── 入力値の処理 ────────────────────────────────────
void processController(ControllerPtr ctl) {
// スティック: -512 〜 511
int lx = ctl->axisX(); // 左スティック X
int ly = ctl->axisY(); // 左スティック Y
int rx = ctl->axisRX(); // 右スティック X
int ry = ctl->axisRY(); // 右スティック Y
// トリガー: 0 〜 1023
int lt = ctl->brake(); // 左トリガー
int rt = ctl->throttle(); // 右トリガー
// ボタン
bool btnA = ctl->a();
bool btnB = ctl->b();
bool btnX = ctl->x();
bool btnY = ctl->y();
bool btnLB = ctl->l1();
bool btnRB = ctl->r1();
bool btnL3 = ctl->thumbL();
bool btnR3 = ctl->thumbR();
bool btnMenu = ctl->miscStart();
bool btnView = ctl->miscBack();
bool btnXbox = ctl->miscSystem();
// DPAD(十字キー): ビットフラグで同時押し対応
uint8_t dpad = ctl->dpad();
bool dpadUp = dpad & DPAD_UP;
bool dpadDown = dpad & DPAD_DOWN;
bool dpadLeft = dpad & DPAD_LEFT;
bool dpadRight = dpad & DPAD_RIGHT;
// 出力
Serial.printf("左スティック LX=%5d LY=%5d\n", lx, ly);
Serial.printf("右スティック RX=%5d RY=%5d\n", rx, ry);
Serial.printf("トリガー LT=%4d RT=%4d\n", lt, rt);
Serial.print("ボタン: ");
if (btnA) Serial.print("[A] ");
if (btnB) Serial.print("[B] ");
if (btnX) Serial.print("[X] ");
if (btnY) Serial.print("[Y] ");
if (btnLB) Serial.print("[LB] ");
if (btnRB) Serial.print("[RB] ");
if (btnL3) Serial.print("[L3] ");
if (btnR3) Serial.print("[R3] ");
if (btnMenu) Serial.print("[Menu] ");
if (btnView) Serial.print("[View] ");
if (btnXbox) Serial.print("[Xbox] ");
Serial.println();
Serial.print("DPAD: ");
if (dpadUp) Serial.print("↑ ");
if (dpadDown) Serial.print("↓ ");
if (dpadLeft) Serial.print("← ");
if (dpadRight) Serial.print("→ ");
Serial.println();
// バッテリー(Xbox BLE接続では取得不可 → 255 が返る)
uint8_t batt = ctl->battery();
if (batt == 255) {
Serial.println("バッテリー: 取得不可(Xbox BLE非対応)");
} else {
Serial.printf("バッテリー: %d / 255\n", batt);
}
}
// ── setup ───────────────────────────────────────────
void setup() {
Serial.begin(115200);
delay(1000);
// MAC アドレスの Allowlist 設定(必ず BP32.setup() の前に行う)
bd_addr_t targetAddr;
memcpy(targetAddr, TARGET_MAC_ADDR, sizeof(TARGET_MAC_ADDR));
uni_bt_allowlist_add_addr(targetAddr);
uni_bt_allowlist_set_enabled(true);
// Bluepad32 初期化
BP32.setup(&onConnectedController, &onDisconnectedController);
BP32.forgetBluetoothKeys(); // 毎回ペアリングし直す
Serial.println("📡 スキャン開始... コントローラのペアリングボタンを長押し");
}
// ── loop ────────────────────────────────────────────
void loop() {
bool dataUpdated = BP32.update();
if (dataUpdated && myController != nullptr) {
if (myController->isConnected()) {
processController(myController);
}
}
delay(50);
}
接続手順
- スケッチをコンパイル・書き込み
- シリアルモニタを 115200 bps で開く
- Xbox コントローラの背面のペアリングボタンを長押し
- Xbox ロゴが速く点滅したらペアリングモード
- 数秒で自動接続 → シリアルモニタに入力値が流れ始める
おわりに
まあ調べたら出るような内容しか書いてないですしなんなら半分くらい記事もaiに書かせてますが役に立ったらうれしいです。
注意点として、ここでは全く触れてないんですがワイヤレスコントローラーはトラブルがとにかく絶えないです。私はこれまでなぜか接続できないトラブルには何度もあってきましたし、なぜかアドレスが変わっていたりライブラリが勝手に更新しててバグったり果てはコントローラー自体が壊れてバッテリーをホテルで交換したりもしました。ロボット本体じゃなくコントローラーの破損で動かないとか死ぬほど馬鹿らしいので対策は忘れずに。