基于小脚丫FPGA实现DDS信号发生器
通过板上的高速DAC(10bits/125Msps)配合FPGA内部DDS的逻辑,生成波形可调(正弦波、三角波、方波)、频率可调(DC-)、幅度可调的波形。
标签
FPGA
数字逻辑
DDS
2022寒假在家练
枳香清酒
更新2022-03-02
浙江师范大学
1327

一、项目介绍
1.通过板上的高速DAC(10bits/125Msps)配合FPGA内部DDS的逻辑,生成波形可调(正弦波、三角波、方波)、频率可调(DC-2MHz)、幅度可调的波形。
2.生成模拟信号的频率范围为DC-2MHz,调节精度为1Hz。
3.生成模拟信号的幅度为最大1Vpp,调节范围为0.1V-1V。
4.在OLED上显示当前波形的形状、波形的频率以及幅度。
5.利用板上旋转编码器和按键能够对波形进行切换、进行参数调节。


二、设计思路和框图

Fo2Tg_oNlco2kRgR2Tpv2uSp3gmf

注:每个框图右下角名称为执行该功能的主要文件

 

三、硬件介绍
1.核心模块: 小脚丫STEP-MXO2-C的FPGA核心板,采用FPGA进行高速DDS信号产生、高速计数器,三色LED显示是否超出调节范围。
2.信息显示:128 * 64 OLED,用来显示参数。
3.控制输入: 旋转编码器/按键,改变调节参数。
4.信号生成:10bit/120Msps 高速DAC,数字信号转换位模拟信号。


四、功能展示
1.OLED参数显示

FiVrqRORqwATk2SzTp9HfRRztdjs


2.三种波形
正弦波(Sine Wave)

FhgREuR2ewor-xwtz6KcMS6eYXm3Fjtz6sPnxuqdoQmgBxQyPxBiPpvO

 


三角波(Triangular Wave)

FpK2w1YVQnLxacGfvlz9EoTWGiMEFgO4KnaPs9QloVaJB9n_t0GEci-6


方波(Square Wave)

FpDQTu0G4zofaZPkd1jP7XDsjGO_FqsSUptIbKF0dPhvfM_gVjknZprv


直流(DC)

FlZRpWSU5RWOptmckqy0PMBFf0dAFsZ13hJBvdbIPX_bBYPgJsWwCB4H


3.调节幅度峰峰值(超出0.11V-1.0V三色LED变红)
顺时针旋转编码调到最大

FraSnOq3DwYnGKZd6nU97EP5koSA


逆时针旋转编码调到最小

FsDUsKqF3r6rHSqrqkp3AdD9ISgy


4.不同步频调节频率
步频100Hz

FgOpQwbmDAipL8yTjhMhwh5NpRy3


步频1KHz

FgOpQwbmDAipL8yTjhMhwh5NpRy3


步频10KHz

Fs-IEdPyveR1mxqoLbDcjixbxk75


步频100KHz

FvieDcgvrDlRFKSFTz7ZELrrsLPC


5.复位(小脚丫K1键,按下的同时LED1闪烁)

Fr3A0QydQobusk6lM6lNwsXIhqUY


五、主要代码说明
1.产生方波、三角波、正弦波

//用计数器产生方波、三角波、正弦波
reg  [32:0] cnt;               // 自由运行的计数器,共计33位,最低频率为cnt[32]的12MHz/2^32
reg  [33:0] c_ver;             //用于调频的因数
wire [9:0]  sinWave,squareWave,trianglularWave;
always@(posedge clk_in) cnt <= cnt + c_ver;
assign squareWave = {10{cnt[31]}}; 	                                    //通过同时改变所有的数据位从0-->1--0来产生方波   	
assign trianglularWave = cnt[31] ? ~cnt[30:21] : cnt[30:21];          // 三角波形
lookup_tables u_lookup_tables(.phase(cnt[31:24]), .sin_out(sinWave)); //产生正弦波信


