XiaoZhi AI бюджетная версия - Реализация на плате ESP32

XiaoZhi AI бюджетная версия - Реализация на плате ESP32

Обзор

XiaoZhi AI бюджетная версия - это низкобюджетное решение AI чат-бота, созданное с использованием ранних версий плат ESP32 (не S3/C3). В этой статье подробно описаны все шаги по созданию XiaoZhi AI с использованием обычных плат ESP32, включая выбор оборудования, методы подключения, прошивку firmware и решение общих проблем. По сравнению с использованием плат ESP32-S3, это решение более экономично и подходит для начинающих пользователей.

ESP32 Плата Разработки Версия XiaoZhi AI (Бюджетная)

Это ESP32 не серии S3, C3, а ранняя версия серии плат ESP32, в основном с 4MB flash, без внешней PSRAM платы разработки. Обычно называется “бюджетная XiaoZhi” или “игровая версия XiaoZhi”, позволяя вам играть с XiaoZhi по цене одной платы S3.

I. Описание серии 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, ограниченная многоязычность

II. Список оборудования и схемы подключения

2.1 Список необходимых компонентов

КомпонентСпецификацияКоличествоСтоимость
ESP32 платаESP32-DevKitC/NodeMcu-32S1$8-12
МикрофонАналоговый или цифровой1$2-4
УсилительPAM8403 или аналогичный1$1-2
Динамик2Вт 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

III. Настройка программного обеспечения

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("XiaoZhi 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_ssid", "ваш_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"] = "ru-RU";
    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=Moscow&appid=YOUR_API_KEY&lang=ru&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);
    
    // Упрощенный TTS через HTTP API
    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"] = "ru-RU-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);
    }
}

IV. Оптимизация для бюджетной версии

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();
}

V. Производительность и ограничения

5.1 Сравнение производительности

ХарактеристикаESP32 (Бюджетная)ESP32-S3 (Стандартная)
SRAM520KB512KB + 8MB PSRAM
Скорость ЦП240MHz (2 ядра)240MHz (2 ядра)
Качество аудиоБазовоеВысокое (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);
}

VI. Устранение неполадок

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 arrays
    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 = {
        "Понял",
        "Повторите",
        "Ошибка"
    };
}

VII. Заключение

Бюджетная версия XiaoZhi AI предоставляет доступный способ познакомиться с технологиями голосового AI, несмотря на некоторые ограничения в производительности. Эта версия идеально подходит для:

Рекомендуется для:

  • 🎓 Образовательных целей: Изучение основ голосового AI
  • 💰 Ограниченного бюджета: Стоимость менее $15
  • 🔧 Прототипирования: Быстрое тестирование концепций
  • 👨‍🎓 Начинающих: Простая настройка и программирование

Не рекомендуется для:

  • 🏭 Промышленного применения: Ограниченная надежность
  • 🎵 Высокого качества аудио: Базовое качество звука
  • Реального времени: Медленное время отклика
  • 🤖 Продвинутого AI: Только облачные возможности

Следующие шаги

После освоения бюджетной версии рассмотрите переход на ESP32-S3 для:

  • Лучшего качества аудио
  • Локальных AI возможностей
  • Повышенной производительности
  • Расширенных функций
Готовы к обновлению? Ознакомьтесь с полным руководством по ESP32-S3 для создания продвинутой версии XiaoZhi AI.