知的好奇心 for IoT

IoT関連の知的好奇心を探求するブログです

パン・チルト機構付きカメラを搭載した、Blynkで操作できるラズパイタンクを作った

以前も同じような志向のものを作ったのですが、その時は駆動部分だけモータードライバを使って、映像の送信にはスマホを使うという変則的なやり方だったため心残りがあったんです。

さすがにESP8266で映像を送信するのは無理があるため、ボードをRaspberry Pi Zero Wに変えてリベンジを果たすことしました。

で、作ったのがこれです。

f:id:IntellectualCuriosity:20200316011036j:plain

デモ映像もアップしました。

これから少しずつ作り方を書いていこうと思います。

 

使用したもの

ハードウェア(値段は2020年3月19日時点)

 

パーツの特徴とか

全ての部品が半田付け不要

Raspberry Pi ZeroとServo pHATのヘッダーピンが既に半田付けされて売られているので、自分で半田付けする部品がありません。ぼくは半田付けが好きな方なので実はちょっと残念なんですけどね。基本は部品をはめ込んだりネジを絞めたりすることで 作ることができます。

唯一、タミヤのユニバーサルプレートセットとスプロケット&クローラーセットはプラモデルのように部品がくっついているので、ニッパーで部品を切り離していく地道な作業が必要です。

この作業に使うニッパーが結構重要で、今回タミヤのプラスチック部品の切り取り専用モデラーズニッパーを調達して使ったら、作業がとっても楽でニッパーで切るだけでバリも殆ど残らない優れものでした。

 

Raspberry Pi Zero WH

Raspberry Pi ZeroはちっちゃくてUSB OTGでPCと繋げてヘッドレスでセットアップができるのでとっても気に入っていますが、USBコネクタがmicro Bだけだったり、HDMIがminiだったりするのがマイナスポイントです。

でも、今回カメラを初めて使って一番の問題はカメラコネクタだということがわかりました。サイズの制約でRaspberry Pi 3や4より一回り小さいカメラコネクタが付いていますが、このカメラコネクタの黒い部品が非常に壊れやすいんです。

試行錯誤中に何度もカメラケーブルを抜き差ししていたら、パキっと黒い部品の留め部分が壊れてしまいました。ネットでも同様の事象があふれていて、カメラをなんとか使えるように修復できましたが少しみっともない状態になっています。

f:id:IntellectualCuriosity:20200319223455j:plain

少し厚手の紙をカメラケーブルと同じ幅に切って、カメラケーブルと一緒にコネクタに差し込み、セロテープで両面から留めたら修復完了です。

 

SparkFun Servo pHAT for Raspberry Pi

ハードウェアでの今回の目玉がこのボードです。

f:id:IntellectualCuriosity:20200320112920j:plain

なんと、このボード1枚で16台のサーボモーターがコントロールできるんです。また、このボードに付いているUSB-Cポート経由でRaspberry Pi本体に給電ができるので、電源回りもシンプルにすることができます。

ただ、サーボモーターは結構電力を消費するので、今回用意した出力2.4Aのモバイルバッテリーではサーボモーター2台ぐらいが適当だと思います。と、いうのも、Raspberry Pi Zero WHの代わりにRaspberyy Pi 3A+を付けてサーボモーター2台を動作させてみたら、ハングアップはしないまでも本体の赤い電源ランプが消えるんですよ。ちょっとヒヤッとしました。

もっと多いサーボモーターを制御したいときは、ボードの電源ジャンパープリントを切断して本体とボードに別々に電源を加えるようにします。

このボードについてはSparkfunのPi Servo pHat (v2) Hookup Guideに詳しく書いてあります。

 

Pan/Tilt 機構作成キット

サーボモーター2個付きで非常にリーズナブルなんですが、Pan/Tilt機構の精度はあまり高くなく、組み立てたものはカメラを取り付ける部分が斜めになってしまっています。

f:id:IntellectualCuriosity:20200320124257j:plain

値段相応ということでしょうか。

