Wi-Fi ラジコンの作り方 スマホで簡単遠隔操作(Arduinoコマンド)

Wi-Fiラジコンカーの作り方

Wi-Fi通信を利用したラジコンカーの作り方を詳しく紹介します。

車体は市販の部品を組み合わせ流だけで、クローラータイプで作成します。
プログラムについてはサンプルプログラムを準備してますので、コピペで書き込むだけです。
操作にはスマホやパソコン等のブラウザを使用して動作確認までできるので、是非チャレンジしてみましょう♪


Bluetooth通信を利用したラジコンカーの作り方も以下のリンクで詳しく紹介しています。

LEGOで作るBluetooth遠隔操作ラジコン(Arduinoコマンド、ESP32系マイコンATOM使用)
M5StickC PlusとATOMを使用してBluetooth通信とジョイスティックで操作するラジコンカーをLEGOブロックで作ります。LEGOなので組み合わせは自由!自分好みのラジコンを作りましょう♪
スポンサーリンク

1.作成するラジコンカーについて

今回作るラジコンカーの部品構成について詳しく紹介します。
できるだけ市販部品の組み合わせで完成できるように部品選定しました。
モーターの配線だけはどうしても半田付けが必要になるので頑張ってチャレンジしてみましょうw

・ラジコンカー本体

ラジコンカー本体はタミヤの「ダブルギヤボックス」と「ユニバーサルプレート」「トラック&ホイールセット」を使用したクローラータイプで作成しています。

Wi-Fiラジコンカーの作り方、ラジコン本体

駆動回路はDCモーター駆動用ドライバ(MX1508)を使用し、制御用マイコンボードには「ATOMS3」を使用しています。

「ATOMS3」については、以下のリンクで詳しく紹介しています。

AtomS3の使い方、端子配列、開発環境、サンプルプログラムで詳しく紹介
プログラミング学習も電子工作もこれ1台でOK!コンパクトな設計に液晶画面,WiFi,シリアル通信,6軸センサまで搭載したAtomS3について詳しく紹介します。

電源はモバイルバッテリー(写真では自作のものを使用)の5Vを分岐タイプのUSBケーブルで「ATOMS3」とモーター駆動回路に供給し、モーターは3Vモーターのため「三端子レギュレータ」で3.3Vに降圧して使用しています。

各部品の詳細は以下でそれぞれ詳しく紹介します。

・駆動回路(モータードライバMX1508、マイコンボード)

駆動回路は以下写真のようになります。モータ駆動用ドライバ(MX1508)とマイコンボード「ATOMS3」をミニブレッドボードに実装しています。

DCモータの速度制御方法 Arduinoコマンド
Wi-Fiラジコンカーの作り方、駆動回路

回路図は下画像のようになります。

Wi-Fiラジコンカーの作り方、駆動回路

3V電源はモバイルバッテリーからの5Vを三端子レギュレータを使って3.3Vに降圧して使用しています。
この駆動部については、以下のリンクでDCモーターの制御方法と合わせて詳しく紹介しています。

DCモータの速度制御方法(Arduinoコマンド)3V以下モータ対応
PWMを使用してDCモータの速度制御をする方法をArduinoコマンドを使用して詳しく紹介します。ドライバ基板MX1508で3V以下のモータに最適。ATOMS3使用

・モーター(タミヤ製ダブルギヤボックス)

モーターにはタミヤ製のダブルギヤボックス等を使用して下写真のように組み立てています。
タミヤ製の部品を組み合わせて作るスタンダードな構成なので、1つ作っておくと色々応用が効きます。

タミヤ製ダブルギヤボックス付きクローラー
タミヤ製ダブルギヤボックス
配線は別途半田付けで接続する必要があります。

ドライバ基板に接続するコネクタは以下の工具とコネクタを1つ持っておくと色々応用が効くので便利です。圧着工具としては格安ですが、値段は値段なりで圧着してそのまま使えるかというと・・・そのまま使うのは難しいように思います。
圧着後に形を整えて端子を挿入することになるので手間はかかりますw、個人的には値段の安さを考えるとお得なので結構便利に使ってます。ご参考までに^^;

・電源部(モバイルバッテリー)

電源部は小型のモバイルバッテリーがあれば十分です。USBケーブルは100均で売っている下写真の「USB-microB」と「USB-TypeC」の分岐端子が便利です。

私は小型のモバイルバッテリーに丁度いい物がなかったので、下写真のような9V充電電池を使用した5V電源を使用しましたので少し紹介しておきます。

以下は今回使用した5V電源で、私がモバイルバッテリー代わりに使っているものです。

Wi-Fiラジコンカーの作り方、電源部
Wi-Fiラジコンカーの作り方、電源部

ケースは、最近はモノによっては袋になってしまいましたが「M5Stack社」のユニットを購入した時に使われているケースを加工して、9Vの充電電池とブレッドボード用の「5V、3.3V、USB-TypeA付電源基板」を加工して作成したモノです。

Wi-Fiラジコンカーの作り方、電源部
Wi-Fiラジコンカーの作り方、電源部

ケースの加工と基板の加工が結構手間ですが便利に使っています。
写真の右が加工前の基板で左が加工したものです。(ゴム足も100均で売っているものなので探してみましょう♪)

Wi-Fiラジコンカーの作り方、電源部
Wi-Fiラジコンカーの作り方、電源部
電池タイプのモバイル充電器といえば、ほとんどが単3電池4本のものばかりです。
電池タイプを使うのは緊急時なので単3電池を4本はちょっと寸法や重量、コスパも悪いと思って9V電池タイプを探したんですが、無いんですよね^^;
9V電池は100均で買えるので、9V電池タイプがあると便利だと思って作ったものです。
ケースはお好きなもので、1つ作っておくと便利です。

以下は100均で売ってるので探してみましょう♪

・操作画面の紹介(スマホ、ブラウザ)

