Подписаться YouTube

Учебное пособие по звуку I2S для ESP32

Учебное пособие по I2S ESP32

В этом руководстве вы узнаете, как использовать протокол связи I2S для передачи цифровых звуковых сигналов для записи и визуализации данных микрофона и воспроизведения музыки из внутренней памяти, а также с внешней SD-карты.

Мы также сравним разные микроконтроллеры и увидим, почему мы предпочитаем микроконтроллер ESP32 для наших проектов I2S.

Зачем нам нужен протокол I2S?

Если мы хотим воспроизвести цифровой аудиофайл с помощью платы микроконтроллера, мы должны рассмотреть всю цепочку цифрового аудио. Следующий схематический рисунок показывает, как аудиофайл сохраняется на SD-карте и считывается с платы микроконтроллера. Затем плата подключается к динамику через цифровой контакт и землю.

Схема I2S

В моем случае у меня есть образец аудиофайла с частотой дискретизации 44,1 кГц, стереоформатом и глубиной звука 16 бит. На нашей стороне ввода, где мы хотим прочитать музыкальный файл, у нас нет проблем, потому что соединение SPI достаточно быстрое, чтобы качество не ухудшалось во время передачи.

Свойства файла

Но со стороны выхода мы должны преобразовать цифровой сигнал в аналоговый сигнал. Это делается с помощью цифро-аналогового преобразователя (ЦАП). В зависимости от используемого микроконтроллера возникают разные проблемы:

  • Arduino и ESP8266: платы Arduino, а также ESP8266 в целом не имеют внутреннего ЦАП, и поэтому вам придется создавать ЦАП с внешними компонентами.
  • ESP32: ESP32 имеет внутренний ЦАП для создания аналогового выходного сигнала, однако ЦАП имеет только 8-битное разрешение. Поскольку у нас есть 16-битный входной сигнал, мы немного потеряем качество.

Но как мы можем преобразовать цифровые данные из файла WAVE в динамик? Решением этой проблемы является протокол связи I2S, который поддерживает от 4 до 32 бит данных на выборку. Чтобы сделать нашу жизнь еще проще, мы используем коммутационную плату для аудио MAX98357 I2S. Но сначала мы углубимся в протокол связи I2S.

Протокол связи I2S

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

  1. 3-проводное соединение I2S
  2. Сетевые компоненты I2S
  3. Временная диаграмма I2S

В следующей таблице показано, какие платы имеют интерфейс I2S, а какие нет.

Имеет интерфейс I2SНе имеет интерфейса I2S
ArduinoПлаты Arduino Arduino Due, Arduino MKR Zero, Arduino MKR1000 WiFiс микроконтроллером ATmega328P, такие как Arduino Uno или Arduino Nano.
ESPESP8266 ESP32

Из таблицы видно, что только некоторые специальные платы Arduino имеют интерфейс I2S, но не наиболее используемые платы, такие как Arduino Uno. Также все платы ESP8266 и ESP32 поддерживают интерфейс I2S, поэтому я рекомендую использовать для этого руководства плату на базе микроконтроллера ESP8266 или ESP32. В моем случае я использую микроконтроллер ESP32, потому что библиотеки, которые мы используем, поддерживают ESP32 лучше, чем ESP8266, по моему опыту.

3-проводное соединение I2S

Протокол I2S использует для связи три провода.

Последовательные часы (SCK), также называемые линией битовых часов (BCLK), используются для получения всех компонентов в одном цикле. Частота тактовых импульсов последовательного интерфейса определяется следующим образом:

Частота = Частота дискретизации * Количество бит на канал * Количество каналов.

Для моего WAVE-файла, который я использую в этом руководстве, мы уже знаем следующие переменные:

  • Частота дискретизации: 44,1 кГц
  • Бит на канал: 16
  • Количество каналов: 2

Следовательно, тактовая частота последовательного интерфейса составляет 44,1 кГц * 16 * 2 = 1,411 МГц.

