2026年8月23日 星期日

[水井村USR] HUB 8735 Ultra實作:microSD卡與三個MP3故事檔案測試

水井三寶故事機|V1.0-03

HUB 8735 Ultra實作:microSD卡與三個MP3故事檔案測試

本次測試讓HUB 8735 Ultra從microSD卡讀取三個MP3故事, 經由AOUT類比音訊輸出,再透過XPT8871功率擴大板推動喇叭, 逐步完成水井三寶樹藝故事機的聲音系統。

測試結果:功能通過,音訊品質仍需改善。

三個MP3故事均可正常讀取及播放,但目前仍有些許雜聲。 初步研判主要與音訊線過長、麵包板接觸、電源干擾及接地方式有關。

一、V1.0-03測試目標

microSD卡 確認HUB 8735 Ultra能正確掛載與讀取記憶卡。
三個故事檔案 檢查001.mp3、002.mp3及003.mp3是否存在。
MP3解碼 驗證三個故事能否依序完成解碼與播放。
音訊輸出 驗證AOUT、XPT8871擴大板與喇叭的完整聲音路徑。

二、使用材料

材料 數量 用途
HUB 8735 Ultra 1 讀取microSD卡並解碼MP3
microSD卡 1 儲存三個水井三寶故事
XPT8871音訊擴大板 1 放大AOUT類比音訊
4Ω 3W或8Ω喇叭 1 播放故事語音
5V穩壓電源 1 供應XPT8871擴大板
麵包板、杜邦線 若干 原型接線與功能測試
USB Type-C資料線 1 程式燒錄及序列監控

三、microSD卡準備

將microSD卡格式化為FAT32,再把三個MP3故事檔案直接放在根目錄:

microSD
├── 001.mp3 姻緣花故事
├── 002.mp3 烏龜故事
└── 003.mp3 白馬故事
檔名建議: 第一階段只使用英文或數字,不加入中文、空格及特殊符號; 副檔名統一使用小寫的.mp3

四、HUB 8735 Ultra與XPT8871接線圖

microSD MP3音訊播放接線圖
HUB 8735 Ultra microSD+MP3解碼
AOUT:類比音訊
GND:音訊參考地
AOUT → IN+
GND → IN−
XPT8871 單聲道功率擴大板
IN+:音訊輸入
IN−:音訊地
OUT+:喇叭輸出
OUT−:喇叭輸出
OUT+ → 喇叭+
OUT− → 喇叭−
喇叭
建議4Ω 3W或8Ω 2W~3W
獨立穩壓5V電源 → XPT8871
● +5V → +5V ● GND → 5V−
來源端 XPT8871端 功能
HUB 8735 Ultra AOUT IN+ 類比音訊訊號
HUB 8735 Ultra GND IN−/GND 音訊參考地
獨立5V正極 +5V 擴大板電源
獨立5V負極 5V− 擴大板電源地
喇叭正端 OUT+ BTL喇叭輸出
喇叭負端 OUT− BTL喇叭輸出
重要: XPT8871採BTL橋接輸出,OUT−不是GND。 喇叭必須接在OUT+OUT−之間, 不可將OUT−接到HUB 8735 Ultra的GND。

五、V1.0-03完整測試程式

/*
 * ============================================================
 * 專案:HUB 8735 Ultra水井三寶故事機
 * 版本:V1.0-03
 * 功能:microSD卡與三個MP3故事檔案測試
 * ============================================================
 *
 * microSD卡根目錄:
 *   001.mp3
 *   002.mp3
 *   003.mp3
 */

#include "AmebaFatFS.h"
#include "AmebaFatFSFile.h"

#define STORY_COUNT 3

// 0xAF為最大值0dB
// 初次測試降低音量,避免擴大器輸入過載
#define MP3_VOLUME 0x90

#define STORY_INTERVAL_MS 2000

AmebaFatFS fs;

const char *storyFiles[STORY_COUNT] = {
    "001.mp3",
    "002.mp3",
    "003.mp3"
};

const char *storyNames[STORY_COUNT] = {
    "姻緣花故事",
    "烏龜故事",
    "白馬故事"
};

