
スマホで操作できるプラレールを作成
今回作成するもの
- 低速ではしるプラレール(子供が切り替えポイントを操作して遊ぶため)
- スマホで速度調整ができる。
- スマホで前進も後進も操作できる。
必要なもの
ボディ等を作成するために必要なもの
まずはスマホの画面(UI)を作成

<!doctype html>
<html lang="ja">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
<title>Go to</title>
<style>input[type="range"] {-webkit-appearance: slider-vertical;}</style>
</head>
<body>
<div class="container">
<div class="row" style="height:50px"></div>
<div class="row">
<div class="col text-center">
<div class="row">
<div class="w-100 text-center">
<div class="slider-wrapper">
<input type="range" value="0" min="0" max="100" step="10" style="height: 500px;" id="input">
</div>
</div>
</div>
<div class="row">
<div class="col text-center text-info" style="font-size:50px">
<output id="output1">0</output>%
</div>
</div>
</div>
<div class="col">
<div class="row mt-1" style="height:150px">
<button type="button" class="btn btn-success w-100 h-100">前進</button>
</div>
<div class="row mt-1" style="height:150px">
<button type="button" class="btn btn-warning w-100 h-100">停止</button>
</div>
<div class="row mt-1" style="height:150px">
<button type="button" class="btn btn-danger w-100 h-100">後進</button>
</div>
</div>
</div>
</div>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<!-- <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script> -->
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV" crossorigin="anonymous"></script>
<script>
$(function() {
var $input = $('#input');
var $output1 = $('#output1');
var value;
$input.on('input', function(event) {
value = $input.val();
$output1.text(value);
$.get("?value=" + value + '&');
});
$("要素").click(function(){
if($(this).hasClass("clicked")){ // クリックされた要素がclickedクラスだったら
$(this).removeClass("clicked");
}else{
$(this).addClass("clicked");
}
});
});
</script>
</body>
</html>
前進・後進の切り替えはモータードライバー(TA7291P)を使用