Вторая линия протокола связи I2S - это провод выбора слова (Word Select - WS) или выбора кадра (Frame Select - FS), который различает левый или правый канал.

  • Если WS = 0 → используется канал 1 (левый канал)
  • Если WS = 1 → используется канал 2 (правый канал)

Последний провод - это линия последовательных данных (Serial Data - SD), по которой полезная нагрузка передается с двумя дополнениями. Важно, чтобы старший бит передавался первым (сначала MSB), потому что передатчик и приемник могут иметь разную длину слова. Следовательно, ни передатчик, ни приемник не должны знать, сколько битов передается. Но что произойдет, если длина слова между передатчиком и приемником не совпадает?

  • Если WS приемника > WS передатчика → слово усекается (младшие значащие биты данных устанавливаются в 0)
  • Если WS приемника < WS передатчика → биты после LSB игнорируются

Сетевые компоненты I2S

Если есть несколько компонентов I2S, подключенных друг к другу, я называю это сетью I2S. Компоненты сети имеют разные имена, а также разные функции. На следующем рисунке показаны три разные сети, которые я опишу.

На первом изображении у нас есть передатчик, а также приемник. Передатчиком может быть плата ESP NodeMCU, а приемником - плата аудиоразъема I2S, которую мы описываем в следующем разделе. Также у нас есть три провода для подключения устройств I2S.

Сетевые компоненты I2S

В этом первом случае передатчик является ведущим, потому что ведущий управляет последовательными линиями синхронизации (SCK) и линиями выбора слова (WS). На втором рисунке мы видим обратное, потому что получатель сообщений I2S также может быть ведущим. Следовательно, линии SCK и WS начинаются от приемника и заканчиваются на передатчике.

На третьем рисунке показано, что внешний контроллер также может быть ведущим устройством, которое генерирует SCK и WS. Контроллер подключен к узлам в сети.

Во всех сетях I2S есть только одно ведущее устройство. Может быть несколько других компонентов, которые принимают или передают звуковые данные.

Временная диаграмма I2S

Чтобы лучше понять поведение, а также функциональность протокола связи I2S, мы взглянем на следующую временную диаграмму I2S.

Временная диаграмма I2S

На временной диаграмме вы видите все три линии: SCK, WS и SD. Сначала у нас есть наши последовательные часы, которые имеют частоту дискретизации * бит на канал * количество каналов. В нашем примере 1,411 МГц. Второй канал - это строка выбора слова, которая изменяется от 1 для правого звукового канала до 0 для левого канала.

Из строки последовательных данных мы видим, что данные отправляются в каждом тактовом цикле на заднем фронте (красная пунктирная линия) → от HIGH до LOW. Для связи I2S также можно отправлять данные при изменении с НИЗКОГО на ВЫСОКИЙ.

Также мы видим, что строка WS изменяется за один такт до того, как будет передан самый старший бит (MSB). Это дает получателю время сохранить предыдущее слово и очистить входной регистр для следующего слова. MSB отправляется, когда SCK изменяется после изменения WS.

Плата MAX98357 I2S Audio Breakout

После того, как мы узнали, что можем использовать протокол связи I2S для получения звуковых данных из микроконтроллера без какого-либо снижения качества, следующая проблема заключается в том, что мы должны декодировать сигналы I2S в аналоговые сигналы, а также нам нужен усилитель для использования динамика.

  • Декодер из сигнала I2S в аналоговый сигнал, потому что динамики работают только с аналоговыми сигналами.
  • Усилитель увеличивает мощность аналогового сигнала для увеличения интенсивности звука.

MAX98357 - это входной усилитель с цифровой импульсно-кодовой модуляцией (PCM), который декодирует сигнал I2S в аналоговый сигнал с помощью цифроаналогового преобразователя (DAC), а также имеет встроенный усилитель. На следующем рисунке показана упрощенная блок-схема из таблицы MAX98357.

MAX98357 I2S аудио коммутационная плата

Исходное изображение

Из блок-схемы MAX98357 вы видите, что сначала сигнал I2S преобразуется в аналоговый сигнал через ЦАП, а послесловия усиливаются усилителем с заранее заданной регулировкой усиления.