String getFullPath(const char *filename)
{
    return String(fs.getRootPath()) + String(filename);
}

bool checkMP3File(const char *filename)
{
    String fullPath = getFullPath(filename);
    File testFile;

    Serial.print("[CHECK] ");
    Serial.print(fullPath);
    Serial.print(" ... ");

    testFile = fs.open(fullPath);

    if (!testFile.isOpen()) {
        Serial.println("NOT FOUND");
        return false;
    }

    Serial.print("OK, ");
    Serial.print(testFile.size());
    Serial.println(" bytes");

    testFile.close();
    return true;
}

bool checkAllStoryFiles()
{
    bool allFilesReady = true;

    Serial.println();
    Serial.println("Checking MP3 story files...");
    Serial.println("--------------------------------");

    for (int i = 0; i < STORY_COUNT; i++) {
        if (!checkMP3File(storyFiles[i])) {
            allFilesReady = false;
        }
    }

    Serial.println("--------------------------------");

    if (allFilesReady) {
        Serial.println("[PASS] All three MP3 files are ready.");
    } else {
        Serial.println("[FAIL] One or more MP3 files are missing.");
    }

    return allFilesReady;
}

bool playStory(int storyIndex)
{
    if (storyIndex < 0 || storyIndex >= STORY_COUNT) {
        Serial.println("[ERROR] Invalid story index.");
        return false;
    }

    String fullPath = getFullPath(storyFiles[storyIndex]);
    File mp3File;

    Serial.println();
    Serial.println("================================");

    Serial.print("[PLAY] Story: ");
    Serial.println(storyNames[storyIndex]);

    Serial.print("[PLAY] File: ");
    Serial.println(fullPath);

    mp3File = fs.open(fullPath);

    if (!mp3File.isOpen()) {
        Serial.println("[ERROR] Unable to open MP3 file.");
        return false;
    }

    Serial.print("[INFO] File size: ");
    Serial.print(mp3File.size());
    Serial.println(" bytes");

    mp3File.setMp3DigitalVol(MP3_VOLUME);

    Serial.println("[AUDIO] Playback started.");

    mp3File.playMp3();

    Serial.println("[AUDIO] Playback completed.");

    mp3File.close();

    return true;
}

void playAllStories()
{
    Serial.println();
    Serial.println("Starting three-story playback test...");

    for (int i = 0; i < STORY_COUNT; i++) {
        if (!playStory(i)) {
            Serial.print("[FAIL] Story ");
            Serial.print(i + 1);
            Serial.println(" playback failed.");
        }

        if (i < STORY_COUNT - 1) {
            delay(STORY_INTERVAL_MS);
        }
    }

    Serial.println();
    Serial.println("[DONE] Three-story playback test completed.");
}

void setup()
{
    Serial.begin(115200);
    delay(2000);

    Serial.println();
    Serial.println("================================================");
    Serial.println("HUB 8735 Ultra Story Player");
    Serial.println("Version: V1.0-03");
    Serial.println("================================================");

    Serial.println("[SD] Initializing microSD card...");

    fs.begin();

    Serial.print("[SD] Root path: ");
    Serial.println(fs.getRootPath());

    if (!checkAllStoryFiles()) {
        Serial.println("[SYSTEM] Playback cancelled.");
        fs.end();
        return;
    }

    playAllStories();

    fs.end();

    Serial.println("[SYSTEM] V1.0-03 test finished.");
}

void loop()
{
    // 本版本只在開機後測試一次
    delay(1000);
}

六、預期序列監控結果

Arduino IDE的序列監控視窗設定為115200 baud。 正常執行時會看到:

HUB 8735 Ultra Story Player
Version: V1.0-03

[SD] Initializing microSD card...
[CHECK] 0:/001.mp3 ... OK
[CHECK] 0:/002.mp3 ... OK
[CHECK] 0:/003.mp3 ... OK
[PASS] All three MP3 files are ready.

[PLAY] Story: 姻緣花故事
[AUDIO] Playback started.
[AUDIO] Playback completed.

[PLAY] Story: 烏龜故事
[AUDIO] Playback started.
[AUDIO] Playback completed.

[PLAY] Story: 白馬故事
[AUDIO] Playback started.
[AUDIO] Playback completed.

