Як реалізувати цифровий генератор?


20

У мене є система цифрової обробки сигналів з плаваючою комою, яка працює з фіксованою швидкістю вибірки fс=32768 зразків в секунду, реалізована за допомогою процесора x86-64. Якщо припустити, що система DSP синхронно заблокована з будь-якого значення, який найкращий спосіб реалізувати цифровий генератор на деякій частоті f ?

Зокрема, я хочу створити сигнал:

у(т)=гріх(2πfт)
де т=н/fс для номера вибірки н .

Одна ідея полягає у тому, щоб відслідковувати вектор (х,у) який ми обертаємо на кут Δϕ=2πf/fс на кожному тактовому циклі.

Як реалізація псевдокоду Matlab (реальна реалізація знаходиться в C):

%% Initialization code

f_s = 32768;             % sample rate [Hz]
f = 19.875;              % some constant frequency [Hz]

v = [1 0];               % initial condition     
d_phi = 2*pi * f / f_s;  % change in angle per clock cycle

% initialize the rotation matrix (only once):
R = [cos(d_phi), -sin(d_phi) ; ...
     sin(d_phi),  cos(d_phi)]

Потім на кожному тактовому циклі ми трохи обертаємо вектор навколо:

%% in-loop code

while (forever),
  v = R*v;        % rotate the vector by d_phi
  y = v(1);       % this is the sine wave we're generating
  output(y);
end

Це дозволяє обчислювати генератор лише з 4 множеннями за цикл. Однак я б переймався фазовою помилкою та стабільністю амплітуди. (У простих тестах я здивувався, що амплітуда не загинула або не вибухнула одразу - можливо, sincosінструкція гарантує гріх2+cos2=1 ?).

Який правильний спосіб це зробити?

Відповіді:


12

Ви маєте рацію, що суворо рекурсивний підхід вразливий до накопичення помилок із збільшенням кількості ітерацій. Ще один надійний спосіб, як правило, це використовувати цифровий керуючий осцилятор (NCO) . В основному у вас є акумулятор, який відстежує миттєву фазу генератора, оновлюється так:

δ=2πffс

ϕ[н]=(ϕ[н-1]+δ)мод2π

Кожен момент, то вам залишається перетворювати накопичену фазу в NCO до потрібних синусоїдальних виходів. Як це зробити, залежить від ваших вимог до складності обчислень, точності тощо. Одним із очевидних способів є просто обчислити результати як

хc[н]=cos(ϕ[н])

хс[н]=гріх(ϕ[н])

використовуючи будь-яку реалізацію синуса / косинуса у вас є. У системах з високою пропускною здатністю та / або вбудованими системами відображення значень від фази до синуса / косинуса часто проводиться за допомогою таблиці пошуку. Розмір таблиці пошуку (тобто величина квантування, яку ви робите на фазовому аргументі синусом і косинусом), може використовуватися як компроміс між витратою пам'яті та помилкою наближення. Приємно, що кількість необхідних обчислень зазвичай не залежить від розміру таблиці. Крім того, ви можете обмежити розмір LUT, якщо це необхідно, скориставшись симетрією, властивою косинусом і синусом; вам потрібно дійсно зберігати одну четверту частину проби синусоїди.

Якщо вам потрібна вища точність, ніж може дати вам LUT з розумним розміром, ви завжди можете переглянути інтерполяцію між зразками таблиці (наприклад, лінійна або кубічна інтерполяція).

Ще одна перевага цього підходу полягає в тому, що в цю структуру ввімкнено частотну або фазову модуляцію. Частоту виходу можна модулювати, змінюючи відповідно, і фазова модуляція може бути реалізована простим додаванням до ϕ [ n ] безпосередньо.δϕ[н]


2
Дякую за відповідь. Як sincosпорівнюється час виконання з кількома множеннями? Чи можливі підводні камені, на які слід стежити за допомогою modоперації?
nibot

Приємно, що однаковий LUT від фази до амплітуди може бути використаний для всіх генераторів в системі.
nibot

Яке призначення моди 2pi? Я також бачив реалізації, які роблять mod 1.0. Чи можете ви розширити, для чого потрібна операція модуля?
BigBrownBear00

1
ϕ[н][0,2π)

1
2π[0,1,0)ϕ[н]

8

г=1r2+i2

г=1r2+i212(3-(r2+i2))

Більше того, нам не потрібно робити це на кожному зразку, але раз на 100 або 1000 зразків більш ніж достатньо, щоб зберегти цю стабільність. Це особливо корисно, якщо ви виконуєте обробку на основі кадру. Оновлення один раз на кадр - це просто добре. Ось швидкий Matlab обчислює 10 000 000 зразків.

%% seed the oscillator
% set parameters
f0 = single(100); % say 100 Hz
fs = single(44100); % sample rate = 44100;
nf = 1024; % frame size

% initialize phasor and state
ph =  single(exp(-j*2*pi*f0/fs));
state = single(1 + 0i); % real part 1, imaginary part 0

% try it
x = zeros(nf,1,'single');
testRuns = 10000;
for k = 1:testRuns
  % overall frames
  % sample: loop
  for i= 1:nf
    % phasor multiply
    state = state *ph;
    % take real part for cosine, or imaginary for sine
    x(i) = real(state);
  end
  % amplitude corrections through a taylor exansion aroud
  % abs(state) very close to 1
  g = single(.5)*(single(3)-real(state)*real(state)-imag(state)*imag(state) );
  state = state*g;
end
fprintf('Deviation from unity amplitude = %f\n',g-1);

Ця відповідь далі пояснюється Гільмаром в іншому запитанні: dsp.stackexchange.com/a/1087/34576
sircolinton

7

Ви можете уникнути дрейфу нестабільної величини, якщо не змусити його рекурсивно оновлювати вектор v. Замість цього поверніть вектор прототипу v до поточної вихідної фази. Це все ще вимагає деяких функцій активації, але лише один раз у буфері.

Немає величини дрейфу та довільної частоти

псевдокод:

init(freq)
  precompute Nphasor samples in phasor
  phase=0

gen(Nsamps)
    done=0
    while done < Nsamps:
       ndo = min(Nsamps -done, Nphasor)
       append to output : multiply buf[done:done+ndo) by cexp( j*phase )
       phase = rem( phase + ndo * 2*pi*freq/fs,2*pi)
       done = done+ndo

Ви можете відмовитися від множення, триггерних функцій, необхідних cexp, і залишку модуля понад 2 пікселів, якщо ви можете перенести квантований переклад частоти. наприклад, fs / 1024 для фазового буфера зразка 1024.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.