Во второй части публикации речь пойдёт о реализации линейного входа описанной ранее звуковой карты USB на встроенном в MCU STM32F411CEU6 АЦП.
В статье будут разобраны несколько неочевидных нюансов подобной реализации, а в финале мы сравним характеристики линейного входа на встроенном АЦП с характеристиками линейного входа на кодеке TLV320AIC3104IRHB.
На КДПВ показана аппаратная часть описываемого в публикации решения. В правом нижнем углу находится двухканальный нормирующий усилитель для входов встроенного АЦП, собранный на операционном усилителе (ОУ) MCP6002.
В предыдущей части публикации особое внимание уделялось таким характеристикам АЦП звуковой карты, как соотношение сигнал-шум и динамический диапазон. Далее мы попытаемся определить их для встроенного в MCU STM32F411CEU6 АЦП, а затем попытаемся их как-нибудь улучшить.
Ссылка на предыдущую часть публикации:
Звуковая карта USB на STM32. Часть 1: Используем I2S-кодек.
Программное обеспечение описываемого решения размещено в репозитории.
Подготовка встроенного АЦП к работе
Производим настройку АЦП из проекта STM32CubeMX. Включаем входы IN0 и IN1 встроенного АЦП. Разрядность выбираем максимальную – 12 бит. Остальные настройки встроенного АЦП приведены на экране ниже:
Необходимо обратить внимание на то, что в настройках АЦП необходимо разрешить для DMA режим Continuous Request, а также на то, что запуск АЦП осуществляется по обоим фронтам сигнала с таймера TIM2.
Для преобразования используются два канала: первым опрашивается ADC_IN0, вторым – ADC_IN1. Период выборки сигнала 15 циклов подобран опытным путём: при уменьшении параметра падает точность, при увеличении – АЦП может просто не успеть обработать сигналы за отведённое для этого время.
Настройки канала DMA приведены на экране ниже:
В качестве источника тактовой частоты для АЦП применён таймер TIM2. В качестве «внешнего триггера» используется событие переполнения счётчика. Настройки таймера приведены на экране ниже:
Запуск АЦП будет производиться по событию переполнения таймера, прерывание включать не требуется. Запуск и установка периода таймера (ARR) будут производиться функцией инициализации и запуска АЦП:
void DSP_Init (void)
{
#if (CODEC)
Codec_Init ();
#endif /* CODEC */
#if (EMBED_ADC)
HAL_TIM_Base_Start (&htim2);
TIM2->ARR = (96000000U / USBD_AUDIO_FREQ / 8U) - 1;
HAL_ADC_Start_DMA (&hadc1, (uint32_t*) adc_buff.buff, ADC_BUFF_SIZE);
#endif /* ADC */
}
#else /*DSP */
void DSP_Init (void)
{
}
#endif /* DSP */
Нормирующий усилитель
Важным условием правильной работы АЦП является наличие на входе ФНЧ с частотой среза не более, чем половина частоты дискретизации, т.е. антиалиасного фильтра. Кроме того, на линейный вход бытовой аппаратуры подаётся сигнал напряжением не более 500 mVRMS, что приблизительно равно 1,41 VP-P. Диапазон входных напряжений встроенного в STM32F411CEU6 АЦП равен по модулю напряжению питания и составляет 3,3 VP-P. Это приводит к необходимости наличия на каждом входе встроенного АЦП нормирующего усилителя, например, как на схеме ниже:
В схеме применён недорогой rail-to-rail ОУ с частотой единичного усиления 1 МГц и встроенными цепями частотной коррекции. Коэффициент передачи усилителя по переменному напряжению в полосе пропускания k = 2. В состоянии покоя напряжение на выходе ОУ равно половине питания. Частота среза ФНЧ определяется номиналами R2C3. Для частоты среза около 184 кГц номинал C3 должен быть порядка 180 пФ.
Подготовка встроенного АЦП 12-бит к запуску на частоте 48 кГц
Тестирование встроенного АЦП с разрядностью 12-бит и частотой дискретизации 48 кГц проводится с целью получения отправной точки для экспериментов по улучшению его характеристик.
Для решения, опубликованного в репозитории, генерация кода из проекта STM32CubeMX не требуется. Если же по какой-либо причине генерация кода потребовалась, нужно помнить, что после этого необходимо изменить три строчки дескриптора, как указано в разделе «Неочевидный нюанс 2» публикации Составное устройство USB на STM32. Часть 4: Два-в-одном, чтобы решение продолжало работать как составное устройство USB.
При тестировании сигнал на вход нормирующего усилителя подаётся с линейного выхода кодека. Проект изначально настроен на частоту дискретизации 384 кГц. Для перенастройки встроенного АЦП на частоту дискретизации 48 кГц необходимо изменить несколько строчек кода.
Перед внесением изменений в код проекта делаем его резервную копию.
Встроенный АЦП включается в проект установкой в единицу ключа EMBED_ADC в файле main.h:
/* USER CODE BEGIN Private defines */
/* SI5351 = 1 if si5351 is used */
#define SI5351 1
/* DSP = 0 if DSP buff is bypassed */
#define DSP 1
/* We may set CODEC and EMBED_ADC if DSP = 1 only */
#if (DSP)
/* ADC = 1 if used Built-In ADC */
#define EMBED_ADC 1
/* CODEC = 0 if codec is dumb */
#define CODEC 1
#endif /* DSP */
/* USER CODE END Private defines */
Затем переходим в файл dsp_if.c и изменяем размер буфера АЦП:
/* Private typedef -----------------------------------------------------------*/
#if (EMBED_ADC)
typedef struct
{
// uint16_t buff [ADC_BUFF_SIZE];
uint16_t buff [ADC_BUFF_SIZE / 8U];
uint32_t wr_ptr;
} ADC_Buff_TypeDef;
#endif /* ADC */
Далее изменяем настройки запуска DMA с учётом нового размера буфера, а также настройки таймера TIM2 для получения частоты дискретизации 48 кГц:
void DSP_Init (void)
{
#if (CODEC)
Codec_Init ();
#endif /* CODEC */
#if (EMBED_ADC)
HAL_TIM_Base_Start (&htim2);
// TIM2->ARR = (96000000U / USBD_AUDIO_FREQ / 8U) - 1;
TIM2->ARR = (96000000U / USBD_AUDIO_FREQ) - 1;
// HAL_ADC_Start_DMA (&hadc1, (uint32_t*) adc_buff.buff, ADC_BUFF_SIZE);
HAL_ADC_Start_DMA (&hadc1, (uint32_t*) adc_buff.buff, ADC_BUFF_SIZE / 8U);
#endif /* ADC */
}
#else /*DSP */
void DSP_Init (void)
{
}
#endif /* DSP */
Изменяем выравнивание данных АЦП в функции инициализации MX_ADC1_Init () в файле main.c заменой ADC_DATAALIGN_RIGHT на ADC_DATAALIGN_LEFT.
Для передачи данных из буфера АЦП в буфер DSP их необходимо преобразовать из беззнакового типа в знаковый. Для этого изменяем код в теле функции DSP_In_Buff_Write ():
#if (EMBED_ADC)
// dsp_in_buff_write_adc (pbuf, size);
/* near for 12-bit 48 kHz embedded ADC testing only */
uint16_t *buff = (uint16_t*) pbuf;
int16_t sample;
for (uint32_t i = 0; i < size; i++)
{
if (buff [i] > 32768)
{
sample = (int16_t)(buff [i] - 32768);
}
else
{
sample = (int16_t)buff [i] - 32768;
}
dsp_in_buff_write (sample);
}
/* above for 12-bit 48 kHz embedded ADC testing only */
#else /* ADC */
uint16_t *buff = (uint16_t*) pbuf;
for (uint32_t i = 0; i < size; i++)
{
dsp_in_buff_write (buff [i]);
}
#endif /* ADC */
Прошиваем микроконтроллер. Соединяем шнуром вход нормирующего усилителя и линейный выход кодека. Проводим тестирование, как указано в руководстве пользователя программы RMAA. Результат сохраняем в файл. Восстанавливаем проект из резервной копии.
Повышаем разрядность встроенного АЦП
Как уже упоминалось выше, важными характеристиками звуковой карты для использования её в составе радиостанции являются соотношение сигнал-шум и динамический диапазон. На обе эти характеристики оказывает существенное влияние такая из характеристик АЦП как разрядность. Чем выше разрядность, тем легче для этих характеристик добиться улучшения.
Наиболее просто повысить разрядность АЦП увеличением частоты дискретизации или «оверсэмплингом». Хорошо и понятно эта процедура описана в документе «AVR121: Enhancing ADC resolution by oversampling».
Если не вдаваться в подробности, разрядность на 1 бит можно увеличить, подняв частоту дискретизации в 4 раза, сложив 4 соседних отсчёта, и поделив результат на 2. А чтобы увеличить разрядность на 2 бита, необходимо поднять частоту в 16 раз, сложить 16 соседних отсчётов и поделить результат на 4.
На частоте дискретизации 768 кГц описываемое решение не запустилось. В результате многочисленных экспериментов удалось увеличить разрядность встроенного АЦП на величину порядка 1.5-бит, запустив его с частотой дискретизации 384 кГц.
Сравнительный анализ характеристик
Тестируем решение на встроенном АЦП с частотой дискретизации 384 кГц программой RMAA. Результат сохраняем в файл.
Открываем ранее сохранённые результаты. Проводим сравнительный анализ характеристик линейного входа на встроенном АЦП с частотой дискретизации fs = 48 кГц, с «оверсемплингом» (fs = 48 * 8 = 384 кГц), а также линейного входа, реализованного на аппаратном кодеке:
Разница характеристик линейного входа на встроенном АЦП с «оверсемплингом» и без него в 9 дБ (три раза по уровню) наглядно показывает эффективность методики.
На графике сравнительного анализа АЧХ по характеристике встроенного АЦП с fs = 48 кГц (белый цвет) очень хорошо видно, зачем на входе АЦП необходим антиалиасный фильтр. При C3 = 180 пФ частота среза ФНЧ нормирующего усилителя около 184 кГц, что намного больше половины частоты дискретизации 48 кГц, поэтому побочные продукты преобразования им не отфильтрованы, и график АЧХ от этого получился «рваным»:
Сравнение характеристик показывает, что даже заявленного для трансивера начального уровня «Радио-76» динамического диапазона 80 дБ, на встроенном АЦП достичь невозможно, а вот характеристик кодека для применения в составе радиостанции вполне достаточно.
Тем не менее, при подаче на вход нормирующего усилителя сигнала с выхода контрольного приёмника Softrock RX Ensemble II сигналы любительских радиостанций на диапазоне 20 м принимаются:
От добра добра не ищут
Как уже понятно из вышесказанного, решение на встроенном АЦП работает. Но неэффективно.
Издержки на нормирующий усилитель в расчёт можно не принимать: в том или ином виде он будет организован на ОУ квадратурного детектора. Гораздо важней тот нюанс, что при повышении частоты дискретизации кратно возрастает объём буфера DMA. И если его размер для кодека равен всего 384 байтам, то для встроенного АЦП с частотой дискретизации 384 кГц его объём возрастает в 8 раз и составляет уже целых 3072 байта.
Ещё одним важным нюансом является загрузка ядра микроконтроллера: при использовании аппаратного кодека вычислений производится значительно меньше, чем при использовании встроенного АЦП.
Исходя из этого, исключать кодек из схемы нецелесообразно. Про использование встроенного АЦП и таймеров в режиме PWM имеет смысл подумать в направлении микротелефонной гарнитуры 8-бит 8 кГц, но не более…
▍ От автора
Скоро будет два года, как наработки по моему любительскому проекту простой SDR-радиостанции стали пригодны для публикации.
Мне будет очень приятно, если эти наработки кому-либо пригодятся. Сам я уже давно подошёл к тому пределу сложности алгоритмов, с которым мне справиться нелегко. Речь идёт, в первую очередь, о цифровой обработке радиосигналов.
Буду признателен за любую помощь в объяснении этой непонятной мне области знаний на понятных примерах.
73! de RD9F (aka Дмитрий Руднев)
Автор: Дмитрий Руднев