NeoPixelリングとArduinoで作るインタラクティブジャイロスコープLEDディスプレイ
部品と材料
| 部品名 | 数量 | 備考 |
|---|---|---|
| SparkFun Arduino Pro Mini 328 - 5V/16MHz | 1 | 小型マイコンボード |
| Adafruit NeoPixel Ring: WS2812 5050 RGB LED (24個) | 1 | フルカラーLEDリング |
| SparkFun IMU Breakout - MPU-9250 | 1 | 9軸 IMU (加速度/ジャイロ/磁気) ※コードは MPU6050 用 |
| ブレッドボード (汎用) | 1 | |
| オス-オス ジャンパーワイヤー | 1式 | |
| オス-メス ジャンパーワイヤー | 1式 | |
| 単4電池ボックス (4本用) + 電池 | 1 | 電源用 |
プロジェクト概要
このチュートリアルでは、MPU6050 ジャイロスコープ、NeoPixel リング、Arduino を使って、傾き角度に対応して LED が光るデバイスを作ります。
シンプルで楽しいプロジェクトで、ブレッドボード上に組み立てます。手順に従えば冒頭の動画のようなデバイスが完成します。ジャイロスコープと NeoPixel リングの使い方を学ぶのに最適なチュートリアルです。
以前のチュートリアルでシンプルな LED を使ったものに反響があったため、今回は NeoPixel リングに置き換えて作り直しました。Adafruit ライブラリで簡単に制御でき、見た目もずっとスペクタクルです。これらのパーツが手元に眠っているなら、有効活用する絶好の機会です。ステップバイステップで製作方法を解説し、最後には仕組みも説明します。
ステップ 1: 必要なもの

パーツ
- Arduino Pro Mini 328P
- ブレッドボード
- MPU6050 ジャイロスコープ (MPU-9250 でも動作しますが、コードは MPU6050 前提)
- 24 LED NeoPixel リング
- 単4電池ボックス (4本用) + 電池4本
- U字型ジャンパーケーブル (任意)。ブレッドボード上で見栄えが良く、LED が見やすくなります。eBay で 140 本セット約 4 ドル。なければデュポンコードで代用可能。
工具:
- USB-シリアル FTDI アダプター FT232RL (Arduino Pro Mini 書き込み用)
- Arduino IDE
スキル:
- はんだ付け
- 基本的な Arduino プログラミング
ステップ 2: 組み立て


