ガイド · 読了まで20分

ArduinoでLEDキューブ:3x3x3と4x4x4キューブの作り方とコード

ArduinoでLEDキューブを作ります。はんだ付け用の治具、3x3x3と4x4x4の組み立て、トランジスタによるレイヤーの駆動、抵抗の計算、74HC595での拡張、配列を使ったアニメーションのコードを解説します。

LEDキューブは、Arduinoで作れる光のプロジェクトの中でも特に目を引くものの1つです。立体の格子状に並べたLEDがレイヤーごとに光り、回転する面や落ちる雨粒、空中で大きくなったり小さくなったりする立方体を描きます。ArduinoのLEDキューブは、3x3x3(LED 27個)か4x4x4(LED 64個)のサイズから始めるのがいちばんです。このサイズなら、Arduino Unoのピンと数個のトランジスタ、そして根気のいるはんだ付けだけで作れます。コツは、LEDごとにピンを割り当てるのではなく、レイヤーと列のダイナミック点灯(マルチプレクス)という方法を使うことです。27個のLEDを12本のピンで、64個のLEDを2個の74HC595チップのおかげでわずか7本のピンで制御できます。

このガイドでは、LEDキューブの仕組み、はんだ付け用の治具の作り方、レイヤーを1段ずつ接合する手順、レイヤーをトランジスタで駆動しなければならない理由、抵抗値の計算方法を学びます。続いて、3x3x3キューブで配列ベースのアニメーションを再生するArduinoのコード、74HC595で4x4x4キューブに拡張する方法、ESP32版とRaspberry Pi Pico(MicroPython)版、カラーキューブを作るためのWS2812Bという選択肢、そしてLEDの逆向きやイモはんだのようなよくある組み立てミスの直し方を紹介します。

LEDキューブ用の5 mm LED
LEDキューブの部品の基本は5 mm LEDです。長い足がアノード(+)、短い足と本体の平らな縁がカソード(−)の目印です。

LEDキューブとは?仕組みは?

LEDキューブは、LEDを等間隔に並べた水平のレイヤーを積み重ねて作ります。各レイヤーでは、すべてのLEDのカソード(−)の足をはんだ付けでつなぐので、レイヤーごとに共通カソードのラインが1本できます。上下に重なるLEDのアノード(+)の足は縦方向にはんだ付けでつなぎ、これが列になります。3x3x3キューブには3つのレイヤーと9本の列があり、4x4x4キューブには4つのレイヤーと16本の列があります。どのLEDも、ちょうど1つのレイヤーと1本の列の交点にあります。LEDを点けるには、その列にプラスの電圧をかけ、そのレイヤーをGNDにつなぎます。

キューブのどのLEDも、3つの座標で表せます。x(左から右)、y(手前から奥)、z(下から上)です。この記事のコードでは、列番号をc = y × 3 + x(4x4x4ではc = y × 4 + x)で計算し、zがレイヤー番号になります。画面のピクセルと同じように、キューブの中の光の点1つをボクセル(体積のピクセル)と呼びます。アニメーションを作るとは、どの瞬間にどのボクセルを点けるかを決めることにほかなりません。

レイヤーと列のダイナミック点灯の仕組み

複数のレイヤーを同時にオンにすると、ある列にかけたプラスの電圧が、その列の有効なすべてのレイヤーでLEDを点けてしまうので、思いどおりの模様を描けません。そのためキューブはレイヤーごとに走査します。Arduinoはまずすべてのレイヤーをオフにし、1つ目のレイヤーの列パターンをピンに書き込み、そのレイヤーだけを数ミリ秒オンにしてからオフにし、2つ目のレイヤーに進みます。このループを1秒に100回以上繰り返すと、目が光を一瞬保持するため(残像効果)、すべてのレイヤーが同時に点いているように見えます。

走査の速さの計算は簡単です。リフレッシュレート = 1 / (レイヤー数 × レイヤー時間)です。3x3x3キューブの各レイヤーを3 msずつ点灯すると、1周に9 msかかり、キューブは1秒に約110回リフレッシュされます。4x4x4でレイヤーあたり2 msなら、1周8 msで125 Hzです。50〜60 Hzを下回るとちらつきが目に付き、100 Hzを超えると目にはまったく安定して見え、スマホのカメラで撮っても縞模様が出にくくなります。代わりに明るさが犠牲になります。各レイヤーは時間の1/3(4x4x4では1/4)しか点灯しないので、LEDの平均電流は瞬間の電流のその割合まで下がります。

キューブLED列 + レイヤーピン数(直接駆動)ピン数(74HC595使用)ピン数(ダイナミック点灯なし)
3x3x3279 + 3123 + 3 = 627
4x4x46416 + 4203 + 4 = 764
5x5x512525 + 5303 + 5 = 8125
8x8x851264 + 8723 + 8 = 11512

部品リスト

部品3x3x3キューブ4x4x4キューブ(74HC595使用)
5 mm LED(単色、できれば拡散型)27個 + 予備3個64個 + 予備6個
列の抵抗220 Ω × 9本330 Ω(青、白)または470 Ω(赤、黄) × 16本
NPNトランジスタ(レイヤー用)2N2222AまたはBC337 × 3個2N2222AまたはBC337 × 4個
ベース抵抗470 Ω × 3本470 Ω × 4本
シフトレジスタ不要74HC595 × 2個(ESP32とPicoでは74HCT595)+ それぞれに100 nF
制御ボードArduino UnoまたはNanoArduino Uno、Nano、ESP32またはPico
組み立てユニバーサル基板、細い単線ユニバーサル基板、0.5〜0.8 mmのすずめっき銅線
工具はんだごて、はんだ、ニッパー、ペンチ、5 mmのドリル刃同じ工具 + 同じ高さのスペーサー
Arduino Uno
Arduino Uno
220 Ω
220 Ω
ブレッドボード
ブレッドボード
ジャンパーワイヤー
ジャンパーワイヤー

はんだ付け用の治具を作る

