今回はRaspberry PiでWebから操作できるラジコンクローラーを作る方法をご紹介したいと思います。
昨年はArduinoを使用してラジコンを作成しました。それを今回はRaspberryPiで作り直してみました。
昨年の記事はこちら↓
ArduinoとXBeeとNode.jsを使用してWeb画面からラジコンを操作できるようにしてみた!
大きな変更点としては、Webカメラの映像を操作画面に表示できるようになったことと、シリアル通信ではなくWebサイトから直接ロボットを操作できるようになったため、通信で使用していたXBeeが不要になったところが大きいです。
目次
今回の概要図
以下に作成手順を紹介していきます。
必要な材料を用意する
今回は以下の材料を用意しました。
本体部分
部品名 | 個数 |
Raspberry PI3 modelB | 1 |
Webカメラ 「HD ウェブカメラ C270」 | 1 |
タミヤ トラック&ホイールセット | 1 |
タミヤ ツインモーターギヤーボックス | 1 |
タミヤ ユニバーサルプレート | 1 |
タミヤ ロングユニバーサルアームセット | 1 |
タミヤ ユニバーサル金具 4本セット | 1 |
マジックテープ | 1 |
ネジ M3 x 50mm | 4本 |
回路部分
部品名 | 個数 |
ブレッドボード | 1 |
DCモータドライブ「TA7267BP」 | 2 |
ジャンパーワイヤ | 15本 |
電池ボックス 9V型 | 1 |
モバイルバッテリー | 1 |
両面テープ | 1 |
モーターと銅線の接続部分
部品名 | 個数 |
ハンダ | 1 |
積層セラミックコンデンサ「0.1μF 50V」 | 2 |
工具
部品名 | 個数 |
+ドライバー | 1 |
ハンダごて | 1 |
Raspbian(Jessie)のインストール
RaspberryPiのOSにはRaspbian(Jessie)をインストールしておきます。後述で記載するRaspberry Piをアクセスポイント化して操作する場合にはRaspbianのJessieでないと方法を確立することができなかったためです。
今回は「Raspberry Pi3 Model B」に「NOOBS_v2_4_1」で「Raspbian Jessie」をインストールした前提で以下の手順を実施していきます。(※Raspberry Pi3 Model B+にもNOOBS_v2_4_1を入れようとしてみましたが、インストールできませんでした。B+に上記のOSは入れられないかもしれません。。)
WebIOPIのインストール
まずはWebIOPIをインストールします。これをインストールするとブラウザからRaspberryPiのGPIOを操作することが可能となります。インストール方法については別の記事にまとめましたのでこちらをご参照ください。
WebIOPIをRaspberry Piにインストールする方法
RaspberryPiを起動するとWebIOPIも同時に起動するように設定しておきます。
Mjpg-Streamerのインストール
今回はRaspberryPiに設置したWebカメラ映像を操作画面に表示するために「Mjpg-Streamer」を使用しますので以下の記事に沿ってインストールしておきます。「Mjpg-Streamer」はRaspberryPiの起動時に同時に起動するように設定しておきましょう。
RaspberryPiとMJPG-Streamerでライブストリーミングをする方法
操作プログラムの作成
ブラウザから操作するようのプログラムを用意しておきます。ブラウザの表示には「HTML」、ロボットの操作には「Python」を使用します。
コンソールを起動し、以下のコマンドでプログラムファイルを格納するフォルダを作成しておきます。
1 2 3 |
mkdir iot mkdir iot/python mkdir iot/html |
フォルダとファイルは以下のような構成にします。
htmlフォルダに移動し、「index.html」を作成します。HTMLにはMJPG-Streamerの動画を表示する部分と、ロボットを操縦する部分で構成します。ボタンが押されたらPythonのスクリプトが呼ばれてモーターが動くようになっています。
MJPG-Streamerの映像を表示するためにIPアドレスの部分は書き換えてください。アクセスポイント化していない場合は割り当てられているIPアドレスを、アクセスポイント化しているときはアクセスポイント化の手順で設定したIPアドレスを指定します。
今回はアクセスポイント化した後のIPアドレスを記載しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta name="viewport" content="width=device-width"> <title>Controller</title> <script type="text/javascript" src="/webiopi.js"></script> <script type="text/javascript"> var imageNr = 0; // Serial number of current image var finished = new Array(); // References to img objects which have finished downloading var paused = false; function createImageLayer() { var img = new Image(); img.style.position = "absolute"; img.style.zIndex = -1; img.style.marginLeft="-160px"; img.onload = imageOnload; img.onclick = imageOnclick; img.src = "http://172.24.1.1:8080/?action=snapshot&n=" + (++imageNr); var webcam = document.getElementById("webcam"); webcam.insertBefore(img, webcam.firstChild); } // Two layers are always present (except at the very beginning), to avoid flicker function imageOnload() { this.style.zIndex = imageNr; // Image finished, bring to front! while (1 < finished.length) { var del = finished.shift(); // Delete old image(s) from document del.parentNode.removeChild(del); } finished.push(this); if (!paused) createImageLayer(); } function imageOnclick() { // Clicking on the image will pause the stream paused = !paused; if (!paused) createImageLayer(); } webiopi().ready(function() { var parts; parts =webiopi().createButton("forwardButton","GO",function() { webiopi().callMacro("forwardCrawler"); }) $("#forward").append(parts); parts =webiopi().createButton("stopButton","STOP",function() { webiopi().callMacro("stopCrawler"); }) $("#stop").append(parts); parts =webiopi().createButton("backwardButton","BACK",function() { webiopi().callMacro("backwardCrawler"); }) $("#backward").append(parts); parts =webiopi().createButton("leftButton","Left",function() { webiopi().callMacro("leftCrawler"); }) $("#left").append(parts); parts =webiopi().createButton("rightButton","Right",function() { webiopi().callMacro("rightCrawler"); }) $("#right").append(parts); $("#forwardButton").css('background-color','green'); $("#stopButton").css('background-color','green'); $("#backwardButton").css('background-color','green'); $("#leftButton").css('background-color','green'); $("#rightButton").css('background-color','green'); }); </script> <style type="text/css"> input[type="range"] { display: block; width: 150px; height: 30px; -webkit-transform:rotate(-90deg); -moz-transform:rotate(-90deg); -o-transform:rotate(-90deg); transform:rotate(-90deg); transform-origin:right bottom; } input[type="range"]::-webkit-slider-thumb{ -webkit-appearance: none; -moz-appearance: none; appearance: none; background-color: #666; text-align:center; width: 40px; height: 40px; border:1px solid transparent; border-radius:20px; cursor:pointer; -moz-box-sizing:border-box; -webkit-box-sizing:border-box; box-sizing:border-box; } button { display: block; margin: 5px 5px 5px 5px; width: 100px; height: 45px; font-size: 24pt; font-weight: bold; color: black; } </style> </head> <body onload="createImageLayer();"> <div align="center"> <div id="webcam" style="width: 320px;height: 240px;" > <noscript> <img src="http://172.24.1.1:8080/?action=snapshot" /> </noscript> </div> </div> <div align="center"> <table> <tr> <td> <table border=0 cellspacing="10″ cellpadding="0″> <tbody> <tr> <td></td> <td><div id="forward"></div></td> <td></td> </tr> <tr> <td><div id="left"></div></td> <td><div id="stop"></div></td> <td><div id="right"></div></td> </tr> <tr> <td></td> <td><div id="backward"></div></td> <td></td> </tr> </tbody> </table> </td> </tr> </table> </div> </div> </body> </html> |
次に「Python」フォルダに移動してロボット操作用の「script.py」を作成します。HTMLでボタンが押されたら呼ばれるようになっています。動作は前進、後進、左旋回、右旋回、停止の動作となるようにしています。Pythonの内容を書き換えても変更内容が反映されない場合は一度リスタートしてみてください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
import webiopi GPIO = webiopi.GPIO IN1 = 15 IN2 = 18 IN3 = 23 IN4 = 24 def setup(): GPIO.setFunction(IN1 , GPIO.OUT ) GPIO.setFunction(IN2 , GPIO.OUT ) GPIO.setFunction(IN3 , GPIO.OUT ) GPIO.setFunction(IN4 , GPIO.OUT ) @webiopi.macro def forwardCrawler(): GPIO.digitalWrite(IN1, True ) GPIO.digitalWrite(IN2, False ) GPIO.digitalWrite(IN3, True ) GPIO.digitalWrite(IN4, False ) @webiopi.macro def backwardCrawler(): GPIO.digitalWrite(IN1, False ) GPIO.digitalWrite(IN2, True ) GPIO.digitalWrite(IN3, False ) GPIO.digitalWrite(IN4, True ) @webiopi.macro def stopCrawler(): GPIO.digitalWrite(IN1, False ) GPIO.digitalWrite(IN2, False ) GPIO.digitalWrite(IN3, False ) GPIO.digitalWrite(IN4, False ) @webiopi.macro def leftCrawler(): GPIO.digitalWrite(IN1, True ) GPIO.digitalWrite(IN2, False ) GPIO.digitalWrite(IN3, False ) GPIO.digitalWrite(IN4, True ) @webiopi.macro def rightCrawler(): GPIO.digitalWrite(IN1, False ) GPIO.digitalWrite(IN2, True ) GPIO.digitalWrite(IN3, True ) GPIO.digitalWrite(IN4, False ) |
回路の作成
RaspberryPiからモーターを動かすために回路を作成します。回路は以下のような構成にしました。
ロボットの組み立て
ロボットは以下のような構成で組み立てます。タミヤのクローラーセットをベースにしていきます。横から見やすいようにクローラーのベルトは取り外していますが、本来は車輪にベルトが巻かれています。
①ツインモーターギヤーボックスを組み立て、モーターの端子に銅線をハンダ付けしておく
②ユニバーサル金具をL字に曲げ、ユニバーサルプレートとツインモーターギヤーボックスを固定
③二段目にロングユニバーサルアームを固定
④ロングユニバーサルアームセットの上にRaspberryPiをマジックテープで固定
⑤RaspberryPiの上にブレッドボードを両面テープで固定
⑥Webカメラを設置し、USBケーブルをRaspberryPiに差し込む
⑦一段目と二段目の間にモバイルバッテリーと9V電池を格納
WebカメラとRaspberry Piを外してみると以下のようになっています。
RaspberryPiのアクセスポイント化
WiFiなどがない場所で操縦したい場合はRaspberryPiのアクセスポイント化を実施しておきます。そうすることで直接PCやスマホなどからRaspberryPiを操作することができるようになります。アクセスポイント化の方法は以下の記事にまとめてありますのでこちらをご参照ください。
Raspberry Pi3(Raspbian Jessie)をアクセスポイント化してWiFiで動画配信する
ラジコンクローラーの操作方法
以下にラジコンクローラーの操作方法を記載していきます。
①モバイルバッテリーを接続してRaspberryPiの電源を入れる
②MJPG-Streamerを起動する(自動起動を設定していない場合のみ)
もしRaspberryPiの起動時に「MJPG-Streamer」の自動起動を設定していない場合は、モニターに接続して以下のコマンドでMJPG-Streamerを起動しておきましょう。
1 |
mjpg_streamer -i "/usr/local/lib/mjpg-streamer/input_uvc.so -d /dev/video0 -y -r 320x240 -f 15" -o "/usr/local/lib/mjpg-streamer/output_http.so -p 8080 -w ./www" |
③他の端末からRaspberryPiのWiFiに接続する
他のPCかスマホで「http://プライベートIPアドレス:8000/」もしくは「http://アクセスポイント化で設定したIPアドレス:8000/」に接続して画面が表示されることを確認します。ポート番号の間違いにご注意ください。
画面が表示されたらモニターはもう必要ないので引き抜いておきます。
④他の端末に表示された操作画面からラジコンを操作する
これでボタンを押せばラジコンを前後左右に動作させることができます。動かない場合はこれまでの手順を見直してみてください。
実際に動かしてみる
実際に動かすとこのような動きになります。今回は公園に持っていって動かしてみました。真っ直ぐ動かしてもつまらないのでスラロームを作ってコースっぽくしています。
左右のクローラーのバランスが悪かったり、旋回が速すぎたりすると操作が難しいです。まだまだ改良の余地はありそうです。カメラの映像だけを見て操作するのも難しかったです。カメラの映像だけで操作するときは、カメラの角度を下向きにしてあげるとコーンが見えて操縦しやすかったです。
まとめ
今回はRaspberry PiでWebから操作できるラジコンクローラーを作ってみました。
上記のラジコンを作る過程で一番つまったのはRaspberryPiのアクセスポイント化でした。
アクセスポイント化するには、ラズパイとOSの組み合わせが重要になってくるようです。最新のバージョンでもできるようになってくれると嬉しいですよね。。
このラジコンをベースにして他の機能を設置したり、また別のラジコンを作ったりすることもできると思うので
また今後試してみたいと思います。
- 投稿タグ
- #IoT, #RaspberryPi, #Webカメラ, #ラジコン, #電子工作