キットの中に組み立てマニュアルは付いていないので(ぼくが千石電商秋葉原本店3階で買ったものには簡単な説明書が付いていましたが、サイトの方がわかりやすいです。)、Sparkfunの「Assemble the Pan-Tilt Mechanism」の記事を見ながら組み立てましょう。

 

360°連続回転サーボ FS90R

Servo pHATで扱えるサーボモーターには360°連続回転サーボ(ローテーションサーボ)も含まれていて、クローラーの駆動用に今回初めて使ってみました。

ユニバーサルプレートには、次の写真のように禁断のネジ留めをしてしまっていて、数回の試行錯誤の跡が残る痛々しい状態になっています。

f:id:IntellectualCuriosity:20200321003052j:plain

f:id:IntellectualCuriosity:20200321003133j:plain

また、ドライブスプロケットの軸の両端の突起を切って、内側にサーボモーターに付いてきた丸い部品を付けてネジ留めしています。

f:id:IntellectualCuriosity:20200321004429p:plain

非常に雑なサーボモーターの付け方になってしまっていますが、これだと非常に手早く済ませることができます!お勧めはしません!

 

カメラモジュール V2と白色LED

カメラモジュール V2は800万画素で非常に画質が良いです。基盤の製造元によってか店により1,000円位の値段差があるようなので要注意ですね。

白色LEDは光度が6000mcdで半減角が60度のものを使いました。Servo pHatのPCA9685というICは16チャネルのPWMコントローラーで、サーボモーター以外のものも制御できるんです。

Pan/Tilt 機構作成キットには写真のような感じでくっつけました。

f:id:IntellectualCuriosity:20200321144535p:plain

厚手の紙でカメラモジュールを貼り付ける枠を作って、厚手のすき間テープをPan/Tilt構成キットの間に挟んで両面テープで貼り付けています。

枠にはLED用の穴も空けました。

 

ソフトウェア

Raspberry Pi

  • Raspbian Buster with desktop
  • mjpg-streamer (映像配信用)
  • Python 3.7.3 (制御プログラム用)
  • camtank.py (制御プログラム)

スマホAndroid)側

  • Android 10
  • Firefox 68.6.0 (映像表示用)
  • Blynk 2.27.13 (制御用)

 

Raspbian

インストールイメージは「Raspbian Buster Lite」ではなく、普通に必要なモジュールが予め入っている「Raspbian Buster with desktop」を使っています。

Raspbianの設定変更

$sudo raspi-configでRaspbianの設定を幾つか変更します。

  • 起動状態をDesktopからCLIに変更
    「3 Boot Options」を選び「B1 Desktop / CLI」から「B2 Console Autologin」を選択します。
  • カメラモジュールの使用
    「5 Interfacing Option」を選び「P1 Camera」をEnableにします。
  • I2Cの使用
    「5 Interfacing Option」を選び「P5 I2C」をEnableにします。

 

mjpg-streamer

実は映像配信用のプログラムには最初v4l2rtspserverを使おうとしたのですが、映像を見ながら操作するには遅延が大きすぎて使用を断念しました。映像の品質はとても良かったんですけどね。

mjpg-streamerの遅延は解像度やフレームレート、ネットワーク環境にも影響されますが非常に短いです。今回は解像度640x480、フレームレート15fpsで使用しています。この設定だとスマホテザリングネットワークでも十分操作が可能でした。

インストールはGitHub上のmjpg-streamerのREADME.mdに書いてあるBuilding & Installationの指示に従いました。

こんな感じです。

$ sudo apt install cmake libjpeg8-dev
$ git clone https://github.com/jacksonliam/mjpg-streamer.git
$ cd mjpg-streamer/mjpg-streamer-experimental
$ make
$ sudo make install

次のシェルスクリプトで動かしています。

#!/bin/bash

# mjpg-streamer
export LD_LIBRARY_PATH=/usr/local/lib/mjpg-streamer/
mjpg_streamer \
-i 'input_raspicam.so -x 640 -y 480 -fps 15 -vs' \
-o 'output_http.so' 