计数器的时钟为12MHz(未使用内部锁相环)的时候,cnt[32]的翻转频率为12MHz/2^32*c_ver,设置c_ver初值为33‘d3579,则cnt[32]的翻转频率为100Hz。所以取cnt[31]作为方波DAC,其频率为100Hz,使用计数器的其它位数,得到的方波的频率也会发生改变;由于三角波的频率由cnt[31]决定,因此取cnt[30:21]作为三角波输出;lookup_tables模块中,sel[1:0]是cnt[31:30],sel[1:0]从2'b00到2'b11的周期为cnt[32]的翻转周期,因此cnt[32]的翻转频率即为正弦波的频率,并取cnt[31:24]作为lookup_tables模块的参数传递。


2.按键功能

//mode:按键1设置Vpp/frq; 
//wave:按键2设置DC/sinWave/squareWave/trianglularWave; 
//step:按键3设置100Hz、1KHz、10KHz、100KHz
reg  mode;
reg [1:0] wave; 	
reg [1:0] step;
always @(posedge clk_in or negedge rst)
	begin
	if(!rst)  begin mode <= 1'b0; wave <= 2'd0; step <= 2'b00; end
	else if(key_pulse1) mode <= mode + 1'b1;
	else if(key_pulse2) wave <= wave + 1'b1;
    else if(key_pulse3) step <= step + 1'b1;		
	end

//选择所产生的信号类型
reg [9:0] signal_dat; //未调幅的波形数据
always @(posedge clk_in)
	begin
	case(wave)
		2'd0: signal_dat <= 10'b1111_1111_11;
		2'd1: signal_dat <= sinWave;
		2'd2: signal_dat <= squareWave;
		2'd3: signal_dat <= trianglularWave;
	endcase
	end

