顯示具有 Web Server阻塞 標籤的文章。 顯示所有文章
顯示具有 Web Server阻塞 標籤的文章。 顯示所有文章

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播放器狀態機WebAP重試

十、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>外層標籤;表格與程式碼已設定橫向捲動,不會把右側資訊欄擠出畫面。