【Raspberry Pi 4B】 「スマートフォンで操作できるラジコンカーの作成」
2021-07-23

Raspberry Piでラジコンカーを作成しました。
作成したラジコンは、「スマホでカメラ映像を見ながら遠隔操作できるラジコン」になります。
今回はこのラズパイラジコンの作成方法について、詳細を綴りたいと思います。
今からRaspberry Piでラジコンを作成しようとしている方の参考になれば幸いです。
※PWM制御は使用しません。HIGH,LOWでの動作、つまり速度制御はできません。
※作成には無線LANの環境が必要です。
まず、今回使用した部材を下記に記します。
・Raspberry Pi 4B
・サインスマート Raspberry Pi 用 カメラモジュール
・TAMIYAタンク工作基本セット(No.108)
・TAMIYAダブルギアボックス(No.168)
・DCモーター × 2(FA-130RA-2270L)
・モータードライバ(東芝TA7291P) × 2
・0.01μコンデンサ × 2
・モバイルバッテリー × 1(5V 2~3A)
・電池ボックス(単三×4本=6V) × 1
・ブレッドボード × 1
・ユニバーサルプレート × 1
・ジャンピングワイヤー(オス-オス、オス-メス) × 複数
・導線
・半田と半田こて
なお、Raspberry Pi 4B を利用する事が前提となってます。
また、Raspberry piはOS(Raspberry pi OS)、Mjpg Streamer、WebIOPi、がインストール済みであることで進めます。
WebIOPiを使ってスマホのブラウザからラズパイを操作する仕組み
 
プログラムの流れ
今回のように、個人で作成したプログラムでブラウザからGPIOを操作したい場合は、Raspberry PiのWebIOPi(WEBサーバー)に操作画面となる「HTML」を格納しなければなりません。
HTMLに走行ボタンを配置すれば、「java script」でイベント処理を行わせて、「python」のマクロプログラムを起動させるなど各プログラムをつなぐAPIの処理が必要になります。
「python」のマクロが動けばGPIOに指示が送れますので、モーターが駆動します。
つまり、モーターが駆動するプログラムの流れは
HTML→java script→python→GPIO
になります。
ハードウェアについて
モーターをGPIOで制御する訳ですが、直接GPIOからモーターをつなぐことは出来ません。
これは、モーターを駆動させる為に必要な電流(1A弱)がGPIOからでは供給できないからです(デフォルトで8mA程度)。強引につなげばGPIOが制限以上の電流を流す事になってしまい、Raspberry Piの故障につながります。
したがって、モーターの駆動には別途「モータードライブIC」を使用し、電源も専用で取る事になります。(6V程度)
また、モーターが駆動した際に電磁波(ノイズ)が発生するため、マイコンなどの部品に悪影響を及ぼします。
これを防ぐ為に、モーターの端子間に「コンデンサ」を並列で接続し、ノイズの発生を抑えています。
モータードライブIC(TA7291P)について
 
