Всем доброго времени суток! В сегодняшней статье мы еще раз вернемся к теме 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) и видим (а точнее слышим), что наша программа работает правильно.
На сегодня на этом заканчиваем, до скорой встречи в новых статьях 🤝