Bluetooth-колонка на базе ESP32 и аудиомодуля PCM5102

          В рамках данной статьи мы подробно рассмотрим как построить Bluetooth-колонку на базе микроконтроллера ESP32 и стерео аудиомодуля с цифро-аналоговым преобразователем (ЦАП) модели PCM5102, используя среду программирования Espressif IDE и фреймворк ESP-IDF.

Содержание


          По мере освоения микроконтроллера ESP32 сложность реализуемых задач постепенно возрастает. И в какой-то момент приходит понимание, что возможностей среды разработки Platform IO или Arduino IDE не хватает для реализации относительно сложных задач. Например, разработка беспроводной колонки с применением технологии Bluetooth.

          В сети Интернет есть множество статей, в которых предлагается использовать библиотеку pschatzmann/ESP32-A2DP как ключевую для реализации данной задачи на фреймворке Arduino. Однако, при работе с этой библиотекой постоянно возникали какие-то сложности или конфликтные ситуации, из-за чего код не компилировался.

          При попытке использования среды Platform IO и фреймворка ESP-IDF также не удавалось добиться успешной компиляции кода, поскольку требовались дополнительные и не совсем очевидные настройки среды.

          В качестве эксперимента было принято решение попробовать написать прошивку для Bluetooth-колонки в среде программирования Espressif IDE и фреймворке ESP-IDF.


          Практически каждая современная беспроводная колонка является устройством, которое работает на стандарте A2DP (Advanced Audio Distribution Profile).

          A2DP — это профиль Bluetooth, разработанный для передачи высококачественного стереоаудиопотока между устройствами. Он позволяет передавать музыку и другие аудиоданные с одного устройства (например, смартфона, компьютера или MP3-плеера) на беспроводные наушники, колонки или автомобильную аудиосистему.

          Основные особенности A2DP:

— поддерживает стереозвук (моно также возможно, но редко используется);

— использует кодек сжатия для передачи аудио (например, SBC, AAC, aptX, LDAC);

— работает в одностороннем режиме (от источника к приемнику);

— обеспечивает достаточно низкую задержку (зависит от кодека).


Описание аудиомодуля ЦАП PCM5102

          PCM5102 — это микросхема стерео аудиодекодера, разработанный компанией Texas Instruments, предназначенная для воспроизведения аудиосигнала с высоким качеством звучания. Декодер обладает 24-битным цифро-аналоговым преобразователем и I2S-интерфейсом, а также поддерживает частоту дискретизации до 384 кГц. При этом микросхема потребляет ток максимум 10…12 мА при напряжении питания 3,3 В,  что делает её идеальной для применения в портативных аудиосистемах.

          Ниже представлены основные параметры декодера:

          Модуль имеет встроенные средства фильтрации и обработки сигнала:

          — встроенный фильтр верхних частот (HPF) для удаления смещения по постоянному току;

          — деэмперсизация (DEMP) для подавления высокочастотного шума;

          — Soft Mute для плавного отключения звука без щелчков.

          Для более удобной экспериментальной работы декодер продаётся в составе готовых модулей в виде печатной платы, на которой распаяна микросхема в корпусе TSSOP-20 с полной обвязкой, штыревыми выводами и гнездом mini jack 3,5 mm:

          Таблица назначения выводов платы PCM5102:

          Также рядом с гнездом mini jack 3,5 mm расположена гребёнка на 3 контакта, предназначенные для подачи аудиосигнала на вход внешнего усилителя звука:

          — L (LEFT): левый канал

          — G (GND): общий провод («земля»)

          — R (RIGHT): правый канал


Схема подключения PCM5102 к ESP32

          Для работы с аудиодекодером используется аппаратный I2S-интерфейс на отладочной плате NodeMCU-32S (38 pin), которая построена на базе модуля ESP-WROOM-32. Ниже представлена таблица и схема подключения PCM5102 к отладочной плате, а также фотография собранного макета:


