Программирование ARM ESP32 Inter-IC Sound (I2S) Fri, October 11 2024  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.

ESP32 Inter-IC Sound (I2S) Печать
Добавил(а) microsin   

I2S (Inter-IC Sound) это синхронный протокол обмена, используемый для последовательной передачи аудиоданных между двумя цифровыми звуковыми устройствами.

У микроконтроллера ESP32 имеется 2 периферийных устройства I2S. Они могут быть сконфигурированы для ввода или вывода данных звуковых выборок ч помощью API-функций драйвера I2S.

Шина I2S, которая осуществляет обмен в стандартном режиме, или в режиме TDM, использует следующие сигналы:

MCLK: сигнал тактов главного устройства шины (master clock line). Это опциональный сигнал в зависимости от используемого подчиненного устройства, он используется главным образом как опорные такты для подчиненного устройства шины (I2S slave).

BCLK: сигнал тактов бит для линии данных.

WS: сигнал выборки слова/слота (Word/Slot). Обычно используется для идентификации тракта вокала, кроме режима PDM.

DIN/DOUT: линия последовательных данных (вход/выход). Данные входа и выхода будут зациклены внутри чипа ESP32, если DIN и DOUT установлены на одну и ту же ножку порта GPIO.

Шина I2S, которая осуществляет обмен в режиме PDM, использует следующие сигналы:

CLK: сигнал тактов PDM.

DIN/DOUT: линия последовательных данных (вход/выход).

У каждого контроллера I2S имеются следующие функции, которые можно конфигурировать драйвером I2S:

• Работать в режиме system master или slave.
• Возможность работать как передатчик или как приемник данных.
• Контроллер DMA, который позволяет выполнять выборку потока данных без необходимости копирования ядром CPU каждой выборки данных.

Каждый контроллер поддерживает один симплексный обмен RX или TX. Поскольку каналы RX и TX используют общие такты, они могут комбинироваться только на одной и той же конфигурации, чтобы установить полнодуплексный обмен.

I2S file structure fig01

Рис. 1. Файловая структура драйвера I2S.

Файлы заголовков, которые должны быть подключены к приложению I2S:

i2s.h: предоставляет устаревшее API (legacy I2S API) - для приложений, которые используют старый драйвер.
i2s_std.h: предоставляет API стандартного режима обмена - для приложений, которые используют новый драйвер в стандартном режиме.
i2s_pdm.h: предоставляет API-функции режима обмена PDM - для приложений, которые используют новый драйвер в режиме PDM.
i2s_tdm.h: предоставляет API-функции режима обмена TDM - для приложений, которые используют новый драйвер в режиме TDM.

Замечание: legacy-драйвер не может сосуществовать с новым. Подключайте i2s.h для устаревшего (legacy) драйвера, или подключайте другие три заголовка для использования нового драйвера. В будущих релизах ESP-IDF устаревший драйвер I2S может быть удален.

Общие файлы заголовков, подключаемые в описанных выше заголовочных файлах:

i2s_types_legacy.h: предоставляет устаревшие типы данных, которые использует только legacy-драйвер I2S.
i2s_types.h: предоставляет публичные типы.
i2s_common.h: предоставляет общий API для всех коммуникационных режимов.

[Такты I2S]

i2s_clock_src_t::I2S_CLK_SRC_DEFAULT: такты PLL по умолчанию.

i2s_clock_src_t::I2S_CLK_SRC_PLL_160M: такты PLL 160 МГц.

i2s_clock_src_t::I2S_CLK_SRC_APLL: такты Audio PLL, которые более точны, чем I2S_CLK_SRC_PLL_160M в приложениях с высокой частотой выборок (sample rate). Их частота конфигурируется в соответствии с частотой выборок. Однако если APLL заняты EMAC или другими каналами, то частоту APLL поменять нельзя, и драйвер будет пытаться работать с этой частотой APLL. Если эта частота не удовлетворяет требованиям I2S, то конфигурация тактов будет неудачной.

Терминология тактирования:

Sample rate: количество оцифрованных данных за одну секунду на слот.

SCLK: тактовая частота источника.

MCLK: тактовая частота устройства Master. BCLK генерируется из этих татов. Сигнал MCLK обычно предоставляет опорную частоту, и обычно нужна для синхронизации BCLK и WS между ролями I2S master и slave.

BCLK: тактовая частота бит. Каждый тик этой частоты обозначает один бит данных на соответствующем сигнале шины. Ширина бита слота конфигурируется в i2s_std_slot_config_t::slot_bit_width равной количеству тиков BCLK, это означает, что в одном слоте будет 8/16/24/32 тиков BCLK.

LRCK/WS: такты левого/правого канала, или такты выбора слова. Не для режима PDM, его частота равна частоте выборок (sample rate).

Замечание: обычно частота MCLK должна быть кратной sample rate и BCLK одновременно. Поле i2s_std_clk_config_t::mclk_multiple показывает кратность MCLK к sample rate. В большинстве случаев должно быть достаточно I2S_MCLK_MULTIPLE_256. Однако если slot_bit_width установлено на I2S_SLOT_BIT_WIDTH_24BIT, то для сохранения кратности MCLK к BCLK поле i2s_std_clk_config_t::mclk_multiple должно быть установлено в значение, нацело делящееся на 3, такое как I2S_MCLK_MULTIPLE_384. Иначе сигнал WS будет неточным.

[Коммуникационные режимы I2S]

