Во второй части публикации речь пойдёт о реализации линейного входа описанной ранее звуковой карты 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. Для частоты среза около 84 кГц номинал 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 пФ частота среза ФНЧ нормирующего усилителя около 84 кГц, что намного больше половины частоты дискретизации 48 кГц, поэтому побочные продукты преобразования им не отфильтрованы, и график АЧХ от этого получился «рваным»:
 

Сравнение характеристик показывает, что даже заявленного для трансивера начального уровня «Радио-76» динамического диапазона 80 дБ, на встроенном АЦП достичь невозможно, а вот характеристик кодека для применения в составе радиостанции вполне достаточно.

Тем не менее, при подаче на вход нормирующего усилителя сигнала с выхода контрольного приёмника Softrock RX Ensemble II сигналы любительских радиостанций на диапазоне 20 м принимаются:
 

От добра добра не ищут


Как уже понятно из вышесказанного, решение на встроенном АЦП работает. Но неэффективно.

Издержки на нормирующий усилитель в расчёт можно не принимать: в том или ином виде он будет организован на ОУ квадратурного детектора. Гораздо важней тот нюанс, что при повышении частоты дискретизации кратно возрастает объём буфера DMA. И если его размер для кодека равен всего 384 байтам, то для встроенного АЦП с частотой дискретизации 384 кГц его объём возрастает в 8 раз и составляет уже целых 3072 байта.

Ещё одним важным нюансом является загрузка ядра микроконтроллера: при использовании аппаратного кодека вычислений производится значительно меньше, чем при использовании встроенного АЦП.

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

▍ От автора


Скоро будет два года, как наработки по моему любительскому проекту простой SDR-радиостанции стали пригодны для публикации.

Мне будет очень приятно, если эти наработки кому-либо пригодятся. Сам я уже давно подошёл к тому пределу сложности алгоритмов, с которым мне справиться нелегко. Речь идёт, в первую очередь, о цифровой обработке радиосигналов.

Буду признателен за любую помощь в объяснении этой непонятной мне области знаний на понятных примерах.

73! de RD9F (aka Дмитрий Руднев)

Telegram-канал с полезностями и уютный чат

Комментарии (16)


  1. Albert2009ru
    14.11.2022 17:27
    +1

    Добрый день! Пытаюсь понять, беру стандартную формулу частоты среза RC фильтра f = 1/2*3,14*RC, получается только 88кГц по параметрам схемы (180пФ и 10кОм) и Вашему пояснению. Не верить Вам повода нет, не верить учебникам тоже - знаю, что дело в моём недопонимании вопроса. Когнитивный диссонанс, прямо. Что я упустил?


    1. dmitriyrudnev Автор
      14.11.2022 18:34

      Спасибо, коллега! Пропустил на вычитке!

      Реально там 84 кГц за счёт ёмкости монтажа и прочей неидеальности. С понижением частоты среза АЧХ АЦП начинала "заваливаться по верхам", с повышением - ухудшалась картина по искажениям


      1. Albert2009ru
        14.11.2022 18:42

        Да я то чего? Вам за статью и пояснения спасибо ????????????????????????


  1. quaer
    14.11.2022 19:17

    Для улучшения качества оцифровки можно попробовать применить правильный драйвер АЦП, способный работать на ёмкостную нагрузку.


    1. dmitriyrudnev Автор
      14.11.2022 20:27

      Спасибо! Он тут, как раз, к месту будет


  1. OldFashionedEngineer
    14.11.2022 19:29

    Я ни когда не верил во встроенный АЦП у F411, Ваша статья только усилила мою веру! Это наверное одно из самых слабых мест у STM32. Помню, что в ранних справочных листах на свои микроконтроллеры ST рекомендовали использовать повторители на ОУ от Микрочипа для повышения входного сопротивления.


    1. dmitriyrudnev Автор
      14.11.2022 20:21

      Это наверное одно из самых слабых мест STM32

      Хорошо бы так... Есть ещё нерешаемая проблема запуска I2S в режиме ведомого. Наличие I2C регулярно надо проверять, ибо склонно к зависанию. Проблем хватает...

      Зато относительно неплохая документация, привлекательная цена и инфраструктура в наличии :)


      1. OldFashionedEngineer
        14.11.2022 20:34

        Наверное инфраструктура - это основное. Мне очень нравилась 91 серия у атмел, но там все руками надо было настраивать - долго, поэтому соскочил с них на stm.


        1. dmitriyrudnev Автор
          14.11.2022 20:44

          Я программировать начал в 50. Понять, что как делается на Си, мне очень здорово помог разбор кода, который генерировался CubeMX. Описание HAL у них сделано добротно. А уж когда CubeMX с Attollic скрестили :)


          1. OldFashionedEngineer
            14.11.2022 20:46

            Я сразу начал читать сами библиотеки хал. Там и без описания все хорошо понятно. Комментариев вагон


  1. VT100
    14.11.2022 22:46

    КМК, этот фрагмент надо переписать или пояснить:


    Период выборки сигнала 15 циклов подобран опытным путём: при уменьшении параметра падает точность, при увеличении – АЦП может просто не успеть обработать сигналы за отведённое для этого время.

    О циклах чего идёт речь? О делении тактовой частоты АЦП? Относительно какой частоты?


    1. dmitriyrudnev Автор
      15.11.2022 05:10

      Это - время заряда конденсатора в схеме выборки-хранения на входе канала измерения АЦП. Чем меньше значение, тем меньшее время заряжается конденсатор, и тем выше погрешность.

      Задаётся, почему-то, в "циклах".Описываемое решение работает только при установке параметра в 3 или 15 циклов. При установке 28 циклов АЦП на скорости сэмплирования 384 кГц начинал выдавать только каждый второй сэмпл: )


  1. romanetz_omsk
    15.11.2022 08:30

    Камень этот неподходящий под задачу, увы. За три копейки только fx2lp+cpld мелкая


    1. dmitriyrudnev Автор
      15.11.2022 09:49

      Рад видеть в комментариях! :)

      По сравнению с stm32f103ret6, с которого я начинал этот проект, это очень достойный «камень». Другое дело, что у меня ещё на запасном пути NUCLEO-F446Z стоит ;)


      1. romanetz_omsk
        15.11.2022 10:11

        А оно примерно тож самое, только у 446го ещё ulpi есть


  1. Zorgovskiy
    15.11.2022 16:06

    CubeMX действительно генерирует понятный код который легко расширить, особенно по сравнению с некоторыми решениями от конкурентов ( привет Freescale Processor Expert )

    В тексте глаз спотыкается о сочетание русскоязычных терминов - АЧХ, АЦП, ФНЧ, англицизмов - "оверсэмплинг", "антиалиасный" и совсем англоязычных PWM, DMA.

    Возможно это личное ( учился не на русском языке и часть аббревиатур из первой группы приходится гуглить ).