2026年8月23日 星期日

[水井村USR] 樹藝AI故事機開發紀錄V1.0-02:用三個按鈕選擇水井三寶故事

樹藝AI故事機開發紀錄V1.0-02:用三個按鈕選擇水井三寶故事

完成V1.0-01功能鍵與板載LED測試後,本階段將HUB 8735 Ultra擴充成三個獨立故事按鈕,分別代表「脂片龜/烏龜」、「白馬」及「姻緣花」。實測過程中發現GPIO 13無法正常觸發,因此將第三顆按鈕改接GPIO 12後,三個故事事件均能正確辨識。

系列前導閱讀:
HUB 8735 Ultra開發環境與基本設定,可先參考: 《國產Wi-Fi晶片8735 Ultra初體驗》

一、本階段開發目標

V1.0-02預定完成以下功能:

  • 建立三個水井三寶故事按鈕。
  • 每顆按鈕對應一個故事編號及故事名稱。
  • 加入按鍵除彈跳,避免按一次卻產生多次事件。
  • 按住按鈕時不連續重複觸發。
  • 使用LED閃爍次數區分三個故事。
  • 透過序列監控顯示完整故事事件。
按下故事按鈕 → 按鍵除彈跳 → 判斷故事角色 → 顯示事件 → LED閃爍

二、實際測試電路


▲ HUB 8735 Ultra連接三顆外接按鈕,分別作為脂片龜、白馬及姻緣花故事選擇鍵。

三顆按鈕可用不同顏色協助辨識:

按鈕 故事角色 GPIO LED提示
按鈕1 脂片龜/烏龜故事 GPIO 10 閃爍1次
按鈕2 白馬故事 GPIO 11 閃爍2次
按鈕3 姻緣花故事 GPIO 12 閃爍3次

三、實測發現:GPIO 13無法正常工作

原始規劃使用:

GPIO 10:脂片龜/烏龜故事
GPIO 11:白馬故事
GPIO 13:姻緣花故事

實際接線與測試後,GPIO 10及GPIO 11可以正常讀取按鍵,但GPIO 13未能正確觸發第三個故事事件。

將第三顆按鈕改接GPIO 12,並把程式改為:

const int FLOWER_BUTTON_PIN = 12;

重新燒錄後,三顆故事按鈕均能正常運作。

原始配置:GPIO 10、11、13
實測修正:GPIO 10、11、12
本次結果代表GPIO 13在目前HUB 8735 Ultra板卡、開發板套件及接線配置中,不適合直接作為第三顆故事按鈕。這是本次實機測試結果,不宜直接推論所有RTL8735B板卡的GPIO 13都不能使用。

四、按鈕接線方式

程式使用內部上拉電阻,因此每顆按鈕只需接在GPIO與GND之間。

故事按鈕 按鈕一端 按鈕另一端
脂片龜/烏龜 GPIO 10 GND
白馬 GPIO 11 GND
姻緣花 GPIO 12 GND

三顆按鈕可以共用同一個GND。

接線安全提醒:使用INPUT_PULLUP時,按鈕應接在GPIO與GND之間,不要接到5V。若接到5V,不但邏輯不符,也可能增加接線錯誤或硬體損壞的風險。

五、GPIO 12與功能鍵的注意事項

HUB 8735 Ultra板上的功能鍵也與GPIO 12相關,因此第三顆姻緣花按鈕使用GPIO 12後,必須特別注意開機與燒錄操作。

  • 正常執行程式時,GPIO 12可以作為姻緣花故事按鈕。
  • 燒錄前仍可使用板上功能鍵進入Flash Mode。
  • 一般重新開機時,不要持續按住GPIO 12外接按鈕。
  • 若GPIO 12在RESET時維持低電位,可能影響開機模式判斷。
  • 燒錄完成並按RESET後,先放開所有外接按鈕。
GPIO 12同時承擔故事輸入與功能鍵相關用途。正式製作故事機時,應避免參觀者在系統開機或重新啟動的瞬間持續按住姻緣花按鈕。

六、V1.0-02完整修正版程式

以下程式已將姻緣花按鈕由GPIO 13修正為GPIO 12。

/*
 * HUB 8735 Ultra 樹藝AI故事機
 * V1.0-02:三個水井三寶故事按鈕
 *
 * GPIO 10:脂片龜/烏龜故事
 * GPIO 11:白馬故事
 * GPIO 12:姻緣花故事
 */

const int TURTLE_BUTTON_PIN = 10;
const int HORSE_BUTTON_PIN  = 11;
const int FLOWER_BUTTON_PIN = 12;