きれいなキューブを作る秘訣は治具です。LEDを手で持ったままはんだ付けすると、間隔がずれ、レイヤーが曲がり、キューブがゆがんで見えます。治具とは、等間隔に穴をあけた木の板で、LEDをドームを下にして逆さまに差し込んでおくものです。間隔はカソードの足の長さで決まります。たいていの5 mm LEDでは短い足が25 mm前後ですが、手元のLEDを定規で測ってください。本体から2〜3 mmの位置で足を90°曲げたとき、残った水平部分が隣のLEDの足と2〜3 mm重なる必要があるので、20 mmの間隔から始めるのがよいでしょう。

  1. 合板、MDF、または厚紙に定規で3x3(または4x4)の格子を描きます。点と点の間は20 mm、端からは2 cm以上の余白を取ります。
  2. まず各点に釘で印を付け、それから5 mmのドリル刃でまっすぐ穴をあけます。LEDは穴にしっかり収まり、力を入れなくても抜けるのが理想です。きつすぎる場合は穴を少し広げてください。
  3. 治具の1辺に、カソードの向きを示す矢印を描きます。すべてのLEDの短い足が同じ向きになるようにします。
  4. レイヤーを接合するときに上のレイヤーを支えるため、レイヤーの間隔に合った同じ高さのスペーサーを4つ用意します。木の角材の切れ端や折りたたんだ厚紙で十分です。
はんだ付けを始める前に、LEDを1個ずつ確認してください。3 VのCR2032コイン電池のプラス側に長い足を、マイナス側に短い足を当てるだけです。テスターのダイオードレンジでもLEDがうっすら光ります。キューブの真ん中から壊れたLEDを取り外すのはとても大変です。見た目のためには拡散型のLEDを選んでください。透明なLEDは光が一方向に集まるので、横から見るとキューブの光り方にむらが出ます。透明なLEDのドームは、目の細かい紙やすりですりガラス状にできます。

組み立ての手順:レイヤーと列

  1. 足を曲げる:各LEDの短い(カソードの)足を、本体から2〜3 mmの位置で治具の矢印の方向へ水平に90°曲げます。長い(アノードの)足はまっすぐ立てたままです。
  2. 1つ目のレイヤーを並べる:LEDをドームを下にして穴に差し込みます。1列に並んだカソードの足は、それぞれ隣のLEDのカソードの足に触れるようにします。列の最後のLEDの足は余ったままで構いません。
  3. カソードをはんだ付けする:320〜350 °Cのはんだごてで、各接点を2〜3秒以内にはんだ付けします。続いて、各列の端をまたぐようにまっすぐな線(切り取った抵抗の足やすずめっき銅線)をはんだ付けして、列どうしをつなぎます。これでレイヤーは1本の電気的なラインになると同時に、丈夫な枠にもなります。4x4x4では、両端に線を入れるとレイヤーがさらに強くなります。
  4. レイヤーをテストする:レイヤーを治具から外さずに、カソードのラインをGNDにつなぎ、220 Ωの抵抗を通した5 Vを各アノードの足に当てます。点かないLEDがあれば、この時点で交換します。
  5. ほかのレイヤーを作る:3つ(または4つ)のレイヤーができるまで同じ作業を繰り返します。列がぴったりそろうように、どのレイヤーでもカソードの足を同じ向きにしてください。
  6. レイヤーを接合する:1つのレイヤーを治具に残し、次のレイヤーをスペーサーの上でそのちょうど真上に合わせます。下のLEDの上向きのアノードの足の先を2 mmほど小さく曲げ、足が上のLEDの本体の横を通ってそのアノードの足に沿うようにします。まず4つの角をはんだ付けし、位置を確認してから残りの列を仕上げます。
  7. レイヤーの線を引き出す:各レイヤーのカソードのラインに線をはんだ付けし、キューブの1つの側面に沿って下へ通します。これらの線どうしや列に触れないよう、数ミリの間隔を空けてください。
  8. 土台に取り付ける:キューブをひっくり返します。最後に加えたレイヤーの余ったアノードの足が下を向き、そのレイヤーが土台にいちばん近い最下層(z = 0)になります。足とレイヤーの線をユニバーサル基板に通してはんだ付けし、各列の横に列の抵抗を配置します。
いちばん高くつく組み立てミスは逆向きのLEDが1個あることです。逆向きのLEDは、それ自体が光らないだけではありません。列のピンがLOWのとき、レイヤーのラインからその列へ逆向きの電流経路を作り、オフのはずのレイヤーでゴーストのようにLEDが点くことがあります。治具にLEDを置くたびに短い足(本体の平らな縁)が矢印の方向を向いているか確認し、接合する前には必ず各レイヤーをテストしてください。

トランジスタでレイヤーを駆動する

NPNトランジスタ
レイヤーの電流はArduinoのピンではなく、NPNトランジスタが流します。2N2222A(E-B-C)やBC337(C-B-E)が適しています。

列のピンが電流を供給するのはLED 1個だけですが、レイヤーをオンにすると、そのレイヤーにあるすべてのLEDの電流が同じカソードのラインを通ってGNDに戻ります。3x3x3キューブでは9個 × 12.7 mA ≈ 115 mA、74HC595を使う4x4x4キューブでは16個 × 5 mA ≈ 80 mAです。Arduino Unoのピンの推奨上限は20 mA、絶対最大定格は40 mAなので、レイヤーのラインをピンに直接つなぐことはできません。解決策は、各レイヤーの下にNPNトランジスタを置くことです。コレクタをレイヤーのラインに、エミッタをGNDに、ベースを抵抗経由でArduinoのピンにつなぎます。ピンがHIGHになるとトランジスタが飽和し、レイヤーをGNDに引き下げます。トランジスタを完全にオンにするには、ベース電流をコレクタ電流の1/10〜1/20程度にします。115 mAなら6〜12 mAが必要で、(5 V − 0.7 V) / 470 Ω ≈ 9 mAはちょうどその範囲に入ります。詳しくはトランジスタとは?を、ピン配置は「トランジスタのピン配置」ツールを参照してください。