Подготовка проекта в среде Espressif IDE ESP-IDF

          После установки среды программирования Espressif IDE и фреймворка ESP-IDF на рабочем столе ОС Windows появится три ярлыка:

          Перед началом работы запустите командную строку ESP-IDF 5.3 CMD, чтобы «прописались» переменные окружения IDF_PATH и IDF_TOOLS_PATH в системе. Дело в том, что по умолчанию переменные назначаются временно на период сессии работы операционной системы. То есть, если Вы завершите работу операционной системы (выключите компьютер), то всё сбрасывается. Вновь включили — снова «прописывайте переменные».

          Теперь запускаем среду разработки Espressif IDE, кликая по соответствующему ярлыку:

          Откроется стартовая страница:

          Создадим новый проект, выбрав из выпадающего меню «File» (в верхнем левом углу окна) пункты New –> Espressif IDF Project:

          Откроется соответствующее окно для создания нового проекта:

          Для удобства пользователей разработчики программного обеспечения предусмотрели возможность использовать в качестве шаблона уже готовые проекты. В частности, задача по созданию беспроводного аудиопроигрывателя также имеет свой шаблон: a2dp_sink.

Читайте также:  Магнитометр HMC5883L (QMC5883L): компас на ESP32

          Чтобы его выбрать, нужно выполнить несколько шагов:

          1) установить галочку «Create a project using one of the templates». Откроется доступ к дереву директорий с шаблонами;

          2) раскрыть папку «bluetooth»;

          3) раскрыть папку «bluedroid»;

          4) раскрыть папку «classic_bt»;

          5) выбрать шаблон «a2dp_sink»;

          6) убрать галочку «Use default location» и выбрать удобную для Вас директорию. Рекомендуется создать внутри штатной директорию свою папку под данный проект. После чего поставить галочку «Use default location», поскольку иначе проджект-мастер блокирует кнопку «Finish»

          7) сменить имя «Project name» на какое-нибудь иное, чтобы не было конфликта с шаблоном;

          8) завершить процесс нажатием кнопки «Finish».

          Проект сформируется и откроется окно среды разработки:

          Первым делом поправим файл CMakeLists.txt, который можно найти в дереве файлов в левой части окна. Открываем этот файл. Выделяем все строки и комментируем их, нажав комбинацию клавиш Ctrl + 7:

          Необходимо вставить и сохранить (Ctrl + S) несколько иной код, подключающий необходимые библиотеки при сборке:

idf_component_register(
    SRCS "bt_app_av.c"
         "bt_app_core.c"
         "main.c"
    INCLUDE_DIRS "."
    PRIV_REQUIRES 
        bt 
        nvs_flash 
        esp_ringbuf 
        esp_driver_i2s
        driver
)

# Подключение компонентов из примера A2DP
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/bluetooth/bluedroid/classic_bt/bt_a2dp_sink/components)

          Далее открываем главный файл с кодом программы main.c, тоже выделяем все строки и комментируем посредством комбинации клавиш Ctrl + 7:

          Вставляем в файл код, представленный и подробно прокомментированный ниже. А потом сохранить, нажав комбинацию кнопок Ctrl + S:

#include "freertos/FreeRTOS.h"       // FreeRTOS ядро
#include "freertos/task.h"           // FreeRTOS задачи
#include "esp_system.h"              // Основные системные функции ESP-IDF
#include "esp_log.h"                 // Логирование
#include "esp_bt.h"                  // Bluetooth базовые функции
#include "esp_bt_main.h"             // Основная инициализация Bluetooth
#include "esp_a2dp_api.h"            // A2DP профиль Bluetooth для аудио
#include "esp_bt_device.h"           // Управление Bluetooth устройством
#include "esp_gap_bt_api.h"          // GAP профиль Bluetooth (видимость, соединения)
#include "driver/i2s.h"              // Драйвер I2S (для аудио передачи)

// Тег для логов
static const char *TAG = "BT_SPEAKER";

// Конфигурация I2S пинов и параметров
#define I2S_NUM         (0)          // Используем I2S порт 0
#define I2S_SAMPLE_RATE (44100)      // Частота дискретизации 44.1kHz (CD качество)
#define I2S_BCK_IO      (26)         // Пин для BCK (битовый такт)
#define I2S_WS_IO       (25)         // Пин для WS (word select)
#define I2S_DO_IO       (22)         // Пин для передачи данных (Data Out)