Таблица 1. Обзор всех режимов.

Target Standard PDM TX PDR RX TDM ADC/DAC LCD/Camera
ESP32 I2S0, I2S1 I2S0 I2S0 нет I2S0 I2S0
ESP32-S2 I2S0 нет нет нет нет I2S0
ESP32-C3 I2S0 I2S0 нет I2S0 нет нет
ESP32-C6 I2S0 I2S0 нет I2S0 нет нет
ESP32-S3 I2S0, I2S1 I2S0 I2S0 I2S0, I2S1 нет нет
ESP32-H2 I2S0 I2S0 нет I2S0 нет нет

Standard Mode. В стандартном режиме всегда есть 2 канала звука, левый и правый, которые называют "слотами". Эти слоты поддерживают ширину данных выборок 8/16/24/32 бита. Формат обмена для слотов включает главным образом следующее:

Формат Philips: у сигнала данных одноразрядный сдвиг по сравнению с сигналом WS, и скважность сигнала WS равна 50%.

I2S Standard Philips timing diagram fig02

Рис. 2. Диаграмма сигналов стандартного формата Philips.

Формат MSB: в основном то же самое, что и формат Philips, но без сдвига данных.

I2S Standard MSB timing diagram fig03

Рис. 3. Диаграмма сигналов стандартного формата MSB.

Формат PCM Short: у данных однобитовый сдвиг, и между тем сигнал WS становится импульсом, продолжающимся в течение одного цикла BCLK.

I2S Standard PCM timing diagram fig04

Рис. 4. Диаграмма сигналов стандартного формата PCM.

PDM Mode (TX). Режим PDM (Pulse-Density Modulation) для канала TX может преобразовать данные PCM в формат PDM, у которого всегда есть слоты left и right. PDM TX поддерживается только на I2S0, и только для 16-разрядных данных выборки. Требуется как минимум вывод CLK для сигнала тактов, и вывод DOUT для сигнала данных (например сигнал WS и SD на следующем рисунке; сигнал BCK это внутренняя частота бит, которая не нуждается в передаче между устройствами PDM). Этот режим позволяет конфигурировать up-sampling параметры i2s_pdm_tx_clk_config_t::up_sample_fp и i2s_pdm_tx_clk_config_t::up_sample_fs. Значение up-sampling rate может быть вычислено по формуле up_sample_rate = i2s_pdm_tx_clk_config_t::up_sample_fp / i2s_pdm_tx_clk_config_t::up_sample_fs. В PDM TX существуют 2 режима up-sampling:

Fixed Clock Frequency: в этом режиме up-sampling rate меняется в соответствии с sample rate. Если установлено fp = 960, и fs = sample_rate / 100, то тактовая частота (Fpdm) на выводе CLK будет фиксирована на значении 128 * 48 кГц = 6.144 МГц. Обратите внимание, что эта частота не равна sample rate (Fpcm).

Fixed Up-sampling Rate: в этом режимеup-sampling rate фиксирована на значении 2. Если установлено fp = 960 и fs = 480, то частота тактов (Fpdm) на выводе CLK будет 128 * sample_rate.

I2S PCM timing diagram fig05

Рис. 5. Диаграмма сигналов PDM.

PDM Mode (RX). PDM для канала RX может принимать данные в формате PDM и преобразовывать их в формат PCM. PDM RX поддерживается только на I2S0, и только для 16-разрядных данных выборки. Требуется как минимум вывод CLK для сигнала тактов, и вывод DIN для сигнала данных. Этот режим позволяет конфигурировать down-sampling параметр i2s_pdm_rx_clk_config_t::dn_sample_mode. В PDM RX существует два режима down-sampling:

i2s_pdm_dsr_t::I2S_PDM_DSR_8S: в этом режиме частота тактов (Fpdm) на выводе WS равна sample_rate (Fpcm) * 64.

i2s_pdm_dsr_t::I2S_PDM_DSR_16S: в этом режиме частота тактов (Fpdm) на выводе WS равна sample_rate (Fpcm) * 128.

LCD/Camera Mode. Режим LCD/Camera поддерживается только на I2S0 через параллельную шину. Для режима LCD контроллер I2S0 должен работать в режиме master TX. Для режима камеры I2S0 должен работать в режиме slave RX. Эти 2 режима не реализованы драйвером I2S. Подробнее про реализацию LCD см. документацию [2]. Также см. раздел "Camera­LCD Controller" в техническом руководстве ESP32 [3].

ADC/DAC Mode. Режимы ADC и DAC существуют только на ESP32, и поддерживаются только контроллером I2S0. По сути это два субрежима LCD/Camera Mode. I2S0 может быть перенаправлен на внутренний аналого-цифровой преобразователь (ADC) и цифро-аналоговый преобразователь (DAC). Другими словами, периферийные устройства ADC и DAC могут непрерывно читать или записывать выборки через I2S0 DMA. Поскольку это не реальные режимы коммуникации, драйвер I2S их не реализует.

[Функциональный обзор драйвера I2S]

Драйвер I2S предоставляет следующие службы:

Resource Management. У драйвера I2S существует 3 вида ресурса:

Уровень платформы: ресурсы всех контроллеров I2S текущей цели (target).
Уровень контроллера: ресурсы одного контроллера I2S.
Уровень канала: ресурсы канала TX или RX в одном контроллере I2S.