トランジスタ連続コレクタ電流(IC)ピンの順番(平らな面を手前に)LEDキューブでの用途
BC547100 mAC-B-E電流の小さいキューブ専用。3x3x3の115 mAは限界を超える
2N2222A600 mAE-B-C3x3x3と4x4x4に最適
BC337800 mAC-B-E3x3x3と4x4x4に最適
IRLZ44N(ロジックレベルMOSFET)数十アンペアG-D-S8x8x8のような大型キューブ。ゲートに220 Ω、ゲートとGNDの間に10 kΩ

抵抗の計算:列の抵抗とベース抵抗

列の抵抗はLEDの電流を制限する唯一の部品で、列ごとに1本ずつ入れます。1本の列で同時に点くLEDは常に1個だけなので、LEDごとに抵抗を入れる必要はありません。計算式はオームの法則から来ています。R = (V電源 − VLED − VCE(sat)) / ILEDです。5 Vの電源、順方向電圧2.0 Vの赤色LED、飽和時に約0.2 V下がるトランジスタで12 mAを目指すと、R = (5 − 2.0 − 0.2) / 0.012 ≈ 233 Ωになります。いちばん近い標準値の220 Ωなら、LEDには約12.7 mAが流れます。3.1 Vの青色LEDでは、同じ220 Ωで約7.7 mAになり、ダイナミック点灯でも十分に見えます。

LEDの色順方向電圧(VF)3x3x3、Arduinoから直接(5 V)4x4x4、74HC595(5 V)
赤、黄1.8〜2.2 V220 Ω(≈12〜13 mA)470 Ω(≈5〜6 mA)
純緑、青2.9〜3.3 V180〜220 Ω(≈8〜9 mA)330 Ω(≈4〜5 mA)
白2.9〜3.4 V180〜220 Ω(≈7〜9 mA)330 Ω(≈4〜5 mA)

74HC595のキューブでは、あえて大きめの抵抗を選びます。このチップのVCCピンまたはGNDピンに流せる電流は合計70 mAまでで、レイヤーがオンのときは8つの出力がすべて同時に点灯することがあります。8 × 5.5 mA ≈ 44 mAなら安全な余裕があります。見た目の明るさを決めるLEDの平均電流は、瞬間の電流をレイヤー数で割った値です。3x3x3では12.7 / 3 ≈ 4.2 mA、4x4x4では5.5 / 4 ≈ 1.4 mAです。最近の高効率なLEDなら室内ではこの電流でもはっきり見えますが、とても明るい部屋では4x4x4キューブが少し暗く見えることがあります。手元のLEDの値で計算するには、「LED用抵抗」ツールと「LEDの電圧」ツールを使ってください。

3x3x3 LEDキューブの配線

接続Arduino Uno / Nano備考
列0〜8(アノード)D2〜D10各列に220 Ωの直列抵抗
最下層(z = 0)A0470 Ω経由でトランジスタ1のベースへ
中間層(z = 1)A1470 Ω経由でトランジスタ2のベースへ
最上層(z = 2)A2470 Ω経由でトランジスタ3のベースへ
トランジスタのコレクタ—それぞれ自分のレイヤーのカソード線へ
トランジスタのエミッタGND3つとも共通GNDへ
上から見た図x = 0x = 1x = 2
y = 2(奥)列6 → D8列7 → D9列8 → D10
y = 1(中央)列3 → D5列4 → D6列5 → D7
y = 0(手前)列0 → D2列1 → D3列2 → D4
D0とD1は列に使わないでください。これらのピンはUSB経由の書き込みやシリアルモニタと共用なので、LEDをつなぐと書き込みに失敗することがあります。そのため3x3x3キューブではD2〜D10とA0〜A2を使います。A0〜A5のピンも、digitalWrite()で普通のデジタル出力として使えます。合計の電流も範囲内に収まります。9本の列すべてに12.7 mAが流れると、Arduinoから出る電流は約115 mAで、ATmega328Pの合計200 mAの限界を下回ります。ボード上のピンの位置は「ピン配置」ツールで確認できます。

3x3x3 LEDキューブのArduinoコード:配列でアニメーション

アニメーションの各フレームは、レイヤーごとに1つの数として保存します。3x3x3のレイヤーにはLEDが9個あるので9ビットの値で足ります。これをuint16_tとして保存し、ビットcが列cを表します。フレームはPROGMEMでフラッシュメモリに書き込むので、数百フレームのアニメーションでもUnoの2 KBのRAMがいっぱいになりません。下のコードには追加のライブラリは要りません。

C++ (Arduino IDE)
1// 3x3x3 LED küp: katman çoklama ve dizilerle animasyon / 3x3x3 LED cube: layer multiplexing and array animations
2const byte colPins[9]   = {2, 3, 4, 5, 6, 7, 8, 9, 10};   // sütun anotları, 220 ohm ile / column anodes via 220 ohm
3const byte layerPins[3] = {A0, A1, A2};                     // alt, orta, üst katman transistörü / bottom, middle, top layer transistor
4
5// Her kare 3 katman, her katman 9 bit: bit c = sütun c (c = y*3 + x)
6// Each frame has 3 layers of 9 bits: bit c = column c (c = y*3 + x)
7const uint16_t frames[][3] PROGMEM = {
8  {0b111111111, 0b000000000, 0b000000000},  // 0: alt katman / bottom layer
9  {0b000000000, 0b111111111, 0b000000000},  // 1: orta katman / middle layer
10  {0b000000000, 0b000000000, 0b111111111},  // 2: üst katman / top layer
11  {0b000000000, 0b000010000, 0b000000000},  // 3: yalnız merkez / centre only
12  {0b111101111, 0b101000101, 0b111101111},  // 4: tel kafes, 12 kenar / wireframe, 12 edges
13  {0b010010010, 0b010010010, 0b010010010},  // 5: dönen düzlem, 1. konum / rotating plane, step 1
14  {0b100010001, 0b100010001, 0b100010001},  // 6: dönen düzlem, 2. konum / rotating plane, step 2
15  {0b000111000, 0b000111000, 0b000111000},  // 7: dönen düzlem, 3. konum / rotating plane, step 3
16  {0b001010100, 0b001010100, 0b001010100},  // 8: dönen düzlem, 4. konum / rotating plane, step 4
17  {0b111111111, 0b111111111, 0b111111111}   // 9: hepsi açık / all on
18};
19
20void setup() {
21  for (byte c = 0; c < 9; c++) pinMode(colPins[c], OUTPUT);
22  for (byte z = 0; z < 3; z++) pinMode(layerPins[z], OUTPUT);  // LOW: katmanlar kapalı / LOW: layers off
23}
24
25// Bir kareyi verilen süre boyunca katman katman tarar / scans one frame layer by layer for the given time
26void showFrame(byte f, unsigned int ms) {
27  unsigned long start = millis();
28  while (millis() - start < ms) {
29    for (byte z = 0; z < 3; z++) {
30      uint16_t bits = pgm_read_word(&frames[f][z]);         // katman verisini oku / read the layer data
31      for (byte c = 0; c < 9; c++) digitalWrite(colPins[c], bitRead(bits, c));
32      digitalWrite(layerPins[z], HIGH);                      // katmanı aç / layer on
33      delay(3);                                              // 3 ms göster / show for 3 ms
34      digitalWrite(layerPins[z], LOW);                       // sütunlar değişmeden önce kapat / off before the columns change
35    }
36  }
37}
38
39void loop() {
40  for (byte f = 0; f <= 4; f++) showFrame(f, 500);           // katmanlar, merkez, kafes / layers, centre, wireframe
41  for (byte turn = 0; turn < 4; turn++)                      // düzlemi 4 tur döndür / spin the plane 4 times
42    for (byte f = 5; f <= 8; f++) showFrame(f, 120);
43  showFrame(9, 800);                                         // hepsi açık / all on
44}

