2026年10月19日 星期一

[DIY專案] 4x4x4 LED 立方體:用 Arduino Mega2560 + 麥克風 + 光感做出「會呼吸的音箱」

這一個 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 的「逐行刷新」就全部通了。

沒有留言:

張貼留言