4×4×4 LED Cube with Arduino Mega2560: Sound- and Motion-Reactive Multiplexing
🌏 繁體中文版:[DIY專案] 4x4x4 LED 立方體:Arduino Mega2560 結合聲音感測器與 PIR 人體感測器
This DIY project is an interactive 4×4×4 cube of 64 hand-soldered LEDs, driven by an Arduino Mega2560 with a sound sensor and a PIR motion sensor attached, so the light patterns respond to noise and to people moving nearby.
Building the cube: a laser-cut soldering jig
The hardest part of an LED cube isn't the electronics — it's getting 64 LEDs into a straight, evenly spaced grid. Soldering them freehand ends up crooked, so I laser-cut a simple jig from MDF: a plate with a 4×4 grid of holes at the LED spacing. Each layer of 16 LEDs is placed into the holes upside down, their legs are bent and soldered together, and the finished layer lifts out perfectly square. Four identical layers are then stacked and joined.
The electronics
- Controller: Arduino Mega2560 with a plug-in shield (RobotWolf Group) that the cube mounts onto
- LEDs: 64 × blue LEDs in a 4×4×4 grid
- Sensors: a sound sensor module and a PIR motion sensor
Why multiplex instead of using 64 pins
Giving every LED its own I/O pin would take 64 pins. The standard trick for LED cubes is multiplexing: in each vertical column, the 4 LEDs share their anodes (16 columns), and in each horizontal layer, the 16 LEDs share their cathodes (4 layers). Only one layer is switched on at any instant, and the 16 column lines decide which LEDs in that layer light up. Cycle through the 4 layers hundreds of times per second and persistence of vision makes it look like any pattern of all 64 LEDs is lit at once — the same idea a display uses when it refreshes line by line.
That brings the pin count down from 64 to 16 + 4 = 20, which the Mega2560 handles with plenty of pins to spare for sensors. Two details matter in practice:
- Current: a layer can carry the current of up to 16 LEDs at once, which is more than a single I/O pin should sink. The usual fix is a transistor per layer (or a ULN2003 driver array) between the layer and ground.
- Brightness: each layer is lit only a quarter of the time, so a multiplexed cube is dimmer than directly driven LEDs. Slightly smaller current-limiting resistors on the columns compensate.
Example sketch: layer scanning with sound and motion
Below is an example sketch that shows the technique — layer scanning plus the two sensors — for anyone building a similar cube. Pin numbers are placeholders: adapt them to your own wiring.
/*
* 4x4x4 LED cube: layer multiplexing + sound sensor + PIR (example)
* 16 columns (anodes) -> COL_PIN[], each through a current-limiting resistor
* 4 layers (cathodes) -> transistor / ULN2003, driven by LAYER_PIN[]
* Sound sensor analog out -> A0
* PIR sensor output -> D2 (HIGH while motion is detected)
*/
const uint8_t COL_PIN[16] = {22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37};
const uint8_t LAYER_PIN[4] = {38,39,40,41}; // HIGH = layer switched on
const uint8_t PIR_PIN = 2;
bool cube[4][16]; // cube[layer][column]
uint8_t layer = 0;
void setup() {
for (uint8_t c = 0; c < 16; c++) pinMode(COL_PIN[c], OUTPUT);
for (uint8_t z = 0; z < 4; z++) pinMode(LAYER_PIN[z], OUTPUT);
pinMode(PIR_PIN, INPUT);
}
void updatePattern() {
static uint32_t last = 0;
uint32_t now = millis();
if (now - last < 30) return; // update the pattern every 30 ms
last = now;
int sound = analogRead(A0);
uint8_t level = constrain(map(sound, 300, 700, 0, 4), 0, 4); // calibrate to your module
bool motion = digitalRead(PIR_PIN);
uint32_t t = now / 150;
for (uint8_t z = 0; z < 4; z++)
for (uint8_t c = 0; c < 16; c++) {
if (motion) cube[z][c] = ((c + z + t) % 4 == 0); // someone nearby: a moving ripple
else cube[z][c] = (z < level); // otherwise: a sound-level meter
}
}
void loop() {
updatePattern();
// scan: switch off the old layer, set up the columns, switch on the next layer
digitalWrite(LAYER_PIN[layer], LOW);
layer = (layer + 1) & 3;
for (uint8_t c = 0; c < 16; c++) digitalWrite(COL_PIN[c], cube[layer][c] ? HIGH : LOW);
digitalWrite(LAYER_PIN[layer], HIGH);
delayMicroseconds(500); // 4 layers x 0.5 ms = 2 ms per frame (~500 fps)
}
Two habits from this sketch are worth keeping. The layer is switched off before the columns change, which prevents "ghosting" (faint copies of the previous layer's pattern). And sensing runs on a timer instead of every pass through loop(), so slow analogRead calls never stretch one layer's on-time and cause flicker.
Troubleshooting tips
- Random flickering LEDs: almost always a missing common ground between the cube, the driver transistors, and the sensors.
- Visible flicker: a full 4-layer pass must finish well under 20 ms (above 50 Hz). Keep any slow work out of the scan path.
- A whole column or layer dead: check the solder joints along that column or layer — with 64 LEDs, one cold joint is the usual culprit.
- PIR "stuck on": most PIR modules have on-board potentiometers for sensitivity and hold time; a long hold time makes the cube look unresponsive.
Wrapping up
An LED cube packs three core electronics ideas into one small build: multiplexing, current, and timing. The 64 LEDs work because of scan speed, not pin count — your eye can't separate hundreds of layer scans per second, so it sees one solid 3D pattern. And the laser-cut jig turned out to be the most valuable part of the build: without it, the grid would never have come out straight.
Related tools: LED Resistor Calculator
This post is adapted from the Traditional Chinese original on Vic's Iteration Notes.
Comments
Post a Comment