Спецификация платы Audio Breakout Board MAX98357

Вы можете купить MAX98357 в качестве коммутационной платы у Adafruit или SparkFun. Продукция точно такая же. В следующей таблице показано техническое описание MAX98357.

Adafruit MAX98357A или SparkFun MAX98357AЗначение
Диапазон напряжения питания2,7… 5,5 В
Выходная мощность3,2 Вт на 4 Ом при 5 В
1,8 Вт на 8 Ом при 5 В
Выбор выходного каналавлево, вправо или влево / 2 + вправо / 2 (по умолчанию)
Частота дискретизации8 кГц… 96 кГц
Разрешение выборки16/32 бит
Ток покоя2,4 мА
Усилитель классаD
Коэффициент усиления3 дБ… 15 дБ (по умолчанию: 9 дБ)
Требуются часы памяти (MCLK)?Нет

Рабочее напряжение MAX98357 составляет от 2,7 В до 5,5 В. Поэтому вы можете запитать микроконтроллер с платой на базе Arduino (5 В) или ESP (3,3 В). Выходная мощность составляет 3,2 Вт для динамика с сопротивлением 4 Ом и 1,8 Вт для динамика с сопротивлением 8 Ом.

Конфигурация платы по умолчанию - «моно», то есть левый и правый сигналы объединяются вместе для управления одним динамиком. Если вы хотите переключиться на стереозвук, вам нужно разрезать моно-перемычку на плате и припаять стерео соединение для левого или правого канала.

Руководство по подключению к I2S Audio Breakout sparkfun

Частота дискретизации MAX98357 находится в диапазоне от 8 кГц до 96 кГц, поэтому музыка в нашем примере с частотой дискретизации 44,1 кГц идеально подходит для этой частоты дискретизации. Разрешение выборки составляет 16 или 32 бита, а ток покоя очень низкий - 2,4 мА.

Поскольку усилитель использует широтно-импульсную модуляцию для управления выходными устройствами, он относится к усилителю класса D. Коэффициент усиления усилителя составляет от 3 дБ до 15 дБ с коэффициентом усиления по умолчанию 9 дБ. В следующей таблице показано, как изменить коэффициент усиления. Ключевым моментом является то, что вывод усиления должен быть подключен к другим выводам, чтобы изменить коэффициент усиления.

УсилениеПодключение контакта GAIN
15 дБ Подключен к GND через резистор 100 кОм
12 дБ Подключен к GND
9 дБ Без подключения (по умолчанию)
6 дБ При подключении к VDD / Vin
3 дБ Подключен к VDD / Vin через резистор 100 кОм

Как записывать и визуализировать данные с помощью микрофона I2S

В первом примере мы начинаем записывать и визуализировать звуковые данные с микроконтроллера микрофона I2S SPH0645 от adafruit. В этом примере мы используем микроконтроллер ESP32 NodeMCU.

На следующем рисунке показана проводка между ESP32 NodeMCU и коммутационной платой SPH0645.

I2S ESP32 SPH0645

Важно подключать микроконтроллер I2S только к выводу 3,3 В. Следующий код Arduino визуализирует аналоговые звуковые данные в последовательном плоттере Arduino.

#include "driver/i2s.h"
const i2s_port_t I2S_PORT = I2S_NUM_0;

void setup() {
  Serial.begin(115200);
  esp_err_t err;
  // The I2S config as per the example
  const i2s_config_t i2s_config = {
    .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX), // Receive, not transfer
    .sample_rate = 16000,                         // 16KHz
    .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT, // could only get it to work with 32bits
    .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT, // use right channel
    .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),
    .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,     // Interrupt level 1
    .dma_buf_count = 4,                           // number of buffers
    .dma_buf_len = 8                              // 8 samples per buffer (minimum)
  };
  // The pin config as per the setup
  const i2s_pin_config_t pin_config = {
    .bck_io_num = 26,   // Serial Clock (SCK)
    .ws_io_num = 25,    // Word Select (WS)
    .data_out_num = I2S_PIN_NO_CHANGE, // not used (only for speakers)
    .data_in_num = 33   // Serial Data (SD)
  };
  // Configuring the I2S driver and pins.
  // This function must be called before any I2S driver read/write operations.
  err = i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL);
  if (err != ESP_OK) {
    Serial.printf("Failed installing driver: %d\n", err);
    while (true);
  }
  err = i2s_set_pin(I2S_PORT, &pin_config);
  if (err != ESP_OK) {
    Serial.printf("Failed setting pin: %d\n", err);
    while (true);
  }
  Serial.println("I2S driver installed.");
}

