小智AI平價版 - ESP32開發板實現方案
概述
小智AI平價版是一種使用早期版本ESP32開發板(非S3/C3)搭建的低成本AI聊天機器人解決方案。本文詳細介紹了使用普通ESP32開發板搭建小智AI的全部步驟,包括硬體選擇、接線方法、韌體燒錄和常見問題解決。相比使用ESP32-S3開發板,這種方案成本更低,適合入門使用者體驗。
ESP32開發板版本小智AI(平價版)
此ESP32非S3、C3系列,而是早期版本的ESP32系列開發板,基本上是4MB flash,沒有外接PSRAM的開發板。俗稱「平價小智」或「暢玩版小智」,讓你花一個S3開發板的價錢玩上小智。
一、關於ESP32系列的說明
1.1 支援的硬體
- 支援ESP32-S
- 支援ESP32-DevKitC
- 支援NodeMcu-32S
- 支援4MB SPI Flash(2MB暫不支援)
- 版本介紹:群組檔案共享裡提供,版本是向下相容的,就是刷最新的也可以用純平價版,即使沒有高配版的配件
1.2 平價版限制
硬體限制
- ❌ 記憶體:僅有520KB SRAM,多工處理能力有限
- ❌ AI功能:不支援本地AI推理
- ❌ 音訊品質:音訊處理能力有限,簡單音訊電路
- ❌ 效能:相比S3版本指令處理較慢
功能限制
- ✅ 基本功能:語音喚醒、簡單指令
- ✅ 網路連接:Wi-Fi連接、雲端AI服務
- ✅ 裝置控制:基礎IoT控制
- ❌ 進階功能:無本地AI、多語言支援有限
二、硬體清單與接線圖
2.1 必需組件清單
組件 | 規格 | 數量 | 成本 |
---|---|---|---|
ESP32板 | ESP32-DevKitC/NodeMcu-32S | 1 | $8-12 |
麥克風 | 類比或數位麥克風 | 1 | $2-4 |
放大器 | PAM8403或類似 | 1 | $1-2 |
揚聲器 | 2W 4Ω迷你揚聲器 | 1 | $1-3 |
按鈕 | 輕觸按鈕 | 1 | $0.5 |
電阻 | 10kΩ, 1kΩ | 2-3 | $0.5 |
連接線 | 各種顏色 | 1套 | $2 |
2.2 ESP32-DevKitC接線圖
ESP32-DevKitC
┌─────────────────────────────────┐
│ 3V3 ┌───────────────────┐ GND │
│ EN │ │ IO23│
│ VP │ ESP32 │ IO22│
│ VN │ WiFi BT │ TXD0│
│ IO34 │ │ RXD0│
│ IO35 │ │ IO21│
│ IO32 │ │ IO19│
│ IO33 │ │ IO18│
│ IO25 │ │ IO5 │
│ IO26 │ │ IO17│
│ IO27 │ │ IO16│
│ IO14 │ │ IO4 │
│ IO12 │ │ IO0 │
│ GND │ │ IO2 │
│ IO13 │ │ IO15│
│ D2 │ │ D1 │
│ D3 │ │ D0 │
│ CMD │ │ CLK │
│ 5V └───────────────────┘ GND │
└─────────────────────────────────┘
2.3 音訊組件連接
麥克風(類比)
類比麥克風 ────── ESP32
VCC ──────────────────── 3.3V
GND ──────────────────── GND
OUT ──────────────────── IO34 (ADC1_CH6)
放大器PAM8403
PAM8403 ──────────────── ESP32
VCC ─────────────────── 5V
GND ─────────────────── GND
IN_L ────────────────── IO25 (DAC1)
IN_R ────────────────── IO26 (DAC2)
喚醒按鈕
按鈕 ──────────────── ESP32
一端 ───────── IO0
另一端 ──────── GND
三、軟體配置
3.1 Arduino IDE配置
// ESP32板設定
// 工具 -> 開發板 -> ESP32 Dev Module
// 主要設定:
// Flash Mode: DIO
// Flash Size: 4MB
// Flash Frequency: 80MHz
// Upload Speed: 921600
// Core Debug Level: None
3.2 平價版核心程式碼
#include "WiFi.h"
#include "HTTPClient.h"
#include "ArduinoJson.h"
#include "driver/dac.h"
#include "driver/adc.h"
// 引腳配置
#define MIC_PIN ADC1_CHANNEL_6 // GPIO34
#define SPEAKER_PIN_L DAC1 // GPIO25
#define SPEAKER_PIN_R DAC2 // GPIO26
#define BUTTON_PIN 0 // GPIO0
// 全域變數
bool listening = false;
String recognized_text = "";
void setup() {
Serial.begin(115200);
// 初始化音訊
initAudio();
// 連接WiFi
connectWiFi();
// 設定按鈕
pinMode(BUTTON_PIN, INPUT_PULLUP);
Serial.println("小智AI平價版準備就緒!");
}
void loop() {
// 檢查按鈕按下
if (digitalRead(BUTTON_PIN) == LOW) {
startListening();
delay(200); // 防彈跳
}
// 處理語音指令
if (listening) {
processVoiceInput();
}
delay(100);
}
void initAudio() {
// 初始化DAC輸出
dac_output_enable(DAC_CHANNEL_1);
dac_output_enable(DAC_CHANNEL_2);
// 初始化ADC輸入
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_DB_11);
Serial.println("音訊系統已初始化");
}
void connectWiFi() {
WiFi.begin("你的wifi名稱", "你的wifi密碼");
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
}
Serial.println("\nWiFi已連接!");
Serial.print("IP位址:");
Serial.println(WiFi.localIP());
}
void startListening() {
listening = true;
Serial.println("🎤 正在聆聽...");
// 錄製音訊並發送識別
String audioData = recordAudio();
recognized_text = speechToText(audioData);
if (recognized_text.length() > 0) {
Serial.println("識別結果:" + recognized_text);
processCommand(recognized_text);
}
listening = false;
}
String recordAudio() {
// 簡化的音訊錄製
const int sampleRate = 8000;
const int duration = 3; // 3秒
const int bufferSize = sampleRate * duration;
int16_t audioBuffer[bufferSize];
for (int i = 0; i < bufferSize; i++) {
audioBuffer[i] = adc1_get_raw(ADC1_CHANNEL_6);
delayMicroseconds(125); // ~8kHz採樣
}
// 轉換為base64發送
return encodeAudio(audioBuffer, bufferSize);
}
String speechToText(String audioData) {
HTTPClient http;
http.begin("https://api.example-asr.com/v1/recognize");
http.addHeader("Content-Type", "application/json");
DynamicJsonDocument doc(1024);
doc["audio"] = audioData;
doc["language"] = "zh-TW";
doc["format"] = "pcm_16khz";
String requestBody;
serializeJson(doc, requestBody);
int httpResponseCode = http.POST(requestBody);
String response = "";
if (httpResponseCode == 200) {
response = http.getString();
// 解析回應
DynamicJsonDocument responseDoc(1024);
deserializeJson(responseDoc, response);
response = responseDoc["text"].as<String>();
}
http.end();
return response;
}
void processCommand(String command) {
command.toLowerCase();
if (command.indexOf("開燈") != -1) {
controlLight(true);
speakResponse("燈光已開啟");
}
else if (command.indexOf("關燈") != -1) {
controlLight(false);
speakResponse("燈光已關閉");
}
else if (command.indexOf("天氣") != -1) {
String weather = getWeather();
speakResponse(weather);
}
else {
speakResponse("抱歉,我沒有理解這個指令");
}
}
void controlLight(bool state) {
// 透過GPIO或MQTT控制燈光
Serial.println(state ? "💡 燈光開啟" : "💡 燈光關閉");
// MQTT指令範例
publishMQTT("home/light/command", state ? "ON" : "OFF");
}
String getWeather() {
HTTPClient http;
http.begin("https://api.openweathermap.org/data/2.5/weather?q=Taipei&appid=YOUR_API_KEY&lang=zh_tw&units=metric");
int httpResponseCode = http.GET();
String weather = "無法取得天氣資訊";
if (httpResponseCode == 200) {
String response = http.getString();
DynamicJsonDocument doc(2048);
deserializeJson(doc, response);
float temp = doc["main"]["temp"];
String description = doc["weather"][0]["description"];
weather = "目前溫度" + String(temp) + "度," + description;
}
http.end();
return weather;
}
void speakResponse(String text) {
Serial.println("🔊 " + text);
// 透過HTTP API簡化TTS
String audioData = textToSpeech(text);
playAudio(audioData);
}
String textToSpeech(String text) {
HTTPClient http;
http.begin("https://api.example-tts.com/v1/synthesize");
http.addHeader("Content-Type", "application/json");
DynamicJsonDocument doc(1024);
doc["text"] = text;
doc["voice"] = "zh-TW-Female";
doc["format"] = "pcm";
String requestBody;
serializeJson(doc, requestBody);
int httpResponseCode = http.POST(requestBody);
String audioData = "";
if (httpResponseCode == 200) {
audioData = http.getString();
}
http.end();
return audioData;
}
void playAudio(String audioData) {
// 透過DAC簡化播放
// 實際實作需要解碼base64並播放PCM資料
for (int i = 0; i < 100; i++) {
dac_output_voltage(DAC_CHANNEL_1, 128 + random(-50, 50));
dac_output_voltage(DAC_CHANNEL_2, 128 + random(-50, 50));
delay(10);
}
}
四、平價版最佳化
4.1 記憶體管理
// 記憶體使用最佳化
void optimizeMemory() {
// 使用PROGMEM儲存常數
const char responses[][32] PROGMEM = {
"指令已執行",
"沒有理解指令",
"網路問題"
};
// 釋放未使用的緩衝區
heap_caps_free(unused_buffer);
// 控制任務堆疊大小
#define STACK_SIZE 2048 // 縮小堆疊大小
}
4.2 節能管理
#include "esp_pm.h"
#include "esp_sleep.h"
void setupPowerManagement() {
// 電源管理配置
esp_pm_config_esp32_t pm_config = {
.max_freq_mhz = 160, // 從240MHz降低
.min_freq_mhz = 40, // 最低頻率
.light_sleep_enable = true // 啟用輕度睡眠
};
esp_pm_configure(&pm_config);
}
void enterDeepSleep() {
Serial.println("進入深度睡眠...");
// 設定按鈕喚醒
esp_sleep_enable_ext0_wakeup(GPIO_NUM_0, 0);
// 進入深度睡眠
esp_deep_sleep_start();
}
五、效能與限制
5.1 效能比較
特性 | ESP32(平價版) | ESP32-S3(標準版) |
---|---|---|
SRAM | 520KB | 512KB + 8MB PSRAM |
CPU速度 | 240MHz(雙核) | 240MHz(雙核) |
音訊品質 | 基礎 | 高品質(I2S) |
AI能力 | 僅雲端 | 雲端+本地 |
回應時間 | 3-8秒 | 1-3秒 |
成本 | $8-12 | $15-25 |
5.2 即時效能
// 效能監控
void performanceMonitor() {
// 記憶體使用
size_t free_heap = esp_get_free_heap_size();
size_t min_free_heap = esp_get_minimum_free_heap_size();
Serial.printf("可用記憶體:%d bytes\n", free_heap);
Serial.printf("最小可用記憶體:%d bytes\n", min_free_heap);
// 指令執行時間
unsigned long start_time = millis();
processVoiceCommand();
unsigned long end_time = millis();
Serial.printf("指令處理時間:%lu ms\n", end_time - start_time);
}
六、故障排除
6.1 常見問題
問題:裝置無回應指令
// 語音輸入除錯
void debugVoiceInput() {
// 檢查麥克風電平
int mic_level = adc1_get_raw(ADC1_CHANNEL_6);
Serial.printf("麥克風電平:%d\n", mic_level);
// 檢查WiFi連接
if (WiFi.status() != WL_CONNECTED) {
Serial.println("❌ WiFi未連接");
connectWiFi();
}
// 測試語音識別
if (mic_level < 100) {
Serial.println("⚠️ 麥克風訊號太弱");
}
}
問題:記憶體使用過高
void memoryDebug() {
// 記憶體使用分析
multi_heap_info_t info;
heap_caps_get_info(&info, MALLOC_CAP_DEFAULT);
Serial.printf("總分配記憶體:%d\n", info.total_allocated_bytes);
Serial.printf("可用記憶體:%d\n", info.total_free_bytes);
Serial.printf("最大可用區塊:%d\n", info.largest_free_block);
// 記憶體不足警告
if (info.largest_free_block < 10240) { // 10KB
Serial.println("⚠️ 記憶體嚴重不足!");
// 清理未使用緩衝區
cleanupMemory();
}
}
6.2 最佳化建議
// 一般最佳化提示
void optimizationTips() {
// 1. 使用靜態緩衝區而非動態
static char response_buffer[256];
// 2. 避免String,使用char陣列
char command[64];
strncpy(command, user_input.c_str(), sizeof(command));
// 3. 使用後釋放記憶體
free(audio_buffer);
audio_buffer = nullptr;
// 4. 使用PROGMEM儲存常數
const char* const responses[] PROGMEM = {
"了解",
"請重複",
"錯誤"
};
}
七、總結
平價版小智AI提供了一種經濟實惠的方式來體驗語音AI技術,儘管在效能上有所限制。這個版本非常適合:
推薦給:
- 🎓 教育用途:學習語音AI基礎
- 💰 預算有限:成本低於$15
- 🔧 原型製作:快速測試概念
- 👨🎓 初學者:簡單設定和程式設計
不推薦給:
- 🏭 工業應用:可靠性有限
- 🎵 高音質需求:基礎音效品質
- ⚡ 即時應用:回應時間較慢
- 🤖 進階AI:僅雲端功能
下一步
在掌握平價版後,考慮升級到ESP32-S3以獲得:
- 更好的音訊品質
- 本地AI能力
- 提升的效能
- 擴展功能
準備升級? 查看完整ESP32-S3指南來建立進階版本的小智AI。