図のような10ピンのICになります。左から順に①~⑩ピンの順番で並んでおり、各役割は下記の通りです。
| 端子番号 | 端子記号 | 役割 | 
| 1 | GND | GND | 
| 2 | OUT1 | 出力1(モーター出力) | 
| 3 | NC | 接続なし | 
| 4 | Vref | モーター制御電圧 | 
| 5 | IN1 | 入力1(モーター入力1) | 
| 6 | IN2 | 入力2(モーター入力2) | 
| 7 | VCC | ロジック側電源端子(4.5~20V) | 
| 8 | VS | 出力側電源端子(モーターの電源0~20V) | 
| 9 | NC | 接続なし | 
| 10 | OUT2 | 出力2(モーター出力2) | 
VCCのロジック電源は、ICを動かす電源になり、VSはモーター用の電源になります。
前途したように、電源はモーター専用で取った方が良い(ラズパイの電源に負担がかかるので)ので、VCCはラズパイの5Vを、VSには乾電池などで6Vを印可させます。
余談ですが、電源の安定化の意味で、例えばラズパイのGPIOからの5V出力に並列で電解コンデンサ(100μF程度)のものを組み込んだりしますが、今回は問題なさそうなのでやっていません。
ちなみに、今回使用するモーターの駆動電圧は1.5~3Vであり、モーターの電源6Vをつなげると一見過電圧になりそうですが、IC内で3V程電圧降下するので、VSは6V位がちょうどいいです。実際にテスターでモーター駆動時の電圧を確認すると良いと思います。
Vrefについては、ここに与える電圧の程度によって、モーターの出力電圧を制御できますが、今回は使用しません。一般的に使用しない場合は、VSとVrefの間に3KΩ以上の抵抗をつなげるとされていますが、直接つないでも問題は無いようなので今回はVSとVrefを直結させます。
最後に入力と出力の関係は、下記の通りとなります。
| IN1 | IN2 | 動作 | 
| 0 | 0 | 停止 | 
| 1 | 0 | 正転(後転) | 
| 0 | 1 | 後転(正転) | 
| 1 | 1 | ブレーキ | 
入力はGPIOからのHIGH、LOW信号になり、組み合わせによって上記のような動作になります。正転か後転かは、モーターに繋げる極性によって変化するのでそのような表記になっています。
HIGHとHIGHでブレーキとありますが、電位差が無いので停止と考えてもいいでしょう。
とりあえずこれが1つのドライブICの役割になります。ですので、これを2つで考えれば次のようになります。
| IN1 | IN2 | IN1 | IN2 | 左motor | 右motor | 動作 | 
| 0 | 0 | 0 | 0 | 停止 | 停止 | 停止 | 
| 1 | 0 | 1 | 0 | 正転 | 正転 | 前進 | 
| 0 | 1 | 0 | 1 | 後転 | 後転 | 後退 | 
| 0 | 1 | 1 | 0 | 後転 | 正転 | 左旋回 | 
| 1 | 0 | 0 | 1 | 正転 | 後転 | 右旋回 | 
GPIOポートを4つ使えば、モータードライブICの入力を変化させて、モーター出力方向を制御し、前後左右任意の方向に進む事が可能になります。
Hブリッジ回路が2つ並んでるのと同じです。
ハードウェアの土台作り
タンクとギアボックス
TAMIYAタンク工作基本セットと、ダブルギアボックスを組み立てます。ここは取説通りで問題ないです。
ダブルギアボックスのギア比は「タイプC」が良いと思います。
タンクを2階建てにするかどうかはお好みで決めます。ラズパイやブレッドボード、バッテリの配置を考えて、ユニバーサルプレートをうまく使います。
ですが、とりあえずタンクの方は完成はさせずに、足回りだけなどの「仮」の状態がいいですね。完全に組みあがった状態だとテストし辛いからです。
 
タンクの組立てが終われば、一旦それは置いといて、図の配線通りに回路をつないでみます。
DCモーターにははんだ付けが必要なので、導線を注意して取り付けます。
今回は、モーターの駆動にGPIOピン26,19,13,12、の4ポートを使用します。
プログラムの実装
前途したように、HTML→java script→python→ の流れでGPIOが操作されるので、最低でも3つのプログラムが必要です。
java scriptはHTMLにインラインで埋め込む事も出来ますが、記述が見えにくいので外部スクリプトとして、今回は3つのファイルに分けてます。(cssを含むと4つ)
HTML/CSS
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>ラズパイカメラ付きラジコン</title>
<meta name="viewport" content=
"width=device-width,
 initial-scale=1">
<link rel="stylesheet" href="style.css">
<script type="text/javascript" src=
"/webiopi.js"><
/script>
<script type="text/javascript" src=
"motor.js"><
/script>
</head>          
    <body>
    <header>
    <img src="http://ラズパイローカルIP/
?action=stream"/>
    </header>
 <main>
  
    <ul>
        <div class="a">
            <li id="forward" class=
"ledoff"><br>前進</li>
        </div>
        <div class="bc">
         <div class="b">
          <li id="left" class=
"ledoff"><br>左旋回</li>
         </div>
         <div class="n">
          <li></li>
         </div>
         <div class="c">
          <li id="right" class=
"ledoff"><br>右旋回</li>
         </div>
        </div>
         <div class="d">
          <li id="backward" class=
"ledoff"><br>後退</li>
        </div>
    </ul>
     
</main>
</body>
</html>
* { 
    margin: 0px; 
    padding: 0px; 
}
html {
    max-width: 1000px;
    height: 100%;
    overflow: hidden;
}
body {
    width: 100%;
    height: 100%;
    position: relative;
    overflow: hidden;
}
img {
    width: 100%;
}
header {
    position: absolute;
    top: 0;
    width: 100%;
    height: 50%;
}
main {
    position: absolute;
    bottom: 0;
    width: 100%;
    height: 50%;
    background: skyblue;
}
ul {
    height: 100%;
    padding-top: 10px;
}
.a ,.bc, .d {
    height: 33%;
}
.bc {
    display: flex;
 }
li {
    width: 70px;
    height: 70px;
    margin-left: auto;
    margin-right: auto;
    background: yellow;
    text-align: center;
    list-style: none;      
}
.b {
    margin-left: auto;
}
.c {
    margin-right: auto;
}
.a li, .b li, .c li, .d li {
    border: solid 1px; 
    }
