從「Web一開、按鈕就停」到雙介面穩定運作:HUB 8735 Ultra+JQ6500除錯全紀錄
這次實作最寶貴的成果,不只是故事能播放,而是我們透過序列監控、狀態機、電源A/B測試與函式庫原始碼,一步步找出「供電重置、AP阻塞、Web Server阻塞、BUSY判斷與停止提示」之間的關係,最後讓手機Web與實體按鈕可以同時穩定操作。
一、最終完成的系統
二、硬體與曲目配置
| 元件 | 工作 | 本次設計角色 |
|---|---|---|
| HUB 8735 Ultra | AP、Web、GPIO與狀態機 | 故事機主控制器 |
| JQ6500 | MP3解碼、播放、暫停與音量 | 專用音訊播放器 |
| 手機 | 瀏覽器控制 | 主要使用介面 |
| 實體按鈕 | 故事選擇、停止與音量 | 沒有手機時仍可操作 |
| 穩定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 | 停止提示 | 停止故事後播放 |
三、最終接線圖
Serial2 TX
Serial2 RX
| 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作為停止鍵。四、除錯歷程:每個症狀都在說話
原本使用HUB的阻塞式MP3播放。故事播放期間,主程式無法持續處理Web、停止與切歌,因此改由JQ6500專責播放,HUB只負責控制。
一開始以為006被設定成循環,後來從序列監控發現程式標題與Boot Loader不斷重現,真正原因是整塊HUB反覆重置。
使用PC USB孔供電時,Wi-Fi AP與音訊啟動的瞬間負載可能造成壓降;改用合格市電轉5V電源後,系統恢復穩定。
JQ6500的Pause、Reset與指定曲目命令需要正確時序,因此建立PAUSE_WAIT、RESET_WAIT、STORY_PLAYING與STOP_PROMPT狀態。
009太短或BUSY沒有形成完整變化時,狀態機無法回到待機,因此加入BUSY啟動逾時與播放上限Watchdog。
每秒短連線與大量序列輸出增加底層資源壓力,最後將狀態更新改為每3秒、防止重疊請求,並在關閉Socket後保留釋放時間。
這是最關鍵的發現: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及音訊都能長時間正常運作。
七、經驗三: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);
}
十、JQ6500非阻塞播放狀態機
| 狀態 | 意義 | 可接受操作 |
|---|---|---|
| BOOT_PROMPT | 播放006 | 可被故事或停止命令中斷 |
| READY_PROMPT | 播放007 | 可切換故事 |
| IDLE | 等待操作 | 所有故事與音量 |
| STORY_PLAYING | 播放001~005 | 停止或立即切歌 |
| PAUSE_WAIT | 送出Pause後等待150ms | 更新最終目標,不重複排隊 |
| RESET_WAIT | Reset後等待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沒有BUSY | 009未啟動或太短 | 交由Watchdog釋放 |
| queue.c斷言 | RTOS資源或非執行緒安全問題 | 降低輪詢與輸出頻率 |
十四、最終驗收結果
- AP名稱:
Shuijing-Story - Web網址:
http://192.168.1.1/ - Web狀態更新:每3秒一次
- 按鈕:GPIO10、11、12、20
- 播放器:JQ6500 UART+BUSY
- 核心原則:按鈕最高優先、網路可以失敗、故事機仍能工作
十五、這次最重要的學習
真正可靠的互動裝置,不能只在「所有條件都正常」時工作。網路可能沒有客戶端、AP可能啟動失敗、提示音可能太短、BUSY可能漏掉、USB供電可能壓降。良好的系統設計必須讓每個外部條件都可以失敗,但不能拖垮故事播放與實體按鈕。
<html>、<head>與<body>外層標籤;表格與程式碼已設定橫向捲動,不會把右側資訊欄擠出畫面。
沒有留言:
張貼留言