49667

[i] Работа с аудиоформатом WAV. Вывод звукового файла посредством DAC на МК К1986ВК01GI.

Автор статьи: Лампадов Илья Александрович (Инженер)
Дата последнего изменения: 17.10.2022 15:30:10
Работа будет вестись в рамках отладочной платы производства компании Миландр для микроконтроллера К1986ВК01GI с использованием стандартного примера dac_music из состава установочного пака IDE Keil для МК 1986ВК01. Если у Вас используется старая версия установочного пака, где данный пример отсутствует, Вы можете либо обновить пак, либо загрузить пример в разделе "Файлы для скачивания" в конце статьи.

Основные положения формата WAV

Формат файла WAV - это формат для хранения данных оцифрованных аудиосигналов. Использует стандартную RIFF-структуру, которая группирует содержимое файла из отдельных секций (chunks). Преимуществом данного формата является то, что он может содержать сырые данные без компрессии, что повсеместно используется профессионалами, занимающимися музыкой, где важно передать исходное содержание аналоговых сигналов, если речь идёт о высококачественной обработке звука. Проще говоря, выборка секции данных формата WAV в сыром виде представляет из себя просто уровни напряжений, которые должен выставлять ЦАП используемого устройства - вкупе с данными из секции формата передачи данных (см. далее по статье), таких как частота дискретизации и т.д., на выходе и получается звук.

В рамках статьи не будет разбираться подробная структура формата WAV, будут рассмотрены только ключевые три секции: секция RIFF с заголовком, секция формата данных и самих данных - большинство WAV-файлов состоят исключительно из трёх этих секций, остальные секции используются мастерами, занимающимися звуком. Приступим:

1. Секция RIFF

Секция RIFF является обязательным заголовком WAV-файла. Занимает 12 байт, полезными данными из данной секции можно считать размер файла. Приведем структуру секции на таблице 1:

Таблица 1 - Структура секции RIFF
 Смещение  Размер  Название  Описание  Значение
 0  4  Chunk ID  ID секции  "RIFF" (0x52494646)
 4  4  Chunk Data Size  Размер данных секции  (размер файла) - 8
 8  4  WAVEID  Тип RIFF  "WAVE" (0x57415645)
 12      Секции WAV-файла  

На смещении "12" приведены секции WAV-файла, идущие следом. А следующей секцией идет секция формата данных "fmt". Рассмотрим её.

2. Секция формата данных "fmt"

Секция "fmt" содержит важную информацию о том, как должна обрабатываться секция данных, а именно: информация о количестве каналов, частота дискретизации, разрядность выборок и т.д. Имеет стандартный размер 24 байта, но секция также может занимать более 26 байт данных за счёт наличия поля "Размер дополнительных данных формата", которое позволяет заполнить больше данных о формате - в большинстве случаев секция занимает 24 байта (столько же она будет занимать и в рассматриваемом в статье примере).

Приведем подробную структуру секции в таблице 2:

Таблица 2 - Структура секции "fmt"
 Смещение   Размер  Название  Описание  Значение
 0  4  Chunk ID  ID секции     "fmt" (0x666D7420)
 4  4  Chunk Data Size     Размер данных секции     16 + размер дополнительных данных
 8  2  Compression Code     Код типа сжатия аудиоданных     1 - 65 535
 10  2  Number of channels     Количество каналов     1 - 65 535
 12  4  Sample rate     Частота дискретизации     1 - 0xFFFFFFFF
 16  4  Average bytes per second     Количество байт в секунду     1 - 0xFFFFFFFF
 20  2  Block align     Размер блока     1 - 65 535
 22  2  Significant bits per sample     Количество значащих бит на выборку     2 - 65 535
 24  2  Extra format bytes     Размер дополнительных данных формата      0 - 65 535
 26          Дополнительные данные формата  

По этой таблице внесем больше ясности:

- Количество каналов (Number of Channels) указывает на количество используемых каналов звука. Значение 1 означает, что сигнал является монофоническим, 2 означает стерео и т.д.

- Частота дискретизации (Sample Rate) указывает на число выборок аудиосигнала, приходящихся на секунду.

- Количество байт в секунду (Average Bytes Per Second) показывает, сколько байт данных должно быть передано за секунду во время воспроизведения.

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

- Размер блока (Block Align) указывает на количество байт в одной выборке. Вычисляется по формуле:

Размер блока = Количество значащих бит на выборку / 8 * Количество каналов