// 按鍵除彈跳時間
const unsigned long DEBOUNCE_TIME = 50;

// 儲存每顆按鈕的狀態
struct StoryButton {
  int pin;
  const char* storyId;
  const char* storyName;
  int ledBlinkCount;

  bool stableState;
  bool lastReading;
  unsigned long lastChangeTime;
};

// 建立三顆故事按鈕
StoryButton storyButtons[] = {
  {
    TURTLE_BUTTON_PIN,
    "001",
    "脂片龜/烏龜故事",
    1,
    HIGH,
    HIGH,
    0
  },
  {
    HORSE_BUTTON_PIN,
    "002",
    "白馬故事",
    2,
    HIGH,
    HIGH,
    0
  },
  {
    FLOWER_BUTTON_PIN,
    "003",
    "姻緣花故事",
    3,
    HIGH,
    HIGH,
    0
  }
};

const int BUTTON_COUNT =
  sizeof(storyButtons) / sizeof(storyButtons[0]);

unsigned long eventCounter = 0;

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

  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, LOW);

  for (int i = 0; i < BUTTON_COUNT; i++) {
    pinMode(storyButtons[i].pin, INPUT_PULLUP);
  }

  Serial.println();
  Serial.println("======================================");
  Serial.println("HUB 8735 Ultra");
  Serial.println("Tree Art AI Story Machine");
  Serial.println("V1.0-02 Three Story Buttons");
  Serial.println("======================================");
  Serial.println("GPIO 10:脂片龜/烏龜故事");
  Serial.println("GPIO 11:白馬故事");
  Serial.println("GPIO 12:姻緣花故事");
  Serial.println("--------------------------------------");
  Serial.println("系統準備完成,請按下故事按鈕。");
}

void loop() {
  for (int i = 0; i < BUTTON_COUNT; i++) {
    updateStoryButton(storyButtons[i]);
  }
}

/*
 * 讀取、除彈跳及判斷單次按鍵事件
 */
void updateStoryButton(StoryButton &button) {
  bool currentReading = digitalRead(button.pin);

  // 發現讀值改變,重新計算穩定時間
  if (currentReading != button.lastReading) {
    button.lastChangeTime = millis();
    button.lastReading = currentReading;
  }

  // 讀值穩定超過除彈跳時間
  if ((millis() - button.lastChangeTime) >=
      DEBOUNCE_TIME) {

    // 穩定狀態真的發生改變
    if (currentReading != button.stableState) {
      button.stableState = currentReading;

      // INPUT_PULLUP:LOW表示按下
      if (button.stableState == LOW) {
        triggerStory(button);
      }
    }
  }
}

/*
 * 單次故事觸發事件
 */
void triggerStory(const StoryButton &button) {
  eventCounter++;

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

  Serial.print("EVENT NO:");
  Serial.println(eventCounter);

  Serial.print("STORY ID:");
  Serial.println(button.storyId);

  Serial.print("STORY NAME:");
  Serial.println(button.storyName);

  Serial.print("BUTTON GPIO:");
  Serial.println(button.pin);

  Serial.println("EVENT TYPE:STORY_SELECTED");
  Serial.println("STATUS:TRIGGERED");
  Serial.println("======================================");

  blinkStoryLED(button.ledBlinkCount);
}

/*
 * 利用LED閃爍次數辨識故事
 */
void blinkStoryLED(int blinkCount) {
  for (int i = 0; i < blinkCount; i++) {
    digitalWrite(LED_BUILTIN, HIGH);
    delay(200);

    digitalWrite(LED_BUILTIN, LOW);
    delay(200);
  }
}
由於文章以HTML方式發表,程式中的小於符號、比較符號及參考符號已使用HTML實體編碼,貼到Blogger後會正常顯示為Arduino程式。

七、按鍵除彈跳與單次觸發

1. 為什麼需要除彈跳?

機械按鈕按下時,接點不會立即形成一次乾淨的電位變化,而可能在數毫秒內產生多次高低跳動。如果直接讀取GPIO,按一次可能被誤判成按了很多次。

本程式設定50毫秒除彈跳:

const unsigned long DEBOUNCE_TIME = 50;

2. 為什麼按住不會一直觸發?

程式只在穩定狀態由HIGH變成 LOW時觸發故事。

if (button.stableState == LOW) {
  triggerStory(button);
}

因此操作結果為:

  • 按下:觸發一次。
  • 持續按住:不重複觸發。
  • 放開:回到等待狀態。
  • 再次按下:產生下一次事件。