Все API-функции уровня канала являются публичными. Дескриптор канала i2s_chan_handle_t может помочь управлять ресурсами определенного канала, не учитывая другие два уровня. Другие два уровня ресурсов являются приватными, и они управляются драйвером автоматически. Пользователи могут вызвать i2s_new_channel() для выделения дескриптора канала и вызвать i2s_del_channel() для его удаления.

Power Management. Когда разрешено управление питанием (например определена опция CONFIG_PM_ENABLE), система будет подстраивать или останавливать источник тактирования I2S перед входом в Light-sleep, что потенциально меняет сигналы I2S и приведет к передаче или приему неправильных данных.

Драйвер I2S может не давать системе изменять или останавливать источник тактов путем захвата блокировки управления питанием (power management lock). Когда источник тактов генерируется из APB, будет установлен тип блокировки esp_pm_lock_type_t::ESP_PM_APB_FREQ_MAX, и когда источником тактов будет APLL (если это поддерживается), тип блокировки будет esp_pm_lock_type_t::ESP_PM_NO_LIGHT_SLEEP. Всякий раз, когда пользователь читает или записывает через I2S (например вызывает i2s_channel_read() или i2s_channel_write()), драйвер будет гарантировать, что захвачена power management. Точно так же драйвер снимает блокировку после окончания чтения или записи.

Finite State Machine. Существуют 3 состояния для канала I2S: registered, ready и running. Их взаимосвязь показана на следующей диаграмме:

I2S Finite State Machine fig06

Рис. 6. I2S Finite State Machine (FSM).

На диаграмме < mode> может быть заменено на соответствующий коммуникационный режим I2S, т. е. std стандартный для режима двух слотов. Описание коммуникационных режимов см. выше в разделе "Коммуникационные режимы I2S".

Data Transport. Транспорт данных периферийного устройства I2S, включающий передачу и прием, реализован на DMA. Перед транспортировкой данных вызовите i2s_channel_enable() для разрешения определенного канала. Когда отправленные или принятые данные достигли размера буфера DMA, сработает прерывание I2S_OUT_EOF или I2S_IN_SUC_EOF. Обратите внимание, что размер буфера DMA не равен i2s_chan_config_t::dma_frame_num. Здесь один кадр (frame) относится ко всем выбранным данным в одном цикле WS. Таким образом, dma_buffer_size = dma_frame_num * slot_num * slot_bit_width / 8. Для передачи данных пользователи могут вводить данные вызовом i2s_channel_write(). Эта функция помогает помогает пользователям копировать данные из буфера источника в буфер DMA TX, и она ждет завершения передачи. Затем это будет повторена, пока отправленные байты не достигнут заданного размера. Для приема данных функция i2s_channel_read() ждет получения сообщения очереди, которая содержит адрес буфера DMA. Это помогает пользователям копировать данные из буфера DMA RX в буфер назначения.

Обе функции i2s_channel_write() и i2s_channel_read() блокирующие. Они продолжают ждать, пока не будет отправлен весь буфер, или пока не будет заполнен весь буфер назначения, за исключением случая, когда превышено максимальное время блокировки, когда будет возвращен код ошибки ESP_ERR_TIMEOUT. Для асинхронной отправки или приема данных можно зарегистрировать callback-и вызовом i2s_channel_register_event_callback(). Пользователи могут обращаться к буферу DMA напрямую из тела callback-функции вместо передачи или приема в двух блокирующих функциях. Однако имейте в виду, что это callback-функции, работающие в контексте прерывания, поэтому в них нельзя добавлять сложную логику, операции над числами с плавающей запятой, или делать вызовы не-реэнтрантных функций.

Конфигурация. Пользователи могут инициализировать канал вызовом соответствующих функций (например i2s_channel_init_std_mode(), i2s_channel_init_pdm_rx_mode(), i2s_channel_init_pdm_tx_mode() или i2s_channel_init_tdm_mode()) для определенного режима. Если после инициализации необходимо обновить конфигурацию, то надо сначала вызвать i2s_channel_disable() для гарантии, что канал остановлен, и затем вызвать соответствующие функции переконфигурирования наподобие i2s_channel_reconfig_std_slot(), i2s_channel_reconfig_std_clock() и i2s_channel_reconfig_std_gpio().

[IRAM Safe]

По умолчанию прерывание I2S будет отложено (deferred), когда кэш запрещен из-за причин наподобие запись/стирание flash. Поэтому прерывание не будет выполнено вовремя.

Чтобы избежать таких ситуаций в приложениях реального времени, вы можете разрешить Kconfig-опцию CONFIG_I2S_ISR_IRAM_SAFE, которая обеспечит:

1. Обработку прерываний даже когда когда кэш запрещен.
2. Поместит объект драйвера в DRAM (в случае, когда случайно линковка поместила его в PSRAM).

Это обеспечит работу прерывания, когда кэш запрещен, но ценой увеличения расхода IRAM.

Thread Safety. Для всех публичных API-функций I2S драйвером гарантируется безопасное использование для потоков (thread safety). Это значит, что пользователи могут вызывать их из разных задач RTOS без дополнительных защитных блокировок. Имейте в виду, что драйвер I2S использует mutex-блокировку для обеспечения thread safety, поэтому его API-функции нельзя использовать в ISR.

Опции Kconfig. Команда idf.py menuconfig позволяет настроить опции:

CONFIG_I2S_ISR_IRAM_SAFE управляет, может ли работать ISR по умолчанию, когда кэш запрещена (см. выше раздел "IRAM Safe").

