DDS原理及FPGA實現
一個按一定速度沿x軸行進,同時半徑按一定頻率在圓周上滑動的圓,最後留下的痕跡就是一個正餘弦波。
DDS全稱直接數字頻率合成(Direct Digital Synthesis),簡單來講,分以下幾步:
1.抽樣
既然是數字頻率合成,那麼從模擬信號變成數字信號的過程必不可少。這個過程就是抽樣的過程。根據奈奎斯特採樣定律,我們在採樣過程要保留源信號的信息,那麼採樣率至少爲源信號頻率的兩倍。換句話說,我們想要在數字合成出來的波形還能還原原始信號的信息的話,我們一個週期中至少要有兩個以上的“點”。
但一般來講,爲了波形的完整,我們一個週期中最少保留的點還會多一些。如果假設一個週期最小4個點,採樣頻率爲爲100MSPS,那我們可以還原的源信號的頻率最大爲25M
我們通常對一個週期採樣的點數爲2^N個,在這裏,我採樣爲2的8次方,即256個。
採樣的過程可以通過matlab進行模擬,設置好採樣的位寬和深度便可以生成採樣數據。
width=10; %rom的位寬
depth=1024; %rom的深度
x=linspace(0,2*pi,depth); %在一個週期內產生1024個採樣點
y_sin=sin(x); %生成正弦數據
y_sin=round(y_sin*(2^(width-1)-1))+2^(width-1)-1; %將餘弦數據全部轉換爲整數
fid=fopen('C:\Users\Leixx\Desktop\sin_txt.txt','w'); %創建.txt文件
fprintf(fid,'%d;\n',y_sin); %向.txt文件中寫入數據
fclose(fid); %關閉.txt文件
得到的部分採樣數據如下
511;
514;
517;
520;
524;
527;
530;
533;
536;
539;
542;
545;
549;
552;
555;
558;
561;
564;
567;
570;
574;
577;
580;
583;
586;
589;
592;
595;
598;
602;
605;
608;
611;
614;
617;
620;
623;
626;
629;
632;
635;
638;
641;
644;
647;
650;
653;
656;
2.合成
DDS技術的核心,簡單來說就是將我們的抽樣數據還原成模擬信號。還原的方式和文章講到的一樣:以一定的頻率將抽樣數據依次輸出,就可以還原波形。
假設,以100M的頻率輸出我們的1024個抽樣數據,則將會得到一個頻率爲
的正弦波。這就達到了最初的信號輸出。
那如何調頻呢?
調頻的方案有兩種:一種是改變我們的時鐘頻率,將我們讀取抽樣數據的速度變快或者變慢,這樣就可以改變頻率。這種方法對於當下很多開發板固定的晶振頻率來說比較難以實現。
另一種方案就是減少我們輸出的抽樣數據,輸出的抽樣數據越少,按照上面的公式,頻率便會越高。
比如說,我們最開始查數據是按照依次加一的方式,那我們改成依次加二,顯然,這樣做之後,輸出頻率便會提高。但也會帶來一個問題,我們輸出的點數少了,那麼點與點之間不再平滑,輸出的波形會變得階梯化。
顯然加一會得到一個頻率,加二會得到另一個頻率,但這兩個頻率都不是我想要的怎麼辦?
如何精準調頻?
假設我們需要得到一個頻率很低的信號,1KHZ,而我們的時鐘頻率爲100M,我們在一個週期內輸出1024個點也達不到這樣的頻率。因此我們就需要在輸出的每個數據之間進行等待,可以通過設置計數器來解決這個問題。爲了使輸出的信號儘可能的低,我們設置一個32位的累加器。將高10位作爲查表的地址。對於1KHZ,有
所以,累加值爲4295。
其餘頻率對應的累加值均可以按此公式計算。
如何解決階梯化?
從數字上來看,波形變得階梯化是因爲我們輸出的抽樣數據減少,點與點之間不再平滑。但是從另一個角度理解,波形變得階梯化的原因是因爲疊加了其他雜波
我們簡單地波形變得階梯化理解爲趨近於方波。下圖是方波的合成。
由方波的傅里葉級展開式
可以得出,方波是由無數個奇次諧波疊加起來的,頻率爲2n-1倍,幅度爲。
因此,若我們的波形也是疊加了高頻的諧波導致波形階梯化,那麼就通過低通濾波器來濾除高頻諧波,得到平滑的波形。
DDS的原理圖如下:
3.實現
基於FPGA的DDS,就是按照上面的原理來實現的。
(1)儲存波形
reg [9:0] wave_sin_buf;
always @(*) begin
case (addr)
8'd0 : begin wave_sin_buf<=511; end
8'd1 : begin wave_sin_buf<=514; end
8'd2 : begin wave_sin_buf<=517; end
8'd3 : begin wave_sin_buf<=520; end
8'd4 : begin wave_sin_buf<=524; end
8'd5 : begin wave_sin_buf<=527; end
8'd6 : begin wave_sin_buf<=530; end
8'd7 : begin wave_sin_buf<=533; end
8'd8 : begin wave_sin_buf<=536; end
8'd9 : begin wave_sin_buf<=539; end
8'd10 : begin wave_sin_buf<=542; end
8'd11 : begin wave_sin_buf<=545; end
8'd12 : begin wave_sin_buf<=549; end
8'd13 : begin wave_sin_buf<=552; end
8'd14 : begin wave_sin_buf<=555; end
8'd15 : begin wave_sin_buf<=558; end
8'd16 : begin wave_sin_buf<=561; end
8'd17 : begin wave_sin_buf<=564; end
我這裏只儲存了256個數據,至於原因,後面會講到。
(2)累加
設置一個32位相位控制字和頻率控制字,進行累加。
always @(posedge clk_165m or negedge rst_n) begin
if (!rst_n) begin
addr_r0 <= 0;
addr_r1 <= 0;
end
else begin
addr_r0 <= addr_r0 + fre_word; //N位累加器
addr_r1 <= addr_r0 + pha_word; //N位寄存器,帶相位偏移
end
end
(3)查表
/////查表,按照不同的相位////////
always @(*) begin
case (addr_cache[9:8])
2'b00:begin addr_r<=addr_cache[7:0]; end
2'b01:begin addr_r<=addr_cache[7:0]^8'b1111_1111; end
//異或可以求得原值的8位補值(原值與現值相加得到256)
2'b10:begin addr_r<=addr_cache[7:0]; end
2'b11:begin addr_r<=addr_cache[7:0]^8'b1111_1111; end
default: begin addr_r <= 8'd0; end
endcase
end
這裏我只儲存了2^8個波形數據,是爲了節省寄存器資源。因爲一個週期的正弦波的四個相位實際上數據是有關聯的,知道第一相位的數據,便可推導出另外三個相位的數據。
代碼中,00表示第一相位,此時按照正常的查表順序即可。
01表示第二相位,此時,查表的順序應當是2^8-地址值。但實際上這個減法的操作就是異或的操作。
相減後,地址值8位中原本的“1”變爲0,原本的“0”變爲1,正好和異或的原理相同。這裏爲了方便,就直接寫了異或。實際上寫256-addr_cache[7:0]也是一個效果。
其餘兩個相位,查表的方式類似。
(4)轉換
因爲我這裏的256個數據是第一相位的,而第三第四相位的數據是等於2^10減去第一相位的值,因此這裏需要將輸出的數據轉換一下。
reg [9:0]dac_data_r;
always @(*) begin
case (addr_cache[9])
1'b0 : begin dac_data_r <= wave_sin_buf; end
1'b1 : begin dac_data_r <= wave_sin_buf^10'b11_1111_1111; end
default : begin dac_data_r <= 10'd0; end
endcase
end
(5)測試
通過串口分別發送01999b60和031a5f60,由上面公式
可以算出分別是1M和2M的頻率控制字
以上便是DDS的FPGA實現。