顯示具有 JQ6500 標籤的文章。 顯示所有文章
顯示具有 JQ6500 標籤的文章。 顯示所有文章

2026年8月14日 星期五

ESP32 × JQ6500 實作筆記 從「沒有聲音」到 UART 成功播放

智慧生活科技實作筆記

ESP32 × JQ6500 實作筆記
從「沒有聲音」到 UART 成功播放

一次實際除錯,重新認識電源、UART、GPIO 與模組測試的重要性

一、為什麼這次實作很值得記錄?

JQ6500 是一款價格低、體積小,而且能直接播放 MP3 語音的模組。 對於語音導覽、作品故事機、互動裝置、AIoT 展示與地方文化導覽而言, 它有一個很大的優點:即使沒有網路,也可以直接從內建 Flash 播放語音。

這次實作的目標,是利用 ESP32 控制 JQ6500, 讓未來的樹藝作品只要按下按鈕,就可以播放不同的作品故事; 再搭配 QR Code,延伸到手機網站與 AI 問答。

原本以為只是「接 TX、RX,送一段 UART 指令」就可以完成, 實際測試卻經歷了:

燒錄成功但沒有聲音 → K1~K5 測試 → 檢查喇叭 → 檢查 Flash → 更換 ESP32 UART 腳位 → UART 收發測試 → 收到 SPIOK → 最後成功由 ESP32 指令播放 MP3。

這次經驗最值得留下來的,不只是最後成功的程式,而是: 電源與通訊腳位,往往才是嵌入式系統除錯的第一現場。

二、使用硬體

ESP32
NodeMCU-32 / ESP32-WROOM-32
語音模組
JQ6500
喇叭
4Ω~8Ω 小型喇叭

JQ6500 使用 UART TTL 通訊,預設鮑率為 9600 bps。 此次採用 JQ6500 內建 Flash 播放 MP3, 並利用 ESP32 的硬體 UART2 控制播放。

三、這次實測最重要的三個發現

1. 電源真的很重要

一開始很容易把注意力全部放在程式、MP3 格式或 UART 指令, 但 JQ6500 本身包含 MP3 解碼與喇叭功率輸出, 播放瞬間的電源狀態會直接影響模組是否正常工作。

實作建議:
ESP32 與 JQ6500 在除錯初期可以各自使用 USB 供電, 但兩者的 GND 必須相連

「兩塊板都有 USB」不代表 UART 就能直接通訊。 數位訊號需要共同的電位參考,所以:

ESP32 GND ↔ JQ6500 GND

2. GPIO26 / GPIO27 最後成為穩定的 UART 配置

最初曾以 GPIO16、GPIO17 測試 UART, 實作過程中一直無法得到理想結果。 後來改用 NodeMCU-32 板上明確標示的:

P26 / GPIO26
ESP32 TX → JQ6500 RX
P27 / GPIO27
ESP32 RX ← JQ6500 TX

這裡特別容易誤解: GPIO26 是板子上印著「P26/26」的腳位, 不是從排針開始數第 26 隻腳。

需要特別說明的是,這並不表示所有 ESP32 都不能使用 GPIO16、17; 這次紀錄的是本次 NodeMCU-32 實機測試中,GPIO26 / 27 成功完成 JQ6500 UART 通訊。 實際專案仍應依開發板版本與腳位配置選擇。

3. TX 與 RX 一定要交叉

ESP32 TX → JQ6500 RX
ESP32 RX ← JQ6500 TX

TX 是「送出去」,RX 是「接進來」, 所以不能 TX 接 TX、RX 接 RX。

四、ESP32 × JQ6500 實測成功接線圖

ESP32 NodeMCU-32 P26 / GPIO26 TX P27 / GPIO27 RX GND JQ6500 RX TX GND SPK+ SPK− UART 9600 共地 4Ω~8Ω 喇叭 USB 供電 USB 供電

五、JQ6500 UART 指令

成功通訊後,ESP32 可以直接送 HEX 命令控制 JQ6500。 例如播放第一首 MP3:

7E 04 03 00 01 EF

其中:

7E:起始碼
04:資料長度
03:指定曲目命令
00 01:第 1 首
EF:結束碼

設定音量 20:

7E 03 06 14 EF

選擇內建 Flash:

7E 03 09 04 EF

六、最關鍵的一刻:收到 SPIOK

在 UART 除錯過程中,ESP32 最後收到:

53 50 49 4F 4B

轉成 ASCII 即為:

SPIOK

這是一個非常重要的除錯訊號。 因為它代表 JQ6500 已經有資料回到 ESP32, 也證明雙向 UART 通訊路徑已經建立。

ESP32 TX → JQ6500 RX ✓
JQ6500 TX → ESP32 RX ✓
UART 9600 ✓
共地 ✓

七、最後成功的 ESP32 最小播放程式

#include <HardwareSerial.h>

HardwareSerial JQ(2);