// Функция настройки и запуска I2S драйвера
void i2s_driver_setup(void)
{
    // Конфигурация I2S
    const i2s_config_t i2s_config = {
        .mode = I2S_MODE_MASTER | I2S_MODE_TX,            // Режим мастер и передача
        .sample_rate = I2S_SAMPLE_RATE,                   // Частота дискретизации
        .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,    // 16 бит на сэмпл
        .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,    // Стерео (правый и левый канал)
        .communication_format = I2S_COMM_FORMAT_I2S,     // Формат I2S
        .intr_alloc_flags = 0,                            // Флаги прерываний (0 - по умолчанию)
        .dma_buf_count = 8,                               // Количество DMA буферов
        .dma_buf_len = 64,                                // Длина каждого DMA буфера
        .use_apll = false,                                // Не использовать APLL
        .tx_desc_auto_clear = true,                        // Автоочистка дескрипторов передачи при ошибках
    };

    // Конфигурация пинов I2S
    const i2s_pin_config_t pin_config = {
        .bck_io_num = I2S_BCK_IO,         // Пин BCK
        .ws_io_num = I2S_WS_IO,           // Пин WS
        .data_out_num = I2S_DO_IO,        // Пин передачи данных
        .data_in_num = I2S_PIN_NO_CHANGE  // Приём данных не используется
    };

    // Устанавливаем драйвер I2S с конфигурацией
    i2s_driver_install(I2S_NUM, &i2s_config, 0, NULL);

    // Назначаем пины для I2S
    i2s_set_pin(I2S_NUM, &pin_config);

    // Очищаем DMA буфер, чтобы не было мусорных данных
    i2s_zero_dma_buffer(I2S_NUM);
}

// Коллбек функция для получения PCM аудиоданных по Bluetooth A2DP
void bt_app_a2d_data_cb(const uint8_t *data, uint32_t len)
{
    size_t written = 0; // Количество реально записанных байт

    // Записываем полученные данные в I2S для воспроизведения
    // portMAX_DELAY — ждать, если буфер занят
    i2s_write(I2S_NUM, data, len, &written, portMAX_DELAY);
}

// Коллбек для обработки событий Bluetooth A2DP
void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param)
{
    switch (event) {
        case ESP_A2D_CONNECTION_STATE_EVT:
            // Событие изменения состояния соединения
            ESP_LOGI(TAG, "Bluetooth device %sconnected",
                     param->conn_stat.state == ESP_A2D_CONNECTION_STATE_CONNECTED ? "" : "dis");
            break;

        case ESP_A2D_AUDIO_STATE_EVT:
            // Событие изменения состояния аудио (старт/стоп)
            ESP_LOGI(TAG, "Audio state: %d", param->audio_stat.state);
            break;

        default:
            // Другие события игнорируем
            break;
    }
}

// Главная функция приложения
void app_main()
{
    // Логируем начало инициализации I2S
    ESP_LOGI(TAG, "Инициализация I2S");
    i2s_driver_setup();  // Настраиваем I2S

    // Логируем начало инициализации Bluetooth
    ESP_LOGI(TAG, "Инициализация Bluetooth");

    // Освобождаем память BLE, так как используем только Classic BT
    esp_bt_controller_mem_release(ESP_BT_MODE_BLE);

    // Получаем конфигурацию Bluetooth контроллера по умолчанию
    esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();

    // Инициализируем Bluetooth контроллер
    esp_bt_controller_init(&bt_cfg);

    // Включаем Bluetooth Classic
    esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT);

    // Инициализируем и запускаем стек Bluedroid
    esp_bluedroid_init();
    esp_bluedroid_enable();

    // Регистрируем коллбеки для A2DP профиля
    esp_a2d_register_callback(&bt_app_a2d_cb);               // События A2DP
    esp_a2d_sink_register_data_callback(bt_app_a2d_data_cb); // Получение аудиоданных
    esp_a2d_sink_init();                                      // Инициализация A2DP sink (приёмник)

    // Устанавливаем имя Bluetooth устройства (будет видно другим устройствам)
    esp_bt_gap_set_device_name("ESP32_SPEAKER");

    // Разрешаем устройству быть обнаруживаемым и подключаемым
    esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);

    // Логируем, что устройство готово к работе
    ESP_LOGI(TAG, "Bluetooth колонка готова!");
}

          Видно, что многие строчки кода, относящиеся к I2S-интерфейсу, подчёркнуты красным. Попробуем выполнить сборку кода, выбрав в меню «Project» пункт «Build Project»:

          Компиляция может выполняться достаточно долго: от 30 секунд до нескольких минут, в зависимости от размера кода и степени изменений относительно предыдущей компиляции:

