2026年8月23日 星期日

[水井村USR] HUB 8735 Ultra實作:用三個實體按鈕選擇三個水井三寶故事

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

HUB 8735 Ultra實作:用三個實體按鈕選擇三個水井三寶故事

V1.0-03已完成microSD卡與三個MP3檔案的依序播放測試;本次V1.0-04加入三個實體按鈕,讓使用者可直接選擇白馬、烏龜或姻緣花故事,故事機正式進入互動操作階段。

一、測試目標與結果

本次使用HUB 8735 Ultra讀取microSD卡中的三個MP3檔案,並以GPIO10、GPIO11、GPIO12分別連接三個按鈕。按下不同按鈕後,播放對應的故事。

測試結果:功能正常。三個按鈕都能正確播放各自指定的MP3故事。

二、按鈕與故事配置

GPIO10
白馬按鈕

001.mp3
GPIO11
烏龜按鈕

002.mp3
GPIO12
姻緣花按鈕

003.mp3
GPIO腳位實體按鈕MP3檔案故事內容
GPIO10白馬按鈕001.mp3白馬故事
GPIO11烏龜按鈕002.mp3烏龜故事
GPIO12姻緣花按鈕003.mp3姻緣花故事

三、準備材料

  • HUB 8735 Ultra開發板
  • microSD卡一張
  • 瞬時按鈕三個
  • XPT8871單聲道功率放大板
  • 喇叭一顆
  • 5V電源、麵包板與杜邦線

四、按鈕接線圖

程式使用INPUT_PULLUP,所以每個按鈕的一端接GPIO,另一端接GND;未按下時讀到HIGH,按下時讀到LOW,不需要另外加外部上拉電阻。

GPIO10
白馬按鈕
GND
GPIO11
烏龜按鈕
GND
GPIO12
姻緣花按鈕
GND
注意:四腳按鈕同一側的兩隻腳通常已互相導通,請使用按鈕兩側的腳位接線。若接到同一側,GPIO可能一直維持相同狀態。

五、音訊接線圖

HUB AOUT
XPT8871 IN+
 
音訊訊號
HUB GND
XPT8871 IN−
 
共地
5V
XPT8871 5V+
 
放大板電源
XPT8871 OUT+/OUT−
喇叭 +/−
 
橋接輸出
重要:XPT8871的OUT−是橋接功率輸出,不可以接GND。喇叭必須接在OUT+與OUT−之間。若有雜聲,應縮短AOUT音訊線、讓訊號線遠離電源線與喇叭輸出線,並確認HUB與放大板共地。

六、microSD卡檔案配置

將三個MP3檔直接放在microSD卡根目錄,檔名必須完全一致:

microSD 根目錄
├── 001.mp3  (白馬)
├── 002.mp3  (烏龜)
└── 003.mp3  (姻緣花)

七、完整Arduino程式|V1.0-04

/*
 * HUB 8735 Ultra 三按鈕故事播放器
 * Version: V1.0-04
 * GPIO10 → 白馬 → 001.mp3
 * GPIO11 → 烏龜 → 002.mp3
 * GPIO12 → 姻緣花 → 003.mp3
 */

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

#define MP3_VOLUME        0x90
#define STORY_COUNT       3
#define DEBOUNCE_MS       50

#define BUTTON_HORSE_PIN   10
#define BUTTON_TURTLE_PIN  11
#define BUTTON_FLOWER_PIN  12

AmebaFatFS fs;

const int buttonPins[STORY_COUNT] = {
    BUTTON_HORSE_PIN,
    BUTTON_TURTLE_PIN,
    BUTTON_FLOWER_PIN
};

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

const char *storyNames[STORY_COUNT] = {
    "White Horse",
    "Turtle",
    "Marriage Flower"
};

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

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

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

    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 allReady = true;

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

bool playStory(int storyIndex)
{
    if (storyIndex < 0 || storyIndex >= STORY_COUNT) {
        return false;
    }

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

    Serial.println();
    Serial.println("========================================");
    Serial.print("[BUTTON] GPIO: ");
    Serial.println(buttonPins[storyIndex]);
    Serial.print("[PLAY] Story: ");
    Serial.println(storyNames[storyIndex]);
    Serial.print("[PLAY] File: ");
    Serial.println(fullPath);

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

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

    // playMp3()會阻塞,直到目前故事播放完成
    mp3File.playMp3();

    Serial.println("[AUDIO] Playback completed.");
    mp3File.close();
    Serial.println("========================================");
    return true;
}

void waitButtonRelease(int pin)
{
    while (digitalRead(pin) == LOW) {
        delay(10);
    }
    delay(DEBOUNCE_MS);
}

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

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

    for (int i = 0; i < STORY_COUNT; i++) {
        pinMode(buttonPins[i], INPUT_PULLUP);
    }

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

    if (!checkAllStoryFiles()) {
        Serial.println("[FAIL] Check microSD card and filenames.");
        while (true) {
            delay(1000);
        }
    }

    Serial.println("[READY] Press a story button.");
}

void loop()
{
    for (int i = 0; i < STORY_COUNT; i++) {
        if (digitalRead(buttonPins[i]) == LOW) {
            delay(DEBOUNCE_MS);

            if (digitalRead(buttonPins[i]) == LOW) {
                playStory(i);
                waitButtonRelease(buttonPins[i]);
                Serial.println("[READY] Press a story button.");
            }
        }
    }

    delay(10);
}

八、操作方式

  1. 將三個MP3檔放入microSD卡根目錄。
  2. 依接線圖接好三個按鈕、XPT8871與喇叭。
  3. 上傳程式後,開啟115200 baud序列監控視窗。
  4. 按白馬按鈕播放001.mp3;按烏龜按鈕播放002.mp3;按姻緣花按鈕播放003.mp3。

九、序列監控預期訊息

HUB 8735 Ultra Story Player V1.0-04
[SD] Initializing microSD card...
[CHECK] ...001.mp3 ... OK
[CHECK] ...002.mp3 ... OK
[CHECK] ...003.mp3 ... OK
[READY] Press a story button.

[BUTTON] GPIO: 10
[PLAY] Story: White Horse
[PLAY] File: ...001.mp3
[AUDIO] Playback started.
[AUDIO] Playback completed.

十、V1.0-03與V1.0-04比較

項目V1.0-03V1.0-04
播放方式開機後依序播放三個故事按下實體按鈕選擇故事
互動性三個獨立按鈕
microSD三個MP3檔沿用相同三個MP3檔
GPIO未使用按鈕GPIO10、11、12

十一、目前限制與後續方向

目前使用的playMp3()會等待故事播放完畢,因此播放期間不會偵測其他按鈕。對「一按一個完整故事」的操作方式而言很單純穩定;未來若需要中途停止、切換故事或調整音量,則要再研究非阻塞播放或額外的播放控制機制。

下一階段可以加入LED播放指示、音量按鈕、停止鍵、開機提示音,或進一步加入網路語音與AI對話功能。

部落格貼文注意事項:請在Blogger的「HTML檢視」中貼上本檔內容。本檔刻意不包含<html><head><body>標籤,並已限制程式碼區塊寬度,避免破壞Blogger版型或把右側資訊欄擠出畫面。

沒有留言:

張貼留言