コードの中心はshowFrame()関数です。各レイヤーについて、まず列のパターンをピンに書き込み、次にそのレイヤーのトランジスタだけを3 msオンにし、列が変わる前にレイヤーをオフにします。この順番が大切です。レイヤーがオンのまま列を変えると、前のレイヤーのパターンが一瞬だけ新しいレイヤーに現れ、ゴースト(残像の映り込み)の原因になります。bitRead(bits, c)は数のビットcを0か1で返し、pgm_read_word()はフラッシュから16ビットの値を読み取ります。loop()はまずレイヤーを1つずつ表示し、次にキューブの中心と12本の辺を表示し、それから縦の面を4つの位置で回して風車のような効果を作ります。

自分でアニメーションをデザインする

新しいフレームを描くには、キューブを真上から見下ろし、各レイヤーを3x3のマス目の紙だと考えます。点けたい列に印を付け、0bに続けて2進数を左から右へ、ビット8からビット0の順に書きます。たとえば4つの角は列0、2、6、8なので、0b101000101になります。同じ値を3つのレイヤーすべてに書くと4つの角に縦の柱ができ、中間のレイヤーだけに書くと空中に浮かぶ4つの点になります。2進数になじみがなければ、数の表し方とビット演算で考え方を詳しく解説しています。コードを書くときに役立つテクニックをいくつか紹介します。

  • ビットシフト:レイヤーの値を3x3x3では3ビット、4x4x4では4ビット左にシフトすると、パターンが1行奥へ移動します。あふれたビットを&でマスクすると、波やスクロールする文字の効果を作れます。
  • レイヤーのコピー:レイヤーの配列を1段下または上にコピーすると、雨、上っていく泡、エレベーターのような効果になります。
  • ランダム:random()で選んだボクセルを短く点滅させると、星空やきらめきの効果になります。
  • 反転:~演算子でフレームをネガに変えられます。2つを素早く切り替えると、点滅で強調する効果になります。

4x4x4 LEDキューブ:74HC595で拡張する

4x4x4キューブには16本の列と4つのレイヤーがあり、直接駆動するには20本のピンが必要です。これはD0とD1を含めてUnoのピンをすべて使うということです。代わりに、縦続接続(デイジーチェーン)した2個の74HC595シフトレジスタで列を駆動すれば、16本の列に必要なのは3本のピン(データ、クロック、ラッチ)だけで、レイヤー用の4本を加えても合計7本です。1個目のチップのQ7'出力(9番ピン)は2個目のチップのDS入力につなぎ、クロックとラッチの線は両方のチップで共有します。列0〜7(手前の2行)は1個目のチップのQ0〜Q7出力に、列8〜15(奥の2行)は2個目のチップにつなぎます。同じ方法で、5x5x5キューブなら4個、8x8x8キューブなら8個のチップに拡張できます。

