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-32S | 1 | $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 (Стандартная) |
---|---|---|
SRAM | 520KB | 512KB + 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 возможностей
- Повышенной производительности
- Расширенных функций