基于小脚丫FPGA的电赛训练平台完成DDS任意波形发生器
本项目利用小脚丫FPGA的电赛训练平台的高速DAC配合FPGA内部DDS的逻辑,生成波形可调(正弦波、三角波、方波)、频率可调、幅度可调的波形。
标签
FPGA
数字逻辑
DDS
OLED
2022寒假在家练
高速DAC
酷酷的胖~
更新2022-03-03
沧州师范学院
1797

1 项目需求

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

2 设计思路及框图

2.1 设计思路

 该项目使用的模块包括:板载的12MHz时钟、PLL分频后的192MHz时钟、0.91寸OLED显示屏、高速DAC模块、旋转编码器以及独立按键,大体思路如下:

  1. 输出的三种波形均使用DDS查找表的方式输出,以实现Top更简单的控制。
  2. 通过使用旋转编码器、独立按键与OLED的配合,以使得使用最少得器件完成对参数得调节。

2.2 功能框图

FkAQqZsF1xjJeV--0lRy2x6KAtUk

3 完成的功能

3.1 oled显示

   如下图所示,在OLED屏上第一行到第四行分别显示当前波形的形状、当前波形的频率、当前波形的幅度、调节的步进值。

   参数的左侧“*”号为光标。“*”号是为未选中状态,“=”号为选中状态。

FgnOhhwLbkjQ2SamhpmFsd3g3RQz

3.2 参数调节

   ①光标控制:在未选中状态下(光标为“*”号),旋转编码器即可使光标在一到四行间切换。当按下板上按键K1后,光标切换状态,

   ②参数调节:当光标在选中状态下(光标为“=”号),旋转编码器即可调节当前行的参数,即在第一行时旋转编码器控制输出信号在四种状态(关闭、正弦波、方波、三角波)间切换;在第二与第三行时,旋转编码器会根据步进值加或减当前参数(频率调节范围为0-20M,分压值调节范围为0-1023);在第四行时,旋转编码器控制步进值在10的零到七次幂间切换。

3.3 波形输出

   ①波形可调:通过上述参数调节部分介绍可使输出在关闭、正弦波、方波、三角波四个状态间切换,输出波形见下图。

FlX8vBLMmEqiA21sa82DfRq_Yqca FuEcnCsrSR9v1v81HsDX5kGeUuv0

FobwkGlUvOGVq0e2KQBKNcssTju8

   ②频率可调:通过上述参数调节部分介绍可调节输出波形的频率,其调节范围为0-20MHz,调节精度为1Hz。其输出正弦波在频率最大值与最小值时的图片如下所示(由于示波器在1Hz时示波器不会自动计算出频率,故使用2Hz时的图片)。

FrTrQDkQ-7-nxz9cT9n4hv86jNpf FjXdpFXJFFMmndnLH0F1TrCXcYXA

   ③幅度可调:通过上述参数调节部分介绍可调节输出波形幅度,在输出5kHz正弦波时无明显失真幅度范围为52mV-2.48V(参数为本人使用DS7102测得,不是很严谨)。

Fg43me40y_LzY7zp-q0R5Y01rYlW FjBox0OE9zANPy4hRK4zf61m8nnZ

4 主要代码

4.1 主要Verilog模块

FvyMs4Ex0c7IPPVkgnlxUFU266sI