Fritzing 回路図 (fzz 形式) と実装写真を掲載しています。接続の可視化にご活用ください。
- NeoPixel リング裏面に 3 本のオスピンを半田付け (写真参照)
- VCC (正極)
- GND (負極)
- DATA IN (データ入力)
- 電池ボックスをブレッドボード接続可能にする
簡単なのは、電池ボックスの端子にオスデュポンコードを 2 本半田付けすること。 - ブレッドボード上に配線
- NeoPixel リング、マイクロコントローラー、ジャイロスコープを画像のように配置
- すべての GND 配線: マイコン、NeoPixel、ジャイロ
- すべての VCC 配線: マイコン、NeoPixel、ジャイロ
- データ配線:
- SDA, SCL: マイコン ↔ ジャイロ (I2C)
- D6: マイコン → NeoPixel リング DATA IN
- 通電前にすべての接続をダブルチェック
- (任意) ガムテープで電池ボックスをブレッドボード裏に固定すると持ち運びしやすくなる
ステップ 3: コードとキャリブレーション
必要なライブラリのインストール
- Adafruit NeoPixel ライブラリ - NeoPixel 制御用
- MPU6050 ライブラリ - ジャイロスコープ用 (I2Cdevlib に含まれる
MPU6050_6Axis_MotionApps20.hを使用)
これらは重い処理を肩代わりしてくれる優秀なライブラリです。NeoPixel 詳細は こちら。
ライブラリとサンプルコード
以下のコードをコピーするか、GitHub からダウンロードしてください。
#include "I2Cdev.h"
#include <Adafruit_NeoPixel.h>
#include "MPU6050_6Axis_MotionApps20.h"
#include "Wire.h"
#define NEOPIXED_CONTROL_PIN 6
#define NUM_LEDS 24
const int MAX_ANGLE = 45; // 最大傾き角度 (度)
const int LED_OFFSET = 12; // LED 位置オフセット (キャリブレーション用)
MPU6050 mpu;
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_LEDS, NEOPIXED_CONTROL_PIN, NEO_RGB + NEO_KHZ800);
unsigned long lastPrintTime = 0;
bool initialization = false; // DMP 初期化成功フラグ
uint8_t mpuIntStatus; // MPU 割り込みステータス
uint8_t devStatus; // デバイス操作ステータス
uint16_t packetSize; // DMP パケットサイズ (デフォルト 42 バイト)
uint16_t fifoCount; // FIFO バイト数
uint8_t fifoBuffer[64]; // FIFO ストレージバッファ
Quaternion q; // [w, x, y, z] クォータニオン
VectorFloat gravity; // [x, y, z] 重力ベクトル
float ypr[3]; // [yaw, pitch, roll] ヨー/ピッチ/ロール
volatile bool mpuInterrupt = false; // MPU 割り込みフラグ
void setup() {
Serial.begin(9600);
Serial.println("Program started");
initialization = initializeGyroscope();
strip.begin();
}
void loop() {
if (!initialization) return;
mpuInterrupt = false;
mpuIntStatus = mpu.getIntStatus();
fifoCount = mpu.getFIFOCount();
if (hasFifoOverflown(mpuIntStatus, fifoCount)) {
mpu.resetFIFO();
return;
}
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);
// ラジアン → 度変換して LED 更新
redrawLeds(ypr[0] * 180/M_PI, ypr[1] * 180/M_PI, ypr[2] * 180/M_PI);
}
}
boolean hasFifoOverflown(int mpuIntStatus, int fifoCount) {
return (mpuIntStatus & 0x10) || (fifoCount == 1024);
}
void redrawLeds(int x, int y, int z) {
// 角度を ±MAX_ANGLE にクランプ
x = constrain(x, -MAX_ANGLE, MAX_ANGLE);
y = constrain(y, -MAX_ANGLE, MAX_ANGLE);
// Y-Z 平面での傾き方向に基づき、4象限に分けて LED 制御
if (y < 0 && z > 0) { // 第1象限
lightLeds(y, z, 0, 5, 0, 89);
} else if (y < 0 && z < 0) { // 第2象限
lightLeds(y, z, 6, 12, 89, 0);
} else if (y > 0 && z < 0) { // 第3象限
lightLeds(y, z, 13, 19, 0, 89);
} else if (y > 0 && z > 0) { // 第4象限
lightLeds(y, z, 20, 24, 89, 0);
}
}
void lightLeds(int x, int y, int fromLedPosition, int toLedPosition, int fromAngle, int toAngle) {
// 2軸の傾きから角度を計算 (アークタンジェント)
double angle = (atan((double)abs(x) / (double)abs(y)) * 4068) / 71;
// 角度を LED 位置にマッピング
int ledNr = map(angle, fromAngle, toAngle, fromLedPosition, toLedPosition);
printDebug(y, z, ledNr, angle);
uint32_t color;
for (int i = 0; i < NUM_LEDS; i++) {
color = strip.Color(0, 0, 0); // 消灯
if (i == ledNr) {
color = strip.Color(0, 180, 0); // 当該 LED: 明るい緑
} else if (i == ledNr - 1) {
color = strip.Color(0, 5, 0); // 1つ前の LED: 暗い緑 (フェード効果)
}
strip.setPixelColor(normalizeLedPosition(i), color);
}
strip.show();
}
int normalizeLedPosition(int position) {
// LED_OFFSET を考慮してリング位置を正規化
if (NUM_LEDS > position + LED_OFFSET) {
return position + LED_OFFSET;
}
return position + LED_OFFSET - NUM_LEDS;
}
void printDebug(int y, int z, int lightLed, int angle) {
if (millis() - lastPrintTime < 500) return;
Serial.print("a="); Serial.print(angle); Serial.print("; ");
Serial.print("ll="); Serial.print(lightLed); Serial.print("; ");
Serial.print("y="); Serial.print(y); Serial.print("; ");
Serial.print("z="); Serial.print(z); Serial.println("; ");
lastPrintTime = millis();
}
bool initializeGyroscope() {
Wire.begin();
TWBR = 24; // I2C クロック 400kHz
mpu.initialize();
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.print(F("DMP Initialization failed (code "));
Serial.print(devStatus);
Serial.println(F(")"));
return false;
}
mpu.setDMPEnabled(true);
Serial.println(F("Enabling interrupt detection (Arduino external interrupt 0)..."));
attachInterrupt(0, dmpDataReady, RISING);
mpuIntStatus = mpu.getIntStatus();
Serial.println(F("DMP ready! Waiting for first interrupt..."));
packetSize = mp.dmpGetFIFOPacketSize();
return true;
}
void dmpDataReady() {
mpuInterrupt = true;
}
コードのアップロード
FTDI アダプターを使って Arduino Pro Mini にコードをアップロードします。
電源接続 (電池)
電池ボックスを接続して電源を入れます。
キャリブレーション
最も重要なのは LED_OFFSET 定数の調整です。サンプルでは 12 ですが、電源投入後に基準方向 (水平) で LED が点灯するよう 0〜23 の範囲で調整してください。NeoPixel リングの取り付け角度とジャイロスコープの向きを合わせるためのものです。
仕組みの詳細は次のステップを参照してください。
ステップ 4: 仕組みの解説 (任意)

