2026年8月23日 星期日

[水井村USR] 從「Web一開、按鈕就停」到雙介面穩定運作:HUB 8735 Ultra+JQ6500除錯全紀錄

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

從「Web一開、按鈕就停」到雙介面穩定運作:HUB 8735 Ultra+JQ6500除錯全紀錄

這次實作最寶貴的成果,不只是故事能播放,而是我們透過序列監控、狀態機、電源A/B測試與函式庫原始碼,一步步找出「供電重置、AP阻塞、Web Server阻塞、BUSY判斷與停止提示」之間的關係,最後讓手機Web與實體按鈕可以同時穩定操作。

一、最終完成的系統

1獨立APHUB自行建立Shuijing-Story,不需要外部路由器。
2雙操作介面手機Web為主要介面,實體按鈕是立即可用的離線備援。
3非阻塞狀態機播放、切歌、停止、Reset與提示音由統一狀態機管理。
最終測試結果:沒有手機連線時,三個故事按鈕與停止鍵可正常操作;手機連入後,Web與實體按鈕仍能交替使用,不再因等待Web客戶端而停住。

二、硬體與曲目配置

元件工作本次設計角色
HUB 8735 UltraAP、Web、GPIO與狀態機故事機主控制器
JQ6500MP3解碼、播放、暫停與音量專用音訊播放器
手機瀏覽器控制主要使用介面
實體按鈕故事選擇、停止與音量沒有手機時仍可操作
穩定5V電源供應Wi-Fi與音訊瞬間電流展場穩定運作的基礎

MP3曲目

檔案內容啟動方式
001.mp3白馬故事Web或GPIO10
002.mp3烏龜故事Web或GPIO11
003.mp3姻緣花故事Web或GPIO12
004.mp3水井三寶總故事Web
005.mp3創作者資訊Web
006.mp3開機提示自動
007.mp3準備完成006結束後自動
008.mp3錯誤提示錯誤狀態
009.mp3停止提示停止故事後播放

三、最終接線圖

GPIO8
Serial2 TX
→
JQ6500 RX
 
播放命令
GPIO15
Serial2 RX
←
JQ6500 TX
 
狀態回傳
GPIO4
←
JQ6500 BUSY
 
播放狀態
HUB GND
—
JQ6500 GND
—
共同參考地
GPIO功能按鈕接法
GPIO10白馬GPIO10→按鈕→GND
GPIO11烏龜GPIO11→按鈕→GND
GPIO12姻緣花GPIO12→按鈕→GND
GPIO20停止GPIO20→按鈕→GND
GPIO7音量+GPIO7→按鈕→GND
GPIO6音量-GPIO6→按鈕→GND
按鈕電位:程式使用INPUT_PULLUP,未按為1、按下為0。GPIO13裸接仍為0,因此本專案最後改用GPIO20作為停止鍵。

四、除錯歷程:每個症狀都在說話

階段1|HUB AOUT可以播放,但Web難以中途控制
原本使用HUB的阻塞式MP3播放。故事播放期間,主程式無法持續處理Web、停止與切歌,因此改由JQ6500專責播放,HUB只負責控制。
階段2|AP可以連,但開機提示不斷重播
一開始以為006被設定成循環,後來從序列監控發現程式標題與Boot Loader不斷重現,真正原因是整塊HUB反覆重置。
階段3|改用穩定電源後重置消失
使用PC USB孔供電時,Wi-Fi AP與音訊啟動的瞬間負載可能造成壓降;改用合格市電轉5V電源後,系統恢復穩定。
階段4|Web正常,停止與切歌卻會排隊
JQ6500的Pause、Reset與指定曲目命令需要正確時序,因此建立PAUSE_WAIT、RESET_WAIT、STORY_PLAYING與STOP_PROMPT狀態。
階段5|009停止提示讓系統停在STOP_PROMPT
009太短或BUSY沒有形成完整變化時,狀態機無法回到待機,因此加入BUSY啟動逾時與播放上限Watchdog。
階段6|Web輪詢造成FreeRTOS Queue斷言
每秒短連線與大量序列輸出增加底層資源壓力,最後將狀態更新改為每3秒、防止重疊請求,並在關閉Socket後保留釋放時間。
階段7|沒有手機時,系統停在「啟動中」
這是最關鍵的發現:AmebaPro2的WiFiServer預設採用BLOCKING_MODE。沒有手機時,server.available()會一直等待,整個主迴圈因此停止。

