上位に戻る スマホで操作できるミニホバークラフト 初版2025/11/29

前回 Bluetoothを使ってスマホをリモコンにしてホバークラフトのラジコン模型を作ってみましたが、もっと小さなものを
作ってみました
相変わらず、操作難しいですが、小さい分小回りが効いて室内でも楽しめます
又、ESP32C3Miniが技適無かったので、今回は、Seeed studioESP32C6を購入しました
なかなかのすぐれもので、アンテナを内部と外部に切替できます リポバッテリーから直接給電して、充電することもできます 
前回から充電ユニットと、3端子レギュレータを削ることができました 
放電保護は、バッテリー電圧を監視して、3.3Vになったら停止させるようにしています 
まあリポバッテリー側にも保護回路はついていますが、給電停止電圧はもっと低いと思われます

追伸 作成動画を作る為、ESP32C3Miniでも仮に作ってみました

■Seed studio ESP32C6を使って作成
    

■ ES32C3 SuperMiniを使って作成 別途充電回路必要
    


■制作動画



■動作試験動画





  1. どんな構成
    制御部には、Bluetooth機能が付いているSeeed studioESP32C6 切手サイズの小型マイコンを使うことにします
    部品余っているので、一応テスト的にESP32C3Miniでも作ってみました
    (ESP32C3Miniは、技適無いので、総務省に、技適未取得機器を用いた実験等の届け出必要)
    スマホ操作アプリは、前回使ったDabbleを使わせてもらいました
    Dabble App - STEMpedia Education STEMpedia IT教育用の教材を作っている会社みたいです
    これのGamepad Moduleを利用させてもらうことにします スマホは、Android 又はiOS(iPhone)どちらでも使用可能
    です
    後は、揚力用モータ、推進用モータは、E010系のドローン用のフレームを購入しました アリエクで安く販売されていました
    リチュム電池の保護は、ポリマー電池側にも保護回路が入っていますが、マイコン側で監視して電圧低下したら停止する
    ように構成しました
  2. 使った材料

    マイコン本体
    アンテナ内蔵
    充電回路内蔵
    マイコン本体
    余っていたので、
    作成動画を撮る
    為に利用
    モータドライバ
    羽付モータ
    E010ドローンの
    フレームキット
    です
    充電保護
    ユニット
    SeedStudioを使う
    場合は不要
    ダイソーで購入した
    発泡スチロール
    SeedStudioで
    使ったバッテリー
    C3miniで使った
    バッテリー
    テストなので安価
    な怪しいものを
    購入
    マイクロサーボ
    以下の互換品です
    ホバークラフトの
    スカート部で使用

    B8サイズ
    C3Miniの場合のみ
    利用

    後は、アルミパネル(0.3mmと、0.5mm) 両面テープ 接着剤 薄いダンボール ユニバーサル基盤 スイッチ 抵抗等


  3. 回路図
    ■Seeed studio ESP32C6_XIAO


    リポバッテリーで直接給電できて、充電回路も内蔵されているすぐれものです


    ■ESP32-C3_mini
    動画作成の為、仮に作ったものです


  4. 筐体図面
    ■筐体

    ■部品関係


    ■羽と、モータの組み合わせについて
     

     クワッドドーローン用モータと、羽は、右回転用2個 左回転用2個になっていますので、
     回転慣性を多少なりとも緩和させるため、モータは、右回転と、左回転の組み合わせで
     利用します 羽は、推力方向に合わせてください
     このモータには、多分、進角があると思われるので逆回転させると、トルクが下がる可能性
     があります


  5. ソフトについて
    ■スマホ側
     小生Androidなので、Google Playストアからインストールします
    Google Playストアで、

    dabble app で検索して
    左記アプリを
    インストールします
    起動すると、左記のような
    メニューが表示されます

    今回は、Gamepadを使
    います
    Gamepadを選択すると
    Digital Padが開きますので
    右上の□ボタンを押します
    モードの選択画面になります
    ので、Joystickモードを
    選択します
    Bluetoothで、ESP32と接続
    するには、
    接続アイコンを選択します
    ESP32が起動していれば、
    一覧に出ますので選択
    することで接続します


    ■ESP32側(Arduino IDE)
    ArudinoIDEの使い方は、別のサイトを参考にしてください
    ●Seeed studio ESP32C6_XIAOの場合 ボードの選択
     
     XIAO_ESP32C6を選択します


    ●ESP32C3 miniの場合
     
     ESP32C3 DevModuleを選択します


    ●留意事項
    ツールの、USB CDC On Bootが、Enabledであることを確認してください
    Disabledですと、シリアルコンソールに表示されない場合があります


    ※C3miniで、初期起動で、USB接続が不安定な場合 接続しないなどの場合 bootボタンを押しながら
    電源を投入してみてください

    ●Dabbleライブラリのインストール
    ライブラリマネージャで、DabbleESP32と検索します

    2025年10月現在バージョンは、1.5.1です

    exampleのGamepadコンパイルしたら、エラーだらけ エラーの内容みたら、Arduino-esp32のバージョンが、v2から
    v3に上がった時に構文の書き方が変更になったところです もうこの時小生もニキシー管の時計を作っているときで、
    バージョン上げたらいきなりコンパイルエラー もうなんじゃコレでした
    V2に下げるのもいまいちなので、エラー部 ライブラリのソースを修正しました
    だいたい、構文変わってもいいけど、古い構文も互換で使えるようにしてほしいものです・・・。

    上記6ファイルの中身を修正しました
    修正分は、下記においておきます 6ファイルを入れ替えます V1.5.1用です
    DabbleESP32.zip


    参考プログラム例 サンプルにちょこっと追記しています

    Startボタンを押すと、揚力ファンを起動します
    Selectボタンを押すと、揚力ファンを停止します
    ▲ボタンを押すと、推力ファンを前進にセットします
    ×ボタンを押すと、推力ファンを後退にセットします
    ■ボタンを押すと、揚力ファンの回転数を下げます
    ●ボタンを押すと、揚力ファンの回転数を上げます
    Joystickは、後ろ方向も前方向と同じように動作します 倒す量によって推力ファンの回転数が変化します
    方向で、ラダーが変化します



  6. プログラム
    ■Seeed studio ESP32C6_XIAOの場合
    /*
       Gamepad module provides three different mode namely Digital, JoyStick and Accerleometer.

       You can reduce the size of library compiled by enabling only those modules that you want to
       use. For this first define CUSTOM_SETTINGS followed by defining INCLUDE_modulename.

       Explore more on: https://thestempedia.com/docs/dabble/game-pad-module/
    */
    #define CUSTOM_SETTINGS
    #define INCLUDE_GAMEPAD_MODULE
    #include <DabbleESP32.h>
    #include <ESP32Servo.h>
    #define SERVO_PIN D6
    #define AIN1 D9 //揚力ファン
    #define AIN2 D10
    #define BIN1 D8 //推力ファン
    #define BIN2 D7

    Servo myServo;
    int Fstart=0;
    int Fford=1;
    int Vdown=0;

    void setup() {
      // put your setup code here, to run once:
      Serial.begin(115200);      // make sure your Serial Monitor is also set at this baud rate.
      Dabble.begin("MyEsp32c6");       //set bluetooth name of your device
      Serial.println("Start");
      myServo.attach(SERVO_PIN);
      pinMode(LED_BUILTIN, OUTPUT); //内蔵LEDを有効化
      pinMode(AIN1, OUTPUT);
      pinMode(AIN2, OUTPUT);
      pinMode(BIN1, OUTPUT);
      pinMode(BIN2, OUTPUT);
      pinMode(A0, INPUT);
      digitalWrite(LED_BUILTIN,HIGH); //内蔵LED消灯 LOWで点灯する
    }

    int pwmB=0;
    int pwmA=0;
    void hovercont(){
      Dabble.processInput();             //this function is used to refresh data obtained from smartphone.Hence calling this function is mandatory in order to get data properly from your mobile.
      //Serial.print("KeyPressed: ");
      if (GamePad.isUpPressed())
      {
        //Serial.print("Up");
      }

      if (GamePad.isDownPressed())
      {
        //Serial.print("Down");
      }

      if (GamePad.isLeftPressed())
      {
        //Serial.print("Left");
      }

      if (GamePad.isRightPressed())
      {
        //Serial.print("Right");
      }

      if (GamePad.isSquarePressed())
      {
        //Serial.print("Square");
        if(pwmA>0){pwmA=pwmA-1;

        analogWrite(AIN1, pwmA); // IN1にPWM信号
        analogWrite(AIN2, LOW);
        delay(100);
        }  // IN2をLOWにして正回転
      }

      if (GamePad.isCirclePressed())
      {
        //Serial.print("Circle");
        if(pwmA<255){pwmA=pwmA+1;
        analogWrite(AIN1, pwmA); // IN1にPWM信号
        analogWrite(AIN2, LOW);  
        delay(100);
        }

      }

      if (GamePad.isCrossPressed())
      {
        //Serial.print("Cross");
        Fford=0;
      }

      if (GamePad.isTrianglePressed())
      {
        //Serial.print("Triangle");
        Fford=1;

      }

      if (GamePad.isStartPressed())
      {
        //Serial.print("Start");
        if(Fstart==0){
          Fstart=1;
          pwmA=120;
          analogWrite(AIN1, pwmA); // IN1にPWM信号
          analogWrite(AIN2, LOW);  // IN2をLOWにして正回転
        }

      }

      if (GamePad.isSelectPressed())
      {
        //Serial.print("Select");
        if(Fstart==1){
          Fstart=0;
          analogWrite(AIN1, LOW); // IN1にPWM信号
          analogWrite(AIN2, LOW);  // IN2をLOWにして正回転
        }
      }
      //Serial.print('\t');

      int a = GamePad.getAngle();
      if (a != 0){
      //Serial.print("Angle: ");
      //Serial.print(a);
      //Serial.print('\t');
        if(pwmB!=0){
          if(a <181){
            int out=map(a,0,180,30,150);
            myServo.write(out);
          }
          if(a>180){
            int out=map(a,181,360,150,30);
            myServo.write(out);        

          }
        }
       
      }


      int b = GamePad.getRadius();
      //Serial.print("Radius: ");
      //Serial.print(b);
      //Serial.print('\t');
      b=abs(b);
      int XB=map(b,0,7,0,255);
      if (XB!=pwmB){
        pwmB=XB;
        if(Fford==1){
          analogWrite(BIN1, pwmB); // IN1にPWM信号
          analogWrite(BIN2, LOW);  // IN2をLOWにして正回転
        }
        else{
          analogWrite(BIN1, LOW); // IN1にPWM信号
          analogWrite(BIN2, pwmB);  // IN2をLOWにして正回転      
        }
          if(pwmB==0){myServo.write(90);}
      }


      float c = GamePad.getXaxisData();
      //Serial.print("x_axis: ");
      //Serial.print(c);
      //Serial.print('\t');
      float d = GamePad.getYaxisData();
      //Serial.print("y_axis: ");
      //Serial.println(d);
      //Serial.println();

    }


    void loop() {

      uint32_t Vbatt = 0;
      for(int i = 0; i < 5; i++) {
        Vbatt += analogReadMilliVolts(A0); // Read and accumulate ADC voltage
      }
      float Vbattf = 2 * Vbatt / 5 / 1000.0;     // Adjust for 1:2 divider and convert to volts
      //Serial.println(Vbattf, 3);                  // Output voltage to 3 decimal places
      if (Vbattf<3.4){
        Vdown=1;
        analogWrite(BIN1, LOW); // IN1に停止
        analogWrite(BIN2, LOW);  // IN2をLOWにして正回転
        analogWrite(AIN1, LOW); // IN1に停止
        analogWrite(AIN2, LOW);  // IN2をLOWにして正回転    
      }
      if (Vdown!=1){
        hovercont();
      }
      else{digitalWrite(LED_BUILTIN,LOW);}//電圧が低くなったら、LEDを点灯させる

    }


    ■ESP32C3 miniの場合
     
    /*
       Gamepad module provides three different mode namely Digital, JoyStick and Accerleometer.

       You can reduce the size of library compiled by enabling only those modules that you want to
       use. For this first define CUSTOM_SETTINGS followed by defining INCLUDE_modulename.

       Explore more on: https://thestempedia.com/docs/dabble/game-pad-module/
    */
    #define CUSTOM_SETTINGS
    #define INCLUDE_GAMEPAD_MODULE
    #include <DabbleESP32.h>
    #include <ESP32Servo.h>
    #define SERVO_PIN 5
    #define AIN1 6
    #define AIN2 7
    #define BIN1 9
    #define BIN2 10
    #define LED_BUL 8

    Servo myServo;
    int Fstart=0;
    int Fford=1;
    int Vdown=0;

    void setup() {
      // put your setup code here, to run once:
      Serial.begin(115200);      // make sure your Serial Monitor is also set at this baud rate.
      Dabble.begin("MyEsp32c3");       //set bluetooth name of your device
      Serial.println("Start");
      myServo.attach(SERVO_PIN);
      pinMode(LED_BUL, OUTPUT);
      pinMode(AIN1, OUTPUT);
      pinMode(AIN2, OUTPUT);
      pinMode(BIN1, OUTPUT);
      pinMode(BIN2, OUTPUT);
      pinMode(3, ANALOG);
      digitalWrite(LED_BUL,HIGH); //内蔵LED消灯 LOWで点灯する
    }

    int pwmB=0;
    int pwmA=100;
    void hovercont(){
      Dabble.processInput();             //this function is used to refresh data obtained from smartphone.Hence calling this function is mandatory in order to get data properly from your mobile.
      //Serial.print("KeyPressed: ");
      if (GamePad.isUpPressed())
      {
        //Serial.print("Up");
      }

      if (GamePad.isDownPressed())
      {
        //Serial.print("Down");
      }

      if (GamePad.isLeftPressed())
      {
        //Serial.print("Left");
      }

      if (GamePad.isRightPressed())
      {
        //Serial.print("Right");
      }

      if (GamePad.isSquarePressed())
      {
        //Serial.print("Square");
        if(pwmA>0){pwmA=pwmA-1;

        analogWrite(AIN1, pwmA); // IN1にPWM信号
        analogWrite(AIN2, LOW);
        delay(100);
        }  // IN2をLOWにして正回転
      }

      if (GamePad.isCirclePressed())
      {
        //Serial.print("Circle");
        if(pwmA<255){pwmA=pwmA+1;
        analogWrite(AIN1, pwmA); // IN1にPWM信号
        analogWrite(AIN2, LOW);  
        delay(100);
        }    
      }

      if (GamePad.isCrossPressed())
      {
        //Serial.print("Cross");
        Fford=0;
      }

      if (GamePad.isTrianglePressed())
      {
        //Serial.print("Triangle");
        Fford=1;

      }

      if (GamePad.isStartPressed())
      {
        //Serial.print("Start");
        if(Fstart==0){
          Fstart=1;
          analogWrite(AIN1, pwmA); // IN1にPWM信号
          analogWrite(AIN2, LOW);  // IN2をLOWにして正回転
        }

      }

      if (GamePad.isSelectPressed())
      {
        //Serial.print("Select");
        if(Fstart==1){
          Fstart=0;
          analogWrite(AIN1, LOW); // IN1にPWM信号
          analogWrite(AIN2, LOW);  // IN2をLOWにして正回転
        }
      }
      //Serial.print('\t');

      int a = GamePad.getAngle();
      if (a != 0){
      //Serial.print("Angle: ");
      //Serial.print(a);
      //Serial.print('\t');
        if(pwmB!=0){
          if(a <181){
            int out=map(a,0,180,30,150);
            myServo.write(out);
          }
          if(a>180){
            int out=map(a,181,360,150,30);
            myServo.write(out);
          }              
        }
       
      }


      int b = GamePad.getRadius();
      //Serial.print("Radius: ");
      //Serial.print(b);
      //Serial.print('\t');
      b=abs(b);
      int XB=map(b,0,7,0,255);
      if (XB!=pwmB){
        pwmB=XB;
        if(Fford==1){
          analogWrite(BIN1, pwmB); // IN1にPWM信号
          analogWrite(BIN2, LOW);  // IN2をLOWにして正回転
        }
        else{
          analogWrite(BIN1, LOW); // IN1にPWM信号
          analogWrite(BIN2, pwmB);  // IN2をLOWにして正回転      
        }
          if(pwmB==0){myServo.write(90);}
      }


      float c = GamePad.getXaxisData();
      //Serial.print("x_axis: ");
      //Serial.print(c);
      //Serial.print('\t');
      float d = GamePad.getYaxisData();
      //Serial.print("y_axis: ");
      //Serial.println(d);
      //Serial.println();


    }


    void loop() {

      uint32_t Vbatt = 0;
      for(int i = 0; i < 5; i++) {
        Vbatt += analogReadMilliVolts(3); // Read and accumulate ADC voltage
      }
      float Vbattf = 2 * Vbatt / 5 / 1000.0;     // Adjust for 1:2 divider and convert to volts
      //Serial.println(Vbattf, 3);                  // Output voltage to 3 decimal places
      if (Vbattf<3.2){
        Vdown=1;
        analogWrite(BIN1, LOW); // IN1に停止
        analogWrite(BIN2, LOW);  // IN2をLOWにして正回転
        analogWrite(AIN1, LOW); // IN1に停止
        analogWrite(AIN2, LOW);  // IN2をLOWにして正回転    
      }
      if (Vdown!=1){
        hovercont();
      }
      else{digitalWrite(LED_BUL,LOW);}//電圧が低くなったら、LEDを点灯させる

    }





    以上間違い等ありましたら、以下ご意見箱にお願いします

   

    ご意見箱