- Количество значащих бит на выборку (Significant Bits Per Sample) указывает на количество бит, формирующих каждую выборку сигнала. Обычно это величина 8, 16, 24 или 32.

Теория становится понятна, но чтобы закрепить результат, рассмотрим реальный пример WAV-файла из примера, приведенного в данной статье. Забегая вперед, это пение птиц. Рассмотрим рисунок 1.

Рисунок 1 - Разбор реального WAV-файла

Из описания следует, что за секунду должно быть передано 22050 байт на частоте 22050 Гц из расчета, что каждая выборка состоит из 8 бит, то есть одного байта. Отсюда следует, что данные сигнала в секции данных (секция данных разбирается далее по статье) расположены побайтно. Каждый байт, фактически, содержит просто сырой уровень напряжения на выходе ЦАП, поэтому для рассматриваемого в статье примера если будут переданы за секунду 22050 таких значений, то на выходе получится требуемая звуковая дорожка.

3. Секция данных "data" 

Содержит данные цифровых выборок аудиосигнала, которые можно декодировать, если знать параметры предыдущей секции формата данных "fmt". Фактически, сырые данные в файле WAV следуют сразу после двух секций, представленных выше, и идентификатора, указывающего на секцию "data", то есть на смещении 12 + 24 + 4 = 40 байт. Имеет структуру, как показано в таблице 3:

Таблица 3 - Структура секции "data"
 Смещение  Длина  Название  Описание  Значение
 0  4  Chunk ID  ID секции  "data" (0x64617461)
 4  4  Chunk Data Size  Размер данных секции  зависит от количества выборок и формата сжатия
 8      Данные выборок  

Пример того, как заполняются данные в секции "data", можно увидеть на рисунке 1 выше.

Особенности при реальной работе с другими файлами из общих положений WAV-файла:

- В примере рассматривается монофонический сигнал (один канал), где данные выборки следуют одна за другой, но если бы сигнал был стерео, то данные располагались бы в секции "data" по одному сначала для первого канала, потом для второго канала (аналогично и для большего числа каналов) - ЦАП каждого канала должны будут выдавать на выход свои данные.

- В примере выборки представлены 8 битами, по стандарту WAV они определяются как значения без знака (unsigned) - просто сырые данные. Все другие битовые размеры указываются как величины со знаком (signed). Например, выборка 16 бит может иметь значение в диапазоне от -32768 до +32768, где средняя точка (напряжения сигнала равно 0) соответствует значению 0. Соответственно, если используется WAV-файл, где данные хранятся по 16 бит, при этом ЦАП на микроконтроллере К1986ВК01GI способен выводить значения строго больше 0 В (чаще всего опорными напряжениями, задающими границы снизу и сверху, выступают напряжения земли и питания), то преобразовать такие данные достаточно легко:

DAC[x] = DAC[x] + 32767;

При этом здесь же сразу отметим, что ЦАП в микроконтроллере К1986ВК01GI имеет разрядность 12 бит, то есть он не сможет выдавать данные по 16-бит. В таком случае каждый полученный элемент массива на итерации выше можно просто разделить на 16, тогда и получатся значения в диапазоне от 0 до 4095. Обратите, пожалуйста, внимание, что в примере данные 8-битные, то есть ЦАП в составе микроконтроллера не используется на максимум.

Схемотехническое решение на отладочной плате К1986ВК01GI для вывода звука. Настройка микросхемы усилителя по I2C

На отладочной плате производства компании Миландр для микроконтроллера К1986ВК01GI устанавливается стереоусилитель TPA6130A2 (то есть два канала). На него заведены два ЦАП: DAC1 по выводу микроконтроллера PB26 и DAC2 по выводу PB23. При этом выход усилителя замкнут на разъем 3.5 мм jack для возможности подключения наушников. Внешний вид части платы, ответственной за выходы ЦАП, представлен на рисунке 2.

Рисунок 2 - Выходы ЦАП на плате К1986ВК01GI

Управление усилителем осуществляется по I2C-интерфейсу. Документация на стереоусилитель (доступна для загрузки после статьи в разделе "Файлы для скачивания") регламентирует адрес микросхемы на шине I2C, но так может быть в документации не на все микросхемы I2C, и для корректного взаимодействия сначала необходимо будет узнать адрес микросхемы на шине, например, просканировав все 127 адресов на ответ ACK при обращении со стороны мастера (соответственно, в идеале микросхема с неизвестным адресом должна быть на шине одна).

Адрес стереоусилителя TPA6130A2 на шине I2C - 0b1100000.

