Pull to refresh

“Сигма дельта” или как сделать хорошую звуковую карту из STM32F401

Reading time5 min
Views16K

Жене мешают смотреть последние новости из телефона и телевизора приходящие поесть (первично ?) и поиграть на компьютере (вторично?) внуки. Она их конечно любит , но звуки их взаимодействия с компьютером ее сильно раздражают. Пришлось надеть на внуков наушники. А звуковой выход у компа в неудобном месте и каждый ребенок хочет звук со своей громкостью. Ну пришлось разработать внешнюю USB звуковую карточку. Хочется и красиво и качественно. Впрочем, внуки скорее только повод для поностальгировать по своей старой специальности радиоконструктора и вообще, так как последние двадцать с лишним лет я далеко от нее и пишу заклинания программы реконструкции изображений для медицинских томографов в больших и не очень фирмах , то есть энжинер-погромист по специальности. Хотел написать статью на эту очень интересную и важную тему (компьютерная томография), но выяснилось что мне нельзя по условиям контракта ...


Итак вернемся к нашим баранам внукам и звуковым карточкам, которые у нас есть в нескольких экземплярах ,овер дофига, купил пока были дешевые, модули из Китая:

  1. Stm32f401ccu6 black pill – сейчас $3 за штучку;

  2. I2S DAC Decoder GY-PCM5102 ->$3.5 за штучку;

  3. SPI display ips 1.3 inch 240x240 (controller st7789) ->$7 за два.

Сначала построим максимальную конфигурацию из двух экранов и I2S GY-PCM5102.

Конфигурируем куб, разбавляем его говно код своим г. кодом, добавляем ФАПЧ ( фазовой автоматической подстройки частоты или PLL на ихнем ) , для согласования скоростей приходящих от компьютера данных и выдачи на i2s внешний ЦАП (DAC). Хмм , звучит очень неплохо, явно лучше большинства встроенных звуковушек. PCM5102 – весьма и весьма качественный ЦАП за свою цену в пару- тройку долларов за модуль с чипом. Добавляем отображение индикаторов уровня на паре неплохих дисплеев st7789...

С ними нужно было немного повозиться. Во первых у этих не выведен CS (Chip Select – ножка выбора чипа) . Поэтому каждому свой SPI ( SPI_1 и SPI_3). Во вторых их DMA (прямой доступ к памяти) сильно тормозит. Соотвественно копирование полного экрана из памяти занимает для SPI_1 - 32 mS , а для SPI_3 - 51 миллисекунды соотвественно.

Исходя из этого стрелки измерителей и их тени(!) отрисовываются и стираются инкрементально что помещается в 8 миллисекунд в сумме на оба экрана. Положение стрелок задается максимумом в примерно 20 мс с постоянной времени затухания 300 мс(примерно как у настоящих VU измерителей).

чудесная и актуальная песня В.Высоцкого "Аисты" (video)

И тут я вспомнил , очень давно, больше 30 лет тому назад меня учили на заклинателя синего дыма , то есть я знаю зачем осциллографу ручки, а микросхемам ножки. Ручки – чтобы их дергать, а ножки - чтобы ими дергать! Может можно ли рендедерить звук самой Stm-кой , без внешнего i2s чипа?

На этом чипе (STM32f401) нет ЦАП-а. К качестве оного можно использовать таймер с его ШИМ (PWM) или SPI.

Таймеры можно инициализировать на два три или 4 канала для N уровней сигнала на частоте Fbus = 84 Мгц мы получим частоту дискретизации Fds = 84 000 000 / N , или наоборот N=Fbus/ Fds . Например для частоты дискретизации 44100 Гц мы получим 84 000 000/44100 = 1904 уровня, что соотвествует примерно log2(1904) = 10.9 бит. Ну или на 96000 Гц соотвественно 84МГц/96КГц = 875 уровней что соотвествует 9.8 бит Маловато будет.

SPI может выдавать только два уровня 0 или 1, правда до 42 мегабит в секунду.

Хмм.. 1 бит мало однако ... есть PDM (Pulse-density modulation не знаю как точно по русски)!

Сформулируем тоже в виде кода(входной сигнал от -1.0 до 1.0 )

struct sigmaDeltaStorage
{
	float integral;
	int    y;
};

struct sigmaDeltaStorage left_chanel;
struct sigmaDeltaStorage right_chanel;

int sigma_delta(struct sigmaDeltaStorage* st,float x)
{
	st->integral += x - st->y;
	st->y =  (st->integral>0.0f) ? 1:-1;
	return st->y;
}

в цикле:

  • Прочитать данные левого и правого каналов из приемного циклического буфера USB c интерполяцией (лучше билинейной).

  • Вызвать функцию sigma_delta.

  • Результат отправить на выдачу .

Фокус тут в том что если входной сигнал ограничен некоторой частотой, а частота семплирования (отсчетов) достаточно велика , то пропустив выходной сигнал через фильтр нижних частот получим сигнал приближенный к исходному, причем тем лучше чем больше частота семплирования!

Рассмотрим пример. Входной сигнал – постоянный x=0.31415

