Dijital osilatör nasıl uygulanır?


20

Bir x86-64 işlemci kullanılarak uygulanan saniyede örnekleri sabit örnek hızında çalışan bir kayan nokta dijital sinyal işleme sistemi var . DSP sistemi eşzamanlı neyse konularla kilitli olduğunu varsayarsak, bazı frekans bir dijital osilatör uygulamak için en iyi yolu nedir ?ffs=32768f

Özellikle, sinyali oluşturmak istiyorum: burada örnek numarası için .t = n / f s n

y(t)=günah(2πft)
t=n/fsn

Bir fikir, her saat döngüsünde \ Delta \ phi = 2 \ pi f / f_s açısıyla döndürdüğümüz bir vektörün (x, y) takip edilmesidir .(x,y)Δφ=2πf/fs

Bir Matlab sözde kodu uygulaması olarak (gerçek uygulama C'dir):

%% 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)]

Sonra, her saat döngüsünde, vektörü biraz döndürüyoruz:

%% 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

Bu, osilatörün döngü başına sadece 4 çarpımla hesaplanmasını sağlar. Ancak, faz hatası ve genlik kararlılığı konusunda endişelenirim. (Basit testlerde ben genlik ölmek veya hemen patlayabilir vermedi şaşırdı - belki sincostalimat teminatların günah2+marul2=1 ?).

Bunu yapmanın doğru yolu nedir?

Yanıtlar:


12

Kesin özyinelemeli yaklaşımın yineleme sayısı arttıkça hata birikimine karşı savunmasız olduğu konusunda haklısınız. Bunun tipik olarak daha sağlam bir yolu, sayısal olarak kontrol edilen bir osilatör (NCO) kullanmaktır . Temel olarak, osilatörün anlık fazını takip eden ve aşağıdaki gibi güncellenen bir akümülatörünüz vardır:

δ=2πffs

φ[n]=(φ[n-1]+δ)şık2π

Her seferinde anında, NCO'da biriken fazı istenen sinüzoidal çıkışlara dönüştürmekle kalırsınız. Bunu nasıl yapacağınız, hesaplama karmaşıklığı, doğruluk vb. Gereksinimlerinize bağlıdır. Açık bir yol, çıktıları yalnızca

xc[n]=marul(φ[n])

xs[n]=günah(φ[n])

mevcut sinüs / kosinüs uygulamasını kullanın. Yüksek verimli ve / veya gömülü sistemlerde, fazdan sinüs / kosinüs değerlerine eşleme genellikle bir arama tablosu aracılığıyla yapılır. Arama tablosunun boyutu (yani sinüs ve kosinüs için faz argümanında yaptığınız nicemleme miktarı) bellek tüketimi ve yaklaşık hata arasında bir denge olarak kullanılabilir. Güzel olan şey, gerekli hesaplama miktarının tipik olarak tablo boyutundan bağımsız olmasıdır. Ek olarak, kosinüs ve sinüs fonksiyonlarında bulunan simetriden yararlanarak gerekirse LUT boyutunuzu sınırlandırabilirsiniz; sadece örneklenmiş sinüzoidin bir periyodunun dörtte birini depolamanız gerekir.

Makul boyutta bir LUT'un size verebileceğinden daha yüksek doğruluğa ihtiyacınız varsa, her zaman tablo örnekleri arasındaki enterpolasyona (örneğin doğrusal veya kübik enterpolasyon) bakabilirsiniz.

Bu yaklaşımın bir diğer yararı da, frekans veya faz modülasyonunun bu yapı ile birleştirilmesinin önemsiz olmasıdır. Çıktının frekansı buna göre değiştirilerek modüle edilebilir ve faz modülasyonu doğrudan doğrudan eklenerek uygulanabilir .δφ[n]


2
Cevap için teşekkürler. Yürütme süresi sincosbir avuç çarpma ile nasıl karşılaştırılır? modOperasyona dikkat edilmesi gereken olası tuzaklar var mı?
nibot

Aynı faz-genlik LUT değerinin sistemdeki tüm osilatörler için kullanılabileceği caziptir.
nibot

Mod 2pi'nin amacı nedir? Ben de mod 1.0 yapan uygulamalar gördüm. Modulo işleminin ne için olduğunu genişletebilir misiniz?
BigBrownBear00

1
@ BigBrownBear00: Modulo işlemi, 'yi yönetilebilir bir aralıkta tutan şeydir . Pratikte, eğer modulo'nuz yoksa, zaman içinde çok büyük bir pozitif veya negatif sayı (toplam birikmiş faz miktarı) olacak şekilde büyüyecektir. Bu, nihayetinde taşma veya aritmetik hassasiyet kaybı ve kosinüs ve sinüs fonksiyon değerlendirmelerinin performansının azaltılması gibi çeşitli nedenlerden dolayı kötü olabilir. İlk önce aralığına argüman azaltma yapmak zorunda kalmazlarsa, tipik uygulamalar daha hızlıdır . [ 0 , 2 π )φ[n][0,2π)
Jason R

1
ve 1.0 faktörü bir uygulama ayrıntısıdır. Platformunuzun trigonometrik işlevlerinin etki alanının ne olduğuna bağlıdır. Eğer aralığında bir değer (yani açı döngü olarak ölçülür), o zaman denklemi bu farklı birimi yansıtacak şekilde ayarlanır. Yukarıdaki cevaptaki açıklama, radyanların tipik açısal biriminin kullanıldığını varsayar. 2π[0,1.0)φ[n]
Jason R

8

Sahip olduğunuz şey çok iyi ve verimli bir osilatördür. Potansiyel sayısal sürüklenme problemi aslında çözülebilir. Durum değişkeninizin v iki kısmı vardır, biri esasen gerçek kısım, diğeri ise hayali kısımdır. Hadi r ve i'yi arayalım. R ^ 2 + i ^ 2 = 1 olduğunu biliyoruz. Zamanla bu yukarı ve aşağı kayabilir, ancak bu gibi bir kazanç düzeltme faktörü ile çarparak kolayca düzeltilebilir

g=1r2+ben2

Açıkçası bu çok pahalı, ancak kazanç düzeltmesinin birliğe çok yakın olduğunu biliyoruz ve bunu

g=1r2+ben212(3-(r2+ben2))

Üstelik bunu her bir numunede yapmamız gerekmiyor, ancak her 100 veya 1000 numunede bir, bunu stabil tutmak için fazlasıyla yeterli. Bu özellikle çerçeve tabanlı işlem yapıyorsanız yararlıdır. Çerçeve başına bir kez güncelleme yapmak iyidir. İşte hızlı bir Matlab 10.000.000 örnek hesaplar.

%% 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);

Bu cevap Hilmar tarafından başka bir soruda daha fazla açıklanmıştır: dsp.stackexchange.com/a/1087/34576
sircolinton

7

Vektör v'yi tekrar tekrar güncellemezseniz dengesiz büyüklükte kaymayı önleyebilirsiniz. Bunun yerine, v prototip vektörünüzü geçerli çıkış fazına döndürün. Bu hala bazı tetikleme fonksiyonları gerektirir, ancak her tampon için sadece bir kez.

Büyüklük kayması ve keyfi frekans yok

pseudocode:

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

Nicel bir frekans çevirisini tolere edebiliyorsanız, çarpma, cexp tarafından gerekli olan trig fonksiyonları ve 2pi'nin üzerinde kalan modül ile ortadan kaldırabilirsiniz. örneğin 1024 örnek fazör tamponu için fs / 1024.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.