Arduino、Google Cardboard、Bluetoothで作るDIY VRスケートボード
必要なパーツと材料
| パーツ | 数量 | 備考 |
|---|---|---|
| Arduino UNO | 1 | Leonardo推奨(Unoの場合は配線が異なる) |
| HC-06 Bluetoothモジュール | 1 | デフォルト名「HC-06」、パスワード「1234」 |
| Androidデバイス | 1 | Google Cardboard対応必須 |
| Google Cardboard VRヘッドセット | 1 | スマートフォン装着型 |
| ジャンパーワイヤー | 1式 | |
| GY-521 MPU-6050 3軸ジャイロ+加速度センサー | 1 | ヘッダーピンのはんだ付け必要 |
必要なアプリとオンラインサービス
- Google Cardboard VR SDKs
プロジェクト概要
古いスケートボードをVRスノーボードコントローラーに変える、約1時間で完成する楽しいプロジェクトです。スケートボードの底面にMPU-6050加速度/ジャイロセンサーとArduinoを搭載し、ボードの傾きをBluetoothでAndroidスマートフォンに送信します。Unityで作成したVRゲーム(Android用)と連携し、体重移動で左右に曲がり、フロントウィールを持ち上げるとジャンプします。
追加で必要なもの
- スケートボード(幅広のオールドスクールボード推奨)
- テニスボール4個(転がり防止用)
- ミニブレッドボード
- 9V電池ボックス(スイッチ付き、バレルプラグ仕様)
- はんだごて、ホットグルーガン
Androidアプリ、Arduinoコード、パーツ購入リンクは こちら から入手可能です。
手順1:Arduinoデバイス(送信機)の組み立て
写真の通りに配線してください。MPU-6050にはヘッダーピンをはんだ付けする必要があります。はんだごての先が綺麗なら約11秒で完了しますが、酸化しているとかなり困難になります。
重要: 写真と全く同じ向きで部品を配置してください。コードの修正が不要になります。スケートボードへの取り付けも写真通りに行ってください(特にMPU-6050の向き)。バッテリーボックスはホットグルーで固定し、Arduino基板はネジ止めすると安定します。
Arduino Unoを使用する場合の配線違い:
- SDA → A4
- SCL → A5
Leonardoの場合はSDA/SCLピンを直接使用できます。
手順2:Arduinoコードのアップロード
重要: コードをアップロードする前に、BluetoothモジュールのRX/TXピンをArduinoから外してください。
提供されているコードは、MPU-6050のDMP(Digital Motion Processor)を使用してヨー・ピッチ・ロールを計算し、閾値に基づいて「前進」「後進」「左」「右」「停止」のコマンドをシリアル(Bluetooth)経由で送信します。
手順3:スマートフォンにアプリをインストール
- 提供リンクからAPKをダウンロード
- Androidの設定で「提供元不明のアプリ」インストールを許可
- Bluetooth設定でHC-06とペアリング(パスワード:1234)
- スケートボードを平らな場所に置き、Arduinoの電源をON
- アプリを起動すると自動的にBluetooth接続され、ゲームが開始
注意点:
- これは作者の初めてのUnityゲームのため、機能は基本的です
- Bluetoothが切断された場合はアプリを再起動してください(起動時のみ接続を試みます)
- 反応の感度はコード内の閾値(y > 5 など)で調整可能
回路図