По умолчанию стереоусилитель не функционирует, поэтому по документации необходимо посмотреть, что для запуска будет достаточно настроить регистр с адресом 1 (включить левый и правый каналы усилителя) и регистр с адресом 2 (снять заглушки с левого и правого каналов, а также выставить громкость - под громкость отведено шесть бит). Наглядно описание регистров 1 и 2 стереоусилителя представлено на рисунке 3.

Рисунок 3 - Описание регистров стереоусилителя TPA6130A2

Запишем значение 0xC0 (включить левый и правый каналы) в первый регистр и 0x3F (снять заглушки с каналов и выставить максимальное усиление или громкость) во второй регистр.

Значения в рамках рассматриваемого примера dac_music заносятся в массивы (особенность работы функции по передаче данных по I2C из стандартной библиотеки, где реализована возможность передать пачку данных) в соответствии с фрагментом кода 1:

Фрагмент кода 1 - Данные для записи в регистры 1 и 2 стереоусилителя по I2C uint8_t EnChannels[1] = 0xC0; // Data for Enable Right and Left channels Amp
uint8_t Volume[1] =0x3F; // Data for settings volume
Запись же этих данных в стереоусилитель производится стандартной функцией библиотеки SPL: 
I2C_Mem_Write( uint8_t devAddress, uint16_t memAddress, uint16_t memAddSize, uint8_t *pData, uint16_t size )

Алгоритм работы с ЦАП К1986ВК01GI

Микроконтроллер К1986ВК01GI имеет в своём составе четыре ЦАП. Преимуществом каждого ЦАП является то, что помимо одиночного вывода значения напряжения в соответствии с цифровым кодом, он имеет режим FIFO, когда можно постоянно подгружать FIFO ЦАП пользовательскими данными, а он будет их выдавать в линию в соответствии с настроенным таймером прямо внутри ЦАП. Таким образом достигается нужная частота дискретизации. В отсутствие режима FIFO необходимо было бы подключать дополнительно, например, таймер, где в прерывании для достижения требуемой частоты дискретизации выводились бы одиночные значения на выход ЦАП. 

Данный режим использования в полной мере представлен и в рассматриваемом примере. Звуковой файл с пением птиц, который записан в виде массива в отдельном файле arraymusic.h (для справки отметим, что массив в формате языка Си был получен без использования дополнительных скриптов, WAV-файл был открыт через стандартный редактор HEX-файлов (Hex Editor Neo), секция данных была перенесена в новый документ в Notepad++, где пробелы были заменены на запятые, чтобы массив можно было удобно интегрировать в массив языка Си) имеет монофонический сигнал, то есть один канал, но возможность по выводу звука на плате с усилителем TPA6130A2 есть на два канала, поэтому чтобы слышать звук в левом и правом каналах, одна и та же дорожка (массив arraymusic[]) будет выводиться как по DAC1, так и по DAC2. В примере это делается при помощи следующей конструкции, расположенной в вечном цикле, в соответствии с фрагментом кода 2:

Фрагмент кода 2 - Алгоритм вывода данных через ЦАП К1986ВК01GI
while(1)
    {
    static uint32_t i = 0;
    static uint32_t j = 0;

    while( ! ( MDR_DAC1->STS & ( 1 << 3 ) ) )
    {
         MDR_DAC1->DATA = arraymusic[ i++ ]; // Play Left Channel

         if( i == sizearraymusic )
             i = 0;
    }

    while( ! ( MDR_DAC2->STS & ( 1 << 3 ) ) )
    {
        MDR_DAC2->DATA = arraymusic[ j++ ]; // Play Right Channel

        if( j == sizearraymusic )
            j = 0;
    }
    }

Строчки while( ! ( MDR_DACx->STS & ( 1 << 3 ) ) ) проверяют, можно ли положить в FIFO ЦАП новые данные. Параметр sizearraymusic рассчитывается через sizeof(arraymusic); в коде и показывает, какой объем данных музыкальной дорожки необходимо выдать на выход. 
Важно отметить, что файл с пением птиц arraymusic.h имеет достаточно большой вес, поэтому проект с этим файлом в исходном виде не может быть собран в бесплатной версии IDE Keil с ограничением написанного кода. В таком случае можно сократить размер массива arraymusic[] до оптимального количества элементов, когда проект будет собираться в бесплатной версии - соответственно, на выходе будет получена дорожка меньшей длины.

Сохранить статью в PDF

Файлы для скачивания

Документация

Теги

Была ли статья полезной?