## 操作方法 1. バッテリーをつける 2. 2つのスイッチを下画像と逆方向にトグルする。(左側がマイコン、右側が7.4V用スイッチ) ![klbTKulR](https://hackmd.io/_uploads/Hk59EBCXgx.jpg) 3. Pythonプログラムを実行。 ページ下のプログラムをコピペ、保存して、以下コマンドで実行します。pipでinputs, bleakを入れて、`python3 main.py`でもよいです。 ``` uv run --with inputs,bleak main.py ``` 4. 操作方法 ![image](https://hackmd.io/_uploads/SkZhC6AVee.png) 5. エラーなどがあり、最初からやり直す場合、マイコンのRボタンで再起動(スイッチのトグルでもよい)します。pythonプログラムも再実行します。 ## 回路図 ![image](https://hackmd.io/_uploads/Hkd6L8AQex.png) ![pin_map-2](https://hackmd.io/_uploads/Hk_Wzei7ee.png) - マイコン(3.3か5V入力なので降圧する) 予備・準備で2個買う [Seeed Studio XIAO ESP32C3](https://akizukidenshi.com/catalog/g/g117454/) 1F 3番売場 - 昇圧コンバーター DC/DC ~~[最大24V出力 昇圧型スイッチング電源モジュール LMR62421使用キット](https://akizukidenshi.com/catalog/g/g107608/) 1F 33番売場 3段目~~ ~~[最大30V出力 昇圧型スイッチング電源モジュール NJW4131使用](https://akizukidenshi.com/catalog/g/g107406/) 2F 71番売場~~ [4.7V~12.6V出力 最大10A 同期式昇圧型スイッチング電源モジュール TPS61088使用](https://akizukidenshi.com/catalog/g/g114566/) 2F 71番売場 - バッテリー モーター、電磁弁用Lipo2セル(7.4V) [DATA POWER TECHNOLOGY DTP502535-2S リチウムイオンポリマー電池 7.4V 400mAh](https://www.sengoku.co.jp/mod/sgk_cart/detail.php?code=EEHD-5GHZ) 秋葉原本店取扱フロア:1階 - バッテリー(マイコン用) 3.7V [DTP502535(PHR) 400mAhリチウムイオンポリマー電池 3.7V 400mAh](https://www.sengoku.co.jp/mod/sgk_cart/detail.php?code=EEHD-4YZL) 秋葉原本店取扱フロア:1階 - モータードライバー(2系統ついてるやつ) ~~[モータードライバー TB6643KQ](https://akizukidenshi.com/catalog/g/g107688/) 1F 11番売場 上段スチール棚の出引~~ [DRV8835使用ステッピング&DCモータードライバーモジュール](https://akizukidenshi.com/catalog/g/g109848/) 1F 11番売場 スチール棚の上 - 電磁弁用のリレー ~~[リードリレーSS1A05D](https://akizukidenshi.com/catalog/g/g104328/) 1F 10番売場 D段~~ [5V小型リレー 接点容量:2A 2回路C接点 941H-2C-5D](https://akizukidenshi.com/catalog/g/g101229/) 1F 10番売場 D段 - ユニバーサル基盤(機体に乗せるのはこっち) [片面ガラスコンポジット・ユニバーサル基板 Cタイプ めっき仕上げ (72×47mm) 日本製](https://akizukidenshi.com/catalog/g/g103229/) 1F 20番売場 上段スチール棚の引出 - LED(動作確認用) [3mm赤色LED 625nm 70度 OSR5JA3Z74A](https://akizukidenshi.com/catalog/g/g111577/) 1F 50番売場 B列 [赤色7セグメントLED表示器 1文字カソードコモン ボディ黒 C-551SRD](https://akizukidenshi.com/catalog/g/g100640/) これも買っておいていいかも 1F 50番売場 D列 右側スチール棚引出 - 抵抗 適当なもの - リード線 適当なもの - ブレッドボード 適当なもの - はんだ 適当なもの ## 追加購入1 - [3V小型リレー 接点容量:2A 946H-1C-3D](https://akizukidenshi.com/catalog/g/g107651/) 1F 10番売場 D段 - LiPo充電器? - [耐熱電子ワイヤー 1m×10色 導体外径0.75mm(AWG22相当)](https://akizukidenshi.com/catalog/g/g111641/) 基盤配線用のワイヤーとか?[参照](https://www.ekit-tech.com/?p=6644) 2F 68番売場 ## 追加購入2 - [2.1mmジャックアダプター](https://duckduckgo.com/?q=2.1mm%E3%82%B8%E3%83%A3%E3%83%83%E3%82%AF&t=vivaldi&ia=web) - リレー - スイッチ ## 追加購入3 - [NchパワーMOSFET 30V260A IRLB3813PBF](https://akizukidenshi.com/catalog/g/g106270/) 1F 36番売場 2段目 - [汎用整流用ダイオード 1000V1A 1N4007](https://akizukidenshi.com/catalog/g/g100934/) 1F 34番売場 2段目 ## マイコン ##コード** ```sketch= #define SERVICE_UUID "12345678-1234-1234-1234-1234567890ab" #define CHARACTERISTIC_UUID "abcdefab-1234-5678-1234-abcdefabcdef" #include <Arduino.h> #include <BLEDevice.h> #include <BLEServer.h> #include <BLEUtils.h> #include <BLE2902.h> #include <string> // C++のstring機能も念のためインクルード // PIN DEFINITIONS const int PIN_MD_AIN1 = 2; const int PIN_MD_AIN2 = 3; const int PIN_MD_BIN1 = 5; const int PIN_MD_BIN2 = 4; const int PIN_MD_DENZI = 20; // PWM SETTINGS #define PWM_FREQ 5000 #define PWM_RESOLUTION 8 const int jumptime[10] = {20,40,60,80,100,120,140,160,180,200}; // Global Pointers BLEServer* pServer = nullptr; BLECharacteristic* pCharacteristic = nullptr; // Motor control helper function void control_motor(int speed, int pin_fwd, int pin_rev) { if (speed > 0) { ledcWrite(pin_fwd, speed); ledcWrite(pin_rev, 0); } else if (speed < 0) { ledcWrite(pin_fwd, 0); ledcWrite(pin_rev, -speed); } else { ledcWrite(pin_fwd, 0); ledcWrite(pin_rev, 0); } } // Characteristic callbacks class class MyCallbacks : public BLECharacteristicCallbacks { void onWrite(BLECharacteristic* pChar) override { // // 【最終修正】ここが唯一の変更点です。 // お使いの環境では、getValue()がstd::stringではなくArduinoのStringを返すため、型をこちらに合わせます。 // String value = pChar->getValue(); if (value.length() > 0) { Serial.print("受信: "); Serial.println(value); // ArduinoのString型でも .c_str() は使えるため、以下の解析ロジックは変更不要です char cmd = value.charAt(0); //int cnt=0; if (cmd == 'M') { // Motor command "M,left,right" char buffer[30]; value.toCharArray(buffer, sizeof(buffer)); // Stringをchar配列にコピー char* token = strtok(buffer, ","); if (token != NULL) { token = strtok(NULL, ","); if (token != NULL) { int left_speed = atoi(token); token = strtok(NULL, ","); if (token != NULL) { int right_speed = atoi(token); Serial.printf("左モーター速度: %d, 右モーター速度: %d\n", left_speed, right_speed); control_motor(left_speed, PIN_MD_AIN1, PIN_MD_AIN2); control_motor(right_speed, PIN_MD_BIN1, PIN_MD_BIN2); } } } } else if (cmd == 'J') { // Jump command char buffer[30]; value.toCharArray(buffer, sizeof(buffer)); // Stringをchar配列にコピー char* token = strtok(buffer, ","); if (token != NULL) { token = strtok(NULL, ","); if (token != NULL) { int i = atoi(token); digitalWrite(PIN_MD_DENZI, LOW); delay(jumptime[i-1]); digitalWrite(PIN_MD_DENZI, HIGH); } } /** Serial.println("ジャンプ動作を実行!"); digitalWrite(PIN_MD_DENZI, LOW); delay(jumptime[cnt]); digitalWrite(PIN_MD_DENZI, HIGH); cnt++; **/ } else { Serial.println("不明なコマンドです。"); } } } }; void setup() { Serial.begin(115200); // PWM setup ledcAttach(PIN_MD_AIN1, PWM_FREQ, PWM_RESOLUTION); ledcAttach(PIN_MD_AIN2, PWM_FREQ, PWM_RESOLUTION); ledcAttach(PIN_MD_BIN1, PWM_FREQ, PWM_RESOLUTION); ledcAttach(PIN_MD_BIN2, PWM_FREQ, PWM_RESOLUTION); pinMode(PIN_MD_DENZI, OUTPUT); digitalWrite(PIN_MD_DENZI, HIGH); // BLE setup BLEDevice::init("ESP32_BLE_MAKAIZO"); pServer = BLEDevice::createServer(); BLEService* pService = pServer->createService(SERVICE_UUID); pCharacteristic = pService->createCharacteristic( CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_WRITE); pCharacteristic->setCallbacks(new MyCallbacks()); pService->start(); BLEAdvertising* pAdvertising = BLEDevice::getAdvertising(); pAdvertising->addServiceUUID(SERVICE_UUID); pAdvertising->setScanResponse(true); BLEDevice::startAdvertising(); Serial.println("BLE デバイス初期化完了。アドバタイズ中..."); } void loop() { delay(2000); } ``` ## Python **コード** ```Python= import asyncio from bleak import BleakClient, BleakScanner import time import math from threading import Thread class DualShock4Controller: ANALOG_CENTER = 128 MAX_ANALOG_VAL = 127 DEADZONE = 20 LEVEL = 1 def __init__(self, loop): self.LeftStickY = 0 self.RightStickY = 0 self.CirclePressed = False self.ExitPressed = False self.SlowModeActive = False self.SuperSlowModeActive = False self.is_split_mode = False self.loop = loop self._monitor_thread = Thread(target=self._monitor_controller, daemon=True) self._monitor_thread.start() def _notify_jumplevel(self): t = self.LEVEL * 20 self.loop.call_soon_threadsafe(print, f"Current jump time is {t}ms") def _notify_drive_mode(self): mode_text = "分割モード (左右のスティックで独立操作)" if self.is_split_mode else "通常モード (左スティックで両輪操作)" self.loop.call_soon_threadsafe(print, f"[Mode Change] {mode_text}") def _monitor_controller(self): while not self.ExitPressed: try: from inputs import get_gamepad events = get_gamepad() except Exception: self.LeftStickY, self.RightStickY = 0, 0 time.sleep(1) continue for event in events: if event.code == 'BTN_TL': if event.state == 1: self.SlowModeActive = True elif event.state == 0: self.SlowModeActive = False elif event.code == 'BTN_TL2': if event.state == 1: self.SuperSlowModeActive = True elif event.state == 0: self.SuperSlowModeActive = False elif event.code == 'ABS_Y': self.LeftStickY = -(event.state - self.ANALOG_CENTER) elif event.code == 'ABS_RY': self.RightStickY = -(event.state - self.ANALOG_CENTER) elif event.code == 'BTN_TR' and event.state == 1: self.CirclePressed = True elif event.code == "ABS_HAT0Y" and event.state == -1: self.LEVEL = min(10, self.LEVEL + 1) self._notify_jumplevel() elif event.code == "ABS_HAT0Y" and event.state == 1: self.LEVEL = max(1, self.LEVEL - 1) self._notify_jumplevel() elif event.code == "ABS_HAT0X" and (event.state == -1 or event.state == 1): self.is_split_mode = not self.is_split_mode self._notify_drive_mode() def get_motor_speeds(self): left = 0 if abs(self.LeftStickY) < self.DEADZONE else self.LeftStickY right = 0 if abs(self.RightStickY) < self.DEADZONE else self.RightStickY left_pwm = int((left / self.MAX_ANALOG_VAL) * 255) right_pwm = int((right / self.MAX_ANALOG_VAL) * 255) left_pwm = max(-255, min(255, left_pwm)) right_pwm = max(-255, min(255, right_pwm)) if not self.is_split_mode: right_pwm = left_pwm if self.SlowModeActive: left_pwm = int(left_pwm * 3 / 4) right_pwm = int(right_pwm * 3 / 4) elif self.SuperSlowModeActive: left_pwm = int(left_pwm / 2) right_pwm = int(right_pwm / 2) return left_pwm, right_pwm def get_jumplevel(self): return self.LEVEL def jump(self): if self.CirclePressed: self.CirclePressed = False return True return False def should_exit(self): return self.ExitPressed # --- BLE設定 --- DEVICE_NAME = "ESP32_BLE_MAKAIZO" SERVICE_UUID = "12345678-1234-1234-1234-1234567890ab" CHARACTERISTIC_UUID = "abcdefab-1234-5678-1234-abcdefabcdef" # --- メインロジック --- async def find_device_address(name: str): print("BLE デバイスをスキャン中…") devices = await BleakScanner.discover(timeout=10) for d in devices: if d.name == name: print(f"見つかったデバイス: {d.name} ({d.address})") return d.address print("指定したデバイスが見つかりませんでした。") return None async def run_client(address: str, controller: DualShock4Controller): async with BleakClient(address) as client: print("BLE 接続完了:", address) print("左右スティックで操作,Rでジャンプ, L長押しで低速化.") prev_command = "" while not controller.should_exit(): left_motor, right_motor = controller.get_motor_speeds() command = f"M,{left_motor},{right_motor}" if command != prev_command: await client.write_gatt_char(CHARACTERISTIC_UUID, command.encode()) print(f"[BLE] 送信: {command}") prev_command = command if controller.jump(): level = controller.get_jumplevel() jump_command = f"J,{level}" await client.write_gatt_char(CHARACTERISTIC_UUID, jump_command.encode()) print(f"[BLE] 送信: {jump_command}") await asyncio.sleep(0.05) print("\n[BLE] プログラムを終了します。") async def main(): address = await find_device_address(DEVICE_NAME) if address is None: return loop = asyncio.get_running_loop() controller = DualShock4Controller(loop=loop) await run_client(address, controller) if __name__ == "__main__": try: asyncio.run(main()) except ImportError: print("ライブラリ 'inputs' が見つかりません。 pip install inputs でインストールしてください。") except Exception as e: print(f"エラーが発生しました: {e}") ```