小智AI平價版 - ESP32開發板實現方案

小智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-32S1$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(標準版)
SRAM520KB512KB + 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。