操作画面にはスマホやパソコンのブラウザ画面を使用します。
マイコンボードの「ATOMS3」をサーバーとして設定し液晶画面に表示される「SSID(自宅のネットワーク環境)」に接続して「IPアドレス」を入力することで以下の画面が表示されます。

簡易編

Wi-Fiラジコンカーの作り方、操作画面

「簡易編」では上画像のような画面が表示され、各ボタンを押すことでラジコンカーを操作できます。
ブラウザ画面のプログラムをできるだけシンプルにしていますので、各ボタンを押すたびに画面が更新されます。
速度についてはプログラムの中で「0〜255」で指定します。初期値は「127」にしています。

応用編(可変速)

Wi-Fiラジコンカーの作り方、操作画面

「応用編」では基本的な操作は「簡易編」と同じですが、画面下のスライドバーで速度を可変できるようにしています。
「簡易編」とは違い各ボタンを押しても画面は更新されません。モーターを可変速するためのデータを送信することもできます。
このため、ブラウザプログラムには「Javascript」を使用しているのでプログラムは少し長くなります。

スポンサーリンク

2.簡易編

簡易編では、以前紹介した以下リンクのDCモーター駆動プログラムにWi-Fi操作用のプログラムを追加したものです。
操作画面のブラウザページはできるだけシンプルに作成しています。

DCモータの速度制御方法(Arduinoコマンド)3V以下モータ対応
PWMを使用してDCモータの速度制御をする方法をArduinoコマンドを使用して詳しく紹介します。ドライバ基板MX1508で3V以下のモータに最適。ATOMS3使用

・サンプルプログラム(コピペ)

簡易編のサンプルプログラムは以下になります。コピペで貼り付けて書き込んで実行してください。
※下コード(黒枠)内の右上角にある小さなアイコンのクリックでコピーできます。

150, 151行目」の「SSID」と「パスワード」はご自宅のネットワーク環境に合わせて設定してください。
#include <M5AtomS3.h>     // ATOMS3用ライブラリ
#include <WiFi.h>         // WiFi接続用
#include <WebServer.h>    // サーバー設定用

#define INT1 8      // モーター1正転信号端子(INT1)
#define INT2 7      // モーター1逆転信号端子(INT2)
#define INT3 6      // モーター2正転信号端子(INT3)
#define INT4 5      // モーター2逆転信号端子(INT4)

#define CH_INT1 0   // PWM出力チャンネル(0,1/ 2,3/ 4,5/ 6,7/ 8,9/ 10,11 /12,13 /14,15で周波数、分解能設定が共通)
#define CH_INT2 1   //  ※チャンネル7は液晶バックライトと共用になるため指定禁止
#define CH_INT3 2
#define CH_INT4 3
#define FREQ 500    // PWM出力周波数(200〜1000推奨 ※高いとトルク低下)
#define BIT_NUM 8   // PWM出力bit数(8bit固定)

// サーバー設定、ポート80で接続
WebServer server(80);

// 変数宣言
int speed = 127; // モーター速度指定値格納用

