ESP32-CAMでカメラ付きロボット(カムロボ)を遠隔制御


はじめに

  • ESP32-CAMを技適取得済のESP32につけ替えて「遠隔制御できる自走カメラ付きロボット」を作ってみた。
    • ロボットに搭載されたESP32-CAMのカメラの映像を見ながら、スマホから上下左右、停止ボタンで制御する。
    • ESP32のSoftAPモードを使用し、ロボット自体がWiFiのアクセスポイントにし、スマホから直接接続。
    • 真っ暗な所にも入って行けるように、ESP32-CAM搭載のフラッシュライトも使用可能。
    • (ライトを点灯させて暗い部屋を探検すると、結構楽しい)

ロボット概要

ボディ

  • タミヤ模型の「カム・プログラム・ロボット」(Amazonで2500円くらい)をベースにする。
  • 非常によく考えられた作りで、四角いボディにマイコン機器を搭載することができ、拡張性がバツグン。
    • 眼の部分に穴が空いていて、LEDがぴったりハマる。
    • ちゃんと腕もキャタピラに連動して動き、コミカルでカワイイ。
    • いきなりこの完成度、この価格で動くロボットはなかなか他に無いんじゃ無いかな。
    • さすがタミヤ!!

制御方法

  • Arduino IDEのCameraWebServerを改造し、リモートコントローラーとする。
  • ESP32のSoftAPモードを使用し、ロボット自体をWiFiのアクセスポイントにする。
    • これでスマホをコントローラーにして、部屋の外でもロボットを動かして遊べる!!

主なパーツ

  • カムプログラムロボット(2500円)
  • ESP32-CAM (820円)
  • モバイルバッテリ 5V
    • ESP32とモーターを1つのバッテリから電源を取る場合、2A以上出力できる高性能なものが必要。
    • ESP32とモーターを別電源にするなら、ESP32用の5V電源はダイソーで500円で手に入る。
    • いずれにしろモータを動かして何度も遊ぶので、充電可能なバッテリが良い。
  • Micro USB DIP ボード (10個で350円くらい)
  • ミニブレッドボード
  • モータードライバ L9110S (5個で400円)

カムロボ改造&組み立て

  • カムロボから、カム制御ギア部分を取り外し、代わりにモータードライバを接続する。
  • 写真はダイソーの500円モバイルバッテリを仮組みした状態。
    • Micro USB DIP ボードをミニブレッドボードに挿してバッテリから 5V, GND を取れるようにする。
    • このバッテリの場合、MAX 1Aしか出ないので、別途モータードライバに電源(1.5〜3.0V)を供給する必要がある。

配線

配線は以下の通り。

  • ESP32-CAMは映像配信で負荷がかかるので 5Vを供給した方が良い。
    • (自分の場合、3.3V 電源だと不安定となりESP32が落ちることがあった)
ESP32-CAM モータドライバ 電源1 電源2
GPIO 12 A-1A
GPIO 13 A-1B
GPIO 14 B-1A
GPIO 15 B-1B
GND GND GND GND
5V - 5V
- VCC 1.5 - 3.0 V
  • モーターから出ている導線(青、黄)についてはここでは気にしない。
    • 前後左右の命令に対して、もし逆だったら後述のプログラム側で修正する。
モータードライバ カムロボ
MOTOR A (+, -) モーター1(青色、黄色の導線)
MOTOR B (+, -) モーター2(青色、黄色の導線)

ソースコード

  • https://github.com/gitnabeshin/ESP32CamRobot
    • 今回はGitHubに丸ごと置いてみた。
    • 本来、GitHubのお作法だと、オリジナルをForkして改変すべきらしいが、Arduino-ESP32のたった一つのサンプルコードで、しかもPullしてもらう内容でもないので、リポジトリにリンクを貼らせてもらうだけとしている。
    • (こういう場合、どうしたらいいのかどなたか教えてください。)
  • ビルド環境要件
    • Arduino-ESP32 Library V1.0.3 (最近リリースされたばかりのやつ) が必要。
    • ベースとしているCameraWebServerのコードがアップデートされているため、こちらに合わせてあります。

上記をローカルフォルダに持ってきてArduino-IDEでビルドすれば動く状態になっています。