Читайте также:  ESP32-C3 и TFT-дисплей: вывод времени по Wi-Fi

          В результате сборка завершилась с предупреждениями в количестве 3 штук. При этом 0 ошибок:

          Тем не менее, мы всё же посмотрим, какие предупреждения компилятор объявил:

          Эти предупреждения говорят о следующем:

           — устаревший драйвер для аналого-цифрового преобразователя (АЦП);

          — устаревшая библиотека для I2S-интерфейса;

          —  устаревшее значение константы I2S_COMM_FORMAT_I2S.

          Для нас неактуальность драйвера АЦП не имеет значения, поэтому пропустим данное замечание.

          А вот для решения вопроса с устаревшей библиотекой I2S нужно внести некоторые правки в настройки проекта.

          Дело в том, что в установленном фреймворке ESP-IDF 5.3.1 актуальной версией библиотеки для I2S-интерфейса является driver/i2s_std. Но в нашем коде используется более ранняя версия — driver/i2s. Чтобы решить этот конфликт, в настройках нужно включить опцию «Suppress legacy driver deprecated warning». Для этого следует открыть меню настройки конфигурации sdkconfig, который размещён в дереве файлов слева. Когда проджект-мастер сформировал пространство для работы, sdkconfig изначально не появился в дереве файлов. Только после первой компиляции он создаётся:

          Кликаем два раза, и откроется SDK Configuration:

          Далее выполняем 3 простых шага:

          1) в поисковой строке пишем «I2S», чтобы быстрее найти нужный пункт;

          2) выбираем пункт «Legacy I2S Driver Configuration»;

          3) ставим галочку напротив «Suppress legacy driver deprecated warning».

          После этого сохраняем изменения, нажав комбинацию клавиш Ctrl + S.

          Попробуем выполнить пересборку (Project –> Build Project или щелкнуть по пиктограмме в виде молотка в верхнем левом углу окна):

          Как видим, компиляция прошла успешно, а предупреждений стало на 1 меньше. Остались вопросы касательно драйвера АЦП и устаревшей версии переменной:

          Но так как мы приняли использовать устаревшую версию драйвера для I2S, то оставим всё как есть.


Как прошить микроконтроллер ESP32 в среде Espressif IDE

          Переходим к процессу прошивки микроконтроллера. Подключаем отладочную плату к компьютеру через USB-кабель (настоятельно рекомендуется использовать кабель длиной не более 1 метра). Также уточните, что драйвер для USB-UART преобразователя у Вас установлен на компьютер, и определите, какой COM-порт назначен для отладочной платы:

          Далее открываем терминал (пиктограмма указана красной стрелкой) и выбираем нужный последовательный порт:

          Теперь нужно уточнить порт для целевого устройства (target), нажав на пиктограмму в виде шестерёнки и тоже выбрать COM-порт:

          Итак, можно приступить непосредственно к прошивке, нажав кнопку “Run” справа от “молоточка” в верхнем левом углу окна:

          Процесс занимает несколько секунд, прогресс отображается в мониторе порта. Нажимать кнопку сброса на отладочной плате не нужно (в отличие от PlatformIO).

Читайте также:  Климатический датчик BME280 и ESP32

Проверка работоспособности Bluetooth-колонки

          Как только загрузка кода успешно завершится, устройство начинает работу. Берём смартфон, включаем Bluetooth и сканируем доступные устройства. Должно быть видно «ESP32_SPEAKER» (название можно менять в коде программы):

          Выбираем устройство и подключаемся к нему:

          При успешном сопряжении устройство будет иметь пометку «Используется»:

          Теперь запускаем на смартфоне какой-нибудь проигрыватель (приложение на самом смартфоне или какой-либо веб-сервис) и воспроизводим аудиотрек:

          В наушниках должна быть слышна музыка.


Заключение

          В рамках этого эксперимента наглядно рассмотрен практический пример создания основы для беспроводного аудиопроигрывателя  на базе микроконтроллера ESP32 и аудиомодуля PCM5102.

          Использование среды программирования Espressif IDE и фреймворка ESP-IDF обеспечивает более гибкий и профессиональный подход к разработке по сравнению с Arduino IDE или Platform IO, хотя и требует дополнительных настроек.