// HTML 文字列を作成する ※R"( ここに書いたものは改行も無視して文字列として扱われる )"
char html[] = R"(
<!DOCTYPE html>
<html lang="ja">
  <head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <style>
    body {display: flex;flex-direction: column;align-items: center;}
    h1 {text-align: center;font-size: 16px;margin: 0;color: #555;}
    hr {width: 320px;border-color: #aaa;border-width: 1px;}
    .button-group {display: flex;justify-content: center;margin: 5px;}
    .button-group > button {width: 100px;height: 70px;background-color: #eaeaea;border: none;border-radius: 5px;font-size: 16px;margin: 0 5px;user-select: none;}
  </style>
  <title>Wi-Fi MOTOR CONTROLLER</title>
</head>
<body>
  <h1>Wi-Fi MOTOR CONTROLLER</h1>
  <hr>
  <div class="button-group">
    <button onclick="location.href='/get/for_left'">左前</button>
    <button onclick="location.href='/get/forword'">前進</button>
    <button onclick="location.href='/get/for_right'">右前</button>
  </div>
  <div class="button-group">
    <button onclick="location.href='/get/left_turn'">左旋回</button>
    <button onclick="location.href='/get/stop'">停止</button>
    <button onclick="location.href='/get/right_turn'">右旋回</button>
  </div>
  <div class="button-group">
    <button onclick="location.href='/get/rev_left'">左後</button>
    <button onclick="location.href='/get/reverse'">後退</button>
    <button onclick="location.href='/get/rev_right'">右後</button>
  </div>
</body>
</html>
)";
// PWM出力実行関数 **************************************************************
void pwmOutput(int duty1, int duty2, int duty3, int duty4) {
  ledcWrite(CH_INT1, duty1);  // PWM出力実行(チャンネル, Duty比)
  ledcWrite(CH_INT2, duty2);
  ledcWrite(CH_INT3, duty3);
  ledcWrite(CH_INT4, duty4);
}
// サーバーリクエスト時処理関数 ****************************************************
// ルート(IPアドレス)アクセス時の応答関数
void handleRoot() {
  server.send(200, "text/html", html);  // レスポンス200を返しhtml送信
}
// エラー(Webページが見つからない)時の応答関数
void handleNotFound() {
  server.send(404, "text/plain", "404 Not Found!");  // text送信
}
// GET受信(モーター動作)処理関数 *************************************************
void forLeft() {                              // 左前進
  pwmOutput(speed/2, 0, speed, 0);            // PWM出力実行関数呼び出し
  server.send(200, "text/html", html);  // レスポンス200を返しhtml送信
}
void forword() {                              // 前進
  pwmOutput(speed, 0, speed, 0);
  server.send(200, "text/html", html);
}
void forRight() {                             // 右前進
  pwmOutput(speed, 0, speed/2, 0);
  server.send(200, "text/html", html);
}
void leftTurn() {                             // 左旋回
  pwmOutput(0, speed, speed, 0);
  server.send(200, "text/html", html);
}
void stop() {                                 // 停止
  pwmOutput(255, 255, 255, 255);              // ブレーキ停止
  delay(300);
  pwmOutput(0, 0, 0, 0);                      // フリー停止
  server.send(200, "text/html", html);
}
void rightTurn() {                            // 右旋回
  pwmOutput(speed, 0, 0, speed);
  server.send(200, "text/html", html);
}
void revLeft() {                              // 左後退
  pwmOutput(0, speed/2, 0, speed);
  server.send(200, "text/html", html);
}
void reverse() {                              // 後退
  pwmOutput(0, speed, 0, speed);
  server.send(200, "text/html", html);
}
void revRight() {                             // 右後退
  pwmOutput(0, speed, 0, speed/2);
  server.send(200, "text/html", html);
}
// 初期設定 ----------------------------------------------------------
void setup() {
  M5.begin(true, true, false, false); // AtomS3初期設定(LCD,UART,I2C,LED)
  USBSerial.begin(9600);              // シリアル通信初期化
  delay(1000);                        // シリアル出力開始待ち
  USBSerial.println("ATOM S3 Wi-Fi MOTOR CONTROLLER!!");  // シリアル出力

  // PWM出力に使用する端子を出力設定
  pinMode(INT1, OUTPUT);   // モーター1正転
  pinMode(INT2, OUTPUT);   // モーター1逆転
  pinMode(INT3, OUTPUT);   // モーター2正転
  pinMode(INT4, OUTPUT);   // モーター2逆転
  // PWM初期設定
  ledcSetup(CH_INT1, FREQ, BIT_NUM); // PWM設定(チャンネル, 周波数, bit数)
  ledcSetup(CH_INT2, FREQ, BIT_NUM); // PWM設定(チャンネル, 周波数, bit数)
  ledcSetup(CH_INT3, FREQ, BIT_NUM); // PWM設定(チャンネル, 周波数, bit数)
  ledcSetup(CH_INT4, FREQ, BIT_NUM); // PWM設定(チャンネル, 周波数, bit数)
  ledcAttachPin(INT1, CH_INT1);  // PWMチャンネル割り当て(端子番号, チャンネル)(モーター1正転)
  ledcAttachPin(INT2, CH_INT2);  // PWMチャンネル割り当て(端子番号, チャンネル)(モーター1逆転)
  ledcAttachPin(INT3, CH_INT3);  // PWMチャンネル割り当て(端子番号, チャンネル)(モーター2正転)
  ledcAttachPin(INT4, CH_INT4);  // PWMチャンネル割り当て(端子番号, チャンネル)(モーター2逆転)

  // 液晶表示初期化
  M5.Lcd.begin();                   // 画面初期化
  M5.Lcd.setRotation(2);            // 画面向き設定(USB位置基準 0:上/ 1:左/ 2:下/ 3:右)
  M5.Lcd.fillScreen(BLACK);         // 背景
  M5.Lcd.println("Wi-Fi Serch!");   // Wi-Fi接続画面表示
  M5.Lcd.println(WiFi.softAPIP());  // SSID画面表示

  // Wi-Fiアクセスポイント接続
  // const char ssid[] = "ATOMS3-01"; // SSID
  // const char pass[] = "11111111";  // パスワード(8文字以上)
  // WiFi.softAP(ssid, pass);         // ソフトAP開始

  // Wi-Fiローカル接続
  const char ssid[] = "自宅のWiFi接続先を記入";  //接続先SSID
  const char pass[] = "接続先のパスワードを記入"; //接続先パスワード
  WiFi.begin(ssid, pass);                 // Wi-Fi接続開始
  while (WiFi.status() != WL_CONNECTED) { // 接続完了するまで繰り返す
    delay(500);                           // 0.5秒待機
    M5.lcd.print(".");                    //「.」画面表示
  }
  // サーバー設定(クライアントからのリクエストに応じて設定した関数を実行)
  server.on("/", handleRoot);               // ルート(IPアドレス)アクセス時の応答関数を設定
  server.onNotFound(handleNotFound);        // Webページが見つからない時の応答関数を設定
  server.on("/get/for_left", forLeft);      // 左前進ボタンオン受信処理
  server.on("/get/forword", forword);       // 前進ボタンオン受信処理
  server.on("/get/for_right", forRight);    // 右前進ボタンオン受信処理
  server.on("/get/left_turn", leftTurn);    // 左旋回ボタンオン受信処理
  server.on("/get/stop", stop);             // 停止ボタンオン受信処理
  server.on("/get/right_turn", rightTurn);  // 右旋回ボタンオン受信処理
  server.on("/get/rev_left", revLeft);      // 左後退ボタンオン受信処理
  server.on("/get/reverse", reverse);       // 後退ボタンオン受信処理
  server.on("/get/rev_right", revRight);    // 右後退ボタンオン受信処理
  server.begin();                           // Webサーバー開始
}
// メイン -------------------------------------------------------------------
void loop() {
  server.handleClient();  // クライアントからのアクセス確認
  // 液晶表示
  M5.Lcd.setTextColor(WHITE, BLACK);              // 文字色
  M5.Lcd.setTextFont(2);                          // フォント
  M5.Lcd.drawCentreString("WiFi Motor", M5.lcd.width()/2, 0, 4);  // 上中央座標を基準に文字表示(表示内容, x, y, フォント)
  M5.Lcd.drawCentreString("Remote Controller", M5.lcd.width()/2, 21, 2);  // 上中央座標を基準に文字表示(表示内容, x, y, フォント)
  M5.Lcd.drawFastHLine(0, 38, 128, WHITE);        // 指定座標から横線
  M5.Lcd.setCursor(0, 40);                        // カーソル座標指定
  M5.Lcd.printf("SSID: %s\n", WiFi.SSID());       // SSID表示
  // M5.Lcd.printf("SSID: %s\n", WiFi.softAPSSID()); // アクセスポイント時のSSID表示
  M5.Lcd.setTextColor(ORANGE, BLACK);             // 文字色
  M5.Lcd.print("IP  : ");                         // IPアドレス表示
  M5.Lcd.println(WiFi.localIP());
  // M5.Lcd.println(WiFi.softAPIP());             // アクセスポイント時のIPアドレス表示
  M5.Lcd.drawFastHLine(0, 74, 128, WHITE);        // 指定座標から横線
  M5.Lcd.setCursor(0, 78);                        // カーソル座標指定
  M5.Lcd.setTextColor(CYAN, BLACK);               // 文字色
  M5.Lcd.printf("Speed:%4d  \n", speed);          // モーター速度指定値表示
  M5.Lcd.printf("%3d, %3d, %3d, %3d\n", ledcRead(CH_INT1), ledcRead(CH_INT2), ledcRead(CH_INT3), ledcRead(CH_INT4));  // 各Duty比取得
  delay(100);               // 遅延時間
}

・プログラムの詳細

21行目」でモーターの速度を指定しています。
設定範囲は「0〜255(0〜3.3V)」で初期値は「127(約1.5V駆動相当)」に設定しています。
大きくするとモーターの回転速度が速くなるので必要に応じて調整してください。


24〜59行目」がスマホやパソコンのブラウザで表示されるラジコン操作画面の「HTMLプログラム」です。
「R”( ここにHTMLプログラムを記入 )” 」ここにコピペで貼り付けるだけで表示できます。
私は「HTML」や「JavaScript」は苦手なので、ほとんど「ChatGPT」に書いてもらってますw

ChatGPTについては、以下のリンクで紹介しています。

Arduinoプログラミング初心者必見!ChatGPTがもたらす新しい可能性
ChatGPTがプログラミングの考え方を変えました「こんなプログラムを書いて」と聞けばコード作成から説明、エラーの指摘までしてくれる。ChatGPTがもたらす新しい可能性を探ってみたいと思います。

76〜114行目」にはクライアント(ブラウザ)からの「リクエスト」に対する「レスポンス」と一緒に実行するプログラム(PWM出力実行関数呼び出し)を記入しています。
ここで操作画面のボタンに応じてモーターの動作を制御しています。

簡易編ではブラウザの操作画面のボタンを押すごとにページが更新されるため、毎回操作画面の「HTML」データを送信しています。
応用編では「JavaScript」を使用することで、ページの更新がされないようにしています。

160〜168行目」でサーバー(ATOMS3)にアクセスがあった時に実行する関数を指定しています。

今回のラジコン操作にはクライアント(ブラウザ)からの「リクエスト」に対して「レスポンス」を返すという「サーバー」の仕組みを利用しています。

「Arduinoコマンド」を使用した「サーバー」の使い方は以下のリンクで詳しく紹介していいます。

WiFi(ローカル接続、サーバー)で遠隔操作する方法(Arduinoプログラミング)
Wi-Fi通信でスマホから遠隔操作を行う方法を詳しく紹介。サーバーの仕組みを利用することで簡単にブラウザからの遠隔操作が実現できます。ATOM LITE使用

150〜156行目」で「Wi-Fi通信」を開始しています。
ご自宅のネットワーク環境の「SSID」と「パスワード」をプログラム内に設定(150, 151行目)して「ATOMS3」の液晶画面に表示される「IPアドレス(192.169.0.xxx)」をスマホやパソコンのブラウザのアドレスバーに入力するだけで接続されて、操作画面が表示されます。

外で遊ぶ場合など、Wi-Fi通信の無い環境では、コメントアウトされている「145〜147行目」のアクセスポイント接続プログラムを有効にして、スマホやパソコンのネットワーク接続先(SSID)を「ATOMS3-01」に設定して、IPアドレス「192.168.4.1」を入力すると、Wi-Fi環境がなくても遊ぶことができます。
アクセスポイント接続時は「150〜156行目」をコメントアウトして無効にしておいてください
液晶画面での「SSID」や「IPアドレス」の表示方法は「ローカルWi-Fi接続」とは異なるので「アクセスポイント接続時」は「181,185行目」をコメントアウトし「182,186行目」を有効にしてください。

アクセスポイントへの接続プログラムについては、以下のリンクで詳しく紹介しています。

WiFi(アクセスポイント、サーバー)で近距離遠隔操作する方法(Arduinoプログラミング)
「ATOM LITE」を例にWiFiアクセスポイントに設定して、スマホ等から近距離遠隔操作を行う方法を紹介します。サーバーのリクエスト(要求)に対してレスポンス(応答)を返すという動作を利用して、簡単なプログラムで遠隔操作が実現できます。

・動作確認

プログラムを書き込んで実行すると、自宅のWi-Fiネットワークに接続が開始されます。
接続が完了すると以下のような画面が「ATOMS3」の画面に表示されます。

表示されない場合はプログラミング内の「SSID」と「パスワード」や、スマホやパソコンのネットワーク接続先の設定を確認してください。
「ATOMS3」側面のボタンを押してリセットすることで繋がることもあります。
Wi-Fiラジコンカーの作り方、駆動回路
液晶画面下の「Speed: 」には現在のモーター速度指定値(0〜255)が表示されます。
その下には左から「モーター1の正転速度」「モーター1の逆転速度」「モーター2の正転速度」「モーター2の逆転速度」指定値が表示されます。

液晶画面の中で「192.168.0.2(お使いの環境によって異なります)」が接続先アドレスです。
これを、スマホやパソコンのブラウザ(ChromeやEdge等なんでも良いです)のアドレスバーに入力すると以下のような画面が表示されます。

Wi-Fiラジコンカーの作り方、操作画面

あとは、各ボタンを押すとラジコンカーが動作します。
速度についてはプログラムの中で指定(21行目)していますので、必要に応じて書き換えてください。

速度設定について、最大の「255」にして前進後退を激しく繰り返したり、モーターに大きな負荷がかかったりすると挙動がおかしくなることがあります。
これはモーター起動時に突入電流という大きな電流が流れて一瞬電圧降下し「ATOMS3」にリセットがかかったり動作が不安定になるためです。
速度設定に関してはあまり大きくせずに、200程度までで様子を見ながら動作確認してみてください。
スポンサーリンク

3.応用編(可変速)

応用編のプログラムも、以前紹介した以下リンクのDCモーター駆動プログラムにWi-Fi操作用のプログラムを追加したものです。
操作画面のブラウザページには「JavaScript」を使用して、より実用的な操作画面にしています。
「JavaScript」を使用すルコとでページの更新もなく、データの送信もできるのでモーターの速度をブラウザ画面のスライドバーで可変速できるようにしています。

DCモータの速度制御方法(Arduinoコマンド)3V以下モータ対応
PWMを使用してDCモータの速度制御をする方法をArduinoコマンドを使用して詳しく紹介します。ドライバ基板MX1508で3V以下のモータに最適。ATOMS3使用

・サンプルプログラム(コピペ)

応用編のサンプルプログラムは以下になります。コピペで貼り付けて書き込んで実行してください。
※下コード(黒枠)内の右上角にある小さなアイコンのクリックでコピーできます。

246, 247行目」の「SSID」と「パスワード」はご自宅のネットワーク環境に合わせて設定してください。
#include <M5AtomS3.h>     // ATOMS3用ライブラリ
#include <WiFi.h>         // WiFi接続用
#include <WebServer.h>    // サーバー設定用
#include <ArduinoJson.h>  // JSONデータ処理用

#define INT1 8      // モーター1正転信号端子(INT1)
#define INT2 7      // モーター1逆転信号端子(INT2)
#define INT3 6      // モーター2正転信号端子(INT3)
#define INT4 5      // モーター2逆転信号端子(INT4)

#define CH_INT1 0   // PWM出力チャンネル(0,1/ 2,3/ 4,5/ 6,7/ 8,9/ 10,11 /12,13 /14,15で周波数、分解能設定が共通)
#define CH_INT2 1   //  ※チャンネル7は液晶バックライトと共用になるため指定禁止
#define CH_INT3 2
#define CH_INT4 3
#define FREQ 500    // PWM出力周波数(200〜1000推奨 ※高いとトルク低下)
#define BIT_NUM 8   // PWM出力bit数(8bit固定)

// サーバー設定、ポート80で接続
WebServer server(80);

// JSONデータ格納用メモリの確保
StaticJsonDocument<200> json;

// 変数宣言
int speed; // モーター速度指定値格納用

// HTML 文字列を作成する ※R"( ここに書いたものは改行も無視して文字列として扱われる )"
char html[] = R"(
<!DOCTYPE html>
<html lang="ja">
  <head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <style>
    body {display: flex;flex-direction: column;align-items: center;}
    h1 {text-align: center;font-size: 16px;margin: 0;color: #555;}
    hr {width: 320px;border-color: #aaa;border-width: 1px;}
    .button-group {display: flex;justify-content: center;margin: 5px;}
    .button-group > button {width: 100px;height: 70px;background-color: #eaeaea;border: none;border-radius: 5px;font-size: 16px;margin: 0 5px;user-select: none;}
    label {display: block;margin-top: 5px;color: #555;text-align: center;}
    output {display: inline-block;width: 30px;}
    input[type="range"] { height: 10px;width: 320px;}
  </style>
  <title>Wi-Fi MOTOR CONTROLLER</title>
</head>

<body>
  <h1>Wi-Fi MOTOR CONTROLLER</h1>
  <hr>
  <div class="button-group">
    <button id="button1">左前</button>
    <button id="button2">前進</button>
    <button id="button3">右前</button>
  </div>
  <div class="button-group">
    <button id="button4">左旋回</button>
    <button id="button5">停止</button>
    <button id="button6">右旋回</button>
  </div>
  <div class="button-group">
    <button id="button7">左後</button>
    <button id="button8">後退</button>
    <button id="button9">右後</button>
  </div>
  <div>
    <label for="motor-slider">SPEED:
      <output id="motor-value">127</output>
    </label>
  </div>
  <input type="range" id="motor-slider" min="0" max="255">

  <script>
    let buttons = {
      button1: "/get/for_left",
      button2: "/get/forword",
      button3: "/get/for_right",
      button4: "/get/left_turn",
      button5: "/get/stop",
      button6: "/get/right_turn",
      button7: "/get/rev_left",
      button8: "/get/reverse",
      button9: "/get/rev_right"
    };

    Object.entries(buttons).forEach(([buttonId, linkAddress]) => {
      let button = document.getElementById(buttonId);
      button.addEventListener("click", function(event) {
        let linkAddress = buttons[event.target.id];
        if (linkAddress) {
          fetch(linkAddress)
            .then(function(response) {
              if (response.ok) {
                response.text().then(function(text) {
                  console.log(text);
                });
              } else {
                console.error("レスポンスがエラーを返しました。ステータスコード: " + response.status);
              }
            })
            .catch(function(error) {
              console.error("通信中にエラーが発生しました:", error);
            });
        }
      });
    });

    const motorSlider = document.getElementById("motor-slider");
    const motorValue = document.getElementById("motor-value");

    motorSlider.addEventListener("input", () => {
      motorValue.innerHTML = motorSlider.value;
    });

    function postSpeed() {
      let speed = motorSlider.value;
      fetch('/post/motor_value', {
        method: 'POST',
        body: JSON.stringify({ motor_speed : speed }),
        headers: { 'Content-Type': 'application/json' }
      })
        .then(response => response.json())
        .then(data => { console.log('Success:', data); })
        .catch((error) => { console.error('Error:', error); });
    }

    motorSlider.addEventListener("mouseup", () => { postSpeed(); });
    motorSlider.addEventListener("touchend", () => { postSpeed(); });
    window.addEventListener("load", () => { postSpeed(); });
  </script>
</body>
</html>
)";
// PWM出力実行関数 **************************************************************
void pwmOutput(int duty1, int duty2, int duty3, int duty4) {
  ledcWrite(CH_INT1, duty1);  // PWM出力実行(チャンネル, Duty比)
  ledcWrite(CH_INT2, duty2);
  ledcWrite(CH_INT3, duty3);
  ledcWrite(CH_INT4, duty4);
}
// サーバーリクエスト時処理関数 ****************************************************
// ルート(IPアドレス)アクセス時の応答関数
void handleRoot() {
  server.send(200, "text/html", html);  // レスポンス200を返しhtml送信
}
// エラー(Webページが見つからない)時の応答関数
void handleNotFound() {
  server.send(404, "text/plain", "404 Not Found!");  // text送信
}
// GET受信(モーター動作)処理関数 *************************************************
void forLeft() {                              // 左前進
  pwmOutput(speed/2, 0, speed, 0);            // PWM出力実行関数呼び出し
  server.send(200, "text/plain", "for_left"); // レスポンス200を返し動作状態送信
}
void forword() {                              // 前進
  pwmOutput(speed, 0, speed, 0);
  server.send(200, "text/plain", "forword");
}
void forRight() {                             // 右前進
  pwmOutput(speed, 0, speed/2, 0);
  server.send(200, "text/plain", "for_right");
}
void leftTurn() {                             // 左旋回
  pwmOutput(0, speed, speed, 0);
  server.send(200, "text/plain", "for_left");
}
void stop() {                                 // 停止
  pwmOutput(255, 255, 255, 255);              // ブレーキ停止
  delay(300);
  pwmOutput(0, 0, 0, 0);                      // フリー停止
  server.send(200, "text/plain", "stop");
}
void rightTurn() {                            // 右旋回
  pwmOutput(speed, 0, 0, speed);
  server.send(200, "text/plain", "right_turn");
}
void revLeft() {                              // 左後退
  pwmOutput(0, speed/2, 0, speed);
  server.send(200, "text/plain", "rev_left");
}
void reverse() {                              // 後退
  pwmOutput(0, speed, 0, speed);
  server.send(200, "text/plain", "reverse");
}
void revRight() {                             // 右後退
  pwmOutput(0, speed, 0, speed/2);
  server.send(200, "text/plain", "rev_right");
}
// POST受信(モーター速度指定値)、速度更新処理関数 *******************************************
void postMotorValue() {
  server.send(200, "text/plain", server.arg(0));  // 受信した結果(200:成功)と受信データを文字列で返す
  String json_data = server.arg(0);               // 受信したJSON形式データを文字列として格納
  DeserializationError error = deserializeJson(json, json_data);  // JSON形式データに変換(エラーも確認)

  // 受信したJSONデータ処理
  if (error) {                                        // エラーの場合
    USBSerial.print(F("deserializeJson() failed: ")); // エラーメッセージの表示
    USBSerial.println(error.f_str());                
  } else {                                            // 正常な場合
    speed = (int)json["motor_speed"];                 // キー"motor_speed"の値を取得
  }
  // モーター速度更新
  int change_speed[4] = {0}; // モーター速度更新Duty値格納配列(0で初期化)
  USBSerial.printf("%d, %d, %d, %d\n", ledcRead(CH_INT1), ledcRead(CH_INT2), ledcRead(CH_INT3), ledcRead(CH_INT4));
  if (ledcRead(CH_INT1)) { change_speed[0] = speed; } // 現在のDuty比を取得して0でなければ速度更新
  if (ledcRead(CH_INT2)) { change_speed[1] = speed; }
  if (ledcRead(CH_INT3)) { change_speed[2] = speed; }
  if (ledcRead(CH_INT4)) { change_speed[3] = speed; }
  pwmOutput(change_speed[0], change_speed[1], change_speed[2], change_speed[3]);  // PWM出力実行関数呼び出し
}
// 初期設定 ----------------------------------------------------------
void setup() {
  M5.begin(true, true, false, false); // AtomS3初期設定(LCD,UART,I2C,LED)
  USBSerial.begin(9600);              // シリアル通信初期化
  delay(1000);                        // シリアル出力開始待ち
  USBSerial.println("ATOM S3 Wi-Fi MOTOR CONTROLLER!!");  // シリアル出力

  // PWM出力に使用する端子を出力設定
  pinMode(INT1, OUTPUT);   // モーター1正転
  pinMode(INT2, OUTPUT);   // モーター1逆転
  pinMode(INT3, OUTPUT);   // モーター2正転
  pinMode(INT4, OUTPUT);   // モーター2逆転

  // PWM初期設定
  ledcSetup(CH_INT1, FREQ, BIT_NUM); // PWM設定(チャンネル, 周波数, bit数)
  ledcSetup(CH_INT2, FREQ, BIT_NUM); // PWM設定(チャンネル, 周波数, bit数)
  ledcSetup(CH_INT3, FREQ, BIT_NUM); // PWM設定(チャンネル, 周波数, bit数)
  ledcSetup(CH_INT4, FREQ, BIT_NUM); // PWM設定(チャンネル, 周波数, bit数)
  ledcAttachPin(INT1, CH_INT1);  // PWMチャンネル割り当て(端子番号, チャンネル)(モーター1正転)
  ledcAttachPin(INT2, CH_INT2);  // PWMチャンネル割り当て(端子番号, チャンネル)(モーター1逆転)
  ledcAttachPin(INT3, CH_INT3);  // PWMチャンネル割り当て(端子番号, チャンネル)(モーター2正転)
  ledcAttachPin(INT4, CH_INT4);  // PWMチャンネル割り当て(端子番号, チャンネル)(モーター2逆転)

  // 液晶表示初期化
  M5.Lcd.begin();                   // 画面初期化
  M5.Lcd.setRotation(2);            // 画面向き設定(USB位置基準 0:上/ 1:左/ 2:下/ 3:右)
  M5.Lcd.fillScreen(BLACK);         // 背景
  M5.Lcd.println("Wi-Fi Serch!");   // Wi-Fi接続画面表示
  M5.Lcd.println("Waiting");        // Wi-Fi接続画面表示

  // Wi-Fiアクセスポイント接続
  // const char ssid[] = "ATOMS3-01"; // SSID
  // const char pass[] = "11111111";  // パスワード(8文字以上)
  // WiFi.softAP(ssid, pass);         // ソフトAP開始

  // Wi-Fiローカル接続
  const char ssid[] = "自宅のWiFi接続先を記入";  //接続先SSID
  const char pass[] = "接続先のパスワードを記入"; //接続先パスワード
  WiFi.begin(ssid, pass);                 // Wi-Fi接続開始
  while (WiFi.status() != WL_CONNECTED) { // 接続完了するまで繰り返す
    delay(500);                           // 0.5秒待機
    M5.lcd.print(".");                    //「.」画面表示
  }
  M5.Lcd.fillScreen(BLACK);               // 背景

  // サーバー設定(クライアントからのリクエストに応じて設定した関数を実行)
  server.on("/", handleRoot);               // ルート(IPアドレス)アクセス時の応答関数を設定
  server.onNotFound(handleNotFound);        // Webページが見つからない時の応答関数を設定
  server.on("/get/for_left", forLeft);      // 左前進ボタンオン受信処理
  server.on("/get/forword", forword);       // 前進ボタンオン受信処理
  server.on("/get/for_right", forRight);    // 右前進ボタンオン受信処理
  server.on("/get/left_turn", leftTurn);    // 左旋回ボタンオン受信処理
  server.on("/get/stop", stop);             // 停止ボタンオン受信処理
  server.on("/get/right_turn", rightTurn);  // 右旋回ボタンオン受信処理
  server.on("/get/rev_left", revLeft);      // 左後退ボタンオン受信処理
  server.on("/get/reverse", reverse);       // 後退ボタンオン受信処理
  server.on("/get/rev_right", revRight);    // 右後退ボタンオン受信処理
  server.on("/post/motor_value", postMotorValue); // モーター速度指定値POST受信処理
  server.begin();                           // Webサーバー開始
}
// メイン -------------------------------------------------------------------
void loop() {
  server.handleClient();  // クライアントからのアクセス確認
  // 液晶表示
  M5.Lcd.setTextColor(WHITE, BLACK);              // 文字色
  M5.Lcd.setTextFont(2);                          // フォント
  M5.Lcd.drawCentreString("WiFi Motor", M5.lcd.width()/2, 0, 4);  // 上中央座標を基準に文字表示(表示内容, x, y, フォント)
  M5.Lcd.drawCentreString("Remote Controller", M5.lcd.width()/2, 21, 2);  // 上中央座標を基準に文字表示(表示内容, x, y, フォント)
  M5.Lcd.drawFastHLine(0, 38, 128, WHITE);        // 指定座標から横線
  M5.Lcd.setCursor(0, 40);                        // カーソル座標指定
  M5.Lcd.printf("SSID: %s\n", WiFi.SSID());       // SSID表示
  // M5.Lcd.printf("SSID: %s\n", WiFi.softAPSSID()); // アクセスポイント時のSSID表示
  M5.Lcd.setTextColor(ORANGE, BLACK);             // 文字色
  M5.Lcd.print("IP  : ");                         // IPアドレス表示
  M5.Lcd.println(WiFi.localIP());
  // M5.Lcd.println(WiFi.softAPIP());             // アクセスポイント時のIPアドレス表示
  M5.Lcd.drawFastHLine(0, 74, 128, WHITE);        // 指定座標から横線
  M5.Lcd.setCursor(0, 78);                        // カーソル座標指定
  M5.Lcd.setTextColor(CYAN, BLACK);               // 文字色
  M5.Lcd.printf("Speed:%4d  \n", speed);          // モーター速度指定値表示
  M5.Lcd.printf("%3d, %3d, %3d, %3d  \n", ledcRead(CH_INT1), ledcRead(CH_INT2), ledcRead(CH_INT3), ledcRead(CH_INT4));  // 各Duty比取得
  delay(100);               // 遅延時間
}

・プログラムの詳細

27〜132行目」がスマホやパソコンのブラウザで表示されるラジコン操作画面の「HTML」と「JavaScript」のプログラムです。
「R”( ここにHTMLプログラムを記入 )” 」ここにコピペで貼り付けるだけで表示できます。
私は「HTML」や「JavaScript」は苦手なので、ほとんど「ChatGPT」に書いてもらってますw

ChatGPTについては、以下のリンクで紹介しています。

Arduinoプログラミング初心者必見!ChatGPTがもたらす新しい可能性
ChatGPTがプログラミングの考え方を変えました「こんなプログラムを書いて」と聞けばコード作成から説明、エラーの指摘までしてくれる。ChatGPTがもたらす新しい可能性を探ってみたいと思います。

149〜187行目」にはクライアント(ブラウザ)からの「リクエスト」に対する「レスポンス」と一緒に、実行するプログラム(PWM出力実行関数呼び出し)を記入しています。
ここで操作画面のボタンに応じてモーターの動作を制御しています。

簡易編ではブラウザの操作画面のボタンを押すごとにページが更新されるため、毎回操作画面の「HTML」データを送信していましたが、応用編では「JavaScript」を使用することで、ページが更新されないため「文字列」で動作状態を送信するようにしています。

189〜209行目」では操作画面のスライドバーを操作した時にモーターの速度(設定範囲0〜255)が送信されて速度を更新しています。
受信されたデータは「JSON」形式のデータで処理されて、速度指定変数「speed」に設定されて速度が更新されます。


258〜267行目」でサーバー(ATOMS3)にアクセスがあった時に実行する関数を指定しています。

今回のラジコン操作にはクライアント(ブラウザ)からの「リクエスト」に対して「レスポンス」を返すという「サーバー」の仕組みを利用しています。

「Arduinoコマンド」を使用した「サーバー」の使い方は以下のリンクで詳しく紹介していいます。

WiFi(ローカル接続、サーバー)で遠隔操作する方法(Arduinoプログラミング)
Wi-Fi通信でスマホから遠隔操作を行う方法を詳しく紹介。サーバーの仕組みを利用することで簡単にブラウザからの遠隔操作が実現できます。ATOM LITE使用

245-253行目」で「Wi-Fi通信」を開始しています。
ご自宅のネットワーク環境の「SSID」と「パスワード」をプログラム内に設定(246, 247行目)して「ATOMS3」の液晶画面に表示される「IPアドレス(192.169.0.xxx)」をスマホやパソコンのブラウザのアドレスバーに入力するだけで接続されて、操作画面が表示されます。

外で遊ぶ場合など、Wi-Fi通信の無い環境では、コメントアウトされている「241〜243行目」のアクセスポイント接続プログラムを有効にして、スマホやパソコンのネットワーク接続先(SSID)を「ATOMS3-01」に設定して、IPアドレス「192.168.4.1」を入力すると、Wi-Fi環境がなくても遊ぶことができます。
アクセスポイント接続時は「246〜253行目」をコメントアウトして無効にしておいてください
液晶画面での「SSID」や「IPアドレス」の表示方法は「ローカルWi-Fi接続」とは異なるので「アクセスポイント接続時」は「280,284行目」をコメントアウトし「281,285行目」を有効にしてください。

アクセスポイントへの接続プログラムについては、以下のリンクで詳しく紹介しています。

WiFi(アクセスポイント、サーバー)で近距離遠隔操作する方法(Arduinoプログラミング)
「ATOM LITE」を例にWiFiアクセスポイントに設定して、スマホ等から近距離遠隔操作を行う方法を紹介します。サーバーのリクエスト(要求)に対してレスポンス(応答)を返すという動作を利用して、簡単なプログラムで遠隔操作が実現できます。

・動作確認

プログラムを書き込んで実行すると、自宅のWi-Fiネットワークに接続が開始されます。
接続が完了すると以下のような画面が「ATOMS3」の画面に表示されます。

表示されない場合はプログラミング内の「SSID」と「パスワード」や、スマホやパソコンのネットワーク接続先の設定を確認してください。
「ATOMS3」側面のボタンを押してリセットすることで繋がることもあります。
Wi-Fiラジコンカーの作り方、駆動回路

液晶画面の中で「192.168.0.2(お使いの環境によって異なります)」が接続先アドレスです。
これを、スマホやパソコンのブラウザ(ChromeやEdge等なんでも良いです)のアドレスバーに入力すると以下のような画面が表示されます。

液晶画面下の「Speed: 」には現在のモーター速度指定値(0〜255)が表示されます。
その下には左から「モーター1の正転速度」「モーター1の逆転速度」「モーター2の正転速度」「モーター2の逆転速度」指定値が表示されます。
Wi-Fiラジコンカーの作り方、操作画面

あとは、各ボタンを押すとラジコンカーが動作します。
速度については画面下部のスライドバーを操作することで速度を変更することができます。

速度設定範囲は「0〜255(0V〜3.3V)」です。
初期値の「127」は約1.5V相当の電圧で駆動した時の速度になります。
速度設定について、最大の「255」にして前進後退を激しく繰り返したり、モーターに大きな負荷がかかったりすると挙動がおかしくなることがあります。
これはモーター起動時に突入電流という大きな電流が流れて一瞬電圧降下し「ATOMS3」にリセットがかかったり動作が不安定になるためです。
速度設定に関してはあまり大きくせずに、200程度までで様子を見ながら動作確認してみてください。

4.まとめ

Wi-Fi通信を利用したラジコンカーの作り方を詳しく紹介しました。

自作のラジコンカーを作る時には、タミヤのキットを使用すると手軽に作成できますが、使用されているモーターの電圧は3Vや1.5Vのものが多いです。

一方、モーターを制御するためのマイコンボード(今回は「ATOMS3」使用)は5Vで、USBの5V電源を使用して使う物が多いです。

このため、電源として使用する5Vからモーター駆動用の3V電源を得るために、今回は「3端子レギュレーター」を使用して、モーターを駆動するドライバも電圧範囲が「2〜10V(制御電圧2〜7V)」のMX1508搭載のものを使用しました。

使用したマイコンボード「ATOMS3」は小型ながら液晶画面やWi-Fi通信も搭載しているため、必要部品をミニブレッドボードに実装することでコンパクトな構成で実現でき、モーターを駆動させるものの動力源として色々便利に使えると思います。

遠隔操作する方法としては、クライアント(ブラウザ)からの「リクエスト」に対して「レスポンス」を返すという「サーバーの仕組み」を利用しました。

応答速度についてはネットワーク環境に依存するため、反応が遅れて思い通りに動かないこともよくあり、速度の速いラジコンの操作は難しいかもしれません・・・

しかし、今回の「サーバーの仕組み」を利用すれば稼働監視や遠隔操作を行う「IoT」でのデータ取得やデータ送信の理解にもつながるのため、遊びながらこの仕組みを理解して応用できるようになりましょう。

「IoT」で稼働監視や遠隔操作を行う方法については、以下のリンクで詳しく紹介しています。

簡易IoT(稼働監視、遠隔操作)テストプログラム(ATOM LITE使用)の紹介
簡易ですが「IoT」やりましょう♪ IoTとは「Internet of Things」の略で「モノのインターネット」と呼ばれるものです。スマホから遠隔操作でLチカしたり、ボタンを押した回数やアナログ値の取得もできます。
WiFi遠隔操作Arduinoコマンドでブラウザベースのスマホ、PCリモートコントローラの紹介
ブラウザベースで遠隔操作、リアルタイムデータ通信を行う方法をコピペ用サンプルプログラムを使って紹介します。 サーバー機能を利用して「JavaScript」の「fetch」を使うことでデータの送受信を行います。

コメント

タイトルとURLをコピーしました