CONFIG_I2S_SUPPRESS_DEPRECATE_WARN управляет подавлением предупреждений компиляции, когда используется устаревший (legacy) драйвер I2S.

CONFIG_I2S_ENABLE_DEBUG_LOG используется для разрешения отладочного лога. Разрешение этой опции увеличит размер бинарника для firmware.

[Пример приложения I2S]

Примеры приложений для драйвера I2S можно найти в директории peripherals/i2s. Это несколько простых примеров использования для каждого из режимов:

Standard TX/RX. Для стандартного режима могут генерироваться разные форматы слота с помощью вспомогательных макросов. Как было описано выше, это 3 формата для стандартного режима, которым соответствуют макросы:

I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG
I2S_STD_PCM_SLOT_DEFAULT_CONFIG
I2S_STD_MSB_SLOT_DEFAULT_CONFIG

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

I2S_STD_CLK_DEFAULT_CONFIG

Обзор API-функций стандартного режима см. далее секцию "Standard Mode" в разделе "API I2S", а также содержимое файла заголовка driver/i2s/include/driver/i2s_std.h.

STD TX Mode. Берутся 16-битные данные для каждой выборки. Запись данных uint16_t в буфер:

data 0 data 1 data 2 data 3 data 4 data 5 data 6 data 7 ...
0x0001 0x0002 0x0003 0x0004 0x0005 0x0006 0x0007 0x0008 ...

Таблица реальных данных на линии с разными i2s_std_slot_config_t::slot_mode и i2s_std_slot_config_t::slot_mask.

ширина данных режим слота маска слота WS
0 1 0 1 0 1 0 1
16 бит mono левый 0x0002 0x0000 0x0001 0x0000 0x0004 0x0000 0x0003 0x0000
правый 0x0000 0x0002 0x0000 0x0001 0x0000 0x0004 0x0000 0x0003
оба канала 0x0002 0x0002 0x0001 0x0001 0x0004 0x0004 0x0003 0x0003
stereo левый 0x0001 0x0001 0x0003 0x0003 0x0005 0x0005 0x0007 0x0007
правый 0x0002 0x0002 0x0004 0x0004 0x0006 0x0006 0x0008 0x0008
оба канала 0x0001 0x0002 0x0003 0x0004 0x0005 0x0006 0x0007 0x0008

Замечание: подобным образом все работает и с 32-разрядными данными, однако надо быть внимательным при использовании 8-битных и 24-битных данных. Для 8-битных данных записываемый буфер все еще должен быть типа uint16_t (т. е. его положение и размер должны быть выровнены на 2 байта), причем достоверные данные находятся только в старших 8 битах, а младшие 8 бит отбрасываются. Для 24-битных данных используется буфер типа uint32_t (т. е. выровненный на 4 байта), и используются только старшие 24 бита каждой 32-битной ячейки буфера, а младшие 8 бит отбрасываются.

Кроме того, для моно-режимов 8 бит и 16 бит реальные данные на линии переставляются. Чтобы получить корректную последовательность данных, записываемый буфер должен переставлять данные между каждыми 2 байтами.

#include "driver/i2s_std.h"
#include "driver/gpio.h"
 
i2s_chan_handle_t tx_handle;
 
/* Получение конфигурации канала по умолчанию вспомогательным макросом.
 * Этот макрос определен в i2s_common.h, и он совместно используется
 * всеми коммуникационными режимами I2S.
 * Это может помочь указать роль I2S и ID порта. */
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
 
/* Выделение нового канала TX, и получение для него дескриптора (handle) */
i2s_new_channel(&chan_cfg, &tx_handle, NULL);
 
/* Установка конфигураций слота и тактов, которые могут быть сгенерированы
 * макросами. Эти два вспомогательных макроса определены в i2s_std.h, который
 * можно использовать только в режиме STD. Они могут помочь с конфигурациями
 * слота и тактов для инициализации или обновления конфигурации */
i2s_std_config_t std_cfg = {
    .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(48000),
    .slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_STEREO),
    .gpio_cfg = {
        .mclk = I2S_GPIO_UNUSED,
        .bclk = GPIO_NUM_4,
        .ws = GPIO_NUM_5,
        .dout = GPIO_NUM_18,
        .din = I2S_GPIO_UNUSED,
        .invert_flags = {
            .mclk_inv = false,
            .bclk_inv = false,
            .ws_inv = false,
        },
    },
};
 
/* Инициализация канала */
i2s_channel_init_std_mode(tx_handle, &std_cfg);
 
/* Перед записью данных сначала запускается канал TX */
i2s_channel_enable(tx_handle);
i2s_channel_write(tx_handle, src_buf, bytes_to_write, bytes_written, ticks_to_wait);
 
/* Если нужно обновить конфигурацию слота или тактов, сначала надо
 * остановить канал, и потом обновлять его настройки */
// i2s_channel_disable(tx_handle);
// std_cfg.slot_cfg.slot_mode = I2S_SLOT_MODE_MONO; // По умолчанию stereo
// i2s_channel_reconfig_std_slot(tx_handle, &std_cfg.slot_cfg);
// std_cfg.clk_cfg.sample_rate_hz = 96000;
// i2s_channel_reconfig_std_clock(tx_handle, &std_cfg.clk_cfg);
 
/* Перед удалением канала надо его остановить */
i2s_channel_disable(tx_handle);
 
