這一個 DIY 專案是「4×4×4 64 顆 LED 的互動式立方體」:用 Arduino Mega2560 做控制核心,外接麥克風模組與光感(LDR),讓立體光譜「聽得到、看得見」——音量越大,底層越亮;環境越暗,頂層抬頭;中層隨時間流動,像一盂會呼吸、會應答的水晶球。
為什麼要「多工」而不能直接 64 腳
64 顆 LED 若各自一個 IO,需要 64 腳——任何 Arduino 都給不起。解法是矩陣掃描(multiplexing):陣列在製作時就把 16 顆 LED 的同一面焊在一起(陽極共接=「行」),另一面也共接(陰極共接=「列」)。任一瞬間只有「1 行 × 1 列」交叉的那一顆 LED 有電流通過;但把這 16×16=256 個交點每秒掃幾百遍,人眼殘像就讀成「64 顆同時亮」——和螢幕的「逐行刷新」是同一个道理。
本專案用 Mega2560 剛好:16 行 + 16 列 = 32 個數位腳位,一個不少。
料件清單
| 料件 | 數量/規格 | 說明 |
|---|---|---|
| 4x4x4 LED 陣列 | 64 顆 3mm LED(本例清/藍)、16pin+16pin 排針 | 陣列陽極共接成 16 行、陰極共接成 16 列 |
| Arduino Mega2560 | 1 片 | 32 個數位腳位剛好吃滿:16 行 + 16 列 |
| 聲音感測器(咪頭模組) | 1 個(電子元件店的藍板電容咪) | 接 A0,模擬量随聲音大小跳動 |
| 光感 sensor(LDR) | 1 個(10kΩ 光電分壓) | 接 A1,亮度 → 電阻 → 模擬值 |
| ULN2003(或 P-MOS 陣列) | 1~2 片 | 列(陰極)需要大電流拉低,IO 直接拉不動 64 顆 |
| 電阻 | 1kΩ × 16 | 行(陽極)限流,每行串一顆 |
| 杜邦線 / 麵包板或 PCB | — | 引腳 32+2,麵包板要够大 |
| 5V 供電 | 3A 以上 | 64 顆全亮峰值 ≈ 1.5~2A,USB 口未必穩,建議獨立 5V |
接線總覽
接線總覽
| 物件 | 目標 |
|---|---|
| 陣列 16 行(陽極) | 各串 1kΩ → Mega D0~D15 |
| 陣列 16 列(陰極) | → ULN2003 輸入 → 輸出 → Mega D16~D31 |
| 咪頭模組 VCC/GND/OUT | 5V / GND / A0 |
| LDR 分壓(10kΩ 下拉) | 節點 → A1 |
| GND | 全部共地(最重要,浮地 = 亂亮) |
控制端程式(重點:掃描)
核心邏輯三件事:(1) 感測 每 30ms 讀一次咪頭/光感,把「音量→底 2 層,暗→頂層」寫進 cube[4][4][4] 模型;(2) 掃描 每幀只點亮「一行」該亮的列,16 次輪巡;(3) 幀間隔 120µs 左右,64 顆全亮的亮度 ≈ 實際的 1/16,所以限流電阻要比一般直驅稍小,或接受稍暗——這是多工的公定代價:
/*
* 4x4x4 LED 立方體 — 聲音+光度互動版
* 硬體: Arduino Mega2560
* 16 行(陽極) → D0..D15 (經 1kΩ x16 限流)
* 16 列(陰極) → D16..D031 (經 ULN2003 拉低)
* 麦克风模块 AIN → A0 (模擬)
* 光感 (LDR 分壓) → A1
*/
const uint8_t ROWS = 16;
const uint8_t COLS = 16;
const uint16_t ROW_PIN[16] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
const uint16_t COL_PIN[16] = {16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31};
// cube[x][y][z] → 行 = y*4+z (層內 z 從下到上), 列 = x*4+y
bool cube[4][4][4] = {0};
void setup() {
for (uint8_t i = 0; i < 16; i++) {
pinMode(ROW_PIN[i], OUTPUT);
pinMode(COL_PIN[i], OUTPUT);
}
for (uint8_t i = 0; i < 16; i++) { digitalWrite(ROW_PIN[i], HIGH); digitalWrite(COL_PIN[i], HIGH); }
Serial.begin(115200);
}
uint16_t scanRow = 0;
void loop() {
/* ---------- 1. 讀感測 → 更新 cube ---------- */
static uint32_t lastSense = 0;
uint32_t now = millis();
if (now - lastSense > 30) { // 30ms 感測一次
lastSense = now;
int16_t mic = analogReadA0avg(20); // 平均20次降噪
int16_t light= analogReadA1avg(20);
uint16_t micDB = map(mic, 200, 800, 0, 1023); // 依你的音量範圍校正
uint16_t darkPct = map(light, 1023, 0, 0, 1023); // 越暗越大
/* 規則: 底層=音量, 頂層=光線, 中層=呼吸 */
uint8_t vol = (uint8_t)(micDB / 1024.0 * 4); // 0..3(音量4層)
uint8_t lig = (uint8_t)(darkPct/ 1024.0 * 4); // 0..3
uint32_t t = now / 200; // 呼吸節拍
for (uint8_t x = 0; x < 4; x++)
for (uint8_t y = 0; y < 4; y++)
for (uint8_t z = 0; z < 4; z++) {
if (z < 2) cube[x][y][z] = (z < vol); // 音量填底2層
else if (z == 3) cube[x][y][z] = (lig <= 1); // 暗 → 頂層亮
else cube[x][y][z] = ((t + x + y) % 4 == 0); // 中層流動
}
}
/* ---------- 2. 掃描:一次一行,快掃讓人眼殘像為「64 顆同時亮」 ---------- */
for (uint8_t i = 0; i < 16; i++) digitalWrite(ROW_PIN[i], HIGH); // 全熄
uint8_t r = scanRow;
scanRow = (scanRow + 1) & 0b1111;
uint8_t y = r >> 2, z = r & 3;
digitalWrite(ROW_PIN[r], LOW); // 選行 (common-anode: 拉低 = 供電)
for (uint8_t c = 0; c < 16; c++) {
uint8_t x = c >> 2, y2 = c & 3;
bool on = (y2 == y) && cube[x][y][z];
digitalWrite(COL_PIN[c], on ? HIGH : LOW); // 拉高 = 通電
}
delayMicroseconds(120); // 16x120us ≈ 1.9ms/幀 → ~500 fps
}
/* 20 次類比取平均, 擋住模組的抖動 */
int16_t analogReadA0avg(uint8_t n){ long s=0; for(uint8_t i=0;i<n;i++){s+=analogRead(A0); delayMicroseconds(60);} return s/n; }
int16_t analogReadA1avg(uint8_t n){ long s=0; for(uint8_t i=0;i<n;i++){s+=analogRead(A1); delayMicroseconds(60);} return s/n; }
調試時最常遇到的三個坑
- 浮地=亂亮:陣列、ULN2003、感測模組的 GND 沒有共地,會看到「隨機一兩顆閃」——不是程式錯,是地線錯。共地是最優先的一條線。
- 電流不夠,亮一半:USB 直供的 500mA 撐不住 64 顆峰值,會看到「上半截亮、下半截暗」或 Mega 重新開機。獨立 5V/3A 一接就好。
- 掃描太慢,閃爍肉眼可見:16 幀總時間 > 50ms 就会看到「一行一行跑」。把
delayMicroseconds收到 80~150µs 區間,或降低亮層數。
玩法建議(感測→效果)
- 音量柱:底層亮 = 音量高;拍手/說話 = 立方體「長高」,最直觀的音頻視覺化。
- 暗室模式:LDR 越暗,頂層越亮 + 掃描頻率越慢,變成一盂「夜燈」;手遮光 → 瞬間熄滅。
- 風暴模式:音量 > 閾值 → 随机閃爍 2 秒(類似警報燈),閾值在
map()裡調。 - 呼吸模式:中層随
sin(t)慢速起伏,不用感測也好看,做「常亮桌面小物」用。
結語
這個專案把「多工、電流、時序」三個電子學核心一次走完:64 顆 LED 靠「快」而不是「多」跑起來——人眼看不見的是 500fps 的掃描,感覺到的是 64 顆同時亮。做完它,LED 矩陣、OLED、甚至 VGA framebuffer 的「逐行刷新」就全部通了。



沒有留言:
張貼留言