void loop() {
  // Read a single sample and log it for the Serial Plotter.
  int32_t sample = 0;
  int bytes_read = i2s_pop_sample(I2S_PORT, (char *)&sample, portMAX_DELAY); // no timeout
  if (bytes_read > 0) {
    Serial.println(sample);
  }
}

В первой строке мы включаем библиотеку I2S для ESP32 и определяем используемую структуру выводов I2S, потому что только GPIO25 и GPIO26 подключены к внутреннему 8-битному ЦАП, что также показано в распиновке ESP32.

В функции настройки мы устанавливаем скорость передачи 115200, которая должна соответствовать скорости передачи в последовательном плоттере Arduino IDE, где мы отображаем аналоговые звуковые данные.

Если мы получим какую-либо ошибку во время выполнения кода, мы можем получить доступ к ошибке с помощью переменной err.

Следующим шагом в коде Arduino является определение структуры связи I2S. Устанавливаем следующие настройки:

  • установите режим I2S на RX для получения данных I2S
  • используйте частоту дискретизации по умолчанию 16 кГц
  • установите бит на выборку равным 32, а не 16
  • мы используем только правый канал микрофона
  • мы используем 4 буфера, каждый длиной 8

После того, как мы установили структуру связи I2S, мы определяем контакты, которые используются на ESP32 NodeMCU для связи. В моем случае я выбираю:

  • Последовательные часы (SCK) = 26
  • Выбор слова (WS) = 25
  • Последовательные данные (SD) = 33
  • В следующем разделе настраиваются драйвер и контакты I2S. Поскольку эта часть кода глубоко погружается во внутренние функции ESP32, мы пропускаем объяснение этого раздела.

В функции цикла мы читаем аналоговый вывод из ЦАП и сохраняем данные в переменной bytes_read. Если мы получаем данные, мы выводим аналоговый звуковой сигнал на последовательный выход, чтобы визуализировать звуковую частоту в последовательном плоттере.

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

Микрофонный последовательный плоттер I2S

Как воспроизводить музыку из внутренней памяти ESP32

Во втором примере мы хотим воспроизводить музыку через динамик. Звуковые данные хранятся в виде массива во внутренней RAM ESP32. Мы используем плату аудиоразъема MAX98357 I2S для преобразования цифрового сигнала в аналоговый. Поэтому мы используем протокол I2S для вывода цифровых звуковых данных без потери качества.

На следующем рисунке показана проводка между ESP32 NodeMCU, коммутационной платой MAX98357 I2S и динамиком.

I2S ESP32 Воспроизведение по памяти

Для кода Arduino мы используем библиотеку ESP8266Audio от Earle F. Philhower. Чтобы включить эту библиотеку в ваш Arduino, выполните 4 шага:

  1. Загрузите папку github в виде zip-файла
  2. разархивируйте загруженную папку
  3. переименуйте распакованную папку в ESP8266Audio
  4. скопируйте папку в путь к вашей библиотеке IDE Arduino (в моем случае: C:\Users\chris\Documents\Arduino\libraries)

Мы используем следующий код Arduino из примеров библиотеки для воспроизведения музыки из внутренней памяти.

#include "AudioGeneratorAAC.h"
#include "AudioOutputI2S.h"
#include "AudioFileSourcePROGMEM.h"
#include "sampleaac.h"

AudioFileSourcePROGMEM *in;
AudioGeneratorAAC *aac;
AudioOutputI2S *out;

