1歳の息子がいます。
息子がプラレールで遊び始める頃までに、ラジコンみたいな制御ができるといいなと思い、30を過ぎてArduinoの勉強を始めてみました。
電子工作は学生時代にPICでLチカを試したくらいです。
今回の回路を作成するにあたり、最低限Arduinoの扱い方をインプットをするために、Arduino Unoのスターターキットを購入してチュートリアルを一通り試しました。
やりたいこと
Androidから通信してプラレールの速さを制御したい。
今回はESP32のBluetoothを使って、乾電池2本だけでDCモーターを制御できるか試してみます。
ちなみに今回やりたいことはこちらの既製品を買えばできたりしますが、勉強だと思ってESP32で1からやってみようと思います。 mabeee.mobi
開発環境
- Mac Book (10.13.6)
- Arduino IDE (1.8.13)
- Unity 2019.4.6f1
Unity側のBLEは今回こちらの有料アセットを使っています。
iOS、tvOS、Android用のBluetooth LE
機材一覧
その他 ブレッドボード、ジャンパワイヤ、電池ボックスなど
テストワイヤはなくても良いけど、ピンヘッダのはんだ付けが不要になるのであると便利です。
回路図
今回利用するモータードライバは、過電流時にスモールモードで約2A、ラージモードで約4Aになると保護回路が作動し止まるようになっています。
モーターの仕様を見ると定格電流は0.66Aなので一見スモールモードで問題なさそうですが、静止電流2.2Aと書かれており、静止状態からの起動時に瞬間的に2Aを超えて保護回路が動作し止まってしまいます。
そのためラージモードで利用することにしました。
ESP32は昇圧コンバータを使わなくても動くのを確認できましたが、安定のため昇圧コンバータを通すようにしています。
実際の配線はこんな感じになります。
テストワイヤが思ったより硬くて、モータードライバと昇圧コンバータが浮いたようになってしまいました。
Arduino側
スケッチ例のESP32 BLE Arduino > BLE_notifyをベースに書き換えています。
ポイントはDisconnect後の再接続待機で、2回目以降の接続がうまくできずにしばらくハマりました。
モータードライバ側の制約でデューティ比が1/4以下になってはいけないらしいので、速度制御は64を下限にしています。
コード
#include <BLEDevice.h> #include <BLEUtils.h> #include <BLEServer.h> // デフォルトのままなので各自設定. #define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" #define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" #define FRONT A4 // 32PIN 正回転 #define FRONT_CHANNEL 0 #define ENABLE 25 // ON #define BACK 26 // 逆回転 #define LOW_POWER 64 // 速度下限. class MyCallbacks: public BLECharacteristicCallbacks { void onWrite(BLECharacteristic *pCharacteristic) { // 受信した文字列をint型変換. std::string str = pCharacteristic->getValue(); int power = atoi(str.c_str()); Serial.print("*** 数値受信 *** : "); Serial.println(power); // 256段階に丸め込み. power = min(max(power, 0), 255); // power = LOW_POWER + power * (255 - LOW_POWER) / 255; power = power == LOW_POWER ? 0 : power; if(power == 0) { digitalWrite(ENABLE, LOW); } else { digitalWrite(ENABLE, HIGH); } ledcWrite(FRONT_CHANNEL, power); } }; class MyServerCallbacks: public BLEServerCallbacks { void onDisconnect(BLEServer* pServer) { // 接続が切れたら止める. digitalWrite(ENABLE, LOW); // 接続が切れたら再接続待機. pServer->getAdvertising()->start(); } }; void setup() { Serial.begin(9600); // デバイス名を設定. BLEDevice::init("ESP32"); BLEServer *pServer = BLEDevice::createServer(); pServer->setCallbacks(new MyServerCallbacks()); BLEService *pService = pServer->createService(SERVICE_UUID); BLECharacteristic *pCharacteristic = pService->createCharacteristic( CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE ); pCharacteristic->setCallbacks(new MyCallbacks()); pService->start(); BLEAdvertising *pAdvertising = pServer->getAdvertising(); pAdvertising->start(); // 回転は正転のみ. pinMode(ENABLE, OUTPUT); pinMode(BACK, OUTPUT); digitalWrite(ENABLE, HIGH); digitalWrite(BACK, LOW); // アナログ操作の準備. ledcSetup(FRONT_CHANNEL, 1000, 8); ledcAttachPin(FRONT, FRONT_CHANNEL); } void loop() { delay(1000); }
Unity側実装
アプリ側はシンプルにスライダー操作で速度が変化するようにしてみました。
接続状況がわかるように上の方にステータス表示を出しています。
コントロール側のポイントはメッセージを送る間隔で、値の変化のたびに送ってしまうとESP32側で処理が追いつかずにリブートすることがありました。
今回はそこまで厳密にリアルタイム性を求める必要もないので、0.2秒間隔で値に変更があれば送信するようにしています。
コード
using UnityEngine; using UnityEngine.UI; using System.Text; public class MotorController : MonoBehaviour { private const string DEVICE_NAME = "ESP32"; // デフォルトのままなので各自設定. private const string SERVICE_UUID = "4fafc201-1fb5-459e-8fcc-c5c9c331914b"; private const string CHARACTERISTIC_UUID = "beb5483e-36e1-4688-b7f5-ea07361b26a8"; public Text statusText; public Slider slider; private string deviceAddress; private Status status = Status.Neutral; private int? updateValue; // 高速で送り続けるとESP32側がリブートするので一定間隔で送信する private const float SEND_INTERVAL = 0.2f; private float time; private enum Status { Neutral, Scaning, Connecting, Connected, Disconnect, } private void Start() { BluetoothLEHardwareInterface.Initialize( true, false, StartScan, (error) => { BluetoothLEHardwareInterface.Log("Error: " + error); }); slider.onValueChanged.AddListener((value) => { updateValue = (int)value; }); } private void Update() { time += Time.deltaTime; if (time < SEND_INTERVAL) { return; } time -= SEND_INTERVAL; // 値に変更があったら送信. if (updateValue.HasValue) { SendString(updateValue.ToString()); updateValue = null; } } private void StartScan() { UpdateStatus(Status.Scaning); BluetoothLEHardwareInterface.ScanForPeripheralsWithServices(null, OnScan, null, false, false); } private void OnScan(string address, string deviceName) { // デバイスが見つかったらスキャンを停止. if (deviceName.Contains(DEVICE_NAME)) { deviceAddress = address; BluetoothLEHardwareInterface.StopScan(); Connect(address); } } private void Connect(string address) { UpdateStatus(Status.Connecting); BluetoothLEHardwareInterface.ConnectToPeripheral(address, null, null, OnCharacteristic, OnDisconnect); } private void OnCharacteristic(string address, string serviceUUID, string characteristicUUID) { if (serviceUUID == SERVICE_UUID && characteristicUUID == CHARACTERISTIC_UUID) { UpdateStatus(Status.Connected); } } private void OnDisconnect(string address) { UpdateStatus(Status.Disconnect); // 接続が切れたら再スキャン. StartScan(); } private void UpdateStatus(Status status) { this.status = status; this.statusText.text = status.ToString(); } private void SendString(string value) { if (string.IsNullOrEmpty(deviceAddress)) return; var data = Encoding.UTF8.GetBytes(value); BluetoothLEHardwareInterface.WriteCharacteristic(deviceAddress, SERVICE_UUID, CHARACTERISTIC_UUID, data, data.Length, false, (characteristicUUID) => { }); } }
結果
youtu.be 線路を持っていないので浮かせた状態になりますが、スライダー変化で速度が変わることを確認できました。
さいごに
なんとかBLE通信でプラレールを動かすことができました。
とはいえブレッドボードを輪ゴムで縛り付けているだけの状態なので、とてもカッコ悪いです。
このままではまだ子供に見せられないので、次は車両内に収まるように基板を整えてみようと思います。
2022/02/13 追記
無事に車両内に収まりました。 nagomi0132.hateblo.jp