端子番号 | 記号 | 端子説明 |
1 | GND | グランド |
2 | OUT1 | モーター端子に接続 |
3 | NC | |
4 | Vref | 速度制御端子 → pin 25 |
5 | IN1 | 入力端子1 → pin 26 HIGH or LOW でモーターの回転向きを制御 |
6 | IN2 | 入力端子2 → pin 27 HIGH or LOW でモーターの回転向きを制御 |
7 | VCC | モータードライブの電源 → pin 14 |
8 | VS | モーターの電源 |
9 | NC | |
10 | OUT2 | モーター端子に接続 |
配線図
配線図イメージ
ESP32のコード
コード概要- WebServerとして利用し、getリクエストがきたらPIN操作する。
- IN1→HIGH IN2→LOW で前進
- IN1→LOW IN2→HIGHで後進
- IN1→LOW IN2→LOWで停止
- Vref端子で速度調整 最大値255 最小値0
#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>
const char *ssid = "your ssid";
const char *password = "your password";
// HTTPリクエストを保存しておく変数
String header;
// ピンの状態を保存する変数の宣言
String goState = "off";
String stopState = "on";
String backState = "off";
String speedSlider = "0";
// 使用するピンに名前を付ける
const int speedPin = 25;
const int pin_in1 = 26;
const int pin_in2 = 27;
const int pin_vcc = 14;
/* TCP server at port 80 will respond to HTTP requests */
WiFiServer server(80);
///////////////////////////
void setup() {
Serial.begin(115200);
Serial.println();
Serial.print("Configuring access point...");
/* You can remove the password parameter if you want the AP to be open. */
// 出力ピンに割り当て
pinMode(pin_in1, OUTPUT);
pinMode(pin_in2, OUTPUT);
pinMode(pin_vcc, OUTPUT);
// ピンを初期化
digitalWrite(pin_in1, LOW);
digitalWrite(pin_in2, LOW);
digitalWrite(pin_vcc, HIGH);
ledcSetup(0, 12800, 8);
ledcAttachPin(speedPin, 0);
// ssidとpasswordを用いてWi-Fiに接続
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
// IPアドレスを出力し、webserverをスタート
Serial.println("");
Serial.println("WiFi connected.");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
server.begin();
}
void loop() {
/* Check if a client has connected */
WiFiClient client = server.available();
if (!client) {
return;
}
if (client) { // クライアントが来たとき
Serial.println("New Client.");
String currentLine = ""; // クライアントからくるデータを格納する変数
while (client.connected()) { // クライアントがつながっている間、以下をループ
if (client.available()) { // クライアントからデータが来ているとき
char c = client.read(); // データを読み込み
Serial.write(c); // 届いたデータをシリアルモニタに出力
header += c;
if (c == '\n') { // 届いたデータが改行コードだった時
// もし現在の行が空白ならば、この改行コードのみ受け取る
// つまりHTTPリクエストの終わりなので、レスポンスを返す
if (currentLine.length() == 0) {
// HTTPヘッダは(HTTP/1.1 200 OK)のようなステータスコードから始まる
// 次にコンテントタイプを送信。今回はhtml形式なので以下のようにする
client.println("HTTP/1.1 200 OK");
client.println("Content-type:text/html");
client.println("Connection: close");
client.println();
// リクエストに従ってGPIOをスイッチする
if (header.indexOf("GET /GO/on") >= 0) {
Serial.println("GO on");
goState = "on";
stopState ="off";
backState ="off";
digitalWrite(pin_in1, HIGH);
digitalWrite(pin_in2, LOW);
} else if(header.indexOf("GET /GO/off") >= 0){
Serial.println("GO off");
goState = "off";
digitalWrite(pin_in1, LOW);
digitalWrite(pin_in2, LOW);
speedSlider = "0";
ledcWrite(0, speedSlider.toInt() * 2.5);
}
if (header.indexOf("GET /STOP/on") >= 0) {
Serial.println("STOP on");
goState = "off";
stopState ="on";
backState ="off";
digitalWrite(pin_in1, LOW);
digitalWrite(pin_in2, LOW);
speedSlider = "0";
ledcWrite(0, speedSlider.toInt() * 2.5);
}
if (header.indexOf("GET /BACK/on") >= 0) {
Serial.println("BACK on");
goState = "off";
stopState ="off";
backState ="on";
digitalWrite(pin_in1, LOW);
digitalWrite(pin_in2, HIGH);
} else if(header.indexOf("GET /BACK/off") >= 0){
Serial.println("BACK off");
goState = "off";
digitalWrite(pin_in1, LOW);
digitalWrite(pin_in2, LOW);
speedSlider = "0";
ledcWrite(0, speedSlider.toInt() * 2.5);
}
if(header.indexOf("GET /GO/on?value=") >= 0){
int pos1 = header.indexOf("=");
int pos2 = header.indexOf("&");
speedSlider = header.substring(pos1+1, pos2);
Serial.print("Value="); Serial.println(speedSlider);
ledcWrite(0, speedSlider.toInt() * 2.5);
}
if(header.indexOf("GET /BACK/on?value=") >= 0){
int pos1 = header.indexOf("=");
int pos2 = header.indexOf("&");
speedSlider = header.substring(pos1+1, pos2);
Serial.print("Value="); Serial.println(speedSlider);
ledcWrite(0, speedSlider.toInt() * 2.5);
}
// htmlを表示
client.println("<!doctype html>");
client.println("<html lang=\"ja\">");
client.println("<head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">");
client.println("<link rel=\"stylesheet\" href=\"https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css\" integrity=\"sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z\" crossorigin=\"anonymous\">");
client.println("<title>Go to</title>");
client.println("<style>input[type=\"range\"] {-webkit-appearance: slider-vertical;}</style>");
client.println("</head>");
client.println("<body><div class=\"container\">");
client.println("<div class=\"row\" style=\"height:50px\"></div>");
client.println("<div class=\"row\">");
client.println("<div class=\"col text-center\">");
client.println("<div class=\"row\">");
client.println("<div class=\"w-100 text-center\">");
client.println("<div class=\"slider-wrapper\">");
if(stopState=="off"){
client.println("<input type=\"range\" class=\"slider\" value=\"0\" min=\"0\" max=\"100\" step=\"10\" style=\"height: 500px;\" id=\"input\">");
}else{
}
client.println("</div></div></div>");
client.println("<div class=\"row\">");
client.println("<div class=\"col text-center text-info\" style=\"font-size:50px\">");
if(stopState=="off"){
client.println("<output id=\"output1\">0</output>%");
}else{
client.println("<output>停車中</output>");
}
client.println("</div></div></div>");
client.println("<div class=\"col\">");
client.println("<div class=\"row mt-1\" style=\"height:150px\">");
if(goState=="on") {
client.println("<a href=\"/GO/off\" class=\"w-100 h-100\"><button type=\"button\" class=\"btn btn-dark w-100 h-100\" style=\"font-size:30px\">前進</button></a></div>");
}else{
client.println("<a href=\"/GO/on\" class=\"w-100 h-100\"><button type=\"button\" class=\"btn btn-success w-100 h-100\" style=\"font-size:30px\">前進</button></a></div>");
}
client.println("<div class=\"row mt-1\" style=\"height:150px\">");
if(stopState=="on"){
client.println("<a href=\"/STOP/off\" class=\"w-100 h-100\"><button type=\"button\" class=\"btn btn-dark w-100 h-100\" style=\"font-size:30px\">停止</button></a></div>");
}else{
client.println("<a href=\"/STOP/on\" class=\"w-100 h-100\"><button type=\"button\" class=\"btn btn-warning w-100 h-100\" style=\"font-size:30px\">停止</button></a></div>");
}
client.println("<div class=\"row mt-1\" style=\"height:150px\">");
if(backState=="on"){
client.println("<a href=\"/BACK/off\" class=\"w-100 h-100\"><button type=\"button\" class=\"btn btn-dark w-100 h-100\" style=\"font-size:30px\">後進</button></a></div>");
}else{
client.println("<a href=\"/BACK/on\" class=\"w-100 h-100\"><button type=\"button\" class=\"btn btn-danger w-100 h-100\" style=\"font-size:30px\">後進</button></a></div>");
}
client.println("</div></div></div>");
client.println("<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js\"></script>");
client.println("<script src=\"https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js\" integrity=\"sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN\" crossorigin=\"anonymous\"></script>");
client.println("<script src=\"https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js\" integrity=\"sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV\" crossorigin=\"anonymous\"></script>");
client.println("<script>$(function(){var $input = $('#input');var $output1 = $('#output1');var value;$input.on('input', function(event) {value = $input.val();$output1.text(value);$.get(\"\?value=\" + value + '&');});});</script>");
client.println("</body></html>");
// HTTPレスポンスの最後は改行で終了
client.println();
// whileループの終了
break;
} else { // 改行コードを取得したら、currentLineをリセット
currentLine = "";
}
} else if (c != '\r') { // 改行以外の何かしらのコードが来ているとき
currentLine += c; // currentLineに追加
}
}
}
// ヘッダーをリセット
header = "";
// 接続をリセット
client.stop();
Serial.println("Client disconnected.");
Serial.println("");
}
}