Всем доброго времени суток! В сегодняшней статье мы еще раз вернемся к теме USB в микроконтроллерах STM32 (статьи, посвященные этому вопросу) и реализуем поддержку USB Audio Device Class. То есть по сути мы должны в результате нашей работы получить внешнюю звуковую карту.
Для работы со звуком будем использовать отладочную плату STM32F4Discovery, поскольку на ней уже установлено все необходимое. Мы попробуем запустить плату на воспроизведение звука, поэтому нам пригодится разъем 3.5 мм для наушников, смонтированный на Discovery. Ну и, конечно, же нельзя обойти вниманием цифровую часть работы с аудио, а именно микросхему CS43L22. Ее мы уже обсуждали на нашем сайте в одной из предыдущих статей, даже писали небольшой пример для воспроизведения звука - ссылка. Таким образом, с электронной частью данной задачи все понятно, давайте переходить к программным аспектам.
Время традиционной вставки: поскольку компания STMicroelectronics прекратила поддержку библиотеки SPL, которая использовалась в этом курсе, я создал новый, посвященный работе уже с новыми инструментами, так что буду рад видеть вас там - STM32CubeMx. Кроме того, вот глобальная рубрика по STM32.
Эта статья будет построена не совсем традиционным образом для нашего сайта, по сути я просто выложу готовый и проверенный проект (проект для Keil, у меня лично Keil v4.70), и мы обсудим некоторые моменты, поясняющие работу этого проекта. Ну и, конечно же, в конце статьи попробуем подключить плату к ПК и посмотрим, что из этого получится.
Итак, вот упомянутый проект - USB_AudioClassProject. Давайте откроем проект и посмотрим, что там есть.
Во-первых, это, конечно, библиотеки для работы с USB от ST. Я поудалял оттуда лишнее, поэтому они не совсем в первозданном виде )
Второй момент, на котором обязательно надо остановиться - это файлы stm32f4_discovery.c и stm32f4_discovery_audio_codec.c, взятые на просторах интернета (эти библиотеки также создали ST, так что, в принципе, их можно считать официальными). В этих файлах находится инициализация всех элементов платы STM32F4Discovery, и, кроме того, приведены функции для работы с ними. Возьмем, например, светодиоды. Для них есть следующие функции:
void STM_EVAL_LEDInit(Led_TypeDef Led); void STM_EVAL_LEDOn(Led_TypeDef Led); void STM_EVAL_LEDOff(Led_TypeDef Led); void STM_EVAL_LEDToggle(Led_TypeDef Led);
Получается, что весь спектр операций, которые поддерживают светодиоды, мы можем осуществить при помощи этих инструментов. На самом деле в файле stm32f4_discovery.c разобрана работа только с "базовыми" элементами платы - светодиодами и кнопками. Если мы хотим подключить к примеру акселерометр, то нужные файлы можно найти на сайте ST. В данной статье нас интересует работа со "звуковым" чипом CS43L22, поэтому мы включаем в наш проект файл stm32f4_discovery_audio_codec.c. И в этом файле есть все, что касается работы с этой микросхемой - от инициализации необходимых портов ввода-вывода и используемой периферии микроконтроллера до функций, непосредственно работающих с CS43L22 и соответствующих обработчиков прерываний:
/* High Layer codec functions */ static uint32_t Codec_Init(uint16_t OutputDevice, uint8_t Volume, uint32_t AudioFreq); static uint32_t Codec_DeInit(void); static uint32_t Codec_Play(void); static uint32_t Codec_PauseResume(uint32_t Cmd); static uint32_t Codec_Stop(uint32_t Cmd); static uint32_t Codec_VolumeCtrl(uint8_t Volume); static uint32_t Codec_Mute(uint32_t Cmd); /* Low layer codec functions */ static void Codec_CtrlInterface_Init(void); static void Codec_CtrlInterface_DeInit(void); static void Codec_AudioInterface_Init(uint32_t AudioFreq); static void Codec_AudioInterface_DeInit(void); static void Codec_Reset(void); static uint32_t Codec_WriteRegister(uint8_t RegisterAddr, uint8_t RegisterValue); static uint32_t Codec_ReadRegister(uint8_t RegisterAddr); static void Codec_GPIO_Init(void); static void Codec_GPIO_DeInit(void); static void Delay(__IO uint32_t nCount);
С этим вопросом разобрались, давайте посмотрим как реализована поддержка USB Audio Device Class, и для этого откроем файл usbd_audio_out_if.c. Суть тут абсолютно такая же как и при работе с другими режимами USB в STM32 - есть ряд функций, которые нужно реализовать в соответствии с тем оборудованием, которое будет использоваться.
В нашем случае это отладочная плата STM32F4Discovery и CS43L22. Для этой связки, как мы уже обсудили, у нас есть готовый набор необходимых функций и по сути нам остается только правильным образом использовать их в данном файле. Рассмотрим, к примеру, функцию инициализации:
/** * @brief Init * Initialize and configures all required resources for audio play function. * @param AudioFreq: Statrtup audio frequency. * @param Volume: Startup volume to be set. * @param options: specific options passed to low layer function. * @retval AUDIO_OK if all operations succeed, AUDIO_FAIL else. */ static uint8_t Init (uint32_t AudioFreq, uint32_t Volume, uint32_t options) { static uint32_t Initialized = 0; /* Check if the low layer has already been initialized */ if (Initialized == 0) { /* Call low layer function */ if (EVAL_AUDIO_Init(OUTPUT_DEVICE_HEADPHONE, Volume, AudioFreq) != 0) { AudioState = AUDIO_STATE_ERROR; return AUDIO_FAIL; } /* Set the Initialization flag to prevent reinitializing the interface again */ Initialized = 1; } /* Update the Audio state machine */ AudioState = AUDIO_STATE_ACTIVE; return AUDIO_OK; }
Как видите, в этой функции мы вызвали нужную нам функцию инициализации EVAL_AUDIO_Init() из файла stm32f4_discovery_audio_codec.c. Аналогичным образом все сделано и для остальных функций. Для примера посмотрим, как реализована обработка команды остановки воспроизведения в AudioCmd(), вот часть этой функции:
/* Process the STOP command */ case AUDIO_CMD_STOP: if (AudioState != AUDIO_STATE_PLAYING) { /* Unsupported command */ return AUDIO_FAIL; } else if (EVAL_AUDIO_Stop(CODEC_PDWN_SW) != 0) { AudioState = AUDIO_STATE_ERROR; return AUDIO_FAIL; } else { AudioState = AUDIO_STATE_STOPPED; return AUDIO_OK; }
Итак, мы получили команду остановки - сразу же проверяем, активно ли воспроизведение (ведь чтобы его остановить оно должно по крайней мере быть запущено):
if (AudioState != AUDIO_STATE_PLAYING)
В случае, если все в порядке, и воспроизведение активно - вызываем функцию остановки:
EVAL_AUDIO_Stop(CODEC_PDWN_SW)
В случае успеха операции меняем текущее состояние на AUDIO_STATE_STOPPED, если на каком-то из этих этапов возникла ошибка, то сигнализируем об этом:
AudioState = AUDIO_STATE_ERROR; return AUDIO_FAIL;
Все организовано крайне логично. Давайте соберем проект и проверим его на практике, прошив контроллер. В диспетчере устройств у нас появляется новое звуковое устройство с поддержкой USB Audio Device Class:
Осталось совсем немного - идем в панель управления и в разделе "Звук" активируем появившееся устройство. После этого открываем какой-нибудь аудио-файл (не забыв подключить наушники к Discovery) и видим (а точнее слышим), что наша программа работает правильно.
На сегодня на этом заканчиваем, до скорой встречи в новых статьях 🤝
Спасибо. А когда появится урок по реализации usb audio через CubeMx? Очень надо именно этот класс реализовать, не получается, а примеров нигде нет.
Я на самом деле через Cube сначала и хотел, но это довольно утомительно оказалось...) В плане USB там такая же суть как и в этом проекте, просто надо отдельно через Cube реализовывать подключение CS43L22 - все интерфейсы, прерывания итд.
Пере-компилировал, заработало на Stm32f401c-disco,
За статью спасибо, чуть по-раньше бы её, а то уже на готовой "Audio device class" микросхеме PCM2705 собрал.
Здравствуйте, решил проверить на своей плате с чипом stm32f407, проект собрался без ошибок, залился, но как новое устройство не отображается. В чем может быть причина?
Скорее всего в схемотехнике. Можно сверить схему платы со схемой Discovery.
А какая функция передаёт полученные аудио данные в i2s? Никак не могу понять где можно посмотреть полученные по аудио биты.
Там через ДМА отправляется, соответственно, данные можно посмотреть по адресам, которые в ДМА указываются.
Hi
many many thanks for this great project and good explanation.
i have a question why when i compile the project with the Keil V5,16a it dose't compile and gives me many errors but compiles well in the V4 .
please do you know what is the problem.
thank you in advance
Hi!
I don't use Keil v5, but as far as I know, Keil4 and Keil5 are very different (files, libraries etc). So I think this is the reason.
Hi
Thank you for your fast reply
Ok i understand that there is a big difference between both versions
but in your opinion is it possible to migrate this project to V5 or i must make a big change in the files to make it work.
What kind of errors do you have in v5?
У меня V5, и там ошибки множественного определения , вроде этой: .\USB_AudioClassProject.axf: Error: L6200E: Symbol SystemCoreClock multiply defined (by system_stm32f4xx_1.o and system_stm32f4xx.o).
Во всех случаях конфликтуют system_stm32f4xx_1.o и system_stm32f4xx.o. system_stm32f4xx.c я нашел, а вот где system_stm32f4xx_1.с непонятно. Если удалить system_stm32f4xx.c, то проект компилится, но звуков плата не издает, хотя выключает их на ПК
5-ый Кейл насколько я помню сам автоматически включает библиотеки в проект, из-за этого и проблемы видимо.
Спасибо за статью. Скомпилировал, собрал под STM32f4Discovery. Звук есть. Всё работает, но есть лишний шум в виде "тиканья". Не получается от него избавится. Вчём может быть проблема? И ещё вопрос, как можно увеличить разрядность звука?
Аналогичная проблема! Буду благодарен за помощь в решении
Добрый день
пытаюсь поставить УСБ аудио-класс на stm32 с помощью cubeMX.
У меня Nucleo-F446RE, добавил к ней USB +/- и gnd. VBUS -sensing в настройках отключил, питание идет через ST-LINK. Клок - добавил внешний 12MHz
Так вот - HID, virtual COM, DFU, - всё хорошо, работает, как надо.
Аудио класс - находит Composite device и все!
Посмотрел дескрипторы, вроде все нормально, должен быть UAC, а вот нет! Стирал драйвер в винде - все рабно каждый раз находит composite device.
Не подскажите, в чем может быть дело, куда копать?
Добрый день!
Не могу ничего посоветовать, через Cube не пробовал этот класс добавлять...
Спасибо все равно.
Я долго искал, в конце концов увидел в дебагере, что malloc не может зарезервировать память.
Куб ограничивает Heap size до 0х200 😉
Поставил побольше, и все запустилось как надо
Отлично! )