五、經驗一:重複語音不一定是MP3循環

曾經看到006開機語音反覆播放,直覺會先懷疑JQ6500的循環模式。然而序列監控不斷重新出現:

================================================
HUB 8735 Ultra + JQ6500 Web Story Player
================================================

甚至再次出現:

== Rtl8735b IoT Platform ==
[Normal mode]
BootFromNORFlash

這代表不是單一曲目循環,而是開發板重新開機。每次進入setup()都會再次播放006,所以聽起來才像無限循環。

除錯原則:先判斷「播放器重播」還是「整板重開」,兩者需要完全不同的修正方向。

六、經驗二:穩定供電是Wi-Fi音訊系統的一部分

單獨測試HUB、JQ6500或Web可能都正常,但整合後會同時產生:

  • Wi-Fi射頻啟動的瞬間電流。
  • JQ6500解碼與儲存讀取負載。
  • 功率放大器及喇叭的動態電流。
  • USB線材與接點造成的壓降。

實測結果是:PC USB供電容易發生重置,使用穩定的市電轉5V電源供應器後,AP、Web及音訊都能長時間正常運作。

安全提醒:市電必須先經合格且規格正確的AC轉DC 5V電源供應器,絕不可將110V直接接入HUB、JQ6500或麵包板。多組電源只共地,不可任意把兩個5V輸出直接並聯。

七、經驗三:AP重試也必須非阻塞

舊設計一次執行6次AP啟動,每次失敗等待10秒:

for (int attempt = 1; attempt <= 6; attempt++) {
    WiFi.apbegin(...);
    delay(10000);
}

最壞情況會有60秒無法掃描按鈕。後來改成一次只嘗試一次,失敗立即返回,15秒後再試:

bool tryStartAPOnce()
{
    int status = WiFi.apbegin(AP_SSID, AP_PASS,
                              AP_CHANNEL, AP_HIDDEN);
    if (status != WL_CONNECTED) {
        wifiReady = false;
        return false;
    }
    wifiReady = true;
    return true;
}

AP失敗不再改變播放器狀態,三個故事鍵、停止鍵、音量與BUSY仍繼續運作。

八、最關鍵發現:WiFiServer預設會阻塞

即使AP重試已經非阻塞,沒有手機連線時,按鈕仍停在開機後第一筆狀態。最後直接查閱AmebaPro2的WiFiServer實作,發現預設值是:

tBlockingMode _is_blocked = BLOCKING_MODE;

因此執行:

WiFiClient client = server.available();

在沒有手機時可能一直等待客戶端,主迴圈無法再執行按鈕、BUSY與播放器狀態機。真正的修正只有一行,但必須放在server.begin()之前:

server.setNonBlockingMode();
server.begin();
修正後:沒有手機時,server.available()立即返回;手機連入後,Web仍可正常回應。這一行讓「Web+實體按鈕」真正可以共存。

九、主迴圈的最終優先順序

展場故事機的核心不是網路,而是使用者按下按鈕後必須立即有反應。因此最終順序設計為:

void loop()
{
    handleButtons();              // 最高:實體按鈕
    updateBusy();                 // 第二:音訊硬體狀態
    updatePlayerStateMachine();   // 第三:播放狀態機

    if (wifiReady) {
        handleWeb();              // 第四:Web
    } else if (到達15秒) {
        tryStartAPOnce();         // 最後:AP恢復
    }

    updateLed();
    delay(3);
}
實體按鈕→BUSY→播放器狀態機→Web→AP重試

十、JQ6500非阻塞播放狀態機