//调节步频,步频分别为:100Hz、1KHz、10KHz、100KHz(计数器cnt[32]转变频率:fo=fc/(2^32)*c_ver)
reg [33:0] step_param;
always @(step)
	begin
		if(step == 2'd0) step_param <= 33'd35791;
		else if(step == 2'd1) step_param <= 33'd357914;
		else if(step == 2'd2) step_param <= 33'd3579139;
		else if(step == 2'd3) step_param <= 33'd35791394;
	end


mode:按键1设置Vpp/frq。切换调节幅值或频率。
wave:按键2设置DC/sinWave/squareWave/trianglularWave。切换DDS输出信号类型。
step:按键3设置100Hz、1KHz、10KHz、100KHz。切换步频,意味着每旋转一次编码器,输出信号的频率增加或减少100Hz/1KHz/10KHz/100KHz。
step_param:频率每增加100Hz/1KHz/10KHz/100KHz,c_ver(cnt[32]的翻转频率为12MHz/2^32*c_ver)应该增加33'd35791/33'd357914/33'd3579139/33'd35791394。


3.幅频调节

//旋转编码器调节幅值和频率因子以改变Vpp和frq的大小
reg  [14:0] k;
reg  [7:0]  a_ver;   //用于调幅的因数
wire        Left_pulse,Right_pulse,OK_pulse; 
always @(posedge clk_in or negedge rst)
    begin
    if(!rst)  begin a_ver <= 8'b100_1111; c_ver <= 33'd35791394; RGLed <= 2'b10; k <= 15'd1000; end
	else if(Right_pulse && mode==1'b0) 
		begin 
		if(a_ver<8'b100_1111) begin a_ver <= a_ver + 1; RGLed <= 2'b10; end
		else begin a_ver <= a_ver; RGLed <= 2'b01; end
		end
	else if(Right_pulse && mode==1'b1) 
		begin 
		if((c_ver+step_param)<=33'd715827883) begin c_ver <= c_ver + step_param; RGLed <= 2'b10;  
			case(step)
				2'd0: k <= k + 15'd1; 
				2'd1: k <= k + 15'd10; 
				2'd2: k <= k + 15'd100;
                2'd3: k <= k + 15'd1000; 
            default:  k <= k;
            endcase			
		end
		else begin c_ver <= c_ver; RGLed <= 2'b01; end
		end
	else if(Left_pulse && mode==1'b0) 
		begin 
		if(a_ver>8'b1001) begin a_ver <= a_ver - 1; RGLed <= 2'b10; end
		else begin a_ver <= a_ver; RGLed <= 2'b01; end
		end
	else if(Left_pulse && mode==1'b1) 
		begin 
		if((c_ver-step_param)>=33'd35791 && c_ver>step_param) begin c_ver <= c_ver - step_param; RGLed <= 2'b10; 
		    case(step)
				2'd0: k <= k - 15'd1; 
				2'd1: k <= k - 15'd10; 
				2'd2: k <= k - 15'd100;
                2'd3: k <= k - 15'd1000; 
            default:  k <= k;
            endcase			
		end
		else begin c_ver <= c_ver; RGLed <= 2'b01; end
		end
	end


幅度调节:通过改变调幅因数,来改变幅值的大小,amp_dat = signal_dat * a_ver,波形数据乘以调幅因数为当前信号的幅值。
频率调节:设定每个k为100Hz,每旋转一次编码器,k值发生改变,通过标记k的值来计算频率的大小,frq = 100/k;假设当前频率frq为1800.0KHz,步频step为100KHz,由于频率范围小于2MHz,那么顺时针旋转一次编码器理应不能改变,因此c_ver+step_param<=33'd715827883(当c_ver=33'd715827883时,cnt[32]的翻转频率为2MHz),最低频率范围同理。
当幅值或者频率超出限定范围时,小脚丫核心板上的三色LED会变红,并且旋转编码器不会使输出频率超出范围,否则三色LED变为绿色。


4.计算BCD码(以幅值为例)

//幅度峰峰值二进制转换BCD码
wire [15:0] bin_code = vpp[17:8] * 16'd32;
reg  [19:0] bcd_code;
reg	 [35:0] shift_reg;  
always@(bin_code or rst)begin
	shift_reg = {20'h0,bin_code};
	if(!rst) bcd_code = 0; 
	else begin 
		repeat(16) begin //循环16次  
			//BCD码各位数据作满5加3操作,
			if (shift_reg[19:16] >= 5) shift_reg[19:16] = shift_reg[19:16] + 2'b11;
			if (shift_reg[23:20] >= 5) shift_reg[23:20] = shift_reg[23:20] + 2'b11;
			if (shift_reg[27:24] >= 5) shift_reg[27:24] = shift_reg[27:24] + 2'b11;
			if (shift_reg[31:28] >= 5) shift_reg[31:28] = shift_reg[31:28] + 2'b11;
			if (shift_reg[35:32] >= 5) shift_reg[35:32] = shift_reg[35:32] + 2'b11;
			shift_reg = shift_reg << 1; 
		end
		bcd_code = shift_reg[35:16];   
	end  
end


将二进制数转换成BCD码的形式,采用左移加三的算法:
左移要转换的二进制码1位;
左移之后,BCD码分别置于万位、千位、百位、十位、个位;
如果移位后所在的BCD码列大于或等于5,则对该值加3;
继续左移的过程直至全部移位完成;
最后得到20位的数据输出,每4位表示一个BCD码,所以有5位有效数据,这里我们还需要将小数点左移4位。


5.OLED显示

case(cnt_main)	//MAIN??
							5'd0:	begin state <= INIT; end
							5'd1:	begin y_p <= 8'hb0; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "Vpp: .  V       ";state <= SCAN; end
							5'd2:	begin y_p <= 8'hb1; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "frq:    . KHz   ";state <= SCAN; end
                            5'd3:	begin y_p <= 8'hb2; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "config:         ";state <= SCAN; end
							5'd4:	begin y_p <= 8'hb3; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "step:           ";state <= SCAN; end
							5'd5:	begin y_p <= 8'hb4; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end
							5'd6:	begin y_p <= 8'hb5; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end
							5'd7:	begin y_p <= 8'hb6; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end
							5'd8:	begin y_p <= 8'hb7; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end
							
							5'd9:	begin y_p <= 8'hb0; x_ph <= 8'h12; x_pl <= 8'h00; num <= 5'd 1; char <= vpp[19:16]; state <= SCAN; end
							5'd10:	begin y_p <= 8'hb0; x_ph <= 8'h13; x_pl <= 8'h00; num <= 5'd 1; char <= vpp[15:12]; state <= SCAN; end
							5'd11:	begin y_p <= 8'hb0; x_ph <= 8'h13; x_pl <= 8'h08; num <= 5'd 1; char <= vpp[11:8]; state <= SCAN; end
							5'd12:	begin y_p <= 8'hb1; x_ph <= 8'h12; x_pl <= 8'h00; num <= 5'd 1; char <= frq[27:24]; state <= SCAN; end
							5'd13:	begin y_p <= 8'hb1; x_ph <= 8'h12; x_pl <= 8'h08; num <= 5'd 1; char <= frq[23:20]; state <= SCAN; end
							5'd14:	begin y_p <= 8'hb1; x_ph <= 8'h13; x_pl <= 8'h00; num <= 5'd 1; char <= frq[19:16]; state <= SCAN; end
							5'd15:	begin y_p <= 8'hb1; x_ph <= 8'h13; x_pl <= 8'h08; num <= 5'd 1; char <= frq[15:12]; state <= SCAN; end
							5'd16:	begin y_p <= 8'hb1; x_ph <= 8'h14; x_pl <= 8'h08; num <= 5'd 1; char <= frq[11: 8]; state <= SCAN; end
							5'd17:	begin y_p <= 8'hb2; x_ph <= 8'h13; x_pl <= 8'h08; num <= 5'd9; 
								if(mode == 2'd0)char <= "Vpp      ";
								else if(mode == 2'd1)char <= "frq      ";
								state <= SCAN; end
							
							5'd18:	begin y_p <= 8'hb3; x_ph <= 8'h12; x_pl <= 8'h08; num <= 5'd11; 
								if(step == 2'd0)char <= "100Hz      ";
								else if(step == 2'd1)char <= "1KHz       ";
								else if(step == 2'd2)char <= "10KHz      ";
								else if(step == 2'd3)char <= "100KHz     ";
								state <= SCAN; end
							5'd19:	begin y_p <= 8'hb7; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; 
								if(wave == 2'd0)char <= "DC              ";
								else if(wave == 2'd1)char <= "Sine Wave       ";
								else if(wave == 2'd2)char <= "Square Wave     ";
								else if(wave == 2'd3)char <= "Triangular Wave ";
								state <= SCAN; end
 
							default: state <= IDLE;
						endcase


显示的内容有幅值(Vpp)、频率(frq)、当前调节的内容(config)、步频(step)以及产生的波形。显示的横坐标位置由两个参数组成:x_ph[3:0]作为高四位,x_pl[3:0]作为低四位,行(page)为y_p[0]。


5.6DAC转换

assign dac_data = amp_dat[17:8];
assign dac_clk  = clk_in;

只要给高速DAC模块一个时钟,再传递10位的数字波形,即可将数字信号转化为模拟信号。


六、遇到的问题及解决方法
1.OLED显示
OLED显示中文出现闪频。由于主时钟只有12MHz,而每显示一个中文所需要的时间较长,在不改变主时钟频率的基础上,英文显示是最适合的办法。
幅值和频率无法直接显示,需要将两者的二进制转换为BCD码一位一位进行显示。
2.幅值和频率范围
软件设计了幅值和频率的范围,然而编码器调节时,总是会超出限定范围。通过修改影响当前幅值与频率变量的位宽,使之与限定范围参数的位宽一致,两者才可以正确比较大小。
3.按键消抖
四个按键使用一个例化的模块消抖,会使程序无法综合。最后只能例化四次消抖模块,每一次输出一个按键的脉冲,使按键功能正常。


七、未来的计划及建议
1.该项目已经成功实现了信号发生器的功能,并基本达到了预期指标,未来可以增加系统时钟频率,生成更高频率的波形,增加累加器位数,实现更高的频率分辨率。
2.优化编码器防抖功能,实现精准输出。
3.更换更高比特DAC模块,提高DAC输出精度。


八、使用资源
Fh_7kdIibEMXxyhhNLNpC68B3fsF

附件下载
2022寒假在家练-电赛训练板-鄢宇彤.zip
重要文件以及项目工程文件
团队介绍
浙江师范大学 鄢宇彤
团队成员
枳香清酒
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号