void setup() {
  Serial.begin(115200);

  in = new AudioFileSourcePROGMEM(sampleaac, sizeof(sampleaac));
  aac = new AudioGeneratorAAC();
  out = new AudioOutputI2S();
  out -> SetGain(0.125);
  out -> SetPinout(26, 25, 22);
  aac->begin(in, out);
}

void loop() {
  if (aac->isRunning()) {
    aac->loop();
  } else {
    aac -> stop();
    Serial.printf("Sound Generator\n");
    delay(1000);
  }
}

В первых строках мы добавляем следующие заголовочные файлы из библиотеки ESP8266Audio:

  • AudioGeneratorAAC: генератор аудиовыхода с использованием декодера Helix AAC.
  • AudioOutputI2S: базовый класс для интерфейсного порта I2S
  • AudioFileSourcePROGMEM: сохранить «файл» как массив PROGMEM и использовать его как данные источника звука.
  • sampleaac: заголовочный файл, в котором аудиофайл хранится в виде массива

Цифровые звуковые данные хранятся в заголовочном файле sampleaac. Чтобы загрузить код Arduino с файлом заголовка в EPS32, важно, чтобы Arduino (файл .ino) и заголовок (файл .h) находились в одной папке.

После того, как мы включили файлы заголовков библиотеки ESP8266Audio, мы даем для первых трех включенных файлов переменные, содержащие ссылки на классы этих файлов.

В функции настройки мы устанавливаем скорость передачи 115200 бод и инициализируем файлы заголовков. Для AudioFileSourcePROGMEM мы определяем, что образец аудиофайла находится в файле sampleaac с размером содержащего его массива.

Объект AudioOutputI2S имеет разные функции. Мы используем функцию SetGain, чтобы уменьшить громкость динамика, и определяем распиновку с помощью функции SetPinout. В моем случае я выбираю следующую распиновку по умолчанию:

  • Последовательные часы (SCK) = 26
  • Выбор слова (WS) = 25
  • Последовательные данные (SD) = 22

Но не стесняйтесь выбирать другие цифровые выводы микроконтроллера EPS32.

Последним шагом функции настройки является подключение входных звуковых данных из внутренней памяти программы к аудиовыходу I2S с помощью AudioGeneratorAAC.

В функции цикла звуковой генератор продолжает работать до тех пор, пока весь звуковой массив не пройдет через генератор. Когда генератор завершит работу, он сообщит об этом на последовательный выход.

Как воспроизвести файл WAVE на ESP32 с внешней SD-карты

В нашем последнем проекте мы хотим воспроизвести WAVE-файл, который я упомянул в начале этого руководства, через ESP32 NodeMCU и динамик. Поскольку ESP32 должен читать файл WAVE и пересылать цифровой аудиосигнал на MAX98357A, мы должны использовать SD-карту с файлом WAVE на ней. Вы также можете использовать файл MP3 вместо файла WAVE.

На следующем рисунке показано подключение ESP32 NodeMCU к модулю карты (Micro) SD, MAX98357A и динамику. На картинке вы видите, что вам нужно изменить контакт DIN MAX98357A, по сравнению со вторым проектом.

I2S ESP32 Воспроизведение с SD-карты

Прежде чем мы погрузимся в код Arduino, мы должны подготовить (Micro) SD-карту. Файловая система должна быть FAT16 или FAT32. В зависимости от модуля SD-карты для SD-карты существует ограничение в 32 ГБ. Я использую карту micro SD объемом 32 ГБ, отформатированную как FAT32, и копирую файл WAVE без папки на SD-карту.

Для этого проекта мы используем библиотеку ESP32-audioI2S Arduino от schreibfaul1. Вы можете скачать библиотеку в виде zip-файла с её страницы на GitHub. Поскольку библиотека имеет имя audio, а в Arduino уже существует библиотека с таким же именем, мы включаем библиотеку через IDE Arduino:

  1. Откройте IDE Arduino.
  2. Перейдите к (см. Следующий рисунок): Скетч → Подключить библиотеку → Добавить .ZIP библиотеку...
  3. Выберите загруженную библиотеку

Включить библиотеку ESP32-audioI2S