これで、ブラウザからhttp://<IPアドレス>:8080/?action=streamにアクセスすると映像が見れます。

 

camtank.py(制御プログラム)

使用ライブラリ

CircuitPython

PCA9685(SparkFun Servo pHAT for Raspberry Piが使用している制御用IC)用のAdafruit社製のライブラリを使用するため、その前提となるRaspberry PiでCircuitPythonの動作環境を提供するライブラリをインストールします。

$ sudo pip3 install --upgrade setuptools
$ pip3 install adafruit-blinka

PCA9685とServoKit

PCA9685がLED制御用で、ServoKitがサーボモーターの制御用です。

LEDの操作はこんな感じです。

pca.channels[15].duty_cycle = 0xffff

これで15番チャネルに付けたLEDを最大輝度にします。

逆に15番チャネルのLEDを消したいときは、このように書きます。

pca.channels[14].duty_cycle = 0x0000

 

次にサーボモーターの場合。

7番チャネルのサーボモーターを0度の位置に動かしたい場合はこのように書きます。

kit.servo[7].angle = 0

7番チャネルのサーボモーターを90度にしたい場合はこう

kit.servo[7].angle = 90

7番チャネルのサーボモーターを180度にしたい場合はこう

kit.servo[7].angle = 180

角度をそのまま指定するだけなので非常に簡単です。

 

最後に360°連続回転サーボ(ローテーションサーボ)の場合。

kit.continuous_servo[0].throttle = 1.0

これで0番チャネルの連続回転サーボが全力で正転します。

逆に0番チャネルの連続回転サーボを全力で逆転させる場合はこう書きます。

kit.continuous_servo[0].throttle = -1.0

0番チャネルの連続回転サーボを停止させる場合はこうです。

kit.continuous_servo[0].throttle = 0.0

throttleには-1.0~1.0までの任意の値を指定できます。簡単ですね。

また、モータードライバでDCモーターをPWM制御する場合と違って、低い値でも確実に動いてくれるため操作が非常に楽です。

ライブラリのインストールは次の2行を実行します。

$ sudo pip3 install adafruit-circuitpython-pca9685
$ sudo pip3 install adafruit-circuitpython-servokit

Blynk

ぼくのブログではすっかりお馴染みになっているスマホ対応IoTプラットフォームのBlynkを今回も使っていて、ラズパイタンクの操作をスマホのBlynkアプリで行っています。

写真のイケメンの方はウクライナ出身のBlynkの共同創業者の方だそうです。

ライブラリのインストールは次のように行いました。

$ git clone https://github.com/vshymanskyy/blynk-library-python.git
$ cd blynk-library-python/
$ sudo pip3 install .

プログラム

かなりコメント少なめな塩対応となっていますが、今後コメントを加えるかもしれません。

※2020/8/30 52行目と53行目の不等号の向きが逆になっていたのを修正。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# camtank.py
# March 25, 2020
# By Hiroyuki ITO
# https://intellectualcuriosity.hatenablog.com/  
# This program is licensed in the public domain.

BLYNK_AUTH = 'YourAuthToken'

from board import SCL, SDA
import busio
from adafruit_pca9685 import PCA9685
i2c_bus = busio.I2C(SCL, SDA)
pca = PCA9685(i2c_bus)
pca.frequency = 60

from adafruit_servokit import ServoKit
kit = ServoKit(channels=8)

joystick_mode = 0   # 0:Motor 1:PanTilt
angleX = 0
angleY = 0

import BlynkLib
from BlynkTimer import BlynkTimer
import os
import math

blynk = BlynkLib.Blynk(BLYNK_AUTH)
timer = BlynkTimer()
     
def limit(value, lower, upper):
    if value < lower:
        value = lower
    elif value > upper:
        value = upper
    return value

@blynk.on("connected")
def blynk_connected():
    blynk.sync_virtual(2, 4)
    blynk.virtual_write(3, 1)