接続Arduino UnoESP32Raspberry Pi Pico
DS(14)、1個目のチップD11GPIO 23GP19
SH_CP(11)、両方のチップで共有D13GPIO 18GP18
ST_CP(12)、両方のチップで共有D10GPIO 5GP17
OE(13) / MR(10)GND / 5VGND / 5VGND / VBUS
VCC(16) / GND(8)5V / GND5V / GNDVBUS / GND
Q7'(9) → 2個目のチップのDS(14)縦続接続縦続接続縦続接続
レイヤー0〜3のトランジスタ(470 Ω経由)D2、D3、D4、D5GPIO 25、26、27、32GP10、GP11、GP12、GP13
チップの種類74HC59574HCT59574HCT595
74HC595のVCCピンまたはGNDピンに流せる電流は合計70 mAまでです。220 Ωのような小さな抵抗を使うと、全点灯したレイヤーでチップ1個あたり80〜100 mAが流れ、チップが発熱し、出力電圧が下がり、最後には壊れます。抵抗の表にある330〜470 Ωの値を使い、各チップのVCCピンとGNDピンの間に、できるだけピンの近くで100 nFのコンデンサを入れてください。
C++ (Arduino IDE)
1// 4x4x4 LED küp: 2 adet 74HC595 (16 sütun) + 4 transistörlü katman / 4x4x4 LED cube: two 74HC595s (16 columns) + 4 transistor layers
2const byte DATA_PIN  = 11;                 // 74HC595 DS (14)
3const byte CLOCK_PIN = 13;                 // 74HC595 SH_CP (11)
4const byte LATCH_PIN = 10;                 // 74HC595 ST_CP (12)
5const byte layerPins[4] = {2, 3, 4, 5};    // alttan üste katman transistörleri, 470 ohm ile / layer transistors bottom to top, via 470 ohm
6
7uint16_t cube[4];                          // ekrandaki görüntü: katman başına 16 bit / current image: 16 bits per layer
8
9// Hazır kareler: bit c = sütun c (c = y*4 + x) / ready-made frames: bit c = column c (c = y*4 + x)
10const uint16_t frames[][4] PROGMEM = {
11  {0xFFFF, 0x0000, 0x0000, 0x0000},        // alt katman / bottom layer
12  {0x0000, 0xFFFF, 0x0000, 0x0000},
13  {0x0000, 0x0000, 0xFFFF, 0x0000},
14  {0x0000, 0x0000, 0x0000, 0xFFFF},        // üst katman / top layer
15  {0x9009, 0x0000, 0x0000, 0x9009},        // 8 köşe / 8 corners
16  {0xF99F, 0x9009, 0x9009, 0xF99F},        // tel kafes / wireframe
17  {0x0000, 0x0660, 0x0660, 0x0000}         // içteki 2x2x2 küp / inner 2x2x2 cube
18};
19const byte FRAME_COUNT = sizeof(frames) / sizeof(frames[0]);
20
21void write16(uint16_t v) {                 // 16 biti iki entegreye gönder / send 16 bits to both chips
22  digitalWrite(LATCH_PIN, LOW);
23  shiftOut(DATA_PIN, CLOCK_PIN, MSBFIRST, highByte(v));  // önce uzaktaki entegre / far chip first
24  shiftOut(DATA_PIN, CLOCK_PIN, MSBFIRST, lowByte(v));
25  digitalWrite(LATCH_PIN, HIGH);           // çıkışlara aktar / copy to the outputs
26}
27
28void refresh() {                           // her 2 ms'de sıradaki katmana geç / move to the next layer every 2 ms
29  static byte z = 0;
30  static unsigned long last = 0;
31  if (micros() - last < 2000) return;
32  last = micros();
33  digitalWrite(layerPins[z], LOW);         // eski katmanı kapat / old layer off
34  z = (z + 1) % 4;
35  write16(cube[z]);                        // yeni katmanın sütunları / columns of the new layer
36  digitalWrite(layerPins[z], HIGH);        // yeni katmanı aç / new layer on
37}
38
39void holdFor(unsigned int ms) {            // beklerken küpü yenilemeye devam et / keep refreshing while waiting
40  unsigned long start = millis();
41  while (millis() - start < ms) refresh();
42}
43
44void clearCube() {
45  for (byte z = 0; z < 4; z++) cube[z] = 0;
46}
47
48void setVoxel(byte x, byte y, byte z) {    // tek LED yak, x/y/z = 0..3 / light one LED, x/y/z = 0..3
49  bitSet(cube[z], y * 4 + x);
50}
51
52void loadFrame(byte f) {                   // PROGMEM'deki kareyi yükle / load a frame from PROGMEM
53  for (byte z = 0; z < 4; z++) cube[z] = pgm_read_word(&frames[f][z]);
54}
55
56void sweep() {                             // dikey düzlemi x ekseninde kaydır / sweep a vertical plane along x
57  for (byte x = 0; x < 4; x++) {
58    clearCube();
59    for (byte y = 0; y < 4; y++)
60      for (byte z = 0; z < 4; z++) setVoxel(x, y, z);
61    holdFor(150);
62  }
63}
64
65void rain(byte steps) {                    // yağmur: damlalar yukarıdan düşer / rain: drops fall from the top
66  clearCube();
67  for (byte s = 0; s < steps; s++) {
68    for (byte z = 0; z < 3; z++) cube[z] = cube[z + 1];   // her şeyi bir kat indir / move everything down a layer
69    cube[3] = bit(random(16)) | bit(random(16));         // üstte 1-2 yeni damla / 1-2 new drops on top
70    holdFor(120);
71  }
72}
73
74void setup() {
75  pinMode(DATA_PIN, OUTPUT);
76  pinMode(CLOCK_PIN, OUTPUT);
77  pinMode(LATCH_PIN, OUTPUT);
78  for (byte z = 0; z < 4; z++) pinMode(layerPins[z], OUTPUT);
79  randomSeed(analogRead(A0));              // boştaki A0'dan rastgele tohum / random seed from the unconnected A0
80}
81
82void loop() {
83  for (byte f = 0; f < FRAME_COUNT; f++) {
84    loadFrame(f);
85    holdFor(500);
86  }
87  for (byte i = 0; i < 3; i++) sweep();
88  rain(80);
89}

このバージョンでは、走査はrefresh()関数の中で、処理を止めない(ノンブロッキングの)設計で行います。呼ばれるたびにmicros()で2 msが経ったかを確認し、経っていれば古いレイヤーをオフにし、新しいレイヤーの16ビットを2個のチップに送り、新しいレイヤーをオンにします。こうすることで、アニメーションのコードはcube[]配列を変えるだけで済み、表示のリフレッシュを気にする必要がありません。holdFor()は待っている間もrefresh()を呼び続けるので、プログラムのどこでも長いdelay()を使わないでください。setVoxel(x, y, z)を使うと、座標を指定してLEDを1個だけ点けられます。sweep()はこれを使って縦の面をキューブの中で動かし、rain()はレイヤーを1段下にコピーして、いちばん上にランダムな雨粒を加えます。delay()とmillis()の違いはmillis()とdelay()の違いで、走査をタイマー割り込みに移す方法はArduinoの割り込みで解説しています。

ESP32で作るLEDキューブ

ESP32のピンの出力は3.3 Vです。順方向電圧が3 V前後の青や白のLEDを直接駆動するには足りません。抵抗とトランジスタでの電圧降下のあとには、ほとんど何も残らないからです。そのためESP32でいちばんすっきりした解決策は、5 Vで動かす74HCT595経由で列を駆動することです。HCTファミリーは2 VでもHIGHとして扱うので3.3 Vの信号を問題なく読み取れ、LEDには5 Vから給電できます。レイヤーのトランジスタのベースには470 Ωをそのまま使います。(3.3 − 0.7) / 470 ≈ 5.5 mAで、80 mAのレイヤー電流には十分です。上の4x4x4のコードはESP32でもそのままコンパイルできます。ピンの定数を表の値(23、18、5と25、26、27、32)に変えるだけです。お好みで、ESP32のWi-Fiを使ってスマホのWebページからアニメーションを選ぶこともできます。手始めにESP32とは?をご覧ください。