.ledon {
 background: #f88888;
}
.n li {
    background: skyblue;
}
ブラウザで表示される大元のプログラムになります。
headタグ内のスクリプトで「webiopi.js」「motor.js」「css」をソースとして読み込んでいます。webiopi.jsは、webiopi独自のjava script(ライブラリ)になり、これがなければwebiopiでGPIOを操作する事が出来ないので必須になります。このファイルは指定先に元々存在しています。
motor.jsは、後ほど作成するオリジナルのやつになります。
header内のimg srcは、Mjpg streamerのカメラ画像を反映させる記述です。'ラズパイのローカルIP'の箇所にそのままIPを記述してもらえればOKです。
mainタグの'li id'は、java scriptでイベント処理をさせるためのidです。
つまり、リストタグの所がボタンになっているので、そこをクリックすれば、java scriptのイベントが発生する仕組みです。
java script
w().ready(function() { 
  var motor = 'STOP';
// 「前進」ボタンが押されたときのイベント処理
  $('#forward').bind(BUTTON_DOWN,
 function(event) {
    // 押されたとき
    if(motor == 'STOP') {
      $(this).addClass('ledon');
      change_motor('FOWARD');
    }
  }).bind(BUTTON_UP, function(event) {
    // 離したとき
    $(this).removeClass('ledon');
    change_motor('STOP');
  });
  // 「後退」ボタンが押されたときのイベント処理
  $('#backward').bind(BUTTON_DOWN, 
function(event) {
    if(motor == 'STOP') {
      $(this).addClass('ledon');
      change_motor('BACKWARD');
    }
  }).bind(BUTTON_UP, function(event) {
    $(this).removeClass('ledon');
    change_motor('STOP');
  });
  // 「右」ボタンが押されたときのイベント処理
  $('#right').bind(BUTTON_DOWN, 
function(event) {
    if(motor == 'STOP') {
      $(this).addClass('ledon');
      change_motor('RIGHT');
    }
  }).bind(BUTTON_UP, function(event) {
    $(this).removeClass('ledon');
    change_motor('STOP');
  });
  // 「左」ボタンが押されたときのイベント処理
  $('#left').bind(BUTTON_DOWN, 
function(event) {
    if(motor == "STOP") {
      $(this).addClass('ledon');
      change_motor('LEFT');
    }
  }).bind(BUTTON_UP, function(event) {
      $(this).removeClass('ledon');
    change_motor('STOP');
  });
 // 関数:モーターを動かすマクロ呼び出し
  function change_motor(type) {
    motor = type;
    if(type == 'FOWARD') {         // 前進
      w().callMacro('FW');
    } else if(type == 'BACKWARD') {// 後退
      w().callMacro('BK'); 
    } else if(type == 'RIGHT') {   // 右旋回
      w().callMacro('RT');
    } else if(type == 'LEFT') {    // 左旋回
      w().callMacro('LT');
    } else if(type == 'STOP') {   // 停止
      w().callMacro('ST');
    }
  }
 });  
初めの行に'w()'がありますが、これは'webiopi()'と同じ意味です。閉じタグが最後にあるので、このプログラムはwebiopiのライブラリを使用すると宣言されています。
初めに、motorの変数に'stop'が格納されます。
ここからボタン押された時の挙動
続いてイベント処理、例えばhtmlで「前進」ボタンがクリックされると、付随するidから$('#forward')のイベント処理が実効されます。
”BUTTON_DOWN”は、webiopi独自のライブラリで、スマホでもPCでも、クリックしたときの反応を感知してくれる処理をしてくれます。名前の通り、押している時間だけ、それ以降の処理が行われます。
続いてのif文は、motorの定数が'stop'になっているかの分岐です。これは単に、同時に他のボタンがクリックされる事を防止する為に行われる確認です。
続く'this'は、'forward'を指しているので、forwardのclassを'ledon'に変更しています。つまり、このclass変更でcssによってボタンの色が変わるようになっています。
そして最後に、change_motor('FOWARD');で下部の関数'function change_motor'を呼び出しています。
そこの引数(type)に'FOWARD'を引き渡し、定数motorに格納後、if文の分岐に入ります。
'FOWARD'と一致するので、('FW')のマクロが呼び出されます。Pythonのmacroが呼び出されます。
Python
# coding: utf-8
## inport文
import webiopi
import RPi.GPIO as GPIO
## 初期化
GPIO.setmode(GPIO.BCM)
MOTOR_A1 = 26;
MOTOR_A2 = 19;
MOTOR_B1 = 13;
MOTOR_B2 = 12;
def setup():
    GPIO.setup(MOTOR_A1, GPIO.OUT)
    GPIO.setup(MOTOR_A2, GPIO.OUT)
    GPIO.setup(MOTOR_B1, GPIO.OUT)
    GPIO.setup(MOTOR_B2, GPIO.OUT)