/* Если дескриптор больше не нужен, удалите его для освобождения ресурсов канала */
i2s_del_channel(tx_handle);

STD RX Mode. Для примера берутся 16-битные данные, когда на линии они представлены в виде:

WS
0 1 0 1 0 1 0 1 ...
0x0001 0x0002 0x0003 0x0004 0x0005 0x0006 0x0007 0x0008 ...

Таблица принятых данных в буфере с разными i2s_std_slot_config_t::slot_mode и i2s_std_slot_config_t::slot_mask.

ширина данных режим слота маска слота data 0 data 1 data 2 data 3 data 4 data 5 data 6 data 7
16 бит mono левый 0x0001 0x0000 0x0005 0x0003 0x0009 0x0007 0x000D 0x000B
правый 0x0002 0x0000 0x0006 0x0004 0x000A 0x0008 0x000E 0x000C
stereo любой канал 0x0001 0x0002 0x0003 0x0004 0x0005 0x0006 0x0007 0x0008

Замечание: на ESP32 случай приема несколько более сложный. Во-первых, когда ширина данных 8 бит или 24 бита, принятые данные все еще будут выровнены на 2 или 4 байта соответственно, т. е. достоверные данные будут находиться в старших 8 битах 2-байтного массива (когда ширина данных 8 бит) и старших 24 битах 4-байтной ячейки массива (когда ширина данных 24 бита). Например, в ячейке буфера будут данные 0x5A00, когда по линии поступят 8-битные данные 0x5A, и 0x00005A00, если на линии были данные 0x00005A. Во-вторых, для моно 8 бит или 16 бит, данные в ячейках буфера должны быть переставлены, что требует обратной ручной перестановки для достижения правильного порядка.

#include "driver/i2s_std.h"
#include "driver/gpio.h"
 
i2s_chan_handle_t rx_handle;
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
 
/* Выделение нового канала RX и получение для него дескриптора */
i2s_new_channel(&chan_cfg, NULL, &rx_handle);
 
/* Установка конфигураций с помощью макросов (см. i2s_std.h) */
i2s_std_config_t std_cfg = {
    .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(48000),
    .slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_STEREO),
    .gpio_cfg = {
        .mclk = I2S_GPIO_UNUSED,
        .bclk = GPIO_NUM_4,
        .ws = GPIO_NUM_5,
        .dout = I2S_GPIO_UNUSED,
        .din = GPIO_NUM_19,
        .invert_flags = {
            .mclk_inv = false,
            .bclk_inv = false,
            .ws_inv = false,
        },
    },
};
 
/* Инициализация канала */
i2s_channel_init_std_mode(rx_handle, &std_cfg);
 
/* Перед чтением данных надо сначала запустить канал RX */
i2s_channel_enable(rx_handle);
i2s_channel_read(rx_handle, desc_buf, bytes_to_read, bytes_read, ticks_to_wait);
 
/* Перед удалением канала надо его остановить */
i2s_channel_disable(rx_handle);
 
/* Если дескриптор больше не нужен, удалите его для освобождения ресурсов канала */
i2s_del_channel(rx_handle);

Использование PDM TX. Для режима PDM в канале TX макрос конфигурации слота:

I2S_PDM_TX_SLOT_DEFAULT_CONFIG

Макрос конфигурации тактов:

I2S_PDM_TX_CLK_DEFAULT_CONFIG

Описание PDM TX API см. далее в соответствующем разделе, также см. driver/i2s/include/driver/i2s_pdm.h.

Данные PDM фиксированы на 16 бит. Запись данных в буфере int16_t:

data 0 data 1 data 2 data 3 data 4 data 5 data 6 data 7 ...
0x0001 0x0002 0x0003 0x0004 0x0005 0x0006 0x0007 0x0008 ...

Таблица реальных данных на линии с разными i2s_pdm_tx_slot_config_t::slot_mode и i2s_pdm_tx_slot_config_t::slot_mask (формат PDM на линии преобразуется в формат PCM для лучшего понимания).

режим слота маска слота левый правый левый правый левый правый левый правый
mono левый 0x0001 0x0000 0x0002 0x0000 0x0003 0x0000 0x0004 0x0000
  правый 0x0000 0x0001 0x0000 0x0002 0x0000 0x0003 0x0000 0x0004
  оба канала 0x0001 0x0001 0x0002 0x0002 0x0003 0x0003 0x0004 0x0004
stereo левый 0x0001 0x0001 0x0003 0x0003 0x0005 0x0005 0x0007 0x0007
  правый 0x0002 0x0002 0x0004 0x0004 0x0006 0x0006 0x0008 0x0008
  оба канала 0x0001 0x0002 0x0003 0x0004 0x0005 0x0006 0x0007 0x0008

 

#include "driver/i2s_pdm.h"
#include "driver/gpio.h"
 
/* Выделение канала I2S TX */
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
 
i2s_new_channel(&chan_cfg, &tx_handle, NULL);
 
/* Инициализация канала в режиме PDM TX */
i2s_pdm_tx_config_t pdm_tx_cfg = {
    .clk_cfg = I2S_PDM_TX_CLK_DEFAULT_CONFIG(36000),
    .slot_cfg = I2S_PDM_TX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO),
    .gpio_cfg = {
        .clk = GPIO_NUM_5,
        .dout = GPIO_NUM_18,
        .invert_flags = {
            .clk_inv = false,
        },
    },
};
 
i2s_channel_init_pdm_tx_mode(tx_handle, &pdm_tx_cfg);
...