実行

  • 正常に起動すると、ESP32-CAMのフラッシュライトが軽く点滅する。
    • 見た目上、ちゃんと起動できてるか全く分からないのでこれで状態表示している。
    • ただ、めちゃめちゃ明るいので顔の近くで点灯すると眩しすぎる。
    • できれば別途赤色LEDでも用意して空いてるGPIOからステータス表示した方が良い。
  • 起動を確認したら、スマホのWiFiアクセスポイント一覧に 「ESP32-NET」が出ているはずなので選択。
    • WifiアクセスポイントのSSIDは ESP32-NET
    • パスワードは Espressif_dev
    • ESP32の固定IPアドレスは 192.168.0.12
    • (ここら辺はソースコードをいじれば自由に設定可能)
  • 接続できたら、ブラウザで http://192.168.0.12 を開くとコントローラ画面が表示される。
  • ここまでできたらEnjoy Yourself !!

ソースコード解説

robot_pins.h

  • ESP32-CAMのロボット制御用のGPIO ピン設定を定義。
    • Arduino-ESP32 Library V1.0.3でGPIOピン定義がヘッダファイルに切り出されたのでこれに習う。 
//CAM Robot Configurations
#define FLASH_GPIO_NUM   4
#define MOTOR_A1        12 
#define MOTOR_A2        13
#define MOTOR_B1        14
#define MOTOR_B2        15

CameraWebServer.ino

  • robot_pins.hをインクルード
  • setup()関数内でSoftAPモードを設定(まあ、これらは各人でご自由に決めて頂ければと。。。)
    • WifiアクセスポイントのSSIDは ESP32-NET
    • パスワードは Espressif_dev
    • ESP32の固定IPアドレスは 192.168.0.12
  • GPIOピン設定
  • 起動完了したかどうか分かるように正常起動後にフラッシュライトを点滅
// Select camera model
//#define CAMERA_MODEL_WROVER_KIT
//#define CAMERA_MODEL_ESP_EYE
//#define CAMERA_MODEL_M5STACK_PSRAM
//#define CAMERA_MODEL_M5STACK_WIDE
#define CAMERA_MODEL_AI_THINKER

#include "camera_pins.h"
#include "robot_pins.h"

//const char* ssid = "*********";
//const char* password = "*********";

//ESP32 SoftAP Configration
const char ssid[] = "ESP32-NET";
const char pass[] = "Espressif_dev";
const IPAddress ip(192,168,0,12);
const IPAddress subnet(255,255,255,0);

void setup() {

// 長いので省略 

  //SoftAP
  WiFi.softAP(ssid,pass);
  delay(100);
  WiFi.softAPConfig(ip,ip,subnet);
  IPAddress myIP = WiFi.softAPIP();

/*
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
*/

  Serial.println("ESP32 SoftAP Mode start.");
  Serial.print("SSID:");
  Serial.println(ssid);

  startCameraServer();

  Serial.print("Camera Ready! Use 'http://");
  //Serial.print(WiFi.localIP());
  Serial.print(myIP);
  Serial.println("' to connect");

  pinMode(FLASH_GPIO_NUM, OUTPUT);
  pinMode(MOTOR_A1, OUTPUT);
  pinMode(MOTOR_A2, OUTPUT);
  pinMode(MOTOR_B1, OUTPUT);
  pinMode(MOTOR_B2, OUTPUT);

  digitalWrite(FLASH_GPIO_NUM, HIGH);
  delay(300);
  digitalWrite(FLASH_GPIO_NUM, LOW);
  delay(300);
  digitalWrite(FLASH_GPIO_NUM, HIGH);
  delay(300);
  digitalWrite(FLASH_GPIO_NUM, LOW);
}

app_httpd.cpp

  • robot_pins.hをインクルード
  • ボタン押下に応じてロボットを制御(前後左右、停止)
  • 実際に動かしてみて、もし想定と逆向きに制御しちゃってたら、実際のモータ接続状況に応じて直す。
  • 特に、ダブルギアボックスには左右逆にモータがついてるので、前進するにはどう制御したらいいかここで考える感じ。
    • 接続を治すよりコードを修正した方が簡単。