Скрипт Arduino основан на примере скрипта schreibfaul1, но я сократил скрипт оставив только части, необходимые для воспроизведения файла WAVE, и удалил все части для потоковой передачи WiFi.

#include "Audio.h"
#include "SD.h"
#include "FS.h"

// Digital I/O used
#define SD_CS          5
#define SPI_MOSI      23
#define SPI_MISO      19
#define SPI_SCK       18
#define I2S_DOUT      25
#define I2S_BCLK      27
#define I2S_LRC       26

Audio audio;

void setup() {
  pinMode(SD_CS, OUTPUT);
  digitalWrite(SD_CS, HIGH);
  SPI.begin(SPI_SCK, SPI_MISO, SPI_MOSI);
  Serial.begin(115200);
  SD.begin(SD_CS);
  audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
  audio.setVolume(10); // 0...21
  audio.connecttoFS(SD, "Ensoniq-ZR-76-01-Dope-77.wav");
}

void loop() {
  audio.loop();
}

// optional
void audio_info(const char *info) {
  Serial.print("info        "); Serial.println(info);
}
void audio_id3data(const char *info) { //id3 metadata
  Serial.print("id3data     "); Serial.println(info);
}
void audio_eof_mp3(const char *info) { //end of file
  Serial.print("eof_mp3     "); Serial.println(info);
}
void audio_showstation(const char *info) {
  Serial.print("station     "); Serial.println(info);
}
void audio_showstreaminfo(const char *info) {
  Serial.print("streaminfo  "); Serial.println(info);
}
void audio_showstreamtitle(const char *info) {
  Serial.print("streamtitle "); Serial.println(info);
}
void audio_bitrate(const char *info) {
  Serial.print("bitrate     "); Serial.println(info);
}
void audio_commercial(const char *info) { //duration in sec
  Serial.print("commercial  "); Serial.println(info);
}
void audio_icyurl(const char *info) { //homepage
  Serial.print("icyurl      "); Serial.println(info);
}
void audio_lasthost(const char *info) { //stream URL played
  Serial.print("lasthost    "); Serial.println(info);
}
void audio_eof_speech(const char *info) {
  Serial.print("eof_speech  "); Serial.println(info);
}

В первой части сценария Arduino для ESP32 мы включаем все библиотеки и определяем контакты, которые используются для подключения ESP32 NodeMCU к MAX98357A и модулю SD-карты.

После инициализации объекта Audio с именем «audio» вызывается функция настройки. В функции настройки определяются контакты и SPI-соединение для связи с SD-картой. Скорость передачи установлена 115200, и объект SD-карты также инициализируется.

Для объекта audio устанавливаем распиновку, и уменьшаем громкость звука до 10. Вы можете регулировать громкость звука от 0 до 21. Последняя часть функции настройки - это подключение входов и выходов в этом примере. Поэтому мы связываем аудиообъект с объектом SD-карты и определяем путь к WAVE-файлу. Если вы помещаете звуковой файл в папку, вам необходимо скопировать весь путь к звуковому файлу с косой чертой («/»).

В функции цикла нам нужно только перебрать предварительно сконфигурированный аудио объект для воспроизведения музыки.

Последняя часть интересна, если вы хотите распечатать некоторые детали звукового файла на последовательном мониторе. На следующем рисунке показан последовательный вывод в моем примере. В первом разделе содержится информация о загрузке ESP32, которая отображается на последовательном мониторе, если скорость передачи установлена 115200.

Воспроизвести звуковой файл с последовательного монитора SD-карты

В первой части этой статьи я вычислил частоту для тактовой частоты последовательного порта 44,1 кГц * 16 * 2 = 1,411 МГц. Теперь я хочу доказать, что частота I2S-соединения (последовательных часов SCK) между ESP32 и MAX98357A составляет 1,411 МГц. Поэтому я подключил линию CLK к своему USB-осциллографу и добавил измерение частоты.

На следующем рисунке видно, что мои расчеты верны и частота составляет 1,411 МГц.

Частота осциллографа I2S CLK

Комментарии пользователей