代码已开源在github
一、什么是DDS
DDS(Direct Digital Synthesizer)直接数字合成技术,是一种用于产生稳定的周期性波形的技术。
简单来说就是在ROM中存取离散的正弦波数据,让后在一个时钟作用下,按照固定的步长将ROM中的数据读出并送入DAC中输出。并在其后添加一个低通滤波器,便可实现一个较为稳定的正弦波。
二、DDS实现原理
1、介绍
第一个框为一个寄存器,输入的是一个频率字,频率字就是我们从ROM表中读取数据的步长。
虚线为整个DDS系统的核心,相位累加器。它的具体作用就是对输入的频率字进行累加。
举个不太恰当的例子,频率字相当于一个人走一步能走多远,相位累加器就是这个人走的路程。
我们可以用一个圆形作为一个正弦波的周期,黑点代表每个点处ROM存储的数据。两点之间的距离即为频率字控制的步长。
由此,频率公式为(fo为输出频率,fc为系统时钟频率,M为频率字大小,n为累加器位数)。
fo=(M/2n))*f_c;
则频率分辨率为
fo=(1/2n))*f_c;
相位字寄存器用来存储相位数据,相位调制器负责利用相位字来控制相位。
对于DDS来说,输出周期性波形的相位就相当于ROM中存储数据的位置。
因此,输出相位增量为(P为输入相位字,n为累加器位数)
Φo = (2π/2n)*p
最后将累加后地址从ROM表中读出发送给DAC,并通过一个低通滤波即可(模拟部分略过)。
2、应用细节
大多数DDS中,相位累加器的范围通常为24-32位,也就是说,通常存在2^{24}--2^{32} 个相位点,但是,由于DAC输出也不过十几位,通常来说用这么大的ROM来存储如此多的重复数据比较浪费。因此就有一种方法。
即只输出累加器所得数据的高位(位数一般与存储深度相当)。
比如,如果我只有10位存储深度的ROM,但是同时,我想要2^{24} 个相位点时,就可以只输出累加器所得结果的高10位。
设计要求
-
能够产生正弦波、三角波、方波,可以通过扩展板上的按键控制波形的切换
-
产生信号的幅度0-3Vpp之间可调,调节分辨率精确到10mV,可以通过电位计进行调节
-
产生信号的频率100Hz - 2MHz之间可调,频率调节分辨率可达10Hz
-
产生的波形、波形的幅度、波形的频率都实时显示在OLED屏幕上
设计思路
- 利用内部锁相环产生48MHz时钟信号
- 创建DDS模块儿。
- 为了便于不同情况下设置不同参数,编写模式状态机。
- 编写ADC采样驱动,用于设置具体数据。
- 编写DDS基本组成,即相位累加器和ROM表,设置频率字24位,最小调节频率2.77Hz
- 因为ADC读取为8位,因此最小调节幅度为12mV。
- 同时,设置两个按键除抖动模块用于交互。(图中省略)
交互思路
按键1:切换模式,在峰峰值、频率、波形之间切换。
按键2:
-
波形模式下,按键二可依照,正弦波-三角波-方波-正弦波的顺序切换波形。
-
频率模式下,按键二用于切换三种频率控制模式。
(1)、L模式,将ADC数据放在频率字低八位。
(2)、M模式,将ADC数据放在频率字的中间八位。
(3)、H模式,将ADC数据放在频率字的高八位。
-
峰峰值模式下,将ADC数据锁存入输出中。
五、DDS各模块儿实现与verilog代码
1、时钟
原理图中板载晶振为12M。
且内部锁相环最高工作在48M,因此设置PLL倍频到48M。
将准备完成信号与复位信号与,变为系统复位
/* PLL产生48MhZ时钟信号 */
pll_sys pll_sys_inst (
.ref_clk_i (iclk),
.rst_n_i (irst_n),
.lock_o (w_pll_lock),
.outcore_o (wpll_sys_clk),
.outglobal_o (wpll_glo_clk)
);
assign w_sys_rstn = w_pll_lock & irst_n;
2、选择模式状态机
在硬禾的开发板中只有两个按键。因此,设计一个按键可以控制频率设置、波形设置、峰峰值设置三个模式。用另外一个按键和ADC读取控制设置的具体数值。
状态机如下
module state_fsm(
input clk ,
input rst_n ,
input key_sel_n ,
output [2:0] state
);
localparam WAVEMODE = 3'b001, FREQMODE = 3'b010, VPPMODE = 3'b100;
reg [2:0] fsm_state;
always @(posedge clk or negedge rst_n) begin
if (~rst_n) begin
// reset
fsm_state <= WAVEMODE;
end
else if (~key_sel_n) begin
case (fsm_state)
WAVEMODE: fsm_state <= FREQMODE ;
FREQMODE: fsm_state <= VPPMODE ;
VPPMODE : fsm_state <= WAVEMODE ;
default : fsm_state <= WAVEMODE ;
endcase
end
else
fsm_state <= fsm_state;
end
assign state = fsm_state;
endmodule
3、rom表matlab生成数据
由于DAC为八位,因此ROM中数据宽度设置为八位。
深度设置为11位。
用matlab编写并生成正弦波、方波、三角波三种数据。
设计生成文件函数。
function wave_generate(data, path, width, depth )
%根据波形数据输出对应二进制文件
%data 波形数据
%path 文件地址,例如'C:\MAT\square_data.txt'
%width 数据输出宽度
%depth 数据存储深度
data = uint32(data);
data = dec2bin(data);
fid = fopen(path,'w');
for i=1:depth
for j=1:width
if data(i,j) == '1'
tb=1;
else
tb=0;
end
fprintf(fid,'%d',tb);
end
fprintf(fid,'\r\n');
end
fclose(fid);
end
调用函数,生成生成文件
ROM输出代码
输出时利用定点数乘法,对峰峰值控制。
小数的定点乘法原理
两个已至定点小数相乘,若第一个数的小数位为n1,第二个数的小数位为n2,则相乘结果小数位为(n1+n2)。
则结果右移(n1+n2)位就是相乘结果的整数部分。
Verilog实现:
输入:
- 时钟和复位(PLL分频后时钟)
- 总状态(只有当模式状态机在WAVEMODE时,ROM中波形才可改变,只有当模式状态机在VPPMODE时,输出峰值才可改变。)
- 波形控制按键
- ADC读取数据(用来改变输出峰峰值)
- 地址(累加器输出结果)
输出
- DAC数据
module user_rom8x2k #(parameter ROM_DEPTH = 11'd2047)(
input clk_i ,
input clk_en_i ,
//输入状态
input [2:0] FSM_state ,
input nkey_wave_con ,
input [7:0] adc_val ,
input [10:0] addr_i ,
output [7:0] rd_data_o
);
localparam WAVEMODE = 3'b001, VPPMODE = 3'b100;
reg [7:0] sin_table [ROM_DEPTH:0];
reg [7:0] tri_table [ROM_DEPTH:0];
reg [7:0] squ_table [ROM_DEPTH:0];
initial begin /* 读取波形数据到ROM中,此处文件路径应根据自己文件位置定 */
$readmemb("C:/Users/11091/Desktop/FPGA_Active_By_yinghe/my_prj/simple_DDS/MAT/sin_data.txt", sin_table);
$readmemb("C:/Users/11091/Desktop/FPGA_Active_By_yinghe/my_prj/simple_DDS/MAT/triangle_data.txt", tri_table);
$readmemb("C:/Users/11091/Desktop/FPGA_Active_By_yinghe/my_prj/simple_DDS/MAT/square_data.txt", squ_table);
end
/* 状态图 */
reg [1:0] state;
localparam SIN = 2'b00, TRIANGLE = 2'b01, SQUARE = 2'b11;
/* 波形状态转换 */
always @(posedge clk_i or negedge clk_en_i) begin
if (~clk_en_i) begin
state <= 2'b0;// reset
end
else if((FSM_state == WAVEMODE) && (nkey_wave_con == 1'b0)) begin
case (state)
SIN: state <= TRIANGLE ;
TRIANGLE: state <= SQUARE ;
SQUARE: state <= SIN ;
default: state <= SIN ;
endcase
end
else
state <= state;
end
/* 不同状态输出不同波形 */
reg [7:0] rd_data_reg;
always @(posedge clk_i or negedge clk_en_i) begin
if (~clk_en_i) begin
rd_data_reg <= 8'b0;// reset
end
else begin
case (state)
SIN: rd_data_reg <= sin_table[addr_i][7:0];
TRIANGLE: rd_data_reg <= tri_table[addr_i][7:0];
SQUARE: rd_data_reg <= squ_table[addr_i][7:0];
default: rd_data_reg <= sin_table[addr_i][7:0];
endcase
end
end
/* 输出幅度控制 */
reg [7:0] adc_val_reg;
always @(posedge clk_i or negedge clk_en_i) begin
if (~clk_en_i)
adc_val_reg <= 8'b0;// reset
else if (FSM_state == VPPMODE)
adc_val_reg <= adc_val;
else
adc_val_reg <= adc_val_reg;
end
wire [15:0] rd_data_buffer;
assign rd_data_buffer = rd_data_reg*adc_val_reg;
assign rd_data_o = rd_data_buffer[15:8];
endmodule
3、累加器
累加器的频率字寄存器,应实现调节频率的效果,因为要求最低为10Hz的精度,我们设置频率字为24位
但是adc读取的数据只有八位。因此可以用另外一个按键设置三个状态,分别调节频率字的高八位、中间八位、和低八位。
Verliog代码
输入:
- 时钟和复位
- 模式状态机(只有在FREQMODE时调节才有效)
- 频率字调节按键
- 频率字大小调节输入(adc输入)
输出:
- ROM表地址
module acculator #(parameter OUTCNT_MAX = 11'd2047)
(
input iclk ,
input irstn ,
//输入状态
input [2:0] FSM_state ,
//频率字高低位控制
input nkey_freq_con ,
input [7:0] pwm_adc_out ,
output [10:0] odac_adder
);
localparam FREQMODE = 3'b010;
localparam FREQ_CONF_L = 2'b00,FREQ_CONF_M = 2'b01,FREQ_CONF_H = 2'b11;
reg [2:0] freq_conf_state;
/* 频率大小段控制--状态机 */
always @(posedge iclk or negedge irstn) begin
if (~irstn)
freq_conf_state <= FREQ_CONF_L;
else if((FSM_state == FREQMODE) && (~nkey_freq_con)) begin
case (freq_conf_state)
FREQ_CONF_L: freq_conf_state <= FREQ_CONF_M;
FREQ_CONF_M: freq_conf_state <= FREQ_CONF_H;
FREQ_CONF_H: freq_conf_state <= FREQ_CONF_L;
default: freq_conf_state <= FREQ_CONF_L;
endcase
end
else
freq_conf_state <= freq_conf_state;
end
/* 频率控制 */
reg [23:0] freq_byte;
always @(posedge iclk or negedge irstn) begin
if (~irstn)
freq_byte <= 1'b1;
else if(FSM_state == FREQMODE) begin
case (freq_conf_state)
FREQ_CONF_L : freq_byte[7 : 0] <= pwm_adc_out;
FREQ_CONF_M : freq_byte[13: 8] <= pwm_adc_out;
FREQ_CONF_H : freq_byte[23:16] <= pwm_adc_out;
default : freq_byte[7 : 0] <= pwm_adc_out;
endcase
end
else
freq_byte <= freq_byte;
end
/* 输出累加地址 */
reg [23:0] odac_reg;
always @(posedge iclk or negedge irstn) begin
if (~irstn)
odac_reg <= 1'b0;
else if(odac_adder == OUTCNT_MAX)
odac_reg <= 1'd0;
else
odac_reg <= odac_reg + freq_byte;
end
assign odac_adder = odac_reg[23:13];
endmodule
4、Σ-△adc
不多赘述,见大佬博客。
5、OLED
改编自硬禾代码,由于比较长,这里就不贴出来了。
6、顶层模块
将OLED和DDS连接。
module top(
input iclk ,
input irstn ,
/* 按键 */
input ikey_sel_n ,
input ikey_fov_n ,
/* ADC输入输出控制 */
input pwm_adc_in ,
output pwm_out ,
output [7:0] owdac_num ,
output oled_csn , //OLCD液晶屏使能
output oled_rst , //OLCD液晶屏复位
output oled_dcn , //OLCD数据指令控制
output oled_clk , //OLCD时钟信号
output oled_dat //OLCD数据信号
);
raw_DDS raw_DDS_inst (
.iclk (iclk),
.irst_n (irstn),
/* 按键 */
.ikey_sel_n (ikey_sel_n),
.ikey_fov_n (ikey_fov_n),
/* ADC输入输出控制 */
.pwm_adc_in (pwm_adc_in),
.pwm_out (pwm_out),
.owdac_num (owdac_num)
);
/* 显示模块 */
wire pll_clk;
wire pll_rst_n;
assign pll_clk = raw_DDS_inst.wpll_sys_clk;
assign pll_rst_n = raw_DDS_inst.w_sys_rstn;
wire [2:0] FSM_state;
assign FSM_state = raw_DDS_inst.FSM_state;
wire [1:0] shape;
assign shape = raw_DDS_inst.user_rom8x2k_inst.state;
wire [23:0] freq;
assign freq = raw_DDS_inst.acculator_inst.freq_byte;
wire [23:0] freq_num;
assign freq_num = ((freq*4'b1011)>>2);
wire [2:0] freq_state;
assign freq_state = raw_DDS_inst.acculator_inst.freq_conf_state;
wire [7:0] vpp_byte;
assign vpp_byte = raw_DDS_inst.user_rom8x2k_inst.adc_val_reg;
wire [23:0] vpp;
assign vpp = 3300 * vpp_byte;
OLED12864 OLED12864_inst (
.clk (pll_clk), //12MHz系统时钟
.rst_n (pll_rst_n), //系统复位,低有效
.FSM_state (FSM_state),
.vpp (vpp[23:8]),
.frq (freq_num[15:0]),
.shape (shape),
.freq_sta (freq_state),
.oled_csn (oled_csn), //OLCD液晶屏使能
.oled_rst (oled_rst), //OLCD液晶屏复位
.oled_dcn (oled_dcn), //OLCD数据指令控制
.oled_clk (oled_clk), //OLCD时钟信号
.oled_dat (oled_dat) //OLCD数据信号
);
endmodule
六、FPGA内部资源使用情况
资源简称 | 资源介绍 | 使用量 |
LUT4 | 4输入查找表 | 1343 |
PFU Register | 可编程逻辑单元, | 462 |
EBR | 嵌入的内存块,每个块120Kbits | 3 |
IO Buffers | IO口缓冲 | 0 |