Программная реализация генератора колебаний
Автор: Matt Donadio
Автор перевода: Зюмин С.А.
Источник (англ.): Donadio M. How to create oscillators in software [Электронный ресурс]. – Режим доступа: http://www.dspguru.com/...
Автор: Matt Donadio
Автор перевода: Зюмин С.А.
Источник (англ.): Donadio M. How to create oscillators in software [Электронный ресурс]. – Режим доступа: http://www.dspguru.com/...
Генератор колебаний может быть реализован программно напрямую, используя функцию формирования синусоидального сигнала, но также может быть вычислен косвенно, используя несколько различных итерационных методов. Мы рассматриваем эти методы здесь.
Предположим, что:
f = необходимая частота генератора колебаний
w = 2 pi f / Fs
phi = начальная фаза
y = выходной массив, содержащий отсчеты сгенерированного сигнала.
Создание программного генератора колебаний эквивалентно взятию отсчетов гармонического колебания:
for i = 0 to N
y[n] = sin(w * i + phi)
end
Это не очень эффективный метод, ведь нам приходится вычислять значение синусоиды для каждого отсчета сигнала. Используя аппроксимированное выражение, можно ускорить этот процесс.
Другой способ реализации программного генератора колебаний состоит в вычислении рекуррентного соотношения:
y[n] = a * y[n-1] - y[n-2], где a = 2 cos(w)
В псевдокоде это будет выглядеть следующим образом:
a = 2 * cos(w)
y[0] = sin(phi)
y[1] = sin(w + phi)
for i = 2 to N
y[n] = a * y[n-1] - y[n-2]
end
Тут существует проблема. Рекуррентное выражение представляет собой систему второго порядка с передаточной функцией вида:
H(z) = 1 / (1 - a*z^-1 + z^-2)
Подставляя:
a = 1
b = -2cos(w)
c = 1
в решение стандартного квадратного уравнения:
x = (-b +/- sqrt(b*b - 4*a*c)) / 2a
получим:
x = (2*cos(w) +/- sqrt(4*cos(w)*cos(w) - 4)) / 2
= (2*cos(w) +/- 2*sqrt(cos(w)*cos(w) - 1)) / 2
= cos(w) +/- sqrt(cos(w)*cos(w) - 1)
= cos(w) +/- sqrt(-1 * (1 - cos(w)*cos(w)))
= cos(w) +/- j*sqrt(1 - cos(w)*cos(w))
= cos(w) +/- j*sqrt(sin(w)*sin(w))
= cos(w) +/- j*sin(w)
= e^+/-jw
Следовательно:
H(z) = 1 / ((1 - e^jw * z^-1) * (1 - e^-jw * z^-1))
Имеет пару сопряженных полюсов на единичной окружности, следовательно система находится на границе устойчивости. Несмотря на это, такой подход хорошо работает на практике, в зависимости от конкретного приложения, точности представления данных и других факторов. Более подробно метод описан в [1].
Для реализации комплексного генератора колебаний можем воспользоваться методом, приведенным выше, но использовать косинусоидальное колебание для синфазного сигнала и синусоидальное для квадратурного колебания. В [1] описано, как генерировать оба сигнала одновременно.
Другой метод состоит в том, чтобы воспользоваться поворачивающимся вектором. Мы можем создать вектор и поворачивать его комплексным умножением для получения каждой точки. Вы можете вывести данный метод из тригонометрических функций cos(A+B) и sin(A+B).
dr = cos(w) /* dr,di используются, чтобы вращать вектор */
di = sin(w)
y[0].r = cos(phi) /* начальные вектор и фаза phi */
y[0].i = sin(phi)
for i = 1 to N
y[n].r = dr * y[n-1].r - di * y[n-1].i
y[n].i = dr * y[n-1].i + di * y[n-1].r
end
Подход имеет проблемы со стабильностью. Новое рекуррентное соотношение:
y[n] = e^jw * y[n-1]
С передаточной функцией:
H(z) = 1 / (1 - e^jw * z^-1)
Система имеет один полюс на единичной окружности. Но мы знаем, что длина вектора равна единице, мы можем нормализовать его длину:
|y[n]| = sqrt(y[n].r * y[n].r + y[n].i * y[n].i)
Мы можем добавить:
y[n].r = y[n].r / |y[n]|
y[n].i = y[n].i / |y[n]|
внутрь цикла для нормализации амплитуды y[n]. Но мы теряем в эффективности, так как приходится вычислять значение квадратного корня для каждой точки. Но если значение х близко к единице:
1 / sqrt(x) ~= (3 - x) / 2
Длина поворачивающегося вектора всегда близка к единице, поэтому мы можем изменить выражение внутри цикла:
mag_sq = y[n].r * y[n].r + y[n].i * y[n].i
y[n].r=y[n].r * (3 - (mag_sq)) / 2
y[n].i=y[n].i * (3 - (mag_sq)) / 2
Другой метод создания генератора колебаний связан с использованием таблиц значений. Ключом к пониманию этого подхода является понятие концепции аккумулирования фазы. Посмотрим на отсчеты синусоиды:
for i=0 to N
y[n] = sin(w * i + phi)
end
Так как:
sin(x) = sin(x + 2pi) = sin(x - 2pi)
Мы можем переписать это выражение:
for i=0 to N
y[n] = sin((w * i + phi) mod 2pi)
end
Так как переменная i монотонно увеличивается каждую итерацию, можем записать:
dw = phi
for i=0 to N
y[n] = sin(dw mod 2pi)
dw=dw + w
end
Или как:
dw = phi
for i=0 to N
y[n] = sin(dw)
dw =(dw + w) mod 2pi
end
Вот где ключ к подходу. Если у нас есть М бит, то самые отрицительные числа будут представлять –pi, а положительные будут представлять pi. В практических приложениях, если нам нужно N-битное разрешение по фазе в таблице значений, то мы используем таблицу с 2 ^ N/4 записями и симметричные свойства синусоидального колебания. Мы также используем фазовый накопитель с M битами, M>N. Для индексации в таблице берутся N верхних бит фазового наклпителя в качестве индекса.
1. Digital Signal Processing in Communication Systems by Marvin E. Frerking