static esp_err_t cmd_handler(httpd_req_t *req){

// 長いので省略

    //Flash Light Action
    else if(!strcmp(variable, "flash_enabled")) {
      flash_enabled = val;
      if(flash_enabled){
        Serial.println("[FLASH] ON");
        digitalWrite(FLASH_GPIO_NUM, HIGH);
      } else{
        Serial.println("[FLASH] OFF");
        digitalWrite(FLASH_GPIO_NUM, LOW);
      }
    }
    //CAM Robot Actions
    else if(!strcmp(variable, "forward")) {
      Serial.println("[Motor] FORWARD");
      digitalWrite(MOTOR_A1, HIGH); //RIGHT
      digitalWrite(MOTOR_A2, LOW);
      digitalWrite(MOTOR_B1, HIGH); //LEFT
      digitalWrite(MOTOR_B2, LOW);
    }
    else if(!strcmp(variable, "turnLeft")) {
      Serial.println("[Motor] LEFT");
      digitalWrite(MOTOR_A1, HIGH); //RIGHT
      digitalWrite(MOTOR_A2, LOW);
      digitalWrite(MOTOR_B1, LOW);  //LEFT
      digitalWrite(MOTOR_B2, HIGH);
    }
    else if(!strcmp(variable, "stop")) {
      Serial.println("[Motor] STOP");
      digitalWrite(MOTOR_A1, LOW);  //RIGHT
      digitalWrite(MOTOR_A2, LOW);
      digitalWrite(MOTOR_B1, LOW);  //LEFT
      digitalWrite(MOTOR_B2, LOW);
    }
    else if(!strcmp(variable, "turnRight")) {
      Serial.println("[Motor] RIGHT");
      digitalWrite(MOTOR_A1, LOW);   //RIGHT
      digitalWrite(MOTOR_A2, HIGH);
      digitalWrite(MOTOR_B1, HIGH);  //LEFT
      digitalWrite(MOTOR_B2, LOW);
    }
    else if(!strcmp(variable, "backward")) {
      Serial.println("[Motor] BACK");
      digitalWrite(MOTOR_A1, LOW);   //RIGHT
      digitalWrite(MOTOR_A2, HIGH);
      digitalWrite(MOTOR_B1, LOW);   //LEFT
      digitalWrite(MOTOR_B2, HIGH);
    }
    else {
        res = -1;
    }

index_cam_robot.html

                    <nav id="menu">
                        <div align="center">
                        <table border="0">
                            <tr>
                               <td> </td>
                               <td><button id="forward"> ↑ </button></td>
                               <td> </td>
                            </tr>
                            <tr>
                                <td><button id="turnLeft"> ← </button></td>
                                <td><button id="stop"> □ </button></td>
                                <td><button id="turnRight"> → </button></td>
                            </tr>
                            <tr>
                                <td> </td>
                                <td><button id="backward"> ↓ </button></td>
                                <td> </td>
                            </tr>
                        </table></div>

  • ここで各ボタンを押した時にサーバにGETリクエストが飛ぶようになっている。
  //---------------------
  //Robot Control Button
  //---------------------
  const forwardButton = document.getElementById('forward')
  const turnLeftButton = document.getElementById('turnLeft')
  const stopButton = document.getElementById('stop')
  const turnRightButton = document.getElementById('turnRight')
  const backwardButton = document.getElementById('backward')

  //---------------------
  //Robot Control Action
  //---------------------
  forwardButton.onclick = () => {
      updateConfig(forwardButton)
  }
  turnLeftButton.onclick = () => {
      updateConfig(turnLeftButton)
  }
  stopButton.onclick = () => {
      updateConfig(stopButton)
  }
  turnRightButton.onclick = () => {
      updateConfig(turnRightButton)
  }
  backwardButton.onclick = () => {
      updateConfig(backwardButton)
  }

最後に

  • こういう工作をする際、意外と難しいのが電源選びである。
    • 充電できるもので高出力のものだと、デカかったり、高価だったりする。
  • そんな中、今の所個人的にベストな組み合わせはこれ。
    • ダイソーのモバイルバッテリ 5V 3000mAh(500円)
    • 秋月電子のニッケル水素電池パック3.6V 830mAh HHR-P104 (100円)
  • ただ、後者はかなり小さくて搭載しやすい半面、充電器が売ってないので自分で用意する必要があるけど。。