Raspberry Pi PicoとMicroPythonで作るLEDキューブ

Picoも3.3 Vのボードで、初期設定ではピンが数ミリアンペアしか供給しないので、同じ74HCT595の回路を使います。チップとLEDには、USBからの5 Vが来ているVBUS(40番ピン)から給電します。MicroPythonでは16ビットをハードウェアSPIで送ります。SPI0のGP18(SCK)とGP19(MOSI)ピンがクロックとデータの線を駆動し、GP17がラッチを駆動します。コードの考え方は同じです。show()は指定した時間だけフレームをレイヤーごとに走査し、雨の効果ではリストのスライス(cube[1:])ですべてのレイヤーを1段下へ移します。セットアップと最初のステップについてはRaspberry Pi Picoとは?をご覧ください。

MicroPython
1# Pico + 2 adet 74HCT595 ile 4x4x4 LED küp / 4x4x4 LED cube with a Pico + two 74HCT595s
2from machine import Pin, SPI
3import time
4import random
5
6# GP18 = SCK -> SH_CP, GP19 = MOSI -> DS; GP16 (MISO) boş kalır / GP16 (MISO) stays unconnected
7spi = SPI(0, baudrate=1000000, polarity=0, phase=0, sck=Pin(18), mosi=Pin(19))
8latch = Pin(17, Pin.OUT, value=0)                                # ST_CP
9layers = [Pin(n, Pin.OUT, value=0) for n in (10, 11, 12, 13)]    # alttan üste, 470 ohm ile / bottom to top, via 470 ohm
10
11def write16(v):
12    latch.value(0)
13    spi.write(bytes((v >> 8, v & 0xFF)))    # önce uzaktaki entegre / far chip first
14    latch.value(1)                          # çıkışlara aktar / copy to the outputs
15
16def show(frame, ms):
17    # kareyi ms boyunca katman katman tara / scan the frame layer by layer for ms
18    end = time.ticks_add(time.ticks_ms(), ms)
19    while time.ticks_diff(end, time.ticks_ms()) > 0:
20        for z in range(4):
21            write16(frame[z])
22            layers[z].value(1)              # katmanı aç / layer on
23            time.sleep_us(2000)
24            layers[z].value(0)              # sütunlar değişmeden kapat / off before the columns change
25
26FRAMES = [
27    [0x9009, 0x0000, 0x0000, 0x9009],       # 8 köşe / 8 corners
28    [0xF99F, 0x9009, 0x9009, 0xF99F],       # tel kafes / wireframe
29    [0x0000, 0x0660, 0x0660, 0x0000],       # içteki küp / inner cube
30]
31
32while True:
33    for f in FRAMES:
34        show(f, 600)
35    cube = [0, 0, 0, 0]
36    for _ in range(60):                     # yağmur: her şey bir kat iner / rain: everything drops a layer
37        drop = (1 << random.getrandbits(4)) | (1 << random.getrandbits(4))
38        cube = cube[1:] + [drop]
39        show(cube, 120)

もっと大きなキューブ:5x5x5と8x8x8

キューブが大きくなると、2つの問題も一緒に大きくなります。1つ目は電流です。8x8x8キューブはレイヤーあたり64個のLEDがあり、LED 1個あたり10 mAならレイヤーの電流は640 mAになります。これはもう小さなTO-92のトランジスタではなく、ロジックレベルMOSFETの仕事です。2つ目は走査時間です。8つのレイヤーで100 Hzを保つには、各レイヤーに1.25 msしかなく、その間にレイヤーごとに64ビット(8バイト)を送らなければなりません。shiftOut()ではこれには遅すぎるので、ハードウェアSPIが必須です。さらに、各LEDは時間の1/8しか点灯しないので明るさが落ちます。大きなキューブには、より強力な列のドライバと、USBではなく外部の5 V ACアダプターが必要です(Arduinoの電源の取り方を参照)。だからこそ、最初のキューブは3x3x3か4x4x4にしましょう。512個のLEDをはんだ付けする前に、技術を身につけられます。

別の選択肢:WS2812Bで作るカラーLEDキューブ

WS2812BアドレサブルRGB LED
WS2812BのLEDはそれぞれ専用のドライバチップを持っています。64個のLEDのキューブを1本のデータピンで制御できます。

単色のキューブではなく、LEDごとに色を選びたい場合は、WS2812BのようなアドレサブルLEDを使えます。これらのLEDはそれぞれ専用のドライバチップを持っているので、ダイナミック点灯もトランジスタも列の抵抗も要りません。64個のLEDを5 V、GND、データ線で縦続接続し、1本のピンで駆動します。キューブには、足が4本ある5 mmのアドレサブルLED(PL9823やAPA106など)か、WS2812Bテープを切った小片を使います。代わりに電流が大きくなります。白の全点灯でLED 1個あたり約60 mA、64個で3.8 Aです。そのため5 V 4 AのACアダプターを使い、電源に1000 µFのコンデンサ、データ線に330 Ωの抵抗を入れ、ソフトウェアで明るさを制限してください。コードはAdafruit NeoPixelライブラリを使います。

