基于硬禾ICE40UP5K开发板设计的简易DDS信号发生器
本人是一名来自河南的电子信息大二学生,本项目是基于硬禾ICE40UP5K开发板和扩展板设计的波形、幅度、频率可调的DDS信号发生器
标签
FPGA
DDS
信号发生器
信号产生
2022寒假在家练
信号源
正弦波
方波
三角波
这个邓某超逊
更新2022-03-02
河南工业大学
1466

代码已开源在github

GITHUB链接

一、什么是DDS

DDS(Direct Digital Synthesizer)直接数字合成技术,是一种用于产生稳定的周期性波形的技术。

简单来说就是在ROM中存取离散的正弦波数据,让后在一个时钟作用下,按照固定的步长将ROM中的数据读出并送入DAC中输出。并在其后添加一个低通滤波器,便可实现一个较为稳定的正弦波。

二、DDS实现原理

1、介绍

FhWxN4Wn7_poKF5K29Z_xQKqzrH_

第一个框为一个寄存器,输入的是一个频率字,频率字就是我们从ROM表中读取数据的步长。

虚线为整个DDS系统的核心,相位累加器。它的具体作用就是对输入的频率字进行累加。

举个不太恰当的例子,频率字相当于一个人走一步能走多远,相位累加器就是这个人走的路程。

 

FlQDSaBEoHKXnovmbNXV-853u0I0

我们可以用一个圆形作为一个正弦波的周期,黑点代表每个点处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屏幕上

设计思路

FkVOfi2HcH_RfTB-N7vfqWuKZw8-

 - 利用内部锁相环产生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读取控制设置的具体数值

状态机如下

Fuy8ogMlVx5NDNwn9M5iIcRsey1V

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编写并生成正弦波、方波、三角波三种数据。

FpUeDABI1eDikqu1s2x_2tvSR-9s

设计生成文件函数。

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

调用函数,生成生成文件

Fpdomp540bZfjxDZZPA-o56-9o7L

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

 

Fttdbsfzxy30u89VzLMiiG6cwn4l

 

附件下载
基于硬禾ICE40UP5K简易DDS信号发生器.docx
文档
DDS.zip
项目和源代码
团队介绍
邓运廷,河南工业大学
团队成员
这个邓某超逊
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号