八、LED閃爍代表哪個故事?

LED閃爍 故事 未來對應音訊
1次 脂片龜/烏龜 001.mp3
2次 白馬 002.mp3
3次 姻緣花 003.mp3

目前LED閃爍是故事事件的視覺測試。進入下一階段後,將把LED事件延伸成SD卡MP3播放。

九、正確燒錄流程

按住板上功能鍵 → 按下並放開RESET → 放開功能鍵 → 上傳 → 成功後再按RESET
步驟1:確認Arduino IDE已選擇HUB 8735 Ultra及正確COM埠。
步驟2:按住板上的功能鍵。
步驟3:按下並放開RESET鍵。
步驟4:最後放開功能鍵。
步驟5:在Arduino IDE按下「上傳」。
步驟6:等待顯示upload success。
步驟7:燒錄完成後,確定三顆外接按鈕都已放開,再按一下RESET。

十、序列監控開機結果

燒錄完成並按下RESET後,序列監控顯示:

== Rtl8735b IoT Platform ==

[Normal mode]

BootFromNORFlash

[Start Boot ROM...]

=== Load PARTBL ===

這些訊息代表:

  • RTL8735B已經重新啟動。
  • 目前處於Normal Mode。
  • 系統由NOR Flash載入剛才燒錄的程式。
  • Boot ROM開始讀取系統分割表。

完成底層開機後,程式會顯示:

======================================
HUB 8735 Ultra
Tree Art AI Story Machine
V1.0-02 Three Story Buttons
======================================
GPIO 10:脂片龜/烏龜故事
GPIO 11:白馬故事
GPIO 12:姻緣花故事
--------------------------------------
系統準備完成,請按下故事按鈕。

十一、預期的故事事件

按下脂片龜/烏龜按鈕

EVENT NO:1
STORY ID:001
STORY NAME:脂片龜/烏龜故事
BUTTON GPIO:10
EVENT TYPE:STORY_SELECTED
STATUS:TRIGGERED

按下白馬按鈕

EVENT NO:2
STORY ID:002
STORY NAME:白馬故事
BUTTON GPIO:11
EVENT TYPE:STORY_SELECTED
STATUS:TRIGGERED

按下姻緣花按鈕

EVENT NO:3
STORY ID:003
STORY NAME:姻緣花故事
BUTTON GPIO:12
EVENT TYPE:STORY_SELECTED
STATUS:TRIGGERED

十二、本次測試結果

V1.0-02測試完成:
  • GPIO 10可以觸發脂片龜/烏龜故事。
  • GPIO 11可以觸發白馬故事。
  • GPIO 13在本次配置中無法正常觸發。
  • 第三顆按鈕改接GPIO 12後,可以觸發姻緣花故事。
  • 三顆按鈕均可維持單次觸發。
  • 按住按鈕時不會連續產生故事事件。
  • LED可依故事分別閃爍1、2、3次。
  • 序列監控可顯示故事ID、名稱、GPIO及事件編號。
測試項目 結果 處理方式
GPIO 10 正常 保留作為脂片龜按鈕
GPIO 11 正常 保留作為白馬按鈕
GPIO 13 未正常觸發 本版本暫不使用
GPIO 12 正常 改作姻緣花按鈕
按鍵除彈跳 正常 50毫秒穩定判斷
單次觸發 正常 放開後才能再次觸發

十三、從三個按鈕走向故事播放

V1.0-02已經建立完整的故事選擇架構:

三個實體按鈕 → 三個故事ID → 三種LED提示 → 三個故事事件

下一步只要將故事事件連接到音訊播放函式:

001 → 播放脂片龜故事
002 → 播放白馬故事
003 → 播放姻緣花故事

就能從「按鈕測試器」進一步發展成真正的離線樹藝故事機。

結語

硬體開發不能只依照腳位圖推測,仍須透過實際接線、序列輸出及功能驗證確認可用性。本次原先選用GPIO 13,但實測無法正常觸發;改用GPIO 12後,三個水井三寶故事按鈕均能穩定工作。

這次修正也再次說明「逐層測試」的重要性:先確認按鈕事件,再加入SD卡及MP3播放,可以大幅降低整合過程的除錯難度。

下一階段將進入V1.0-03:microSD卡與三個MP3故事檔案測試,讓三顆按鈕真正播放脂片龜、白馬及姻緣花的故事。

延伸閱讀: 《國產Wi-Fi晶片8735 Ultra初體驗》

沒有留言:

張貼留言