MPU6050 ジャイロスコープについて
MPU6050 は MEMS (Microelectromechanical systems) ジャイロスコープです。各種 MEMS ジャイロには何らかの振動子があり、加速度と方向変化を検出します。運動保存の法則により、振動物体は同じ平面で振動し続けようとし、振動面のズレから方向変化を導出できます。
ジャイロ内部のマイクロコントローラーが高度な数学演算でロール、ピッチ、ヨーを算出します。しかし生データにはノイズとドリフトが含まれるため、外部ライブラリ (DMP: Digital Motion Processor) を使ってクリーンなデータを取得しています。
NeoPixel について
NeoPixel は個別にアドレス指定可能な RGB LED で、バンドやリング状にチェーン接続されています。5V 動作で独自の駆動回路を内蔵しているため、電源とデータライン 1 本だけで制御可能です。通信はクロックとデータを含む単一データラインで行われます (詳細は こちら)。Adafruit ライブラリで簡単に操作できます。
コードの流れ
loop() 関数内で MPU6050_6Axis_MotionApps20 ライブラリを呼び出し、ジャイロスコープから新しいデータが得られると redrawLeds(x, y, z) を呼び出します。3つの引数はヨー、ピッチ、ロール (度) です。
redrawLeds() 内部:
- Y 軸と Z 軸に注目 (X 軸/ヨーは今回使用しない)
- 両軸を
±MAX_ANGLE(デフォルト ±45°) にクランプ - 360° を 4 象限に分割し、各象限で
lightLeds()を呼び出し:- Y負・Z正: 第1象限 → LED 0〜5、角度 0〜89°
- Y負・Z負: 第2象限 → LED 6〜12、角度 89〜0°
- Y正・Z負: 第3象限 → LED 13〜19、角度 0〜89°
- Y正・Z正: 第4象限 → LED 20〜24、角度 89〜0°
lightLeds() 内部:
- 2軸の傾きから
atan(|x|/|y|)で角度を計算 (添付図参照) - Arduino
map()関数で角度を LED 位置にマッピング - LED ストリップを全消灯し、計算された位置の LED とその 1 つ前の LED のみ点灯 (フェード効果)
normalizeLedPosition()でキャリブレーション (LED_OFFSET) を考慮。NeoPixel リングの取り付け向きをジャイロに合わせるため- デバッグ情報をシリアル出力 (2軸値、点灯 LED 番号、計算角度)
数学的背景
添付画像に LED リングと、角度決定に使用した三角関数 (アークタンジェント) の関係を図解しています。
ソースコード
回路図
sketch_gMlSrk3rCX.fzz (Fritzing 形式)
製造プロセス
- Arduino NanoとMPU-6050で作るリアルタイムジャイロスコープゲーム
- Adafruit 60 LED NeoPixelリングとArduinoで作るスタイリッシュな壁掛け時計
- K30 CO2センサで室内空気質をリアルタイム監視する
- 1SheeldとArduinoで盲ろう者のコミュニケーションを支援する
- DIY Arduino USBゲームコントローラー - 高性能ゲームパッドを自作
- スマート貯金箱を作ろう:Arduino Nanoでコインアクセプターを制御
- Pixie: Arduino Pro Miniで作るNeoPixel腕時計 - アナログとデジタル融合のDIY光る時計
- ArduinoでBluetooth経由でLEDを制御 – シンプルDIYガイド
- DFRobot静電容量式指紋センサーをArduino/ESP8266で使う – 登録・認証・削除の完全ガイド
- Arduino Dueでマスターする音声認識と音声合成
- Arduino UNOとEthernetシールドで太陽放射を測定する自作ピラノメーター