携帯型リモート監視カメラロボット - Raspberry PiとArduinoで構築
必要な部品と材料
| SparkFun Arduino Pro Mini 328 - 5V/16MHz | ×1 |
| タイヤ付きDCモーター(ギアボックス+ブラケット付き) | ×1 |
| SparkFun Dual H-Bridge モータードライバー L298 | ×1 |
| Raspberry Pi 3 Model B | ×1 |
| Raspberry Pi Camera Module | ×1 |
必要な工具
- USB-シリアル変換アダプター FT232RL(Pro Mini書き込み用)
- Arduino IDE
- 電動ドリル
- 細刃ノコギリ
- ドライバーセット
- はんだごて
- ワイヤーカッター
プロジェクト概要
ロボット設計・製作から、Linux(Raspberry Pi)の高度な設定、Androidアプリ開発、リモート制御までをカバーする野心的なプロジェクトです。全体を通して多くの学びが得られます。
扱う技術と概念
- 開発プラットフォーム: Arduino、Raspberry Pi、Android
- 電子工作: Hブリッジ、トランジスタによる大電流駆動、赤外線センサー
- Linux: Docker、Docker Compose、systemctlでのサービス管理、ビデオストリーミング
- プログラミング: Androidアプリ、Python、Arduino言語、シリアル通信、MQTT
Step 1: 必要なもの
パーツ: プレキシガラス板、プラスチック板、接着剤、タイヤ付きDCモーター(ギアボックス+ブラケット)、小型ナット/ボルト、六角スペーサー、全方向キャスター×2、小型LEDライト(ヘッドライト化)、Arduino Pro Mini 328P、赤外線障害物センサー×2、PCB、NPNトランジスタ(ライト駆動用)、L7805CV 5Vレギュレーター、L298 Hブリッジ、220Ω抵抗、USB Type-Aオスコネクター、Micro USBオスコネクター、各種配線、3.3Vレギュレーター(Pi通信用)、オス/メスPCBコネクター、電源スイッチ、XT-60メスLiPoコネクター、2S 1300mAh LiPoバッテリー、5Vモバイルバッテリー、Raspberry Pi 3、microSDカード、Piケース、Piカメラ
工具: FT232RLアダプター、Arduino IDE、ドリル、細刃ノコ、ドライバー、はんだごて、ニッパー
必要スキル: はんだ付け、基本的なArduinoプログラミング、Linuxサービス設定・パッケージ管理
Step 2: ロボットプラットフォーム製作
仕様:
- 前輪2輪を個別のDCモーターで駆動
- 後輪は360°自由回転キャスター
- 前輪の速度差で方向制御(その場旋回も可能)
- トップにライト搭載
- 電子部品、バッテリー、Piケース+カメラを収納
- 数cmのグラウンドクリアランス確保
ベースプレート: 18×13cm。DCモーターをブラケットで固定。Hブリッジを中央底面に配置。後輪は2cm六角スペーサーで取り付け。Hブリッジ付近に配線通し用の大きな穴を開ける。
トップ部分: L字型プレート2枚(12×13cm、6.5×13cm)を接着。電子部品カバー、ヘッドライトマウント、Piケース支持台を兼ねる。6cm六角スペーサーでベースと連結。
Step 3: 電子回路製作
ピンアサイン(Arduino側)
- ヘッドライト: D3
- 左モーター: PWM (D5), EN1/EN2 (A4, A5)
- 右モーター: PWM (D6), EN1/EN2 (A3, A2)
- 赤外線センサー: 前方 (A0), 後方 (A1)
- Raspberry Pi通信: Tx (D11), Rx (D10)
PCB組み立て手順
- Hブリッジをベース底面に固定済み。赤外線センサーを前後にL字金具で取り付け
- ヘッドライト: 5V LEDライトを加工し、トップ中央に接着。配線を穴通ししてコネクター化
- Raspberry Piケース組み立て(Pi、カメラ、microSDカード、カメラケーブル)
- メインPCB製作(Fritzing回路図参照)
- コネクター類を基板裏面に半田付け
- KF301-2Pコネクター、NPNトランジスタ+ベース抵抗、L7805CV、3.3Vレギュレーター(Pi通信Txライン用)を実装
- Arduino Pro Miniにオスピン半田付け
- 赤/黒/白の細線で配線(Fritzing図参照)
- ヘッドライト用メスコネクター実装
- 各種コネクター製作
- 5V USBバッテリー→Pi/Arduino給電ケーブル
- LiPoバッテリー→電子基板用コネクター(XT-60、スイッチ経由)
- 赤外線センサー、Hブリッジ、モーター接続
- 拡大鏡とマルチメーターで全配線確認後、Arduinoをソケットに装着
Step 4: Arduinoコード
なぜArduinoを中間層に置くのか?
- モジュール性: PiなしでもArduinoロボットとして再利用可能
- 安全性: 3$のPro Miniなら壊れても安心(Piは35$)
- リアルタイム性: OSの割り込みを受けず、PWM制御・センサーポーリングが確実
- フェイルセーフ: Pythonスクリプトエラーで暴走しても、Arduino側でタイムアウト停止可能
- デバッグ容易性: 疎結合システムは問題切り分けが容易
スケッチの機能:
- シリアル経由でモーター/ライトコマンド受信し駆動
M:-25:16;→ 左-25、右16(前進しながら左旋回)M:44:19;→ 左44、右19(前進しながら右旋回)L:1;ライトON /L:0;ライトOFF
- 前後赤外線センサーをポーリングし、距離データをシリアル送信
- 形式:
F=5:B=3;(前方障害物レベル5、後方レベル3)
- 形式:
ライブラリ: TextMotorCommandsInterpretter
// TextMotorCommandsInterpretter source: "https://github.com/danionescu0/arduino/tree/master/libraries/TextMotorCommandsInterpretter"
#include <SoftwareSerial.h>
#include <TextMotorCommandsInterpretter.h>
const char MOTOR_COMMAND = 'M';
const char LIGHT_COMMAND = 'L';
const long maxDurationForMottorCommand = 300; // コマンド有効期限(ms)
const byte maxPwmValue = 230; // 速度制限
const long transmitingInterval = 500; // 距離データ送信間隔(ms)
const int maxObstacleDetection = 1000; // アナログ最大検出値
const int minObstacleDetection = 500; // アナログ最小検出値
const byte FLASH_PIN = 3;
const byte RIGHT_MOTOR_PWM_PIN = 5;
const byte RIGHT_MOTOR_EN1_PIN = A4;
const byte RIGHT_MOTOR_EN2_PIN = A5;
const byte LEFT_MOTOR_PWM_PIN = 6;
const byte LEFT_MOTOR_EN1_PIN = A3;
const byte LEFT_MOTOR_EN2_PIN = A2;
const byte FRONT_DISTANCE_SENSOR = A0;
const byte BACK_DISTANCE_SENSOR = A1;
SoftwareSerial masterComm(11, 10); // RX, TX (Pi通信用)
TextMotorCommandsInterpretter motorCommandsInterpretter(-50, 50, -50, 50);
String currentCommand;
long lastCheckedTime;
long lastTransmitTime;
boolean inMotion = false;
void setup() {
Serial.begin(9600);
masterComm.begin(9600);
masterComm.setTimeout(10);
pinMode(FLASH_PIN, OUTPUT);
pinMode(LEFT_MOTOR_PWM_PIN, OUTPUT);
pinMode(LEFT_MOTOR_EN1_PIN, OUTPUT);
pinMode(LEFT_MOTOR_EN2_PIN, OUTPUT);
pinMode(RIGHT_MOTOR_PWM_PIN, OUTPUT);
pinMode(RIGHT_MOTOR_EN1_PIN, OUTPUT);
pinMode(RIGHT_MOTOR_EN2_PIN, OUTPUT);
lastCheckedTime = millis();
lastTransmitTime = millis();
}
void loop() {
if (masterComm.available() > 0) {
currentCommand = masterComm.readString();
processCommand();
}
if (inMotion && millis() - lastCheckedTime > maxDurationForMottorCommand) {
stopMotors();
}
if (millis() - lastTransmitTime > transmitingInterval) {
lastTransmitTime = millis();
masterComm.print(getObstacleData());
Serial.print(analogRead(BACK_DISTANCE_SENSOR));Serial.print("---");
Serial.println(getObstacleData());
}
}
String getObstacleData() {
int frontDistance = analogRead(FRONT_DISTANCE_SENSOR);
int backDistace = analogRead(BACK_DISTANCE_SENSOR);
frontDistance = map(frontDistance, maxObstacleDetection, minObstacleDetection, 0, 10);
backDistace = map(backDistace, maxObstacleDetection, minObstacleDetection, 0, 10);
return String("F=" + String(frontDistance) + ":B=" + String(backDistace) + ";");
}
void processCommand() {
switch (currentCommand.charAt(0)) {
case (MOTOR_COMMAND): steerCar(); break;
case (LIGHT_COMMAND): toggleLight(currentCommand.charAt(2)); break;
}
}
void steerCar() {
motorCommandsInterpretter.analizeText(currentCommand);
float percentLeftMotor = motorCommandsInterpretter.getPercentLeft();
float percentRightMotor = motorCommandsInterpretter.getPercentRight();
Serial.write("Left=");Serial.println(percentLeftMotor);
Serial.write("Right=");Serial.println(percentRightMotor);
setMotorsDirection(motorCommandsInterpretter.getDirection());
analogWrite(LEFT_MOTOR_PWM_PIN, percentLeftMotor * maxPwmValue);
analogWrite(RIGHT_MOTOR_PWM_PIN, percentRightMotor * maxPwmValue);
inMotion = true;
lastCheckedTime = millis();
}
void setMotorsDirection(boolean forward) {
if (forward) {
digitalWrite(LEFT_MOTOR_EN1_PIN, HIGH);
digitalWrite(LEFT_MOTOR_EN2_PIN, LOW);
digitalWrite(RIGHT_MOTOR_EN1_PIN, HIGH);
digitalWrite(RIGHT_MOTOR_EN2_PIN, LOW);
} else {
digitalWrite(LEFT_MOTOR_EN1_PIN, LOW);
digitalWrite(LEFT_MOTOR_EN2_PIN, HIGH);
digitalWrite(RIGHT_MOTOR_EN1_PIN, LOW);
digitalWrite(RIGHT_MOTOR_EN2_PIN, HIGH);
}
}
void stopMotors() {
Serial.println("Stopping motors");
analogWrite(LEFT_MOTOR_PWM_PIN, 0);
analogWrite(RIGHT_MOTOR_PWM_PIN, 0);
inMotion = false;
}
void toggleLight(char command) {
Serial.println("Toggle light");
if (command == '1') digitalWrite(FLASH_PIN, HIGH);
else digitalWrite(FLASH_PIN, LOW);
}
Step 5: Raspberry Piプロジェクトと依存関係のインストール・設定
システムアーキテクチャ
- Androidアプリ: uv4lストリーミングをWebViewで表示。操作コマンドをMQTTサーバーへ送信
- MQTTサーバー(mosquitto): Dockerコンテナで稼働
- Pythonサーバー: Dockerコンテナ内で稼働。MQTTコマンド受信→シリアルでArduinoへ転送。ArduinoからのセンサーデータをMQTTへ転送
- Arduino: モーター/ライト制御、前後センサー監視
前提条件
- Raspbian完全インストール、カメラ物理接続・有効化、SSH有効化
- ポートフォワーディング設定(外部アクセス時): 9090(uv4l)、1883(mosquitto)
gitインストール- プロジェクトクローン:
git clone https://github.com/danionescu0/robot-camera-platform.git(フォルダ位置固定:/home/pi/robot-camera-platform) - Piのシリアルコンソール無効化
uv4lストリーミングインストール
chmod +x uv4l/install.sh
chmod +x uv4l/start.sh
sh ./uv4l/install.sh
設定: uv4l/start.shでパスワード、ポート、フレームレート、解像度、回転等を調整
DockerとDocker Composeインストール
設定ファイル編集
config.py: MQTTパスワード、シリアルポート/ボーレート設定docker-container/mosquitto/Dockerfile:RUN mosquitto_passwd -b /etc/mosquitto/pwfile user your_passwordを自分の認証情報に書き換え
uv4lテスト
sh ./uv4l/start.sh # 開始
# ブラウザで https://your_ip:9090/stream を確認
sudo pkill uv4l # 停止
systemctlで自動起動設定
# systemctlフォルダ内のファイルを /etc/systemd/system/ にコピー
sudo systemctl enable robot-camera.service
sudo systemctl enable robot-camera-video.service
# 再起動後、ステータス確認
sudo systemctl status robot-camera.service
sudo systemctl status robot-camera-video.service
Step 6: Androidアプリの設定とビルド
- GitHubからクローン:
git clone https://github.com/danionescu0/android-robot-camera - 開発者オプション有効化、Android Studioインストール
- プロジェクトインポート
./app/src/main/values/passwords.xmlでMQTT/ストリーミング認証情報設定- アプリを実機にインストール・実行
Step 7: 使用方法とデバッグ
アプリ操作
- 左側: カメラストリーミング表示
- 右側: ステアリングホイール型コントローラー(タッチ方向に移動)、ヘッドライトトグルボタン
- 右上ステータス:
- Batt Connected(-=障害物なし、矢印=障害物方向、Batt=未実装、Connected/Disconnected=MQTT接続状態)
デバッグ手順
- Arduino層: FTDIアダプターで第2シリアル接続し、手動コマンド送信テスト。モーター逆回転なら配線入れ替え
- Raspberry Pi層:
docker psでコンテナ確認、netstat -nltpでポート9090/1883確認、ls -l /dev/ttyS0でシリアルポート確認、picocomで直接シリアルテスト、シリアルコンソール無効化確認、外部からMQTT/ストリーミングアクセス確認 - Android層: デバッグモード有効化、
passwords.xml確認、外部アクセス確認、アプリ右上の「Connected」確認、Androidデバッガーでエラー確認
Step 8: その他のユースケースと拡張
物体追跡ロボット: 特定色・サイズの物体を追従
sudo pip3 install -r /home/pi/robot-camera-platform/navigation/requirements.txt(時間かかります)navigation/config_navigation.pyでHSV色範囲とサイズ閾値設定- VNC環境で
python3 object_tracking.py colored-object --show-video実行(検出時に円表示)
拡張アイデア: 顔認識追従、巡回・異常検知報告、3Gモデム搭載で屋外危険区域探査など。プラットフォームの汎用性が最大の利点です。
コード
- Androidアプリ: https://github.com/danionescu0/android-robot-camera
- メインリポジトリ(Arduino/Pythonコード): https://github.com/danionescu0/robot-camera-platform
回路図
製造プロセス
- 現代カメラの進化と設計:フィルムからデジタルへ
- カメラレンズの理解:設計、歴史、未来の革新
- MrRobot: Ubuntu Mobile で操作する Rapiro ロボット – 音声、タッチ、シェイクインタラクション
- Roomberry:Raspberry Pi Zero WでRoombaを監視ロボット化
- Bluetooth対応 Arduino制御 モータライズド カメラ スライダー 〜滑らかなシネマティック撮影を実現〜
- NeoPixelリングとArduinoで作るインタラクティブジャイロスコープLEDディスプレイ
- Arduinoと1Sheeldで作る万能Androidリモコン──IR制御で家電を一括操作
- DIY Arduino USBゲームコントローラー - 高性能ゲームパッドを自作
- Pixie: Arduino Pro Miniで作るNeoPixel腕時計 - アナログとデジタル融合のDIY光る時計
- Arduino Dueでマスターする音声認識と音声合成
- Arduinoで作るワイヤレスPowerPointポインター - シームレスなプレゼンテーション制御