Использование PDM RX. Для режима PDM в канале RX используется макрос:

I2S_PDM_RX_SLOT_DEFAULT_CONFIG

Макрос конфигурации тактов:

I2S_PDM_RX_CLK_DEFAULT_CONFIG

См. PDM Mode для информации по API-функциям PDM RX, также см. driver/i2s/include/driver/i2s_pdm.h.

Данные PDM фиксированы на 16 бит. Когда данные на линии (формат PDM линии преобразован в формат PCM для лучшего понимания):

левый правый левый правый левый правый левый правый ...
0x0001 0x0002 0x0003 0x0004 0x0005 0x0006 0x0007 0x0008 ...

Таблица принятых данных в буфере int16_t с разными i2s_pdm_rx_slot_config_t::slot_mode и i2s_pdm_rx_slot_config_t::slot_mask.

режим слота маска слота data 0 data 1 data 2 data 3 data 4 data 5 data 6 data 7
mono левый 0x0001 0x0003 0x0005 0x0007 0x0009 0x000B 0x000D 0x000F
правый 0x0002 0x0004 0x0006 0x0008 0x000A 0x000C 0x000E 0x0010
stereo оба канала 0x0001 0x0002 0x0003 0x0004 0x0005 0x0006 0x0007 0x0008

 

#include "driver/i2s_pdm.h"
#include "driver/gpio.h"
 
i2s_chan_handle_t rx_handle;
 
/* Выделение канала I2S RX */
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
 
i2s_new_channel(&chan_cfg, NULL, &rx_handle);
 
/* Инициализация канала в режиме PDM RX */
i2s_pdm_rx_config_t pdm_rx_cfg = {
    .clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(36000),
    .slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO),
    .gpio_cfg = {
        .clk = GPIO_NUM_5,
        .din = GPIO_NUM_19,
        .invert_flags = {
            .clk_inv = false,
        },
    },
};
 
i2s_channel_init_pdm_rx_mode(rx_handle, &pdm_rx_cfg);
...

Full-duplex. Режим полного дуплекса одновременно регистрирует канал TX и RX в порту I2S, и эти каналы совместно используют сигналы BCLK и WS. В настоящий момент режимы коммуникации STD и TDM поддерживают полнодуплексный режим следующим способом, однако полный дуплекс PDM не поддерживается, потому что такты PDM TX и RX разные.

Обратите внимание, что один дескриптор может быть только для одного канала. Таким образом, все еще нужно конфигурировать слот и такты для обоих каналов TX и RX, поочередно.

Пример, как выделить пару каналов полного дуплекса:

#include "driver/i2s_std.h"
#include "driver/gpio.h"
 
i2s_chan_handle_t tx_handle;
i2s_chan_handle_t rx_handle;
 
/* Выделение пары каналов I2S */
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
 
/* Одновременное выделение канала для TX и RX, затем они будут работать в режиме full-duplex */
i2s_new_channel(&chan_cfg, &tx_handle, &rx_handle);
 
/* Установка конфигураций для ОБОИХ этих двух каналов, поскольку канал TX и RX должен быть
   одинаковым в режиме full-duplex */
i2s_std_config_t std_cfg = {
    .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(32000),
    .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO),
    .gpio_cfg = {
        .mclk = I2S_GPIO_UNUSED,
        .bclk = GPIO_NUM_4,
        .ws = GPIO_NUM_5,
        .dout = GPIO_NUM_18,
        .din = GPIO_NUM_19,
        .invert_flags = {
            .mclk_inv = false,
            .bclk_inv = false,
            .ws_inv = false,
        },
    },
};
 
i2s_channel_init_std_mode(tx_handle, &std_cfg);
i2s_channel_init_std_mode(rx_handle, &std_cfg);
i2s_channel_enable(tx_handle);
i2s_channel_enable(rx_handle);
...

Simplex Mode. Для выделения дескриптора канала в симплексном режиме, функция i2s_new_channel() должна быть вызвана для каждого канала. Такты и выводы GPIO канала TX/RX на ESP32 не являются независимыми, так что в симплексном режиме канал TX и RX не могут сосуществовать одновременно на одном и том же порту I2S.

#include "driver/i2s_std.h"
#include "driver/gpio.h"
 
i2s_chan_handle_t tx_handle;
i2s_chan_handle_t rx_handle;
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
 
i2s_new_channel(&chan_cfg, &tx_handle, NULL);
 
i2s_std_config_t std_tx_cfg = {
    .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(48000),
    .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO),
    .gpio_cfg = {
        .mclk = GPIO_NUM_0,
        .bclk = GPIO_NUM_4,
        .ws = GPIO_NUM_5,
        .dout = GPIO_NUM_18,
        .din = I2S_GPIO_UNUSED,
        .invert_flags = {
            .mclk_inv = false,
            .bclk_inv = false,
            .ws_inv = false,
        },
    },
};
 
/* Инициализация канала */
i2s_channel_init_std_mode(tx_handle, &std_tx_cfg);
i2s_channel_enable(tx_handle);
 
/* Канал RX будет зарегистрирован на другом I2S. Если нет других доступных юнитов I2S,
 * то будет возвращено ESP_ERR_NOT_FOUND */
i2s_new_channel(&chan_cfg, NULL, &rx_handle);
 