[DONE] Three-story playback test completed.

七、實際測試結果

  • microSD卡可以正常掛載。
  • 三個MP3故事檔案均可找到。
  • 三個故事可以依序播放。
  • HUB 8735 Ultra的AOUT可以輸出MP3音訊。
  • XPT8871可以驅動外接喇叭。
  • 目前音訊仍有些許雜聲,需要改善接線及供電。
V1.0-03判定:功能測試成功。

下一步不是立即增加功能,而是先把類比音訊、接地與電源整理好。

八、為什麼目前會有雜聲?

1. AOUT音訊線過長

AOUT屬於小訊號類比音訊。使用過長且沒有屏蔽的杜邦線, 容易接收USB、Wi-Fi、開關電源及數位GPIO產生的干擾。

建議將AOUT與GND縮短至5~10公分,並把兩條線互相纏繞。 正式版本可以改用屏蔽音訊線:

中心導線 → HUB 8735 Ultra AOUT
屏蔽層   → HUB 8735 Ultra GND

2. 音訊線與電源線交錯

AOUT到XPT8871的輸入線應遠離:

  • 5V大電流電源線
  • USB傳輸線
  • 開關電源模組
  • WS2812燈條電源線
  • 伺服馬達與馬達線路
  • XPT8871的喇叭輸出線

3. 多點接地形成地迴路

HUB 8735 Ultra由電腦USB供電,而擴大板由另一組5V電源供電時, 應採用單點共地,避免GND經過多條不同路徑。

建議採用單點共地
單一共地點
↓ ↓ ↓
HUB 8735 Ultra
GND
XPT8871音訊輸入
IN−/GND
5V電源
負極

4. 5V電源紋波

XPT8871播放較大音量時,負載電流會快速變化。 如果5V電源含有開關紋波,雜訊可能直接進入擴大器。

建議在XPT8871電源端附近並聯一顆470µF電解電容, 再並聯一顆0.1µF陶瓷電容:

+5V ───┬──── XPT8871 +5V
       ├──── 470µF正極
       └──── 0.1µF

GND ───┬──── XPT8871 GND
       ├──── 470µF負極
       └──── 0.1µF

5. 輸入音量過高

若HUB 8735 Ultra輸出音量太大,可能讓XPT8871輸入過載, 產生破音或沙沙聲。目前程式使用:

#define MP3_VOLUME 0x90

如果仍有明顯失真,可以降低為:

#define MP3_VOLUME 0x80

如果降低音量後破音明顯改善,表示問題可能包含輸入過載, 不完全是外部電磁干擾。

九、建議的改善接線

HUB 8735 Ultra 5~10公分屏蔽音訊線 XPT8871 短喇叭線 喇叭
  1. 將AOUT與GND線縮短到5~10公分。
  2. 把AOUT與GND互相纏繞,或改用屏蔽音訊線。
  3. 將XPT8871移到靠近喇叭的位置。
  4. 讓音訊線遠離USB線、開關電源及5V大電流線。
  5. 在XPT8871旁增加470µF及0.1µF電容。
  6. 使用單點共地,避免形成地迴路。
  7. 將MP3數位音量由0x90降低到0x80進行比較。
  8. 正式版本改用焊接或洞洞板,減少麵包板接觸問題。
電源安全: 如果使用具有裸露端子的金屬外殼開關電源, AC市電輸入端必須加裝絕緣護蓋。 通電時不要在裸露的L、N端子附近調整杜邦線。 原型測試建議先使用有外殼、具安全認證的5V變壓器。

十、從聲音播放走向樹藝AI

microSD卡 三個MP3故事 HUB 8735解碼 AOUT XPT8871 喇叭

V1.0-03證明HUB 8735 Ultra除了可以進行影像辨識, 也能結合microSD卡、MP3解碼、類比音訊輸出與外接擴大器, 成為地方故事播放裝置。

對水井三寶而言,這不只是「播放三個MP3」, 而是讓姻緣花、烏龜與白馬開始擁有自己的聲音, 為後續樹藝作品導覽、影像辨識與AI對話建立基礎。

沒有留言:

張貼留言