Arduinoで作る8x8x8 RGB LEDキューブ 完全ガイド
必要な部品と材料
| 部品 | 数量 | 備考 |
|---|---|---|
| 5mm RGB拡散型LED 共通アノード | 512個 | 8×8×8 = 512ボクセル、eBayで約$30 |
| DM13A LEDドライバー (定電流シンク) | 12個 | 1チップ16ch、24個のシフトレジスタ+トランジスタ置換 |
| 74HC138 3-8ラインデコーダー | 1個 | 8段アノード選択用 |
| IRF9640 PチャネルMOSFET | 8個 | アノード側ハイサイドスイッチング |
| Arduino UNO (またはATmega328P単体) | 1個 | 16MHz水晶、22pF×2で単体化可能 |
| 5V 2.5A スイッチング電源 | 1個 | 最大約20W、全点灯時のピーク電流対応 |
| 抵抗 10kΩ | 8本 | MOSFETゲートプルアップ |
| 抵抗 1kΩ | 8本 | MOSFETゲート駆動用 |
| 抵抗 100Ω | 8本 | LED電流制限 (DM13A側で定電流制御だが保護用) |
使用ソフトウェア
- Arduino IDE
プロジェクト概要
Kevin Darrah氏のRGBキューブ制作記に触発されて製作した8×8×8 RGB LEDキューブです。Kevin氏の設計では24個のシフトレジスタ(74HC595)、192個のトランジスタ、640本の抵抗が必要でしたが、DM13A定電流LEDドライバー (16ch × 12個 = 192ch) を採用することで部品点数を大幅に削減しました。DM13Aは1チップ約$1 (eBay) と安価です。
キューブ本体の組み立て
キューブ構造の組み立て手法はKevin氏の動画を参考にしています。512個のLEDを8層×8行×8列の立方体マトリクスに配線する作業は非常に時間がかかりますが、ジグを自作すれば効率化可能です。
制御基板の製作
市販基板 (スルーホールメッキ) のコストを避けるため、トナーメソッドで自作基板を作成し、DM13Aチップを実装。各LEDの接続はワイヤーラッピングワイヤーで手配線しました (Eagleファイルに手配線版とオートルート版の両方を同梱)。
マイコン・アノード基板にはMSGEQ7 (7バンドグラフィックイコライザー) とマイクプリアンプも搭載済みですが、現バージョンでは未使用です。将来的に音声連動エフェクトを実装予定。市販のUNO/Nanoを使う場合は、74HC138とPチャネルMOSFET 8個 + 抵抗のみをユニバーサル基板に実装すれば動作します。
アニメーションはKevin氏のコードをベースに、いくつか追加しています。
完成と所感
電子回路の簡略化により部品点数・配線工数は大幅に減らせましたが、512個のLEDを立体配線する作業自体は非常に時間がかかります。しかし完成時の達成感と美しさは格別です。600個の5mm共通アノード拡散RGB LEDはeBayで約$30で入手可能です。
Arduinoスケッチ (Cube_8x8x8_V1.ino)
SPI通信でDM13Aへデータ転送、Timer1割り込みによる多重化 + 4bit BAM (Bit Angle Modulation) で16階調フルカラー表示を実現。主要構造:
- フレームバッファ: 各色 (R/G/B) × 4ビットプレーン × 64バイト = 768バイト
- 多重化: 8段アノードを順次走査 (約1kHzリフレッシュ)
- BAM: 4ビット輝度制御のため、ビットプレーンごとに表示時間を 1:2:4:8 の比率で配分
- アニメーションエンジン: 雨、波、ボール跳ね、ワイプ、カラーホイール等10種類以上
/* 8x8x8 RGB LED Cube by John Bradnam based on Kevin Darrah
* V12 04/17/2013 - BAMタイミング修正、ブランクピン制御移動、ビット演算修正
*/
#include <SPI.h>
#define LATCH_PIN 2
#define BLANK_PIN 4
#define DATA_PIN 11 // SPI MOSI (固定)
#define CLOCK_PIN 13 // SPI SCK (固定)
#define LAYER_A 5 // 74HC138 A
#define LAYER_B 6 // 74HC138 B
#define LAYER_C 7 // 74HC138 C
#define SWITCH_PGM 10
#define SWITCH_SEQ 9
#define CUBE_SIZE 8
#define CUBE_MAX (CUBE_SIZE - 1)
#define LEDS_PER_LEVEL (CUBE_SIZE * CUBE_SIZE) // 64
// 4ビットBAM用: 各色4プレーン × 64バイト
byte red0[LEDS_PER_LEVEL], red1[LEDS_PER_LEVEL], red2[LEDS_PER_LEVEL], red3[LEDS_PER_LEVEL];
byte green0[LEDS_PER_LEVEL], green1[LEDS_PER_LEVEL], green2[LEDS_PER_LEVEL], green3[LEDS_PER_LEVEL];
byte blue0[LEDS_PER_LEVEL], blue1[LEDS_PER_LEVEL], blue2[LEDS_PER_LEVEL], blue3[LEDS_PER_LEVEL];
int level = 0;
int anodeLevel = 0;
int BAM_Bit = 0, BAM_Counter = 0;
int animation = 0;
void setup() {
SPI.setBitOrder(MSBFIRST);
SPI.setDataMode(SPI_MODE0);
SPI.setClockDivider(SPI_CLOCK_DIV2); // 8MHz
noInterrupts();
// Timer1: CTCモード、64分周 (250kHz)、OCR1A=30 → 124μs周期 (約8kHz/段)
TCCR1A = 0;
TCCR1B = B00001011;
TIMSK1 = B00000010;
OCR1A = 30;
pinMode(LATCH_PIN, OUTPUT);
pinMode(DATA_PIN, OUTPUT);
pinMode(CLOCK_PIN, OUTPUT);
pinMode(LAYER_A, OUTPUT);
pinMode(LAYER_B, OUTPUT);
pinMode(LAYER_C, OUTPUT);
digitalWrite(LAYER_A, LOW);
digitalWrite(LAYER_B, LOW);
digitalWrite(LAYER_C, LOW);
pinMode(SWITCH_PGM, INPUT);
pinMode(SWITCH_SEQ, INPUT);
// BLANK_PINは割り込み内で出力モードに (起動時フラッシュ防止)
SPI.begin();
interrupts();
}
void loop() {
if (digitalRead(SWITCH_PGM) == HIGH) {
test_leds(); // テストモード
} else {
clean(); // 全消灯
animation++;
switch (animation) {
case 1: rainVersionTwo(20); break;
case 2: folder(10); break;
case 3: sinwaveTwo(15); break;
case 4: randomColor(10); break;
case 5: wipe_out(10); break;
case 6: bouncyvTwo(15); break;
case 7: color_wheelTWO(10); break;
case 8: harlem_shake(); break;
case 9: ripples(10); break;
case 10: animation = 0; break;
}
}
}
// LED制御: (段, 行, 列, R輝度0-15, G輝度0-15, B輝度0-15)
void LED(int level, int row, int col, byte red, byte green, byte blue) {
level = constrain(level, 0, CUBE_MAX);
row = constrain(row, 0, CUBE_MAX);
col = constrain(col, 0, CUBE_MAX);
red = constrain(red, 0, 15);
green = constrain(green, 0, 15);
blue = constrain(blue, 0, 15);
int wholebyte = (level * LEDS_PER_LEVEL) + (row * CUBE_SIZE) + col; // 0-511
int whichbyte = wholebyte / 8; // 0-63
int whichbit = wholebyte & 7; // 0-7
// 4ビットプレーンへビット書き込み
bitWrite(red0[whichbyte], whichbit, bitRead(red, 0));
bitWrite(red1[whichbyte], whichbit, bitRead(red, 1));
bitWrite(red2[whichbyte], whichbit, bitRead(red, 2));
bitWrite(red3[whichbyte], whichbit, bitRead(red, 3));
bitWrite(green0[whichbyte], whichbit, bitRead(green, 0));
bitWrite(green1[whichbyte], whichbit, bitRead(green, 1));
bitWrite(green2[whichbyte], whichbit, bitRead(green, 2));
bitWrite(green3[whichbyte], whichbit, bitRead(green, 3));
bitWrite(blue0[whichbyte], whichbit, bitRead(blue, 0));
bitWrite(blue1[whichbyte], whichbit, bitRead(blue, 1));
bitWrite(blue2[whichbyte], whichbit, bitRead(blue, 2));
bitWrite(blue3[whichbyte], whichbit, bitRead(blue, 3));
}
// Timer1割り込み: 多重化 + BAM出力
ISR(TIMER1_COMPA_vect) {
PORTD |= 1 << BLANK_PIN; // 全消灯 (ブランキング)
// BAMビット決定 (カウンターベース)
if (BAM_Counter == (CUBE_SIZE * 1)) BAM_Bit++;
else if (BAM_Counter == (CUBE_SIZE * 3)) BAM_Bit++;
else if (BAM_Counter == (CUBE_SIZE * 7)) BAM_Bit++;
BAM_Counter++;
int bytes_per_level = LEDS_PER_LEVEL / 8; // 8
int shift_max = level + bytes_per_level;
// 現在のBAMビットに対応するプレーンをSPI転送
switch (BAM_Bit) {
case 0:
for (int i = level; i < shift_max; i++) SPI.transfer(blue0[i]);
for (int i = level; i < shift_max; i++) SPI.transfer(green0[i]);
for (int i = level; i < shift_max; i++) SPI.transfer(red0[i]);
break;
case 1:
for (int i = level; i < shift_max; i++) SPI.transfer(blue1[i]);
for (int i = level; i < shift_max; i++) SPI.transfer(green1[i]);
for (int i = level; i < shift_max; i++) SPI.transfer(red1[i]);
break;
case 2:
for (int i = level; i < shift_max; i++) SPI.transfer(blue2[i]);
for (int i = level; i < shift_max; i++) SPI.transfer(green2[i]);
for (int i = level; i < shift_max; i++) SPI.transfer(red2[i]);
break;
case 3:
for (int i = level; i < shift_max; i++) SPI.transfer(blue3[i]);
for (int i = level; i < shift_max; i++) SPI.transfer(green3[i]);
for (int i = level; i < shift_max; i++) SPI.transfer(red3[i]);
if (BAM_Counter == (CUBE_SIZE * 15)) { BAM_Counter = 0; BAM_Bit = 0; }
break;
}
// アノード選択 (74HC138)
digitalWrite(LAYER_A, bitRead(anodeLevel, 0));
digitalWrite(LAYER_B, bitRead(anodeLevel, 1));
digitalWrite(LAYER_C, bitRead(anodeLevel, 2));
// ラッチ → ブランキング解除
PORTD |= 1 << LATCH_PIN;
PORTD &= ~(1 << LATCH_PIN);
PORTD &= ~(1 << BLANK_PIN);
anodeLevel = (anodeLevel + 1) & CUBE_MAX;
level = (level + bytes_per_level) & (LEDS_PER_LEVEL - 1);
pinMode(BLANK_PIN, OUTPUT); // 初回のみ出力モード化
}
// 以下、アニメーション関数群 (rainVersionTwo, folder, sinwaveTwo,
// randomColor, wipe_out, bouncyvTwo, color_wheelTWO, harlem_shake, ripples 等)
// コード長大のため省略。GitHubまたは添付ファイル参照。
回路図・設計ファイル
Eagle設計ファイル: eagle_files_WfqPEUP7Mp.zip (手配線版 + オートルート版)
製作のポイントとアドバイス
LEDキューブ組み立て
- ジグの自作: LEDの足の曲げ加工と位置決め用ジグを3Dプリントまたはアクリル板で作ると劇的に効率化
- 共通アノード配線: 各層のアノードをまとめて8本のアノード線に、カソードはDM13Aの192chに個別接続
- はんだ付け順序: 層ごとに組み立て → テスト → 積層の順で、不良LEDの早期発見を
- 配線整理: ワイヤーラッピングワイヤー (Kynar AWG30) が細くて扱いやすい
電子回路
- DM13Aの利点: 定電流駆動でLED輝度均一、抵抗不要、シフトレジスタ内蔵でSPI一括制御、OEピンでブランキング対応
- 電源容量: 全点灯白色時 512×3チップ × 20mA ≈ 30A 理論値だが、BAMで平均電流は1/15程度。実測2-3A程度。5V 5A以上推奨。
- 信号品質: 高速SPI (8MHz) + 長配線でリンギング/クロストーク発生しやすい。終端抵抗 (100Ω) やシールド線検討。
ソフトウェア拡張
- より高い階調: 8bit/色 (24bitカラー) にはメモリ増設 (外部SRAM) または時間分割工夫が必要
- 動画再生: SDカードからフレームデータ読み出し再生
- 音声連動: 搭載済みMSGEQ7で周波数帯域別レベル取得 → ビジュアライザー実装
- インタラクティブ: 加速度センサー、タッチセンサー、赤外線リモコン等で操作
参考リソース
- Kevin Darrah "RGB LED Cube" シリーズ (YouTube) - 組み立て手法の詳細
- DM13A データシート - 定電流設定、熱設計
- BAM (Bit Angle Modulation) / BCM (Binary Code Modulation) 解説記事
製造プロセス
- スマート渇きアラート植物アラーム – 最適な水やりタイミングを知らせるDIYセンサー
- Arduino Megaで作る美しい5x5x5 LEDキューブ
- イタリア語ワードクロック自作:Arduino Nano、NeoPixel、LCD、RTCで時刻を文字で表示
- Arduino MKR Fox 1200で作る信頼性の高いSigfox kWhメーター
- Arduino RGB LEDカラーミキサー:初心者向けDIYプロジェクト
- Bluetooth対応温度モニター:Arduino NanoとHC-06でスマホ監視
- 屋外対応DMX制御RGB LED投光器 – 低コスト・高性能・自作PCB
- 非接触ジェスチャー錠: APDS-9960とArduinoでセキュアアクセス
- Arduino Nanoコンパニオンキット – 電子工作に必須の部品とツールを搭載した多機能IC型デバイス
- Arduino用絶縁アナログ入力回路の設計:LM331 V-F変換+フォトカプラでノイズ耐性を確保
- Arduino UNOで作る精密反応速度テスター