Arduinoコード
#include "I2Cdev.h"
#include "MPU6050_6Axis_MotionApps20.h"
#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
#include "Wire.h"
#endif
MPU6050 mpu;
bool dmpReady = false;
uint8_t mpuIntStatus;
uint8_t devStatus;
uint16_t packetSize;
uint16_t fifoCount;
uint8_t fifoBuffer[64];
Quaternion q;
VectorInt16 aa;
VectorInt16 aaReal;
VectorInt16 aaWorld;
VectorFloat gravity;
float euler[3];
float ypr[3];
volatile bool mpuInterrupt = false;
void setup() {
#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
Wire.begin();
TWBR = 24;
#elif I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE
Fastwire::setup(400, true);
#endif
Serial.begin(9600); // Uno用
Serial1.begin(9600); // Leonardo用
Serial.println(F("Initializing I2C devices..."));
mpu.initialize();
Serial.println(F("Testing device connections..."));
Serial.println(mpu.testConnection() ? F("MPU6050 connection successful") : F("MPU6050 connection failed"));
Serial.println(F("Initializing DMP..."));
devStatus = mpu.dmpInitialize();
// キャリブレーション値(個体差により調整必要)
mpu.setXGyroOffset(220);
mpu.setYGyroOffset(76);
mpu.setZGyroOffset(-85);
mpu.setZAccelOffset(1788);
if (devStatus == 0) {
Serial.println(F("Enabling DMP..."));
mpu.setDMPEnabled(true);
Serial.println(F("Enabling interrupt detection..."));
attachInterrupt(0, dmpDataReady, RISING);
mpuIntStatus = mpu.getIntStatus();
Serial.println(F("DMP ready! Waiting for first interrupt..."));
dmpReady = true;
packetSize = mpu.dmpGetFIFOPacketSize();
} else {
Serial.print(F("DMP Initialization failed (code "));
Serial.print(devStatus);
Serial.println(F(")"));
}
}
void sendData(int x, int y, int z) {
if(z < -10){ // 前進
Serial1.write("f"); Serial1.write(10);
Serial.write("f"); Serial.write(10);
} else if (z > 0){ // 後進
Serial1.write("b"); Serial1.write(10);
Serial.write("b"); Serial.write(10);
} else if (y > 5){ // 右
Serial1.write("r"); Serial1.write(10);
Serial.write("r"); Serial.write(10);
} else if (y < -5){ // 左
Serial1.write("l"); Serial1.write(10);
Serial.write("l"); Serial.write(10);
} else { // 停止
Serial1.write("s"); Serial1.write(10);
Serial.write("s"); Serial.write(10);
}
}
void loop() {
if (!dmpReady) return;
mpuInterrupt = false;
mpuIntStatus = mpu.getIntStatus();
fifoCount = mpu.getFIFOCount();
if ((mpuIntStatus & 0x10) || fifoCount == 1024) {
mpu.resetFIFO();
Serial.println(F("FIFO Overflow"));
} else if (mpuIntStatus & 0x02) {
while (fifoCount < packetSize) {
fifoCount = mpu.getFIFOCount();
}
mpu.getFIFOBytes(fifoBuffer, packetSize);
fifoCount -= packetSize;
mpu.dmpGetQuaternion(&q, fifoBuffer);
mpu.dmpGetGravity(&gravity, &q);
mpu.dmpGetYawPitchRoll(ypr, &q, &gravity);
sendData(ypr[0] * 180/M_PI, ypr[1] * 180/M_PI, ypr[2] * 180/M_PI);
}
}
void dmpDataReady() {
mpuInterrupt = true;
}
製造プロセス
- 自作LUMAZOID:Arduino音楽ビジュアライザーDIYガイド
- Arduino Nanoで水滴を高速撮影 – DIYハイスピードフォトグラフィ
- Arduinoとスマホで作るスマート電圧計 – 簡単DIYプロジェクト
- JX Wave Generator – Arduino互換DDS発振器(OLEDディスプレイ搭載)
- 自作Arduino J.A.R.V.I.S. 音声アシスタント - アークリアクター風デザイン
- DIY ArduinoラジオニクスEMR治療デバイスの構築
- Arduino NanoとSG90サーボで作るDIY 2軸モーションレーシングシミュレータ
- Arduino UNOとNEO-6MモジュールでGPS目的地通知装置を作る
- ArduRadio:Arduino自作FMラジオ付き目覚まし時計
- Arduinoテニスゲーム:NeoPixelマトリクス・9DoFセンサー・Bluetoothで作るバーチャルラケット体験
- Arduinoで作るポータブルRFIDドアロック - ステップバイステップガイド