樹藝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閃爍次數區分三個故事。
- 透過序列監控顯示完整故事事件。
二、實際測試電路
三顆按鈕可用不同顏色協助辨識:
| 按鈕 | 故事角色 | 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、12
四、按鈕接線方式
程式使用內部上拉電阻,因此每顆按鈕只需接在GPIO與GND之間。
| 故事按鈕 | 按鈕一端 | 按鈕另一端 |
|---|---|---|
| 脂片龜/烏龜 | GPIO 10 | GND |
| 白馬 | GPIO 11 | GND |
| 姻緣花 | GPIO 12 | GND |
三顆按鈕可以共用同一個GND。
五、GPIO 12與功能鍵的注意事項
HUB 8735 Ultra板上的功能鍵也與GPIO 12相關,因此第三顆姻緣花按鈕使用GPIO 12後,必須特別注意開機與燒錄操作。
- 正常執行程式時,GPIO 12可以作為姻緣花故事按鈕。
- 燒錄前仍可使用板上功能鍵進入Flash Mode。
- 一般重新開機時,不要持續按住GPIO 12外接按鈕。
- 若GPIO 12在RESET時維持低電位,可能影響開機模式判斷。
- 燒錄完成並按RESET後,先放開所有外接按鈕。
六、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);
}
}
七、按鍵除彈跳與單次觸發
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後,序列監控顯示:
== 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
十二、本次測試結果
- 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已經建立完整的故事選擇架構:
下一步只要將故事事件連接到音訊播放函式:
001 → 播放脂片龜故事
002 → 播放白馬故事
003 → 播放姻緣花故事
就能從「按鈕測試器」進一步發展成真正的離線樹藝故事機。
結語
硬體開發不能只依照腳位圖推測,仍須透過實際接線、序列輸出及功能驗證確認可用性。本次原先選用GPIO 13,但實測無法正常觸發;改用GPIO 12後,三個水井三寶故事按鈕均能穩定工作。
這次修正也再次說明「逐層測試」的重要性:先確認按鈕事件,再加入SD卡及MP3播放,可以大幅降低整合過程的除錯難度。
下一階段將進入V1.0-03:microSD卡與三個MP3故事檔案測試,讓三顆按鈕真正播放脂片龜、白馬及姻緣花的故事。
延伸閱讀: 《國產Wi-Fi晶片8735 Ultra初體驗》
沒有留言:
張貼留言