狀態意義可接受操作
BOOT_PROMPT播放006可被故事或停止命令中斷
READY_PROMPT播放007可切換故事
IDLE等待操作所有故事與音量
STORY_PLAYING播放001~005停止或立即切歌
PAUSE_WAIT送出Pause後等待150ms更新最終目標,不重複排隊
RESET_WAITReset後等待700ms保留最後命令
STOP_PROMPT播放009可取消或改播故事
ERROR系統錯誤顯示診斷

中途切歌流程:

故事播放中
→ 0x0E Pause立即中斷
→ PAUSE_WAIT 150ms
→ 0x0C Reset清除Pause與待播狀態
→ RESET_WAIT 700ms
→ 恢復音量與ONE_STOP
→ 播放最後選擇的故事

十一、009停止提示不再鎖住系統

009可能很短,BUSY變化可能未被掃描到。因此加入兩層Watchdog:

#define STOP_BUSY_START_TIMEOUT_MS 600
#define STOP_PROMPT_MAX_MS        8000
  • 009送出600ms後沒有BUSY啟動:自動回到待機。
  • BUSY長時間沒有結束:8秒後強制釋放狀態。
  • 009播放時選故事:立即中斷009並切歌。
  • 009播放時再按停止:取消009,不重複播放。

十二、Web輪詢與FreeRTOS Queue經驗

早期Web每秒建立一次狀態連線,並把每個請求與六個GPIO值大量印到序列監控,曾觸發:

line 1473
freertos_v202012.00/Source/queue.c

最後採取:

  • 狀態輪詢改為每3秒。
  • 前一個請求未完成時,不送出重疊請求。
  • 不列印每一次/api/status。
  • client.stop()後保留短暫資源釋放時間。
  • 按鈕原始電位診斷降低輸出頻率。

這提醒我們:嵌入式Web不是一般伺服器,Socket、記憶體、RTOS Queue與序列輸出都必須節制。

十三、如何用序列監控快速判讀

序列現象代表意義處理方向
程式標題不斷重現整板重置檢查電源與Reset
Download Image over UART1進入UART燒錄模式檢查功能鍵與BOOT_MODE
只有一筆BUTTON RAW後停止主迴圈被阻塞檢查WiFiServer模式
G10由1變0白馬按鈕硬體正常追查狀態機
BUSY離開idle再回idle曲目完整開始與結束狀態可回IDLE
STOP_PROMPT沒有BUSY009未啟動或太短交由Watchdog釋放
queue.c斷言RTOS資源或非執行緒安全問題降低輪詢與輸出頻率

十四、最終驗收結果

V1.0-06驗收通過:使用穩定5V供電,HUB可建立AP;沒有手機時按鈕持續更新,手機連入後Web與按鈕都能正常;故事播放、播放中切歌、停止提示、BUSY狀態及AP重試均不再長時間鎖住主迴圈。
  • AP名稱:Shuijing-Story
  • Web網址:http://192.168.1.1/
  • Web狀態更新:每3秒一次
  • 按鈕:GPIO10、11、12、20
  • 播放器:JQ6500 UART+BUSY
  • 核心原則:按鈕最高優先、網路可以失敗、故事機仍能工作

十五、這次最重要的學習

真正可靠的互動裝置,不能只在「所有條件都正常」時工作。網路可能沒有客戶端、AP可能啟動失敗、提示音可能太短、BUSY可能漏掉、USB供電可能壓降。良好的系統設計必須讓每個外部條件都可以失敗,但不能拖垮故事播放與實體按鈕。

一句話總結:讓Web與實體按鈕共存的關鍵,不只是寫出Web頁面,而是確保任何網路等待都不能阻塞主迴圈。
Blogger貼文提醒:請切換至Blogger「HTML檢視」後貼入本檔全部內容。本檔沒有<html>、<head>與<body>外層標籤;表格與程式碼已設定橫向捲動,不會把右側資訊欄擠出畫面。

沒有留言:

張貼留言