C++ (Arduino IDE)
1// WS2812B ile 4x4x4 RGB LED küp / 4x4x4 RGB LED cube with WS2812B
2#include <Adafruit_NeoPixel.h>
3
4const byte DATA_PIN = 6;                   // 330 ohm ile ilk LED'in DIN ucuna / to the first LED's DIN via 330 ohm
5const byte N = 4;
6Adafruit_NeoPixel cube(N * N * N, DATA_PIN, NEO_GRB + NEO_KHZ800);
7
8uint16_t idx(byte x, byte y, byte z) {     // koordinattan zincirdeki sıraya / coordinate to position in the chain
9  return z * 16 + y * 4 + x;               // düz (zikzaksız) kablolama varsayılır / assumes straight, non-zigzag wiring
10}
11
12void setup() {
13  cube.begin();
14  cube.setBrightness(40);                  // parlaklığı ve akımı sınırla (0-255) / limit brightness and current (0-255)
15}
16
17void loop() {
18  static uint16_t hue = 0;
19  for (byte z = 0; z < N; z++)
20    for (byte y = 0; y < N; y++)
21      for (byte x = 0; x < N; x++)         // her katman farklı tonda / each layer gets a different hue
22        cube.setPixelColor(idx(x, y, z), cube.gamma32(cube.ColorHSV(hue + z * 8192U)));
23  cube.show();
24  hue += 256;                              // gökkuşağını kaydır / shift the rainbow
25  delay(20);
26}

コードのidx()関数は、x、y、zの座標をチェーン上の位置に変換します。すべての行を同じ向きに配線したことを前提にしています。ジグザグに配線した場合は、奇数の行でxの代わりに3 − xを使う必要があります。5 mmのアドレサブルLEDの中には、色をRGBの順で受け取るものがあります。赤のはずが緑に見える場合は、NEO_GRBをNEO_RGBに変えてください。setBrightness(40)は明るさを約16%に下げるので、最悪の場合でも電流は0.6 A前後に収まります。

項目単色LEDキューブWS2812B LEDキューブ
色1色(LED自体の色)1,600万色、LEDごとに設定
使用ピン数(4x4x4)7(74HC595使用)1
追加の部品トランジスタ、抵抗、74HC595コンデンサ、データ線の抵抗、強力なACアダプター
はんだ付けの作業LED 1個あたり足2本LED 1個あたり足4本、線も多い
明るさの制御オン/オフ(LEDごとのPWMは難しい)すべてのLEDで256段階
電流(4x4x4)100 mA前後、USBで十分最大≈3.8 A、外部電源が必要
学べることダイナミック点灯、トランジスタ、ビット演算アドレス指定、色相、電源管理

LEDキューブのプロジェクトアイデア

  • 音に反応するキューブ:サウンドセンサーで測った音の大きさに応じて点灯するレイヤーの数を増やし、立体のイコライザーを作ります。
  • 3Dスネークゲーム:4x4x4キューブの上で、ジョイスティックとボタンを使って3軸方向にヘビを操作します。ほかのゲームのアイデアはArduinoのゲームプロジェクトをご覧ください。
  • サイコロキューブ:ボタンを押すとボクセルが素早く入れ替わり、キューブが1〜6の数を点の模様で表示します。
  • 科学コンテストのプロジェクト:ポテンショメーターでレイヤー時間を延ばしていき、ちらつきが始まる瞬間を見せて残像効果を説明します。ほかのアイデアはArduinoの科学コンテスト向けプロジェクトアイデアをご覧ください。
  • スマホでアニメーションを選ぶ:HC-05 Bluetoothモジュールで送った文字ごとに、別のアニメーションを始めます。
  • カラフルな常夜灯:WS2812Bキューブでゆっくりした色のフェードを設定します。ESP32を使うなら、WLEDでスマホから効果を操作できます。

よくあるミスと直し方

症状考えられる原因対策
1個のLEDがまったく点かないLEDが逆向きか壊れている、または足の接合部が外れているカソードの向きを確認し、コイン電池でLEDをテストし、必要なら交換する
消えているはずのLEDがうっすら光る(ゴースト)レイヤーがオンのまま列が変わっている、またはキューブに逆向きのLEDがある先にレイヤーをオフにしてから列を書き込む。コードが正しければ逆向きのLEDを探す
キューブに触るとLEDがちらつく、または消えるイモはんだ:つやのない、ざらついた玉状の接合部きれいなこて先で接合部を温め直し、新しいはんだを少し足す
1つのレイヤー全体が点かないトランジスタの足が逆(E-B-CとC-B-Eの取り違え)、またはベース抵抗が外れているデータシートでピンの順番を確認し、ベースの接続を測る
1本の列全体が点かない列が土台にはんだ付けされていない、または抵抗やピンが間違っているテスターで列を端から端まで測り、コードのピン配列を確認する
キューブがちらつくレイヤー時間が長すぎる、または走査ループにSerial.print()やdelay()があるレイヤー時間を2〜3 msにし、走査を止めるコードを取り除く
キューブがとても暗い抵抗が大きすぎる、青色LEDを3.3 Vで駆動している、またはレイヤーが多すぎる抵抗を計算し直す。3.3 Vのボードでは74HCT595を5 Vで動かす
スケッチを書き込めないD0かD1にLEDがつながっている列をD2から始めるか、書き込みの間はこれらの線を外す
トラブルシューティングにいちばん良いのは、テスト用スケッチでキューブを走査することです。各LEDを200 msずつ順番に点ける小さなループを書きましょう。点かないLEDや違う場所で光るLEDがあれば、外れた接合部や取り違えた列がすぐにわかります。イモはんだは、つやがなくざらざらして見えます。良い接合部は、足を包むように少しへこんだ、つやのある円すい形です。技術を磨き直すにははんだ付けのやり方をご覧ください。

LEDキューブは、はんだ付け、トランジスタ、抵抗の計算、ダイナミック点灯、ビット演算を一度に学べる数少ないプロジェクトの1つです。まずは3x3x3から始め、コードとアニメーションの考え方がつかめたら、74HC595を使う4x4x4に、色を付けたいならWS2812Bに進みましょう。まず1列に並んだLEDを順番に点ける考え方を試したい場合は、Arduinoのナイトライダーと流れるLEDがよいウォーミングアップになります。BlueGraysアプリには、流れるLED、ナイトライダー風LED、WS2812Bのアニメーション、LEDマトリクスのプロジェクトがあり、ステップごとの回路図とコードが付いています。

よくある質問

LEDキューブにはLEDとピンがいくつ必要ですか?

3x3x3キューブにはLEDが27個、4x4x4キューブには64個必要です。予備を何個か買っておくと安心です。レイヤーと列のダイナミック点灯を使うと、3x3x3キューブは9本の列と3つのレイヤーで12本のピンが必要です。4x4x4キューブは直接駆動では20本のピンを使いますが、2個の74HC595で列を駆動すれば合計7本に減ります。