void testFunc(int NumberSamples)
{
	for(int t=0;t<NumberSamples;t++)
	{
		x[t] = 0.31415f;
	}
	for(int t=0;t<NumberSamples;t++)
	{
		y[t] = sigma_delta(&Ch0,x[t]);
	}
	float meanSumm = 0;
	
	for(int t=0;t<NumberSamples;t++)
	{
		meanSumm += y[t];
		if(NumberSamples<20)
		printf("y[%d]=%f\n",t,y[t]);
		
	}
	meanSumm/=NumberSamples;
	
	printf("meanSum=%f \n",meanSumm);
}

для 10 вызовов фунция даст вот такую последовательность [1,-1, 1,1,-1,1,1,-1,1,1] их среднее будет 0.4 , для 1000 вызовов среднее будет 0.314 , для 10000 -> 0.3142 и тд.

То есть, чем больше частота отсчетов, тем аккуратнее апроксимация ограниченного по частоте входного сигнала. Фокус изобрели в 60-х годах прошлого века.

К сожалению, реализовать этот алгоритм прямо на stm -ке с генерацией бит и выдачей на SPI у меня не получилось, неободимая частота должна быть около 2.884 Мгц (https://en.wikipedia.org/wiki/Super_Audio_CD) . Выглядит малореальным сгенерировать два канала с семплированием с интерполяцией из usb буфера и вызовом функции sigma_delta для каждого бита и упаковки бит на выдачу в SPI при частоте процессора всего 84 Мгц. Всего 15 клоков на левый и 15 клоков на правый каналы на бит при забитой DMA шине – это нереально...

Облом ?

А что если переписать sigma_delta так, чтобы она выдавала не два уровня сигнала [1 -1] (с одним компаратором около нуля ), а больше ? Например 4 уровня [3 1 -1 -3] ? Или еще больше, например N+1 (мы заодно сместили входной и выходной сигнал к диапазону от [0 N], так нужно для выдачи ШИМ на таймер):

int sigma_delta(struct sigmaDeltaStorage* st,float x)
{
	st->integral+= x - st->y;
	st->y = floorf(st->integral+0.5f);  // nearest integer
	if(st->y<0) st->y = 0;                    
	if(st->y>N) st->y = N;

	return st->y;
}

Ура – мы изобрели велосипед. Если бы я внимательней читал статью про Delta-sigma modulation то обратил бы внимание что один бит – это частный случай, о чем там написано черным по белому :(

Теперь мы можем выбрать удобный для реализации компромисс между частотой отсчетов и количеством дискретных уровней. Простая дискретизация сигнала дает равномерный по частоте шум величиной в половину шага дискретизации. Сигма дельта дает шум прямо пропорциональный частоте сигнала(в первом приближении). То есть энергия спектра шума дискретизации смещается вверх, за пределы слышимых частот! Фокус можно повторить с двойным , тройным и тд интегралом:

float sigma_delta2(struct sigmaDeltaStorage2* st,float x)
{
	st->integral0+= x             - st->y;
	st->integral1+= st->integral0 - st->y;
	st->y = floorf(st->integral1+0.5f);
	if(st->y<0)st->y = 0;
	if(st->y>MAX_VOL)st->y = MAX_VOL;

	return st->y;
}

Есть аналогия с цифровыми фильтрами низкой частоты высоких порядков. Однако дизайн стабильных фильтров высокого порядка оставим профессионалам (по дизайну фильтров) . В коде мы реализуем простой фильтры первого и второго порядка, с реализацией в числах с фиксированной точкой (у нас на STM это самый быстрый тип int32_t ).

Для реализации я выбрал частоту 101 Кгц соотвественно количество уровней 828 (84МГц/101КГц) на сигма-дельта второго порядка интегрирования, впрочем при желаниии можно поиграть с опциями. Количество дисплеев лучше задать 0 для конфигурации с сигма- дельта, поскольку помехи от них будут слышны в наушниках, впрочем не очень сильные, слушать можно. Но предпочтительный вместе с индикаторами уровня использовать внешний DAC или организовывать развязку питания для них. Без дисплеев , с RC фильтром низкой частоты сигма-дельта на STM32f401 дает очень хороший звук, лучше большинства встроенных звуковых карточек на недорогих компах и явно лучше дешевых китайских звуковых затычек USB->3.5мм для наушников.

PS. Профи в области обработки звука и радиоконструкторы, не придирайтесь. Во первых за 30 с лишним лет я много забыл и сейчас в этой области я просто любитель. Во вторых не хотелось переусложнять математикой статью тут ее реально нужно много для детального описания ..

Еще Видео : Снег кружится группы Пламя удалил ,жадные правообладатели уже нажаловались. Ну и фиг с ними.

На эту тему Analog Devices ( осторожно English, есть много интересных ссылок в конце )

Ну и исходный код на гитхабе

Only registered users can participate in poll. Log in, please.
А что дальше?
43.04% Развить вариант с сигма дельта и дисплеями (развязка питания и тд)34
26.58% SPDIF выход (с пина данных i2s)21
30.38% эмуляция 6Е1П «Зеленый глаз»24
79 users voted. 19 users abstained.
Tags:
Hubs:
+40
Comments62

Articles

Change theme settings