4.2顶层模块

   顶层模块的功能为调节输出波形的类型以及参数并链接各模组。(代码的具体功能看代码注释)

	always @(posedge clk_in  or  negedge rst)
		begin
			if (!rst) //系统复位后设定各参数为初始值
				begin
					mode <= 2'b0;
					oled_CSR <= 2'b0;
					oled_square <= 3'b0;
					Freq <= 5000;
					Amp <= 1023;
				end
			else if(oled_ok)//光标锁定,调节各参数。
				begin
					if(oled_CSR[1:0] == 2'b00)//光标锁定在第一行控制输出模式
					begin
						if (Encoder_Left)
						begin
							if(mode[1:0] == 2'b00) mode[1:0] <= 2'b11;
							else mode <= mode - 1;
						end
						else if (Encoder_Right)
						begin
							if(mode[1:0] == 2'b11) mode[1:0] <= 2'b00;
							else mode <= mode + 1;
						end
						else mode <= mode;
					end
	
					if(oled_CSR[1:0] == 2'b01)//光标锁定在第二行调节输出频率
					begin
						if (Encoder_Left)
						begin
							if(Freq + (10 ** oled_square) >= 20_000_000 ) Freq <= 20000000;
							else Freq <= Freq + (10 ** oled_square);
						end
						else if (Encoder_Right)
						begin
							if(Freq <= (10 ** oled_square)) Freq <= 0;
							else Freq <= Freq - (10 ** oled_square);
						end
						else Freq <= Freq;
					end

					if(oled_CSR[1:0] == 2'b10)//光标锁定在第二行调节输出分压系数
					begin
						if (Encoder_Left)
						begin
							if(Amp <= (10 ** oled_square)) Amp <= 0;
							else Amp <= Amp - (10 ** oled_square);
							
						end
						else if (Encoder_Right)
						begin
							if(Amp + (10 ** oled_square) >= 1023 ) Amp <= 1023;
							else Amp <= Amp + (10 ** oled_square);
						end
						else Amp <= Amp;
					end


					if(oled_CSR[1:0] == 2'b11)//光标锁定在第二行调节步进值
					begin
						if (Encoder_Left)
						begin
							if(oled_square ==3'b111) oled_square <= 3'b000;
							else oled_square <= oled_square + 1;
						end
						else if (Encoder_Right)
						begin
							if(oled_square == 3'b000) oled_square <= 3'b111;
							else oled_square <= oled_square - 1;
						end
						else oled_square <= oled_square;
					end

				end
			else			//光标未锁定,控制光标位置。
				begin
					if (Encoder_Left)
					begin
						if(oled_CSR[1:0] == 2'b00) oled_CSR[1:0] <= 2'b11;
						else oled_CSR <= oled_CSR - 1;
					end
					else if (Encoder_Right)
					begin
						if(oled_CSR[1:0] == 2'b11) oled_CSR[1:0] <= 2'b00;
						else oled_CSR <= oled_CSR + 1;
					end
					else oled_CSR <= oled_CSR;
				end
		end 

		always @(posedge clk_in  or  negedge rst)//由独立按键控制光标状态是否锁定
           begin
             if (!rst) //系统复位后光标默认未锁定
				oled_ok <= 1'b0;
			else if (key_pulse)
				oled_ok <= ~oled_ok;//按下按键切换光标状态
			else
                oled_ok <= oled_ok;
		end    

4.3PLL分频

   在小脚丫开发板上板载的时钟为12MHz,12MHz不符合输出最高20MHz的需求,故使用PLL锁相环将频率分频至192MHz用于DDS输出频率。

FkoKKyflDnltbTvGD9EojtCAGo6u

4.4按键消抖与旋转编码器

   按键消抖以及编码器均使用电子森林的例程,下面附上源地址:

   旋转编码器的输入以及译码

   按键消抖

4.5OLED显示

   oled显示的代码使用电子森林的例程改动,可以先看电子森林的例程在参考我附上的代码,具体介绍看代码注释。

   oled12864_ssd1315

   在OLED显示数字时需要将一个数字转化为字符串,我这里将二进制数转换成BCD码的形式,采用左移加三的算法(以8’hff为例): 1、左移要转换的二进制码1位 2、左移之后,BCD码分别置于百位、十位、个位 3、如果移位后所在的BCD码列大于或等于5,则对该值加3 4、继续左移的过程直至全部移位完成。

   在本项目中将频率的二进制转BCD码程序实现如下:

	reg		[56:0]		shift_Freq; 
	always@(Freq)begin
		shift_Freq = {32'h0,Freq};
		repeat(25) begin //循环25次  
			//BCD码各位数据作满5加3操作,
			if (shift_Freq[28:25] >= 5) shift_Freq[28:25] = shift_Freq[28:25] + 2'b11;
			if (shift_Freq[32:29] >= 5) shift_Freq[32:29] = shift_Freq[32:29] + 2'b11;
			if (shift_Freq[36:33] >= 5) shift_Freq[36:33] = shift_Freq[36:33] + 2'b11;
			if (shift_Freq[40:37] >= 5) shift_Freq[40:37] = shift_Freq[40:37] + 2'b11;
			if (shift_Freq[44:41] >= 5) shift_Freq[44:41] = shift_Freq[44:41] + 2'b11;
			if (shift_Freq[48:45] >= 5) shift_Freq[48:45] = shift_Freq[48:45] + 2'b11;
			if (shift_Freq[52:49] >= 5) shift_Freq[52:49] = shift_Freq[52:49] + 2'b11;
			if (shift_Freq[56:53] >= 5) shift_Freq[56:53] = shift_Freq[56:53] + 2'b11;
			shift_Freq = shift_Freq << 1; 
		end  
	end

   以下程序为对电子森林的OLED例程更改的部分,以实现对光标、频率、分压系数以及步进值的显示。

                   MAIN:begin               //多少次改这里  ↓
						if(cnt_main >= 6'd29) cnt_main <= 6'd9;
						else cnt_main <= cnt_main + 1'b1;
						case(cnt_main)	//MAIN??
							//初始化屏幕界面
							6'd0:	begin state <= INIT; end
							6'd1:	begin y_p <= 8'hb0; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " Type:          ";state <= SCAN; end
							6'd2:	begin y_p <= 8'hb1; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " Freq:          ";state <= SCAN; end
							6'd3:	begin y_p <= 8'hb2; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " Amp:           ";state <= SCAN; end
							6'd4:	begin y_p <= 8'hb3; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " SFS:           ";state <= SCAN; end
							6'd5:	begin y_p <= 8'hb4; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end
							6'd6:	begin y_p <= 8'hb5; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end
							6'd7:	begin y_p <= 8'hb6; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end
							6'd8:	begin y_p <= 8'hb7; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "By:KuKuDePang_er";state <= SCAN; end

							//自定义功能
							6'd9:	//显示光标
							begin 
								if(oled_ok == 0)
									begin
										if(oled_CSR[1:0] == 2'b00)
											begin y_p <= 8'hb0; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd01; char <= 42; state <= SCAN; end	
										else
											begin y_p <= 8'hb0; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd01; char <= " "; state <= SCAN; end	
									end
								else
									begin
										if(oled_CSR[1:0] == 2'b00)
											begin y_p <= 8'hb0; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd01; char <= 61; state <= SCAN; end	
										else
											begin y_p <= 8'hb0; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd01; char <= " "; state <= SCAN; end	
									end
							end
							6'd10:	//显示光标
							begin 
								if(oled_ok == 0)
									begin
										if(oled_CSR[1:0] == 2'b01)
											begin y_p <= 8'hb1; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd01; char <= 42; state <= SCAN; end	
										else
											begin y_p <= 8'hb1; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd01; char <= " "; state <= SCAN; end	
									end
								else
									begin
										if(oled_CSR[1:0] == 2'b01)
											begin y_p <= 8'hb1; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd01; char <= 61; state <= SCAN; end	
										else
											begin y_p <= 8'hb1; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd01; char <= " "; state <= SCAN; end	
									end
							end
							6'd11:	//显示光标
							begin 
								if(oled_ok == 0)
									begin
										if(oled_CSR[1:0] == 2'b10)
											begin y_p <= 8'hb2; x_ph <= 8'h00; x_pl <= 8'h00; num <= 5'd01; char <= 42; state <= SCAN; end	
										else
											begin y_p <= 8'hb2; x_ph <= 8'h00; x_pl <= 8'h00; num <= 5'd01; char <= " "; state <= SCAN; end	
									end
								else
									begin
										if(oled_CSR[1:0] == 2'b10)
											begin y_p <= 8'hb2; x_ph <= 8'h00; x_pl <= 8'h00; num <= 5'd01; char <= 61; state <= SCAN; end	
										else
											begin y_p <= 8'hb2; x_ph <= 8'h00; x_pl <= 8'h00; num <= 5'd01; char <= " "; state <= SCAN; end	
									end
							end
							6'd12:	//显示光标
							begin 
								if(oled_ok == 0)
									begin
										if(oled_CSR[1:0] == 2'b11)
											begin y_p <= 8'hb3; x_ph <= 8'h00; x_pl <= 8'h00; num <= 5'd01; char <= 42; state <= SCAN; end	
										else
											begin y_p <= 8'hb3; x_ph <= 8'h00; x_pl <= 8'h00; num <= 5'd01; char <= " "; state <= SCAN; end	
									end
								else
									begin
										if(oled_CSR[1:0] == 2'b11)
											begin y_p <= 8'hb3; x_ph <= 8'h00; x_pl <= 8'h00; num <= 5'd01; char <= 61; state <= SCAN; end	
										else
											begin y_p <= 8'hb3; x_ph <= 8'h00; x_pl <= 8'h00; num <= 5'd01; char <= " "; state <= SCAN; end	
									end
							end

							6'd13://波形类型
							begin
								if(mode == 2'b00) 		begin y_p <= 8'hb0; x_ph <= 8'h13; x_pl <= 8'h00; num <= 5'd10; char <= "   OFF    "; state <= SCAN; end
								else if(mode == 2'b01) 	begin y_p <= 8'hb0; x_ph <= 8'h13; x_pl <= 8'h00; num <= 5'd10; char <= "   sine   "; state <= SCAN; end
								else if(mode == 2'b10) 	begin y_p <= 8'hb0; x_ph <= 8'h13; x_pl <= 8'h00; num <= 5'd10; char <= "  square  "; state <= SCAN; end
								else  					begin y_p <= 8'hb0; x_ph <= 8'h13; x_pl <= 8'h00; num <= 5'd10; char <= "triangular"; state <= SCAN; end
							end

							6'd14://Freq_ge    14-23显示输出波形的频率
							begin y_p <= 8'hb1; x_ph <= 8'h17; x_pl <= 8'h08; num <= 5'd01; char <= shift_Freq[28:25]; state <= SCAN; end
							6'd15://Freq_shi
							begin y_p <= 8'hb1; x_ph <= 8'h17; x_pl <= 8'h00; num <= 5'd01; char <= shift_Freq[32:29]; state <= SCAN; end
							6'd16://Freq_bai
							begin y_p <= 8'hb1; x_ph <= 8'h16; x_pl <= 8'h08; num <= 5'd01; char <= shift_Freq[36:33] ; state <= SCAN; end
							6'd17://千分符
							begin y_p <= 8'hb1; x_ph <= 8'h16; x_pl <= 8'h00; num <= 5'd01; char <= ","; state <= SCAN; end
							6'd18://Freq_qian
							begin y_p <= 8'hb1; x_ph <= 8'h15; x_pl <= 8'h08; num <= 5'd01; char <= shift_Freq[40:37]; state <= SCAN; end
							6'd19://Freq_wan
							begin y_p <= 8'hb1; x_ph <= 8'h15; x_pl <= 8'h00; num <= 5'd01; char <= shift_Freq[44:41]; state <= SCAN; end
							6'd20://Freq_shiwan
							begin y_p <= 8'hb1; x_ph <= 8'h14; x_pl <= 8'h08; num <= 5'd01; char <= shift_Freq[48:45]; state <= SCAN; end
							6'd21://百万分符号
							begin y_p <= 8'hb1; x_ph <= 8'h14; x_pl <= 8'h00; num <= 5'd01; char <= ","; state <= SCAN; end
							6'd22://Freq_baiwan
							begin y_p <= 8'hb1; x_ph <= 8'h13; x_pl <= 8'h08; num <= 5'd01; char <= shift_Freq[52:49]; state <= SCAN; end
							6'd23://Freq_qianwan
							begin y_p <= 8'hb1; x_ph <= 8'h13; x_pl <= 8'h00; num <= 5'd01; char <= shift_Freq[56:53]; state <= SCAN; end

							6'd24://步进显示
							begin
								if(oled_square == 3'b000)		begin y_p <= 8'hb3; x_ph <= 8'h13; x_pl <= 8'h00; num <= 5'd10; char <= "*1        "; state <= SCAN; end
								else if(oled_square == 3'b001) 	begin y_p <= 8'hb3; x_ph <= 8'h13; x_pl <= 8'h00; num <= 5'd10; char <= "*10       "; state <= SCAN; end
								else if(oled_square == 3'b010) 	begin y_p <= 8'hb3; x_ph <= 8'h13; x_pl <= 8'h00; num <= 5'd10; char <= "*100      "; state <= SCAN; end
								else if(oled_square == 3'b011) 	begin y_p <= 8'hb3; x_ph <= 8'h13; x_pl <= 8'h00; num <= 5'd10; char <= "*1000     "; state <= SCAN; end
								else if(oled_square == 3'b100) 	begin y_p <= 8'hb3; x_ph <= 8'h13; x_pl <= 8'h00; num <= 5'd10; char <= "*10000    "; state <= SCAN; end
								else if(oled_square == 3'b101) 	begin y_p <= 8'hb3; x_ph <= 8'h13; x_pl <= 8'h00; num <= 5'd10; char <= "*100000   "; state <= SCAN; end
								else if(oled_square == 3'b110) 	begin y_p <= 8'hb3; x_ph <= 8'h13; x_pl <= 8'h00; num <= 5'd10; char <= "*1000000  "; state <= SCAN; end
								else 						 	begin y_p <= 8'hb3; x_ph <= 8'h13; x_pl <= 8'h00; num <= 5'd10; char <= "*10000000 "; state <= SCAN; end
							end

							6'd25://Amp_ge		25-29显示输出波形的分压系数
								begin y_p <= 8'hb2; x_ph <= 8'h15; x_pl <= 8'h00; num <= 5'd01; char <= shift_Amp[19:16]; state <= SCAN; end
							6'd26://Amp_shi
								begin y_p <= 8'hb2; x_ph <= 8'h14; x_pl <= 8'h08; num <= 5'd01; char <= shift_Amp[23:20]; state <= SCAN; end
							6'd27://Amp_bai
								begin y_p <= 8'hb2; x_ph <= 8'h14; x_pl <= 8'h00; num <= 5'd01; char <= shift_Amp[27:24]; state <= SCAN; end
							6'd28://千分符
								begin y_p <= 8'hb2; x_ph <= 8'h13; x_pl <= 8'h08; num <= 5'd01; char <= ","; state <= SCAN; end
							6'd29://Amp_qian
								begin y_p <= 8'hb2; x_ph <= 8'h13; x_pl <= 8'h00; num <= 5'd01; char <= shift_Amp[31:28]; state <= SCAN; end

							default: state <= IDLE;
						endcase
					end

4.6DDS

   DDS在电子森林的例程讲解的非常详细,我只是将查一个正弦波表改为正弦波、方波、三角波三个波表,下面附上电子森林的讲解,以及我改动的地方:

   DDS生成任意波形的方法及Verilog代码实例

  

sin_table u_sin_table(address,sine_table_out);						//例化正弦波表
square_table u_square_table(address,square_table_out);				//例化方波表
triangular_table u_triangular_table(address,triangular_table_out);	//例化三角波表

always @(sel or sine_table_out or square_table_out or triangular_table_out or mode)
begin
	case(sel)
	2'b00: 	begin
			case(mode)
				2'b00:	onecycle_amp = 10'h0;
				2'b01:	onecycle_amp = 9'h1ff + sine_table_out[8:0];
				2'b10:	onecycle_amp = 9'h1ff + square_table_out[8:0];
				2'b11:	onecycle_amp = 9'h1ff + triangular_table_out[8:0];
			endcase
			address = phase[5:0];
	     	end
  	2'b01: 	begin
			case(mode)
				2'b00:	onecycle_amp = 10'h0;
				2'b01:	onecycle_amp = 9'h1ff + sine_table_out[8:0];
				2'b10:	onecycle_amp = 9'h1ff + square_table_out[8:0];
				2'b11:	onecycle_amp = 9'h1ff + triangular_table_out[8:0];
			endcase
			address = ~phase[5:0];
	     	end
  	2'b10: 	begin
			case(mode)
				2'b00:	onecycle_amp = 10'h0;
				2'b01:	onecycle_amp = 9'h1ff - sine_table_out[8:0];
				2'b10:	onecycle_amp = 9'h1ff - square_table_out[8:0];
				2'b11:	onecycle_amp = 9'h1ff - triangular_table_out[8:0];
			endcase
			address = phase[5:0];
     		end
  	2'b11: 	begin
			case(mode)
				2'b00:	onecycle_amp = 10'h0;
				2'b01:	onecycle_amp = 9'h1ff - sine_table_out[8:0];
				2'b10:	onecycle_amp = 9'h1ff - square_table_out[8:0];
				2'b11:	onecycle_amp = 9'h1ff - triangular_table_out[8:0];
			endcase
			address = ~ phase[5:0];
     		end
	endcase
end

5 片上资源

5.1IO口分配

Fn3sHVsy4-rbnieFXSWzkaSjTNtl

5.2资源占用率

FjDVLneLcqNnA6_JfjeljGrwxFRx

6 遇到的问题

   首先给电子森林点个赞,资源非常的多,以至于本项目更像是使用电子森林上例程拼凑起来的一个项目。先一个模块一个模块的学习怎么使用,再添加上自己的想法,最后组合在一起。所以我遇到的问题大部分是这个模块怎么怎么用,还有逻辑上的一些问题。

   对于模块在怎么使用在电子森林例程的介绍中都可以解决,搭配上小脚丫STEP从入门一小时就能点亮一个LED。

   因为没有系统的学习过Verilog,程序刚开始都是照葫芦画瓢,后面加上自己的想法就会冒出很多的逻辑错误与语法错误,在看了Verilog 教程后一点一点的解决了这些错误。

7 未来的计划

   学习一下FPGA仿真的步骤,在本项目开始想要尝试使用仿真,各种不知道怎么解决的问题没有时间去处理,只能是直接上烧程序上机跑。但是仿真是FPGA很重要的一部分,所以第一步要学习一下仿真。

   在本学期备赛电子设计大赛中,使用这个电赛训练板做几个历年的信号类的题,让今年省赛不只是能选择控制类的题型,为以后仪器仪表类工作积累经验。

 

感谢硬禾学堂的本次活动,动辄几百块的FPGA开发板对于学生也是一笔不小的开支,我也一直在犹豫何时入手一块FPGA开发板,感谢硬禾学堂将我带进了FPGA的大门。

最后祝硬禾学堂越办越好!!!电子森林可以帮到更多的同学。

附件下载
MY_DSG_impl1.jed
可直接下载文件
MY_DSG.rar
工程源文件
团队介绍
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号