LEDキューブにはどんな抵抗を使えばいいですか?

抵抗は列ごとに1本です。Arduinoから5 Vで直接駆動する3x3x3キューブなら、220 Ωが一般的で安全な値です。74HC595のキューブでは、チップの合計70 mAの限界を下回るように、青と白のLEDには330 Ω、赤と黄のLEDには470 Ωを使ってください。手元の値を求めるには、R = (V電源 − VLED − 0.2 V) / ILEDを使います。

LEDキューブのレイヤーをトランジスタで駆動するのはなぜですか?

レイヤーをオンにすると、そのレイヤーにあるすべてのLEDの電流が1本のラインを通ってGNDに戻ります。3x3x3キューブでは100 mAを超えます。Arduinoのピンの推奨上限は20 mAなので、2N2222AやBC337のようなNPNトランジスタでレイヤーのラインをGNDに引き下げます。ベース抵抗は470 Ωで十分です。

LEDキューブがちらつくのはなぜですか?

ちらつきは走査の速さが遅すぎると起こります。リフレッシュレートは1 / (レイヤー数 × レイヤー時間)で、100 Hzより高くする必要があるので、レイヤー時間は2〜3 msに保ち、走査ループに長いdelay()やSerial.print()を入れないでください。キューブに触ったときだけちらつくなら、原因はおそらくイモはんだです。

消えているはずのLEDが点くのはなぜですか?

これはゴーストと呼ばれ、主な原因は2つあります。レイヤーがオンのまま列の値を変えると、前のパターンが一瞬だけ新しいレイヤーに映ります。先にレイヤーをオフにしてから列を書き込んでください。コードが正しい場合は、キューブの中の逆向きのLEDがほかのレイヤーへの電流経路を作っている可能性があります。

Arduino Unoで8x8x8のLEDキューブは作れますか?

作れますが、ピンだけでは足りません。64本の列には74HC595のチェーンか同様のドライバ、8つのレイヤーにはロジックレベルMOSFET、高速な走査にはハードウェアSPIが必要です。512個のLEDのはんだ付けにも数日かかります。まず3x3x3か4x4x4のキューブで技術を身につけるのがいちばんの近道です。

全プロジェクトを見る
流れるLED
#5中級

流れるLED

LEDが次々に点灯し、光が走るように進みます。Arduinoが6個のLEDを1個ずつ短く点灯して消灯させることで、光が端から端へと移動し、また最初に戻ります。この簡単なプロジェクトで、1つのパターンで複数の光を制御する方法を学びます。

4.6(85)
207
Knight Rider風LED
#6中級

Knight Rider風LED

Knight Riderでおなじみの、光が左右に往復する動きです。6個のLEDの上を光の点が端から端へ進み、折り返します。Arduinoが各LEDを10分の1秒ずつ点灯させ、端に着くたびに向きを変えて往復し続けます。装飾用の照明や警告灯作りの第一歩にぴったりです。

4.8(106)
202
速度調整できるKnight Rider風LED
#8中級

速度調整できるKnight Rider風LED

6個のLEDの上を光が端から端へ走り、折り返して戻ってきます。ポテンショメーターのつまみを回すと、その速さをすぐに調整できます。つまみを一方の端にすると光が飛ぶように進み、反対の端にするとゆっくり進みます。1つのつまみで動作中のアニメーションを操る楽しさを体験できます。

4.5(60)
120
WS2812B LEDテープの使い方
#69中級
プレミアム

WS2812B LEDテープの使い方

1つずつ自由に色を変えられるスマートLEDテープを使ってみます。Arduinoが最初の5個のLEDを赤く点灯し、1秒後に残りの5個を青く点灯します。その後すべて消灯し、最初から繰り返します。1本の配線で何十個ものLEDを個別の色にするための第一歩です。このようなテープは、装飾用のライトや部屋の照明にも使われています。

4.8(54)
70
WS2812Bアニメーション1
#75中級
プレミアム

WS2812Bアニメーション1

56個のLEDが並ぶテープで、虹のすべての色が隣り合って途切れずに流れます。まるで光の川がテープの上を流れるようです。各LEDは1つ前のLEDと少し違う色に光り、色が移り変わるたびにテープが生き生きと見えます。部屋や机を彩る、いちばん手軽な方法です。

4.9(69)
89
WS2812Bアニメーション2
#76中級
プレミアム

WS2812Bアニメーション2

自動で色が変わる光のショーを部屋に作ります。Arduinoが30個のLEDを備えたテープに色を流し続け、数秒ごとに新しい配色へ切り替えます。虹色、紫と緑の縞模様、白黒の縞模様、雲を思わせる色、パーティー向けの色、ランダムな色の組み合わせが登場します。何も操作せずに、テープが1分間のショーを最初から最後まで再生します。

4.8(65)
84
ピクセルスネーク:8x8マトリクス
#88上級
プレミアム

ピクセルスネーク:8x8マトリクス

定番のスネークゲームを自分の手で作って遊びましょう。カラフルな8x8のLEDマトリクスで、ジョイスティックを使って緑のヘビを動かし、赤いリンゴを食べます。リンゴを食べるたびにヘビが長くなり、少しずつ速くなって、小さな数字ディスプレイのスコアも増えます。壁や自分のしっぽにぶつかると画面が赤く3回点滅し、ボタンを押すと再び始められます。

4.7(39)
57
32x8 LEDマトリックス4枚で作る6種のアニメーション
#91中級
プレミアム

32x8 LEDマトリックス4枚で作る6種のアニメーション

32x8 LEDパネル4枚を縦に並べた正方形のディスプレイで、6種類のアニメーションが順番に再生されます。降り注ぐデジタルの雨、広がる渦巻き、自ら生まれては消える細胞、広がる波紋、手前に飛んでくる星、そして花火です。それぞれ約8秒続き、ピクセルがゆっくり消えながら次のアニメーションに移ります。すべてを1台のArduino Unoで動かします。

4.5(35)
48