#define JQ_TX 26
#define JQ_RX 27

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

  JQ.begin(
    9600,
    SERIAL_8N1,
    JQ_RX,
    JQ_TX
  );

  delay(3000);

  // 選擇內建 Flash
  uint8_t flash[] = {
    0x7E, 0x03, 0x09, 0x04, 0xEF
  };

  JQ.write(flash, sizeof(flash));
  JQ.flush();

  delay(2500);

  // 音量 = 20
  uint8_t volume[] = {
    0x7E, 0x03, 0x06, 0x14, 0xEF
  };

  JQ.write(volume, sizeof(volume));
  JQ.flush();

  delay(1000);

  // 播放第一首
  uint8_t play1[] = {
    0x7E,
    0x04,
    0x03,
    0x00,
    0x01,
    0xEF
  };

  JQ.write(play1, sizeof(play1));
  JQ.flush();
}

void loop()
{
}

八、真正值得學生學習的是「怎麼除錯」

這次實作最有價值的地方,其實不是最後那幾行程式, 而是建立一個合理的除錯順序。

Step 1 先不要接 ESP32
JQ6500 燒入 MP3,用 K1~K5 測試能否直接播放。

Step 2 確認 Flash、喇叭與播放核心正常
只要 K1 能播放,就不要再一直懷疑 MP3 或喇叭。

Step 3 再加入 ESP32
先建立 TX、RX、GND 三條 UART 線。

Step 4 確認 GPIO 名稱
GPIO26 是 P26,不是「第 26 隻實體腳」。

Step 5 先送,再收
觀察 Serial Monitor 是否真的送出 HEX 指令。

Step 6 確認回傳
看到 SPIOK,代表 UART 雙向通訊已建立。

Step 7 最後才加入按鈕、QR Code、網路與 AI

九、下一步:把它變成「樹藝故事機」

JQ6500 成功之後,就可以把系統進一步做成三層式架構:

第一層|離線故事

實體按鈕

ESP32

JQ6500

MP3 語音故事
第二層|QR Code

掃描作品 QR Code

手機 Web

作品圖文、影片與導覽
第三層|AI

Django API

LLM + RAG

AI 作品問答

例如三個按鈕可以分別設定:

① 001.mp3:作品介紹
② 002.mp3:創作者故事
③ 003.mp3:地方文化與永續故事

十、結語:AIoT 教育不能只教「成功的接法」

這次 ESP32 × JQ6500 的測試再次說明, 真正的工程學習,不只是取得一份「可以跑的程式」。

接錯、沒聲音、沒有回覆,
其實都是理解系統最好的教材。

從電源、共地、Flash、喇叭,到 UART 的 TX/RX、GPIO 選擇與 HEX 指令, 每一次排除問題,都讓學生更理解: 硬體、軟體與通訊,其實是一個完整的系統。

當這個基礎完成之後, JQ6500 就不再只是一塊「MP3 播放板」, 而可以成為樹藝、工藝、地方文化與 AI 導覽之間, 一個低成本、可靠而有溫度的聲音介面。

關鍵字: ESP32、JQ6500、UART、AIoT、語音模組、樹藝故事機、QR Code、Django、RAG、智慧生活科技

2024年12月22日 星期日

[8051] 用CH552設計偵測拍掌聲,控制伺服馬達和語音IC(JQ6500)

 電路圖

程式碼:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <Servo.h>
const int analogInPin = 11;  
const int servoPin = 15;
const int ControlPin = 16;
const int SoundPin = 17; 
// 設定時間間隔為 5 分鐘300,000 毫秒
const int INTERVAL = 300000;
int threshold = 60;

int sensorValue = 0;        // value read from the pot

// 上次執行的時間點
unsigned long previousMillis = 0;

void setup() {
  pinMode(analogInPin,INPUT);
  Servo_init();
  pinMode(servoPin, OUTPUT);
  Servo_attach(servoPin);
  pinMode(ControlPin, OUTPUT);
  digitalWrite(ControlPin, HIGH);  // 初始狀態設為 HIGH
  pinMode(SoundPin, OUTPUT);
  digitalWrite(SoundPin, HIGH);  // 初始狀態設為 HIGH
}

void loop() {
  // read the analog in value:
  sensorValue = analogRead(analogInPin);

  // 取得目前的時間
  unsigned long currentMillis = millis();

  // 每隔 5 分鐘執行一次
  if (currentMillis - previousMillis >= INTERVAL) {
    previousMillis = currentMillis; // 更新上次執行的時間點

    // 將 P3.6 拉低 1000 毫秒
    digitalWrite(ControlPin, LOW);
    delay(1000);
    digitalWrite(ControlPin, HIGH);
  }

  if (sensorValue>=threshold){
    USBSerial_print(sensorValue);
    USBSerial_print("\n");
    digitalWrite(SoundPin, LOW);
    delay(1000);
    digitalWrite(SoundPin, HIGH);

    Servo_write(servoPin,0);
    delay(1000);
    Servo_write(servoPin,180);
    delay(1000);
    Servo_write(servoPin,90);
    delay(3000);
  }
  delay(2);
}