@blynk.on("V1")     # Joystick
def v1_write_handler(value):
    global joystick_mode, angleX, angleY
    joystickX = int(value[0])
    joystickY = int(value[1])
    # PanTilt
    if joystick_mode:
        if   joystickX >  30: angleX =  4
        elif joystickX < -30: angleX = -4
        else:                 angleX =  0
        if   joystickY >  30: angleY =  4
        elif joystickY < -30: angleY = -4
        else:                 angleY =  0
    # Motor
    else:
        joystickY = joystickY * 0.7
        if joystickY >= 0:
            if joystickX >= 0:
                throttleL = joystickY + joystickX * 0.3
                throttleR = -joystickY + joystickX
            else:
                throttleL = joystickY + joystickX
                throttleR = -joystickY + joystickX * 0.3
        elif joystickX >= 0:
            throttleL = joystickY - joystickX * 0.3
            throttleR = -joystickY - joystickX
        else:
            throttleL = joystickY - joystickX
            throttleR = -joystickY - joystickX * 0.3
        kit.continuous_servo[0].throttle = throttleL / 100
        kit.continuous_servo[1].throttle = throttleR / 100

@blynk.on("V2")     # Motor and Pan/Tilt switch button
def v2_write_handler(value):
    global joystick_mode
    joystick_mode = int(value[0])
    if joystick_mode == 0:  # Motor
        kit.servo[6].angle = 90
        kit.servo[7].angle = 110

@blynk.on("V3")     # Power button
def v3_write_handler(value):
    if int(value[0]) == 0:  # Off
        os.system('sudo shutdown -h now')

@blynk.on("V4")     # LED ON/OFF button
def v4_write_handler(value):
    if int(value[0]):       # ON
        pca.channels[14].duty_cycle = 0xffff
        pca.channels[15].duty_cycle = 0xffff
    else:                   # OFF
        pca.channels[14].duty_cycle = 0x0000
        pca.channels[15].duty_cycle = 0x0000

def panTiltMove():
    global angleX, angleY
    kit.servo[6].angle = limit(kit.servo[6].angle - angleX, 0, 180)
    kit.servo[7].angle = limit(kit.servo[7].angle - angleY, 0, 180)

# Define Timers
timer.set_interval(0.1, panTiltMove)    # 100ms

while True:
    blynk.run()
    timer.run()

ほんの少しだけ説明すると、19行目がServoKit(channels=16)ではなくServoKit(channels=8)となっているのは、ぼくが無理やりLEDとサーボモーターを一緒に使おうとしたからです。チャネル0~7までをサーボ用に、チャネル8~15をLED用に割り振るための設定です。公式に認められている使い方じゃなさそうなのですが、一応動いています。

 

スマホ画面

スマホの画面はAndroidの2画面表示機能を使って、上にブラウザで映像を表示し、下に操作用のBlynkアプリを表示しています。

f:id:IntellectualCuriosity:20200327010543j:plain

Blynkにも映像表示用のウィジットが用意されていますが、映像をバッファリングして遅延が発生するため使用を断念しました。

ブラウザだとほぼ遅延なしに映像を表示することができます。今回はFirefoxを使っていますがChromeでも大丈夫だと思います。

 

Blynkアプリの設定

Blynkアプリの設定画面はこんな感じです。

f:id:IntellectualCuriosity:20200327012758j:plain

ラズパイをスマホからシャットダウンできるようにPOWERボタンを付けていますが、操作中に誤って押さないように2画面表示で表示されるエリアの外に配置しています。

 

各ウィジットの設定はこうなっています。

Joystick(駆動、パン・チルト用)

f:id:IntellectualCuriosity:20200327015222j:plain

Styled Button(Joystickの駆動/パン・チルト切替用)

f:id:IntellectualCuriosity:20200327015634j:plain

Button(シャットダウン用)

f:id:IntellectualCuriosity:20200327015825j:plain

Button(LEDオン/オフ用)

f:id:IntellectualCuriosity:20200327020026j:plain

 

おわり