i2s_std_config_t std_rx_cfg = {
    .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(16000),
    .slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_STEREO),
    .gpio_cfg = {
        .mclk = I2S_GPIO_UNUSED,
        .bclk = GPIO_NUM_6,
        .ws = GPIO_NUM_7,
        .dout = I2S_GPIO_UNUSED,
        .din = GPIO_NUM_19,
        .invert_flags = {
            .mclk_inv = false,
            .bclk_inv = false,
            .ws_inv = false,
        },
    },
};
 
i2s_channel_init_std_mode(rx_handle, &std_rx_cfg);
i2s_channel_enable(rx_handle);

[Указания по применению драйвера I2S]

Как предотвратить потерю данных. Для приложений, которым требуется высокая частота выборок (sample rate), массивный поток данных может привести к потере данных. Пользователи могут получить событие потери данных (data lost event) путем регистрации callback-функции ISR, чтобы принять событие очереди:

static IRAM_ATTR bool i2s_rx_queue_overflow_callback(i2s_chan_handle_t handle,
                                                     i2s_event_data_t *event,
                                                     void *user_ctx)
{
    // Обработка события переполнения очереди RX ...
    return false;
}
 
i2s_event_callbacks_t cbs = {
    .on_recv = NULL,
    .on_recv_q_ovf = i2s_rx_queue_overflow_callback,
    .on_sent = NULL,
    .on_send_q_ovf = NULL,
};
TEST_ESP_OK(i2s_channel_register_event_callback(rx_handle, &cbs, NULL));

Чтобы предотвратить потерю данных, выполните следующие шаги:

1. Определите интервал прерывания. Обычно когда теряются данные, применение увеличенного прерывания будет предпочтительнее, что поможет уменьшить относительное время работы прерывания. Это значит, что dma_frame_num должно быть настолько большим, насколько это возможно, в то время как размер буфера DMA должен быть меньше максимального значения 4092. Взаимозависимости следующие:

interrupt_interval(unit: sec) = dma_frame_num / sample_rate
dma_buffer_size = dma_frame_num * slot_num * data_bit_width / 8 должно быть меньше или равно 4092

2. Определите dma_desc_num. Значение dma_desc_num выбирается по максимуму времени цикла опроса i2s_channel_read. Подразумевается, что все принятые данные сохраняются между двумя i2s_channel_read. Этот цикл может быть измерен таймером, или с помощью вывода сигнала на ножке GPIO. Взаимозависимость следующая:

dma_desc_num > polling_cycle / interrupt_interval

3. Определите размер буфера приема. Предоставленный буфер в i2s_channel_read должен быть в состоянии уместить все данные во всех буферах DMA. Это значит, что он должен быть больше чем общий размер всех буферов DMA:

recv_buffer_size > dma_desc_num * dma_buffer_size

Для примера, если это приложение I2S, и известны значения:

sample_rate = 144000 Гц
data_bit_width = 32 бита
slot_num = 2
polling_cycle = 10 мс

... то параметры dma_frame_num, dma_desc_num и recv_buf_size можно вычислить следующим образом:

dma_frame_num * slot_num * data_bit_width / 8 = dma_buffer_size < = 4092
dma_frame_num < = 511
interrupt_interval = dma_frame_num / sample_rate = 511 / 144000 = 0.003549 s = 3.549 мс
dma_desc_num > polling_cycle / interrupt_interval = cell(10 / 3.549) = cell(2.818) = 3
recv_buffer_size > dma_desc_num * dma_buffer_size = 3 * 4092 = 12276 байт

[API I2S]

Описание типов, структур, макросов см. в документации [1].

Standard Mode. Файл заголовка components/driver/i2s/include/driver/i2s_std.h.

Функция Описание
i2s_channel_init_std_mode Инициализирует канал I2S для стандартного режима(1).
i2s_channel_reconfig_std_clock Переконфигурирует такты I2S для стандартного режима(2, 3).
i2s_channel_reconfig_std_slot Переконфигурирует слот I2S для стандартного режима(2, 3).
i2s_channel_reconfig_std_gpio Переконфигурирует GPIO I2S для стандартного режима(2, 3).

PDM Mode. Файл заголовка components/driver/i2s/include/driver/i2s_pdm.h.

Функция Описание
i2s_channel_init_pdm_rx_mode Инициализирует канал I2S для режима PDM RX(1).
i2s_channel_reconfig_pdm_rx_clock Переконфигурирует такты I2S для для режима PDM RX(2, 4).
i2s_channel_reconfig_pdm_rx_slot Переконфигурирует слот I2S для режима PDM RX(2, 4).
i2s_channel_reconfig_pdm_rx_gpio Переконфигурирует GPIO I2S для режима PDM RX(2, 4).
i2s_channel_init_pdm_tx_mode Инициализирует канал I2S для режима PDM TX(1).
i2s_channel_reconfig_pdm_tx_clock Переконфигурирует такты I2S для режима PDM TX(2, 5).
i2s_channel_reconfig_pdm_tx_slot Переконфигурирует слот I2S для режима PDM TX(2, 5).
i2s_channel_reconfig_pdm_tx_gpio Переконфигурирует GPIO I2S для режима PDM TX(2, 5).

Примечания: 

