この作品は、スマホでお手軽に操作するWifi操作のロボットタンクです。
市販のキットとマイコンと家にある使わなくなったプラモデル、そして操作するスマホで構成されています。
半田不要でIoTを実感できるものです。
動作している動画
作成の動機
- ロボット作成としてZumoTankを貰いました。この製品は、各辺10cm以下のコンパクトな無限軌道式ロボット用プラットフォームでモーターも付属し組み立て済みです。そして、Arudinoのシールドとして使えます。しかし、Arudinoをそのまま載せて遊ぶのでは芸がないと思いカメラ付きの操作可能なタンクを作成することにしました。
最終的には、MQTTを使い、スマートスピーカ連携まで行ったのですが、記事にするには複雑なため
この投稿は、最初にベースとして作ったスマホからのZumoTankの操作とカメラ画像の閲覧までとしています。
パーツ
- 作成に使用したパーツは下記となります。
品目 |
---|
GR-LYCHEE |
カメラ延長用フレキシブルフラットケーブル |
Polou Zumo ロボット Arduino用 |
micro sd card 16GB(GR-LYCHEE挿入用) |
プラモデル(カメラ仕込み用) |
GR-LYCHEEは、カメラと無線を搭載したIoTプロトタイピングボードです。
2個のマイコン(RZ/A1LU、ESP32)が搭載されており、シキノハイテック製のカメラが付属し、OpenCVも動作可能という、非常にパワフルなマイコンボードです。
- IPアドレス指定で、ZUMOのタンクに接続し、前後移動、左右回転、カメラ撮影が行えます。移動量は、スライダーで調整可能です。
筐体の作成
- ZumoにGR-LYCHEEを接続します。GR-LYCHEEは、Arudino互換のため、そのまま刺さります。
付属のカメラのケーブルは短いため、長いものに変更し、カメラにロボットのプラモデルの頭を覆わせて雰囲気をだしてみました。そして、プラモデルの中に配線を隠しています。
使用ライブラリ
- Zumo
zumo-shield
プログラムの作成方法
Githubからソースコードを取得してください。
「GR-LYCHEEとZumoを使ったタンクのプログラム開発」と「スマホで操作するアプリケーションの開発」を行います。
GR-LYCHEEとZumoを使ったタンクのプログラム開発
-
【作成手順】
1.IDE for Gadget Renesasで、下記プログラムを開いてください。
2.環境に応じて、プログラムのSSIDとパスワードを書き換えてください。
3.マイコンボードの設定は、[ツール]-[マイコンボード]-[GR-LYCHEE]に変更し、マイコンボードに書き込んでください。 -
【補足】
スマホとの接続はWifi経由で行います。
タンクの操作は、下記となり、数値は移動量の大きさになります。
・IPアドレス/camera
・IPアドレス/auto/数値
・IPアドレス/foward/数値
・IPアドレス/back/数値
・IPアドレス/right/数値
・IPアドレス/left/数値
・IPアドレス/stop
Zumoを載せたGR-LYCHEEのプログラム(GR-LYCHEE_CAMTANK.ino)
#include <Arduino.h>
#include <Camera.h>
#include <SD.h>
#include <HTTPServer.h>
#include <mbed_rpc.h>
#include <SdUsbConnect.h>
#include <ESP32Interface.h>
//Zumo
#include <ZumoMotors.h>
//Camera Image Size
#define IMAGE_HW 320
#define IMAGE_VW 240
//Wifi Infomation
#define WIFI_SSID "xxxxxxx"
#define WIFI_PW "xxxxxxx"
//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
//Wifi
ESP32Interface wifi;
// Camera
Camera camera(IMAGE_HW, IMAGE_VW);
SdUsbConnect storage("storage");
static char result_str[] = "success";
// Zumo
#define LED_PIN 13
ZumoMotors _motors;
int _speed = 0;
// Program
enum ZUMO_MODE_TYPE
{
Unknown,
Auto,
Foward,
Back,
Left,
Right,
Stop,
};
ZUMO_MODE_TYPE _zumo_mode = ZUMO_MODE_TYPE::Unknown;
static Thread httpTask(osPriorityAboveNormal, (1024 * 4));
////////////////////////////////////////////////////////
//snapshot_req
static int snapshot_req(const char* rootPath, const char* path, const char ** pp_data)
{
if (strcmp(rootPath, "/camera") == 0)
{
Serial.println("snap shot");
size_t size = camera.createJpeg();
*pp_data = (const char*)camera.getJpegAdr();
return size;
}
else if (strcmp(rootPath, "/auto") == 0)
{
_speed = atoi(path+1);
Serial.println("auto");
Serial.println(_speed);
_zumo_mode = ZUMO_MODE_TYPE::Auto;
*pp_data = (const char *)result_str;
return strlen(result_str);
}
else if (strcmp(rootPath, "/foward") == 0)
{
_speed = atoi(path+1);
Serial.print("foward ");
Serial.println(_speed);
_zumo_mode = ZUMO_MODE_TYPE::Foward;
*pp_data = (const char *)result_str;
return strlen(result_str);
}
else if (strcmp(rootPath, "/back") == 0)
{
_speed = atoi(path+1);
Serial.print("back ");
Serial.println(_speed);
_zumo_mode = ZUMO_MODE_TYPE::Back;
*pp_data = (const char *)result_str;
return strlen(result_str);
}
else if (strcmp(rootPath, "/right") == 0)
{
_speed = atoi(path+1);
Serial.print("right ");
Serial.println(_speed);
_zumo_mode = ZUMO_MODE_TYPE::Right;
*pp_data = (const char *)result_str;
return strlen(result_str);
}
else if (strcmp(rootPath, "/left") == 0)
{
_speed = atoi(path+1);
Serial.print("left ");
Serial.println(_speed);
_zumo_mode = ZUMO_MODE_TYPE::Left;
*pp_data = (const char *)result_str;
return strlen(result_str);
}
else if (strcmp(rootPath, "/stop") == 0)
{
Serial.println("stop");
_speed = 0;
_zumo_mode = ZUMO_MODE_TYPE::Stop;
*pp_data = (const char *)result_str;
return strlen(result_str);
}
else
{
Serial.println("unknown");
}
return 0;
}
////////////////////////////////////////////////////////
//http_task
void http_task(void)
{
// wifi
Serial.print("Connecting Wi-Fi..");
if (wifi.connect(WIFI_SSID, WIFI_PW, NSAPI_SECURITY_WPA_WPA2) == 0)
{
Serial.println("success");
}
else
{
Serial.println("fail");
}
Serial.print("MAC Address is ");
Serial.println(wifi.get_mac_address());
Serial.print("IP Address is ");
Serial.println(wifi.get_ip_address());
Serial.print("NetMask is ");
Serial.println(wifi.get_netmask());
Serial.print("Gateway Address is ");
Serial.println(wifi.get_gateway());
Serial.println("Network Setup OK\r\n");
//req
SnapshotHandler::attach_req(&snapshot_req);
HTTPServerAddHandler<SnapshotHandler>("/camera"); //Camera
HTTPServerAddHandler<SnapshotHandler>("/auto");
HTTPServerAddHandler<SnapshotHandler>("/foward");
HTTPServerAddHandler<SnapshotHandler>("/back");
HTTPServerAddHandler<SnapshotHandler>("/right");
HTTPServerAddHandler<SnapshotHandler>("/left");
HTTPServerAddHandler<SnapshotHandler>("/stop");
Serial.println("Handler");
FSHandler::mount("/storage", "/");
HTTPServerAddHandler<FSHandler>("/");
HTTPServerAddHandler<RPCHandler>("/rpc");
Serial.println("Server Start");
HTTPServerStart(&wifi, 80);
}
//Tank foward
void fowardFunction(int spd)
{
// run left motor forward
// run right motor forward
for (int speed = 0; speed <= spd; speed++)
{
_motors.setLeftSpeed(-speed);
_motors.setRightSpeed(-speed);
delay(2);
}
for (int speed = spd; speed >= 0; speed--)
{
_motors.setLeftSpeed(-speed);
_motors.setRightSpeed(-speed);
delay(2);
}
}
//Tank back
void backFunction(int spd)
{
// run left motor backward
// run right motor backward
for (int speed = 0; speed <= spd; speed++)
{
_motors.setLeftSpeed(speed);
_motors.setRightSpeed(speed);
delay(2);
}
for (int speed = spd; speed >= 0; speed--)
{
_motors.setLeftSpeed(speed);
_motors.setRightSpeed(speed);
delay(2);
}
}
//Tank right turn
void rightFunction(int spd)
{
// run left motor forward
// run right motor backward
for (int speed = 0; speed <= spd; speed++)
{
_motors.setLeftSpeed(-speed);
_motors.setRightSpeed(speed);
delay(2);
}
for (int speed = spd; speed >= 0; speed--)
{
_motors.setLeftSpeed(-speed);
_motors.setRightSpeed(speed);
delay(2);
}
}
//Tank left turn
void leftFunction(int spd)
{
// run left motor backward
// run right motor forward
for (int speed = 0; speed <= spd; speed++)
{
_motors.setLeftSpeed(speed);
_motors.setRightSpeed(-speed);
delay(2);
}
for (int speed = spd; speed >= 0; speed--)
{
_motors.setLeftSpeed(speed);
_motors.setRightSpeed(-speed);
delay(2);
}
}
//Tank stop
void stopFunction()
{
_motors.setLeftSpeed(0);
_motors.setRightSpeed(0);
}
////////////////////////////////////////////////////////
//setup
void setup(void)
{
Serial.begin(9600);
Serial.println("Starts.");
// SD & USB
Serial.print("Finding strage..");
storage.wait_connect();
Serial.println("done");
// Zumo
pinMode(LED_PIN, OUTPUT);
_motors.flipLeftMotor(true);
_motors.flipRightMotor(true);
// camera
camera.begin();
// http
httpTask.start(&http_task);
Serial.println("start");
}
////////////////////////////////////////////////////////
//loop
void loop()
{
// Test Input
while (Serial.available() > 0)
{
char mode = Serial.read();
Serial.print("modeNo=");
Serial.println(mode);
int modeNo = (mode - '0');
_zumo_mode = (ZUMO_MODE_TYPE)modeNo;
}
if(_zumo_mode == ZUMO_MODE_TYPE::Unknown)
{
return;
}
// Control
switch(_zumo_mode)
{
case ZUMO_MODE_TYPE::Auto:
digitalWrite(LED_PIN, HIGH);
// front
fowardFunction(_speed);
// back
backFunction(_speed);
// left
leftFunction(225);
// front
fowardFunction(_speed);
// left
leftFunction(225);
// front
fowardFunction(_speed);
// left
leftFunction(225);
// front
fowardFunction(_speed);
// left
leftFunction(225);
// front
fowardFunction(_speed);
digitalWrite(LED_PIN, LOW);
break;
case ZUMO_MODE_TYPE::Stop:
stopFunction();
break;
case ZUMO_MODE_TYPE::Back:
digitalWrite(LED_PIN, HIGH);
backFunction(_speed);
digitalWrite(LED_PIN, LOW);
break;
case ZUMO_MODE_TYPE::Foward:
digitalWrite(LED_PIN, HIGH);
fowardFunction(_speed);
digitalWrite(LED_PIN, LOW);
break;
case ZUMO_MODE_TYPE::Right:
digitalWrite(LED_PIN, HIGH);
rightFunction(_speed);
digitalWrite(LED_PIN, LOW);
break;
case ZUMO_MODE_TYPE::Left:
digitalWrite(LED_PIN, HIGH);
leftFunction(_speed);
digitalWrite(LED_PIN, LOW);
break;
}
_zumo_mode = ZUMO_MODE_TYPE::Unknown;
}
スマホで操作するアプリケーションの開発
-
プログラムは、Unityを使用しています。
事前にUnityがインストールされた環境での説明となります。 -
【作成手順】
1.Githubからソースコードをダウンロードしてください
2.Unityを起動し、プロジェクトを読み込んでください
フォルダ階層は、ダウンロードした「GRLycheeTank\Unity」になります
3.Unityのプログラムシーン「Assets\Main」を開いてください
5.Androidプロジェクトで出力してください
6.Androidスマートフォンにインストールしてください -
コードの説明
・MainシーンのManagerにアタッチしているスクリプト(ZumoControlScript.cs)が本体になります。
uGUIの各操作ボタンを押すことで、スクリプトに対応したコードが実行されます。
スマホ用コントロールのメインスクリプト(ZumoControlScript.cs)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Networking;
/// <summary>
/// ZumoControlScript
/// </summary>
public class ZumoControlScript : MonoBehaviour
{
public RawImage cameraImage;
public InputField ipAddressText;
public Slider powerSlider;
public SampleWebView webView;
public enum ZUMO_MODE
{
Front,
Back,
Left,
Right,
Round,
Camera
}
/// <summary>
/// Start
/// </summary>
void Start()
{
if (ipAddressText != null)
{
var address = PlayerPrefs.GetString("address");
ipAddressText.text = string.IsNullOrEmpty(address) ? "http://192.168.10.12" : address;
}
}
/// <summary>
/// OnApplicationQuit
/// </summary>
private void OnApplicationQuit()
{
if (ipAddressText != null)
{
PlayerPrefs.SetString("address", ipAddressText.text);
}
}
/// <summary>
/// CreateSpriteFromByte
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
protected Sprite CreateSpriteFromByte(byte[] data)
{
var tex = new Texture2D(320, 240);
tex.LoadImage(data);
return Sprite.Create(tex, new Rect(0, 0, 320, 240), Vector2.zero);
}
public Texture readByBinary(byte[] bytes)
{
var texture = new Texture2D(1, 1);
texture.LoadImage(bytes);
return texture;
}
/// <summary>
/// SendZumo
/// </summary>
protected IEnumerator SendZumo(ZUMO_MODE mode)
{
if (ipAddressText != null && powerSlider != null)
{
var url = ipAddressText.text;
var power = powerSlider.value * 600f;
if (power <= 10)
{
power = 50;
}
switch (mode)
{
case ZUMO_MODE.Front:
url += string.Format("/foward/{0}", (int)power);
break;
case ZUMO_MODE.Back:
url += string.Format("/back/{0}", (int)power);
break;
case ZUMO_MODE.Left:
url += string.Format("/left/{0}", (int)power);
break;
case ZUMO_MODE.Right:
url += string.Format("/right/{0}", (int)power);
break;
case ZUMO_MODE.Round:
url += string.Format("/auto/{0}", (int)power);
break;
case ZUMO_MODE.Camera:
url += "/camera";
break;
}
Debug.Log(url);
// texture
if (mode == ZUMO_MODE.Camera && cameraImage != null)
{
webView.Url = url;
webView.LoadContent();
}
else
{
WWW www = new WWW(url);
yield return www;
}
}
}
public void FrontAction()
{
StartCoroutine(SendZumo(ZUMO_MODE.Front));
}
public void BackAction()
{
StartCoroutine(SendZumo(ZUMO_MODE.Back));
}
public void LeftAction()
{
StartCoroutine(SendZumo(ZUMO_MODE.Left));
}
public void RightAction()
{
StartCoroutine(SendZumo(ZUMO_MODE.Right));
}
public void RoundAction()
{
StartCoroutine(SendZumo(ZUMO_MODE.Round));
}
public void CameraAction()
{
StartCoroutine(SendZumo(ZUMO_MODE.Camera));
}
}
最後に
半田は極力避けたいけど、マイコンで何か面白いものを作ってみたいという方のとっかかりになればと思います。
電子工作要素は薄いですが、ここからの発展要素は満載だと思います。
LEDで発行させるも良し、スマートスピーカ連携も面白いです。
是非、試してみてください。
-
tktk360
さんが
2021/01/10
に
編集
をしました。
(メッセージ: 初版)
ログインしてコメントを投稿する