วิธีการใช้ออสซิลเลเตอร์ดิจิตอล


20

ฉันมีระบบประมวลผลสัญญาณดิจิตอลแบบลอยตัวที่ทำงานที่อัตราตัวอย่างคงที่ที่s=32768ตัวอย่างต่อวินาทีที่ใช้งานโดยใช้โปรเซสเซอร์ x86-64 สมมติว่าระบบ DSP ถูกล็อคแบบซิงโครนัสกับสิ่งใดก็ตามวิธีที่ดีที่สุดในการใช้ออสซิลเลเตอร์ดิจิตอลในความถี่คืออะไร?

โดยเฉพาะผมต้องการที่จะสร้างสัญญาณ:

Y(เสื้อ)=บาป(2πเสื้อ)
ที่ จำนวนตัวอย่างn nเสื้อ=n/sn

แนวคิดหนึ่งคือการติดตามเวกเตอร์ที่เราหมุนตามมุมในแต่ละรอบนาฬิกา(x,Y)Δφ=2π/s

ในฐานะที่เป็น Matlab pseudocode การใช้งาน (การใช้งานจริงใน 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

คุณพูดถูกว่าวิธีแบบเรียกซ้ำอย่างเคร่งครัดเสี่ยงต่อการสะสมความผิดพลาดเมื่อจำนวนการทำซ้ำเพิ่มขึ้น วิธีการหนึ่งที่มีประสิทธิภาพมากขึ้นนี้จะทำโดยทั่วไปคือการใช้oscillator ตัวเลขควบคุม (NCO) โดยพื้นฐานแล้วคุณมีแอคคูมูเลเตอร์ที่ติดตามเฟสของออสซิลเลเตอร์ทันทีซึ่งได้รับการอัพเดตดังนี้:

δ=2πs

φ[n]=(φ[n-1]+δ)พอควร2π

ในแต่ละช่วงเวลาทันทีคุณจะเหลือการแปลงเฟสสะสมใน NCO ให้เป็นเอาต์พุตไซน์ที่ต้องการ วิธีการที่คุณทำขึ้นอยู่กับความต้องการของคุณสำหรับความซับซ้อนในการคำนวณความถูกต้องและอื่น ๆ วิธีหนึ่งที่ชัดเจนคือการคำนวณผลลัพธ์เพียงอย่างเดียว

xc[n]=cos(ϕ[n])

xs[n]=sin(ϕ[n])

ใช้อะไรก็ตามที่สามารถใช้งานไซน์ / โคไซน์ได้ ในความเร็วสูงและ / หรือระบบฝังตัวการทำแผนที่จากเฟสไปยังค่าไซน์ / โคไซน์มักจะทำผ่านตารางการค้นหา ขนาดของตารางการค้นหา (เช่นปริมาณของการหาปริมาณที่คุณทำบนอาร์กิวเมนต์เฟสเป็นไซน์และโคไซน์) สามารถใช้เป็นข้อแลกเปลี่ยนระหว่างปริมาณการใช้หน่วยความจำและข้อผิดพลาดโดยประมาณ สิ่งที่ดีคือจำนวนของการคำนวณที่ต้องการนั้นโดยทั่วไปแล้วไม่ขึ้นกับขนาดของตาราง นอกจากนี้คุณสามารถ จำกัด ขนาด LUT ได้หากต้องการโดยการใช้ประโยชน์จากความสมมาตรที่มีอยู่ในฟังก์ชันโคไซน์และไซน์ คุณต้องการเก็บไซนัสหนึ่งในสี่ของช่วงเวลาหนึ่งเท่านั้น

หากคุณต้องการความแม่นยำสูงกว่า LUT ที่มีขนาดพอเหมาะสามารถให้คุณได้ดังนั้นคุณสามารถดูการแก้ไขระหว่างตัวอย่างตารางได้เสมอ (เช่นการประมาณเชิงเส้นหรือลูกบาศก์เป็นต้น)

ข้อดีอีกประการของวิธีนี้คือมันไม่สำคัญที่จะรวมความถี่หรือการมอดูเลตเฟสเข้ากับโครงสร้างนี้ ความถี่ของการส่งออกสามารถปรับความแตกต่างกันโดยตามและการปรับขั้นตอนสามารถดำเนินการได้โดยเพียงแค่การเพิ่มไว[ n ]โดยตรงδφ[n]


2
ขอบคุณสำหรับคำตอบ. เวลาในการดำเนินการของการsincosเปรียบเทียบกับการคูณหลาย ๆ ครั้งเป็นอย่างไร มีข้อผิดพลาดที่เป็นไปได้ที่ต้องระวังในmodการปฏิบัติงานหรือไม่?
nibot

มันน่าสนใจที่ LUT แบบเฟสต่อแอมพลิจูดเดียวกันสามารถใช้กับออสซิลเลเตอร์ทั้งหมดในระบบ
nibot

อะไรคือจุดประสงค์ของ mod 2pi? ฉันเคยเห็นการใช้งานที่ทำ mod 1.0 คุณสามารถขยายสิ่งที่การดำเนินการแบบโมดูโลมีไว้เพื่ออะไร?
BigBrownBear00

1
φ[n][0,2π)

1
2π[0,1.0)φ[n]

8

ก.=1R2+ผม2

ก.=1R2+ผม212(3-(R2+ผม2))

ยิ่งกว่านั้นเราไม่จำเป็นต้องทำสิ่งนี้ในทุก ๆ ตัวอย่าง แต่เมื่อทุกๆ 100 หรือ 1,000 ตัวอย่างนั้นมากเกินพอที่จะรักษาเสถียรภาพนี้ สิ่งนี้มีประโยชน์อย่างยิ่งหากคุณทำการประมวลผลแบบเฟรม การอัปเดตหนึ่งครั้งต่อเฟรมถือว่าใช้ได้ นี่คือ 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 ของคุณไปยังเฟสเอาต์พุตปัจจุบันแทน สิ่งนี้ยังต้องการฟังก์ชันตรีโกณมิติ แต่มีเพียงหนึ่งครั้งต่อบัฟเฟอร์

ไม่มีขนาดลอยและความถี่โดยพลการ

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

คุณสามารถทำได้ด้วยฟังก์ชันทวีคูณฟังก์ชันตรีโกณฯ ที่ต้องการโดย cexp และโมดูลัสที่เหลือมากกว่า 2pi หากคุณสามารถทนต่อการแปลความถี่เชิงปริมาณ เช่น fs / 1024 สำหรับบัฟเฟอร์ตัวอย่าง phasor 1024 ตัวอย่าง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.