## 以下マクロ 前進 
@webiopi.macro
def FW():
    GPIO.output(MOTOR_A1,GPIO.HIGH)
    GPIO.output(MOTOR_A2,GPIO.LOW)
    GPIO.output(MOTOR_B1,GPIO.HIGH)
    GPIO.output(MOTOR_B2,GPIO.LOW)
## 後退
@webiopi.macro
def BK():
    GPIO.output(MOTOR_A1,GPIO.LOW)
    GPIO.output(MOTOR_A2,GPIO.HIGH)
    GPIO.output(MOTOR_B1,GPIO.LOW)
    GPIO.output(MOTOR_B2,GPIO.HIGH)
## 左旋回
@webiopi.macro
def LT():
    GPIO.output(MOTOR_A1,GPIO.LOW)
    GPIO.output(MOTOR_A2,GPIO.HIGH)
    GPIO.output(MOTOR_B1,GPIO.HIGH)
    GPIO.output(MOTOR_B2,GPIO.LOW)
    
## 右旋回
@webiopi.macro
def RT():
    GPIO.output(MOTOR_A1,GPIO.HIGH)
    GPIO.output(MOTOR_A2,GPIO.LOW)
    GPIO.output(MOTOR_B1,GPIO.LOW)
    GPIO.output(MOTOR_B2,GPIO.HIGH)
## 停止
@webiopi.macro
def ST():
    GPIO.output(MOTOR_A1,GPIO.LOW)
    GPIO.output(MOTOR_A2,GPIO.LOW)
    GPIO.output(MOTOR_B1,GPIO.LOW)
    GPIO.output(MOTOR_B2,GPIO.LOW)
PythonはRaspberry PiのGPIOを制御できるプログラムになるので、GPIOに関する設定や入出力の指示を行う事が出来ます。
初めに、import文でwebiopiとRPi.gpioのパッケージ(GPIOや@webiopi.macroなど独自の名称)を使えるようにしています。
初期化で使用するGPIOのポートをMOTORの変数に格納。セットアップで使用するGPIOピンに「出力」するように設定。
後はjava scriptより呼び出された('FW')のマクロを実行する事になります。各モーターにHIGH、LOWの信号が振り分けられています。これで前進します。
これが、ボタンを押し続けている間は常に続く感じです。
違うボタンでも、流れは同じです。
ボタンを離した時の挙動
java script の#forward イベントの BUTTON_UPが実効されます。
thisは#forwardを指しているので、removeClass、で変更されていたclass ledonが解除されます。つまり、ボタンの色が元に戻ります。
change_motor('STOP');によって下部の関数”function change_motor”の引数”(type)にstopが渡されます。
motorの変数にtype(stop)を格納後、if文の分岐へ移行。
typeはstopなので、分岐の最後にある”w().callMacro('ST');”のマクロが呼び出されます。
Pythonを見れば、全てGPIOがLOWになっているので、停止する事が解ります。
このように、HTML→java script→Python、とプログラムの受け渡しがAPIによって循環して、PythonがGPIOを動作させる仕組みになっています。
動作テスト
プログラムをwebiopiに格納
 
それでは、上記のプログラムを作成し、webiopiの配下にある「motor」ディレクトリに全て格納します。
図の通り、4つのファイルになります。それぞれのファイル名は、
motor.html
style.css
motor.js
macro.py
これで動作テストの準備は整いましたので、実際に動くか試してみましょう。
※pycacheのディレクトリは勝手に生成されるので無視してください。
プログラムの立ち上げ方
Raspberry Piのターミナルでwebiopiを立ち上げます。
$sudo service webiopi start
その後、Mjpg-streamerを立ち上げます。
$cd mjpg-streamer/mjpg-streamer-experimental
$bash strt.sh
この状態で、あとはスマホのブラウザからRaspberry PiのIP+htmlファイル名まで入力します。
http://ラズパイのローカルIP:8000/motor/motor.html
 
図のような画面が表示されましたら、実際にボタンを押してモーターが動作する事を確認してください。
※カメラが映らない場合はIPアドレスが間違っている可能性があるのでHTMLのheader部を確認下さい。
仕上げて終わり
問題なく動作が確認できれば、あとはタンクを好みの形に完成させます。
一応こんな感じで動くと思います。(タイヤとかバッテリが今回のパーツと異なるけど気にしないで下さい)↓