(1) Разрешается вызывать только когда канал в состоянии REGISTERED (т. е. канал был выделен, но не инициализирован), и состояние будет обновлено на READY если инициализация прошла успешно, иначе состояние вернется в REGISTERED.
(2) Разрешается вызывать только когда канал в состоянии READY (т. е. канал был инициализирован, но не запущен), эта функция не поменяет состояние канала. Перед вызовом этой функции должна быть вызвана функция i2s_channel_disable, если I2S был запущен.
(3) Дескриптор канала ввода должен быть инициализирован в стандартном режиме, т. е. перед переконфигурированием должна быть вызвана i2s_channel_init_std_mode.
(4) Дескриптор канала ввода должен быть инициализирован в режиме PDM RX, т. е. перед переконфигурированием должна быть вызвана i2s_channel_init_pdm_rx_mode.
(5) Дескриптор канала ввода должен быть инициализирован в режиме PDM TX, т. е. перед переконфигурированием должна быть вызвана i2s_channel_init_pdm_tx_mode.

Драйвер I2S. Файл заголовка components/driver/i2s/include/driver/i2s_common.h.

Функция Описание
i2s_new_channel Выделит новый канал (каналы) I2S. После успешного выделения дескриптора канала его состояние поменяется на REGISTERED. Когда ID порта в конфигурации канала задан I2S_NUM_AUTO, драйвер выделит порт I2S автоматически на одном из контроллеров I2S, иначе драйвер попытается выделить новый канал на выбранном порту. Если оба значения tx_handle и rx_handle не NULL, то это значит, что этот контроллер I2S будет работать в режиме полного дуплекса (full-duplex), и в этом случае каналы RX и TX будут выделены на одном и том же порту I2S. Обратите внимание, что некоторые конфигурации канала TX/RX общие на ESP32 и ESP32S2, так что убедитесь, что они работают в одних и тех же условиях с одинаковым статусом (start/stop). В настоящее время режим full-duplex не может гарантировать синхронные запись/чтение каналов TX/RX, сейчас они могут только совместно использовать сигналы тактов. Если tx_handle ИЛИ rx_handle NULL, то это означает, что контроллер I2S будет работать в симплексном режиме. Для ESP32 и ESP32S2 будет занят контроллер I2S целиком (т. е. оба канала RX и TX), даже если зарегистрирован только один из каналов RX или TX. Для других процессоров другой канал на этом контроллере все еще будет доступен.
i2s_del_channel Удалит канал I2S. Разрешается вызывать только когда канал I2S в состоянии REGISTERED или READY (т. е. он должен быть остановлен перед удалением). Ресурсы будут освобождены автоматически, если удалены все каналы на одном порту.
i2s_channel_get_info Извлечет информацию канала I2S.
i2s_channel_enable Разрешит канал I2S(6). Если канал был успешно разрешен, то он сразу войдет в состояние RUNNING. Разрешение канала может запустить коммуникацию I2S на аппаратуре. Начнут выводиться сигналы BCLK и WS. Вывод сигнала MCLK начнется когда его инициализация завершится.
i2s_channel_disable Запретит канал I2S. Разрешается вызвать только когда состояние канала RUNNING (т. е. канал был запущен). Как только канал был успешно запрещен, он войдет в состояние READY. Запрет канала может остановить коммуникацию I2S на аппаратуре. Выдача сигналов BCLK и WS прекратится, но сигнал MCLK будет продолжать генерироваться.
i2s_channel_preload_data Выполняет предварительную загрузку данных в буфер TX DMA(6). Поскольку в изначальном буфере DMA нет данных, I2S будет передавать пустой буфер после разрешения канала. Эта функция используется для предварительной загрузки данных в буфер DMA, чтобы после разрешения канала сразу начали передаваться достоверные данные. Эта функция может быть вызвана несколько раз перед разрешением канала, добавленный позже буфер будет приклеиваться к ранее загруженному буферу. Однако когда были загружены все буферы DMA, больше нельзя предварительно загрузить никакие данные, поэтому проверьте параметр bytes_loaded чтобы определить, сколько байт было успешно загружено. Когда оказалось, что bytes_loaded меньше, чем указанный размер size, это означает, что буферы DMA заполнены.
i2s_channel_write Записывает данные I2S. Разрешается вызвать только когда состояние канала RUNNING (т. е. канал TX был запущен, и в него сейчас нет записи), однако RUNNING означает только программное состояние, это не означает, что нет сигнала, передаваемого по линии.
i2s_channel_read Читает данные I2S. Разрешается вызвать только когда состояние канала RUNNING, однако RUNNING означает только программное состояние, это не означает, что нет сигнала, передаваемого по линии.
i2s_channel_register_event_callback Установит callback-и события для канала I2S. Разрешается вызвать только когда состояние канала REGISTERED / READY (т. е. перед тем, как канал запущен). Пользователь может отменить регистрацию ранее зарегистрированного callback путем вызова этой функции и установкой в NULL поля callback в структуре callback-функций. Когда разрешена опция CONFIG_I2S_ISR_IRAM_SAFE, сама callback-функция и вызываемые из неё функции должны быть размещены в IRAM. Переменные, используемые в этих функциях, также должны быть размещены в SRAM. Также user_data должны находиться в SRAM или внутреннем RAM.

Примечание:

(6) Разрешается вызвать только когда канал в состоянии READY (т. е. канал был инициализирован, но не запущен).

[Ссылки]

1. Inter-IC Sound (I2S) site:espressif.com.
2. ESP32 LCD site:espressif.com.
3. ESP32 Technical Reference Manual > I2S Controller (I2S) > LCD Mode [PDF] site:espressif.com.

 

Добавить комментарий


Защитный код
Обновить

Top of Page