基于小脚丫FPGA实现本地控制的DDS信号发生器
本次项目使用小脚丫FPGA和电赛开发板完成了本地控制的DAC波形发生器。本项目主要分为几个部分:DDS波形时序控制模块,OLED初始化与控制模块,编码器等外设控制逻辑模块等。
标签
FPGA
DDS
OLED
2022寒假在家练
curious
更新2022-03-03
北京理工大学
1414

2022寒假在家练 - 基于小脚丫FPGA实现本地控制的DDS信号发生器

一、项目介绍

本次项目使用小脚丫FPGA和电赛开发板完成。本次使用的FPGA的具体型号为Lattice LCMXO2-4000HC-4MG132。本次使用的电赛开发板上使用的板载资源有:按键、旋转编码器,128*64的OLED显示屏,10bit/120Msps的高速DAC。硬件框图如下所示:Fi5XQvsd3E_lLqTWVtWeBgUkB3ad

由于我使用的是FPGA核心板,本次项目完全使用FPGA组合与时序逻辑实现,并未使用软核、硬核的嵌入式实现。本项目主要分为几个部分:DDS波形时序控制模块,OLED初始化与控制模块,编码器等外设控制逻辑模块等。具体的设计思路我会在后面详细说明。

二、设计思路与电路模块

为了实现一个本地控制的DAC我需要解决以下几个问题:①生成的输出信号参数设置:如波形、幅度、频率等等。②调整波形参数的时候,我应该如何控制OLED同步显示当前的波形参数。③给定一个波形参数,如何利用DDS生成信号波形。

围绕着以上三个问题,对于本项目我的设计思路如下图所示:

FsURPUohjvODQlV_4OeVexuHqmrF

其中逻辑控制模块是为了解决第一个问题:输出信号的参数设置。参数设置是需要和用户交互的过程,用户可以控制按键、拨码开关、旋转编码器等外设来调整参数。同时,参数设置的时候,也需要实施通过OLED告诉用户当前的选择参数,以便用户能够正确的调整。最后,逻辑控制模块将确定下来的波形参数告诉DDS模块,以便DDS模块能够后续生成正确的信号。实现起来如上述框图:①该模块读取消抖的按键和旋转编码器,用以选择需要修改的参数,并修改。②模块根据内部确定的波形参数,传给OLED控制模块和bin2bcd模块进行回显控制。③模块根据内部确定的波形参数传给DDS生成模块用以生成模型。

OLED控制模块用以控制OLED显示图形,它的时序由两大部分组成:初始化时序和写入数据时序。根据bin2bcd模块的输出,以二进制表示的频率数据被转化为了BCD码表示的数字,这样就可以方便的回显到屏幕上进行显示。具体的逻辑在OLED模块讲解中会提到。

DDS模块用于根据波形、频率、幅度生成DDS所需要的数据。通过PLL ip核为该模块提供一个较高速度的时钟,驱动相位累加器产生索引,读取ROM_IP核中的数据,最终驱动DAC输出波形。

文件结构:

FkSNx__YjERCSjIpXHPC-ZTmCG0R

三、开发流程简述与资源使用情况报告

由于曾经设置过中文用户名,我的Lattice Diamond不能高亮,所以我在本次开发中使用VSCODE中的verilog相关插件进行代码编辑,导出test_bench也比较方便。开发流程如下图所示:

FrbPn55nXTT1ddVI-lui2F7PUpZJ

编译后文件的资源使用情况报告:

Fim9bMtzXqB2n4Na2NXi0bCQmWpY

可以看出占用了少部分的IO接口,70%的SLICE,一个GSR,30%的EBR和50%的 PLL。

四、主要模块原理、功能及效果

1.OLED显示模块

模块输入输出介绍:

module OLED12864
(
	input				clk,		//12MHz系统时钟
	input				rst_n,		//系统复位,低有效
	
	input judge_control,//0为选择幅度 1为选择频率
	input [1:0] wave_select_control,//0为选择正弦波,1为选择三角波,2位选择方波
	input          [24:0] freq_input, //输入当前频率
	input          [3:0]  amp_input,//输入当前幅度
	input                output_control,
	
	
	output	reg			oled_rst,	//OLCD液晶屏复位
	output	reg			oled_dcn,	//OLCD数据指令控制
	output	reg			oled_clk,	//OLCD时钟信号
	output	reg			oled_dat	//OLCD数据信号
);

控制接口里包括:

①选择幅度和选择频率,在屏幕上会显示一个小三角。

②输入当前的频率与幅度,经过bin2bcd及组合逻辑后显示到屏幕上。

③定义SPI接口,控制OLED芯片。

显示部分的代码主要参考寒假在家练项目(https://www.eetree.cn/project/detail/251)分享的LAttice小脚丫网盘资料(链接:https://pan.baidu.com/s/11iIyV7EqUJo99JAZj0GKwQ 提取码:yaic)中的OLED12832项目。由于代码过长,就不将全部代码放入正文中,我将解释显示逻辑架构并将主要修改的代码部分放入其中。

给出的示例代码的显示逻辑如下:

FmSdSZ7c54b3CRpOpq1XEpIp6RsH

上述逻辑的主要依据为SSD1306的数据手册。

由于上述示例程序给出的显示时128*32的OLED屏幕,通过对SSD1306寄存器的研究,我发现需要相关的初始化时序修改为如下模式:

//OLED配置指令数据
	always@(posedge rst_n)
		begin
			cmd[ 0] = {8'hae}; //Set Display OFF
			cmd[ 1] = {8'h00}; //Set Lower Column Start Address for Page Addressing Modde
			cmd[ 2] = {8'h10}; //Set Higher Column Start Address for Page Addressing Mode  ->Start Address:0
			cmd[ 3] = {8'h40}; //Set Start line
			cmd[ 4] = {8'hb0}; //Set Page Start Address:0
			cmd[ 5] = {8'h81}; //Set Contrast Control
			cmd[ 6] = {8'hff}; //对比度:256拉满
			cmd[ 7] = {8'ha1}; //Set Segment Remap:127 is mapped to SEG0
			cmd[ 8] = {8'ha6}; //Set Normal Display
			cmd[ 9] = {8'ha8}; //Set Multiplex Ratio
			cmd[10] = {8'h3f}; //64
			cmd[11] = {8'hc8}; //Set COM Output Scan Direction:(N-1)->0
			cmd[12] = {8'hd3}; //Set Display Offset 
			cmd[13] = {8'h00}; //-> vertical shift by COM , 0d
			cmd[14] = {8'hd5}; //Set Display Clock Divide and Oscillator Frequency
			cmd[15] = {8'h80}; //Oscillator:1000b Divide:0+1=1
			cmd[16] = {8'hd9}; //Set Pre-charge Period
			cmd[17] = {8'h1f}; //
			cmd[18] = {8'hda}; //Set COM Pins Hardware Configuration
			cmd[19] = {8'h12}; //Sequential COM pin configuration + Disable COM Left/Right remap
			cmd[20] = {8'hdb}; //Set V_COMH Deselect Level
			cmd[21] = {8'h40}; //~0.65 VCC
			cmd[22] = {8'h8d};
			cmd[23] = {8'h14};
			cmd[24] = {8'haf}; //Display On
		end

将128*32的OLED改为128*64的OLED需要经过很大的修改。在上述的配置下OLED仍然是按PAGE寻址的模式。每一个PAGE包含8行的数据,而中文字符经过汉字取模软件操作后无法压缩到8行的模式(否则会出现严重失真),对于中文字符而言,每个数据应该有16行。经过页面布局的规划,我通过取模软件获得了以下三种字体的数据:5*8的ASCII字符点阵、16*8的大型英文字符点阵、16*16的中文字符点阵。

为了解决上面的问题,我在扫描字符写入阶段定义了了几种不同的模式:

localparam  DIS_BLACK=2'd0, DIS_1616=2'd1, DIS_1608=2'd2, DIS_GRAPH=2'd3, DIS_0805=3'd4;

下面以DIS_1616模式扫频为例说明,代码如下所示:

else if(dis_type == DIS_1616) begin
						if(dis_cnt_scan_1616 == 8'd38) dis_cnt_scan_1616 <= 8'b0;
						else
							dis_cnt_scan_1616 <= dis_cnt_scan_1616 + 1'b1;
						case(dis_cnt_scan_1616)
							8'd 0:	begin oled_dcn <= CMD; char_reg <= y_p; state <= WRITE; state_back <= SCAN; end		//定位列页地址
							8'd 1:	begin oled_dcn <= CMD; char_reg <= x_pl; state <= WRITE; state_back <= SCAN; end	//定位行地址低位
							8'd 2:	begin oled_dcn <= CMD; char_reg <= x_ph; state <= WRITE; state_back <= SCAN; end	//定位行地址高位
							
							8'd 3:	begin oled_dcn <= DATA; char_reg <= mem_chinese[dis_index][255:248]; state <= WRITE; state_back <= SCAN; end	
							8'd 4:	begin oled_dcn <= DATA; char_reg <= mem_chinese[dis_index][247:240]; state <= WRITE; state_back <= SCAN; end	
							8'd 5:	begin oled_dcn <= DATA; char_reg <= mem_chinese[dis_index][239:232]; state <= WRITE; state_back <= SCAN; end	
							8'd 6:	begin oled_dcn <= DATA; char_reg <= mem_chinese[dis_index][231:224]; state <= WRITE; state_back <= SCAN; end
							8'd 7:	begin oled_dcn <= DATA; char_reg <= mem_chinese[dis_index][223:216]; state <= WRITE; state_back <= SCAN; end
							8'd 8:	begin oled_dcn <= DATA; char_reg <= mem_chinese[dis_index][215:208]; state <= WRITE; state_back <= SCAN; end
							8'd 9:	begin oled_dcn <= DATA; char_reg <= mem_chinese[dis_index][207:200]; state <= WRITE; state_back <= SCAN; end
							8'd10:	begin oled_dcn <= DATA; char_reg <= mem_chinese[dis_index][199:192]; state <= WRITE; state_back <= SCAN; end

							8'd11:	begin oled_dcn <= DATA; char_reg <= mem_chinese[dis_index][191:184]; state <= WRITE; state_back <= SCAN; end	
							8'd12:	begin oled_dcn <= DATA; char_reg <= mem_chinese[dis_index][183:176]; state <= WRITE; state_back <= SCAN; end	
							8'd13:	begin oled_dcn <= DATA; char_reg <= mem_chinese[dis_index][175:168]; state <= WRITE; state_back <= SCAN; end	
							8'd14:	begin oled_dcn <= DATA; char_reg <= mem_chinese[dis_index][167:160]; state <= WRITE; state_back <= SCAN; end
							8'd15:	begin oled_dcn <= DATA; char_reg <= mem_chinese[dis_index][159:152]; state <= WRITE; state_back <= SCAN; end
							8'd16:	begin oled_dcn <= DATA; char_reg <= mem_chinese[dis_index][151:144]; state <= WRITE; state_back <= SCAN; end
							8'd17:	begin oled_dcn <= DATA; char_reg <= mem_chinese[dis_index][143:136]; state <= WRITE; state_back <= SCAN; end
							8'd18:	begin oled_dcn <= DATA; char_reg <= mem_chinese[dis_index][135:128]; state <= WRITE; state_back <= SCAN; end

							8'd19:	begin oled_dcn <= CMD; char_reg <= y_p+1'b1; state <= WRITE; state_back <= SCAN; end		//定位列页地址
							8'd20:	begin oled_dcn <= CMD; char_reg <= x_pl; state <= WRITE; state_back <= SCAN; end	//定位行地址低位
							8'd21:	begin oled_dcn <= CMD; char_reg <= x_ph; state <= WRITE; state_back <= SCAN; end	//定位行地址高位
							
							8'd22:	begin oled_dcn <= DATA; char_reg <= mem_chinese[dis_index][127:120]; state <= WRITE; state_back <= SCAN; end	
							8'd23:	begin oled_dcn <= DATA; char_reg <= mem_chinese[dis_index][119:112]; state <= WRITE; state_back <= SCAN; end	
							8'd24:	begin oled_dcn <= DATA; char_reg <= mem_chinese[dis_index][111:104]; state <= WRITE; state_back <= SCAN; end	
							8'd25:	begin oled_dcn <= DATA; char_reg <= mem_chinese[dis_index][103:96]; state <= WRITE; state_back <= SCAN; end
							8'd26:	begin oled_dcn <= DATA; char_reg <= mem_chinese[dis_index][95 :88]; state <= WRITE; state_back <= SCAN; end
							8'd27:	begin oled_dcn <= DATA; char_reg <= mem_chinese[dis_index][87 :80]; state <= WRITE; state_back <= SCAN; end
							8'd28:	begin oled_dcn <= DATA; char_reg <= mem_chinese[dis_index][79 :72]; state <= WRITE; state_back <= SCAN; end
							8'd29:	begin oled_dcn <= DATA; char_reg <= mem_chinese[dis_index][71 :64]; state <= WRITE; state_back <= SCAN; end

							8'd30:	begin oled_dcn <= DATA; char_reg <= mem_chinese[dis_index][63:56]; state <= WRITE; state_back <= SCAN; end	
							8'd31:	begin oled_dcn <= DATA; char_reg <= mem_chinese[dis_index][55:48]; state <= WRITE; state_back <= SCAN; end	
							8'd32:	begin oled_dcn <= DATA; char_reg <= mem_chinese[dis_index][47:40]; state <= WRITE; state_back <= SCAN; end	
							8'd33:	begin oled_dcn <= DATA; char_reg <= mem_chinese[dis_index][39:32]; state <= WRITE; state_back <= SCAN; end
							8'd34:	begin oled_dcn <= DATA; char_reg <= mem_chinese[dis_index][31:24]; state <= WRITE; state_back <= SCAN; end
							8'd35:	begin oled_dcn <= DATA; char_reg <= mem_chinese[dis_index][23:16]; state <= WRITE; state_back <= SCAN; end
							8'd36:	begin oled_dcn <= DATA; char_reg <= mem_chinese[dis_index][15:8]; state <= WRITE; state_back <= SCAN; end
							8'd37:	begin oled_dcn <= DATA; char_reg <= mem_chinese[dis_index][7: 0]; state <= WRITE; state_back <= SCAN; end

							8'd38:	begin state <= MAIN; end
							default: state <= IDLE;
						endcase
					end

由于每8个PAGE为一组,通过上面的方式手动操作,一下子对两个PAGE进行了操作,达到了显示16*16字符的目的。剩下的模式DIS_BLACK、DIS_1616、DIS_1608、DIS_GRAPH、DIS_0805均有类似操作,在此就不赘述了,具体可以查看代码。

MAIN中可以通过增加dis_type控制当前应当以何种数据刷屏,使用dis_index控制当前应当显示哪一组点阵数据。y_p依然不变,代表当前的Page,x_pl和x_ph分别代表高位和低位坐标,部分代码如下:

MAIN:begin
						if(cnt_main >= 8'd40) cnt_main <= 8'd23;
						else cnt_main <= cnt_main + 1'b1;
						case(cnt_main)	//MAIN状�
							8'd0:	begin state <= INIT; end
							8'd1:	begin y_p <= 8'hb0; x_ph <= 8'h10; x_pl <= 8'h00; state <= SCAN;dis_type <= DIS_BLACK;  dis_index<=8'd0; end//先刷�

							8'd2:	begin y_p <= 8'hb0; x_ph <= 8'h11; x_pl <= 8'h0c; state <= SCAN;dis_type <= DIS_1608;  dis_index<=8'd0;end//D
							8'd3:	begin y_p <= 8'hb0; x_ph <= 8'h12; x_pl <= 8'h04; state <= SCAN;dis_type <= DIS_1608;  dis_index<=8'd0;end//D
							8'd4:	begin y_p <= 8'hb0; x_ph <= 8'h12; x_pl <= 8'h0c; state <= SCAN;dis_type <= DIS_1608;  dis_index<=8'd1;end//S
							8'd5:	begin y_p <= 8'hb0; x_ph <= 8'h13; x_pl <= 8'h04; state <= SCAN;dis_type <= DIS_1616;  dis_index<=8'd0; end//�
							8'd6:	begin y_p <= 8'hb0; x_ph <= 8'h14; x_pl <= 8'h04; state <= SCAN;dis_type <= DIS_1616;  dis_index<=8'd1; end//�
							8'd7:	begin y_p <= 8'hb0; x_ph <= 8'h15; x_pl <= 8'h04; state <= SCAN;dis_type <= DIS_1616;  dis_index<=8'd2; end//�

							8'd8:	begin y_p <= 8'hb6; x_ph <= 8'h10; x_pl <= 8'h00; state <= SCAN;dis_type <= DIS_0805;  dis_index<=8'd15; end//A
							8'd9:	begin y_p <= 8'hb6; x_ph <= 8'h10; x_pl <= 8'h08; state <= SCAN;dis_type <= DIS_0805;  dis_index<=8'd16; end//:
							8'd10:	begin y_p <= 8'hb6; x_ph <= 8'h11; x_pl <= 8'h08; state <= SCAN;dis_type <= DIS_0805;  dis_index<=8'd10; end//.
							8'd11:	begin y_p <= 8'hb6; x_ph <= 8'h12; x_pl <= 8'h08; state <= SCAN;dis_type <= DIS_0805;  dis_index<=8'd12; end//V
							8'd12:	begin y_p <= 8'hb7; x_ph <= 8'h10; x_pl <= 8'h00; state <= SCAN;dis_type <= DIS_0805;  dis_index<=8'd14; end//f
							8'd13:	begin y_p <= 8'hb7; x_ph <= 8'h10; x_pl <= 8'h08; state <= SCAN;dis_type <= DIS_0805;  dis_index<=8'd16; end//:

							8'd14:	begin y_p <= 8'hb7; x_ph <= 8'h15; x_pl <= 8'h00; state <= SCAN;dis_type <= DIS_0805;  dis_index<=8'd11; end//H
							8'd15:	begin y_p <= 8'hb7; x_ph <= 8'h15; x_pl <= 8'h08; state <= SCAN;dis_type <= DIS_0805;  dis_index<=8'd13; end//z
							8'd16:	begin y_p <= 8'hb2; x_ph <= 8'h10; x_pl <= 8'h00; state <= SCAN;dis_type <= DIS_1616;  dis_index<=8'd3; end//�
							8'd17:	begin y_p <= 8'hb2; x_ph <= 8'h11; x_pl <= 8'h00; state <= SCAN;dis_type <= DIS_1616;  dis_index<=8'd4; end//�
							8'd18:	begin y_p <= 8'hb2; x_ph <= 8'h12; x_pl <= 8'h00; state <= SCAN;dis_type <= DIS_1608;  dis_index<=8'd2; end// 
							8'd19:	begin y_p <= 8'hb2; x_ph <= 8'h14; x_pl <= 8'h08; state <= SCAN;dis_type <= DIS_1616;  dis_index<=8'd7; end//�
							8'd20:	begin y_p <= 8'hb4; x_ph <= 8'h10; x_pl <= 8'h00; state <= SCAN;dis_type <= DIS_1616;  dis_index<=8'd11; end//�
							8'd21:	begin y_p <= 8'hb4; x_ph <= 8'h11; x_pl <= 8'h00; state <= SCAN;dis_type <= DIS_1616;  dis_index<=8'd12; end//�
							8'd22:	begin y_p <= 8'hb4; x_ph <= 8'h12; x_pl <= 8'h00; state <= SCAN;dis_type <= DIS_1608;  dis_index<=8'd2; end//

							8'd23:	begin y_p <= 8'hb6; x_ph <= 8'h17; x_pl <= 8'h00; state <= SCAN;dis_type <= DIS_0805;  dis_index<=judge_obey_l1; end//< 选择指示
							8'd24:	begin y_p <= 8'hb6;x_ph <= 8'h17; x_pl <= 8'h08; state <= SCAN;dis_type <= DIS_0805;  dis_index<=judge_obey_r1; end//- 选择指示
							8'd25:	begin y_p <= 8'hb7; x_ph <= 8'h17; x_pl <= 8'h00; state <= SCAN;dis_type <= DIS_0805;  dis_index<=judge_obey_l2; end//< 选择指示
							8'd26:	begin y_p <= 8'hb7;x_ph <= 8'h17; x_pl <= 8'h08; state <= SCAN;dis_type <= DIS_0805;  dis_index<=judge_obey_r2; end//- 选择指示

							8'd27:	begin y_p <= 8'hb6; x_ph <= 8'h11; x_pl <= 8'h00; state <= SCAN;dis_type <= DIS_0805;  dis_index<=amp_l; end//0 电压的整数位
							8'd28:	begin y_p <= 8'hb6; x_ph <= 8'h12; x_pl <= 8'h00; state <= SCAN;dis_type <= DIS_0805;  dis_index<=amp_r; end//1 电压的小数位

							8'd29:	begin y_p <= 8'hb7; x_ph <= 8'h11; x_pl <= 8'h00; state <= SCAN;dis_type <= DIS_0805;  dis_index<=freq_dec_display_7; end//1 //频率�位[最低位�]
							8'd30:	begin y_p <= 8'hb7; x_ph <= 8'h11; x_pl <= 8'h08; state <= SCAN;dis_type <= DIS_0805;  dis_index<=freq_dec_display_6; end//2 //频率�位[最低位�]
							8'd31:	begin y_p <= 8'hb7; x_ph <= 8'h12; x_pl <= 8'h00; state <= SCAN;dis_type <= DIS_0805;  dis_index<=freq_dec_display_5; end//0 //频率�位[最低位�]
							8'd32:	begin y_p <= 8'hb7; x_ph <= 8'h12; x_pl <= 8'h08; state <= SCAN;dis_type <= DIS_0805;  dis_index<=freq_dec_display_4; end//0 //频率�位[最低位�]
							8'd33:	begin y_p <= 8'hb7; x_ph <= 8'h13; x_pl <= 8'h00; state <= SCAN;dis_type <= DIS_0805;  dis_index<=freq_dec_display_3; end//0 //频率�位[最低位�]
							8'd34:	begin y_p <= 8'hb7; x_ph <= 8'h13; x_pl <= 8'h08; state <= SCAN;dis_type <= DIS_0805;  dis_index<=freq_dec_display_2; end//0 //频率�位[最低位�]
							8'd35:	begin y_p <= 8'hb7; x_ph <= 8'h14; x_pl <= 8'h00; state <= SCAN;dis_type <= DIS_0805;  dis_index<=freq_dec_display_1; end//0 //频率�位[最低位�]
							8'd36:	begin y_p <= 8'hb7; x_ph <= 8'h14; x_pl <= 8'h08; state <= SCAN;dis_type <= DIS_0805;  dis_index<=freq_dec_display_0; end//0 //频率�位[最低位�]
							
							8'd37:	begin y_p <= 8'hb2; x_ph <= 8'h12; x_pl <= 8'h08; state <= SCAN;dis_type <= DIS_1616;  dis_index<=wave_select_obey_l; end//�波形的左面一�
							8'd38:	begin y_p <= 8'hb2; x_ph <= 8'h13; x_pl <= 8'h08; state <= SCAN;dis_type <= DIS_1616;  dis_index<=wave_select_obey_r; end//�波形的右面一�

							8'd39:	begin y_p <= 8'hb4; x_ph <= 8'h17; x_pl <= 8'h00; state <= SCAN;dis_type <= DIS_0805;  dis_index<=output_obey_l; end//- 选择指示
							8'd40:	begin y_p <= 8'hb4; x_ph <= 8'h17; x_pl <= 8'h08; state <= SCAN;dis_type <= DIS_0805;  dis_index<=output_obey_r; end//> 选择指示
							
							default: state <= IDLE;
						endcase
					end

经过上述操作可以在给定频率、波形、频率、幅度信息、当前修改参数、是否输出的条件下达到如下的显示效果:左箭头表示当前选择的参数,右箭头表示是否输出。

实现效果如下图所示:

FgPOBevW7aUTDLdpNVTwgv_Ar6vZ

 

2.DDS模块原理

DDS发生器主要有两部分组成:相位累加器,波形查找表。

它的原理是:(图片来自于https://www.eetree.cn/wiki/dds_verilog

FlJGcnsAQ-dBIwL21UsueAZkIvfB

用一个查找表来储存对应信号不同相位所对应的电压数值,再由相位累加器维护t时刻信号的相位信息,由查找表的映射关系就能得到

根据项目的要求:生成模拟信号的频率范围为DC-20MHz,调节精度为1Hz。

本项目实现了一个41bit的相位累加器,采用120MHz的时钟(120MHz的时钟由12MHz时钟倍频10倍得到),相位累加器的精度为:

Fj_rXqn_u409OxKb0ys3YwijzmSF

由于调节精度为1Hz,在该条件下,相位累加器的步进被设置为:

Fr5W9OQ1EydG6xUl-QO_qrTNIPt0

所以一个KHz的频率信号,对应的相位累加器的步进为KS,由于未要求信号的初始相位,则设置为0即可。

为了使得模拟信号的波形更加好看,我在设计的时候使用了255深度的查找表。根据相位累加器和当前所选的波形,只需要把对应的查找表数据给DAC,DDS信号发生器就近乎完成了。

考虑到:生成模拟信号的幅度为最大1Vpp,调节范围为0.1V-1V

还需要对DDS的信号进行一定的缩放,最简单的做法是将幅度乘以一个“浮点数”,由于在整数运算中没有浮点数的概念,所以使用先乘一个数M,再右移动N位,得到近似的M/(2^N)缩放效果。

上述实现的代码如下:

module dds_gen (
    input wire clk,
    input wire rst_n,
    input wire [24:0] freq_input_dds,
    input wire [3:0]  ampl_input_dds,
    input wire [1:0]  wave_choice,
    input wire        output_control,


    output wire [9:0]  dds_to_dac
);
    localparam Hz_step = 15'd18325 ;
    wire [7:0] address;
    reg [40:0] phase_add ;
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            phase_add <=41'd0;
        end
        else
            phase_add <= phase_add + freq_input_dds*Hz_step;
    end
    assign address = phase_add[40-:8];


reg          sin_OutClockEn    ;
reg          sin_Reset         ;
wire  [9:0]  sin_Q             ;
reg          tri_OutClockEn    ;
reg          tri_Reset         ;
wire  [9:0]  tri_Q             ;
reg          rec_OutClockEn    ;
reg          rec_Reset         ;
wire  [9:0]  rec_Q             ;

always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        sin_OutClockEn <=1'b0;
        tri_OutClockEn <=1'b0;
        rec_OutClockEn <=1'b0;
    end
    else if(!output_control)begin
        sin_OutClockEn <=1'b0;
        tri_OutClockEn <=1'b0;
        rec_OutClockEn <=1'b0;
    end
    else begin
        case(wave_choice)
            2'd0:begin   sin_OutClockEn <=1'b1;tri_OutClockEn <=1'b0;rec_OutClockEn <=1'b0;     end
            2'd1:begin   sin_OutClockEn <=1'b0;tri_OutClockEn <=1'b1;rec_OutClockEn <=1'b0;     end
            2'd2:begin   sin_OutClockEn <=1'b1;tri_OutClockEn <=1'b0;rec_OutClockEn <=1'b1;     end
            default:begin   sin_OutClockEn <=1'b1;tri_OutClockEn <=1'b0;rec_OutClockEn <=1'b0;     end
        endcase
    end
end

reg [9:0] signal_dat;
reg [9:0] a_ver; //用于调幅的因数
reg [19:0] amp_dat; //调幅后的波形数据
always @(*) begin
    case(wave_choice)
        2'd0:signal_dat = sin_Q;
        2'd1:signal_dat = tri_Q;
        2'd2:signal_dat = rec_Q;
        default:signal_dat = sin_Q;
    endcase
end
always @(*) begin
    case(ampl_input_dds)
        // 4'd0:a_ver = 10'd0;
        // 4'd1:a_ver = 10'd51;
        // 4'd2:a_ver = 10'd102;
        // 4'd3:a_ver = 10'd153;
        // 4'd4:a_ver = 10'd205;
        // 4'd5:a_ver = 10'd256;
        // 4'd6:a_ver = 10'd307;
        // 4'd7:a_ver = 10'd358;
        // 4'd8:a_ver = 10'd409;
        // 4'd9:a_ver = 10'd461;
        // 4'd10:a_ver = 10'd512;
        // default:a_ver = 8'd0;
        4'd0:a_ver = 10'd0;
        4'd1:a_ver = 10'd42;
        4'd2:a_ver = 10'd84;
        4'd3:a_ver = 10'd125;
        4'd4:a_ver = 10'd167;
        4'd5:a_ver = 10'd209;
        4'd6:a_ver = 10'd251;
        4'd7:a_ver = 10'd293;
        4'd8:a_ver = 10'd334;
        4'd9:a_ver = 10'd376;
        4'd10:a_ver = 10'd418;
        default:a_ver = 8'd0;
        
    endcase
end
always @(posedge clk) begin
    amp_dat = signal_dat +signal_dat * a_ver;  //波形数据乘以调幅因数
end 
 
assign dds_to_dac = amp_dat[19-:10] ; //取高十位输出,相当于右移10位


my_sin_rom  u_sin_rom (
    .Address                 ( address               ),
    .OutClock                ( clk          ),
    .OutClockEn              ( sin_OutClockEn        ),
    .Reset                   ( ~rst_n             ),

    .Q                       ( sin_Q           [9:0] )
);

my_tri_rom  u_tri_rom (
    .Address                 ( address               ),
    .OutClock                ( clk          ),
    .OutClockEn              ( tri_OutClockEn        ),
    .Reset                   ( ~rst_n             ),

    .Q                       ( tri_Q           [9:0] )
);

my_rec_rom  u_rec_rom(
    .Address                 ( address              ),
    .OutClock                ( clk          ),
    .OutClockEn              ( rec_OutClockEn        ),
    .Reset                   ( ~rst_n             ),

    .Q                       ( rec_Q           [9:0] )
);
Endmodule

 

3.调节参数模块及实现:

本项目中,我使用拨码开关来选择当前的输入波形,通过两个按键:一个选择需要调节的参数、另一个选择是否输出信号来控制系统交互,通过旋转编码器来改变电压、频率的大小,通过旋转编码器上的按键来选择步进的挡位:1Hz 10Hz 100Hz 1000Hz 10000Hz

控制逻辑代码如下:

module input_control (
    input wire clk,
    input wire rst_n,
    input wire key_a,
    input wire key_b,
    input wire key_ok,

    input wire judge_control,
    input wire [1:0] wave_select_control,
    input wire output_control,

    input wire Left_pulse ,
    input wire Right_pulse ,   
    input wire OK_pulse    ,   


    output reg [24:0] freq_input, //= 25'h58dd11;
    output reg [3:0] amp_input,// = 4'd8;
    
    output reg       judge_control_state,
    output reg       output_control_state
);


reg [24:0] freq_step;// = 25'd10;
reg [2:0]  freq_state;

localparam MAX_FREQ = 25'd20000000;
localparam MAX_AMPL = 4'd10;
localparam STEP_NUM =3'd5; //五个档位

wire      judege_input_pulse;
wire      output_control_pulse;

always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        freq_state<=3'd0;
    end
    else begin
        if(OK_pulse==1'b1) begin
            freq_state <= freq_state + 3'd1;
        end
        else begin
            if(freq_state == STEP_NUM)
                freq_state <= 3'd0;
            else
            freq_state<=freq_state;
        end
    end
end

always @(*) begin
    case(freq_state)
        3'd0:freq_step<=25'd100;
        3'd1:freq_step<=25'd1000;
        3'd2:freq_step<=25'd10000;
        3'd3:freq_step<=25'd1;
        3'd4:freq_step<=25'd10;
        default:freq_step<=25'd100;
    endcase
end

always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        freq_input <= 25'd15000;
    end
    else if(judge_control_state) begin
        if(Left_pulse == 1'b1 && (freq_input+freq_step)<=MAX_FREQ)
            freq_input <= freq_input+freq_step;
        else if(Right_pulse == 1'b1  && (freq_input>=freq_step))
            freq_input <= freq_input-freq_step;
        else
            freq_input <= freq_input;
    end
    else
        freq_input <= freq_input;
end

always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        amp_input <= 4'd3;
    end
    else if(!judge_control_state) begin
        if(Left_pulse == 1'b1 && (amp_input+4'd1)<=MAX_AMPL)
            amp_input <= amp_input+4'd1;
        else if(Right_pulse == 1'b1  && (amp_input>=4'd1))
            amp_input <= amp_input-4'd1;
        else
            amp_input <= amp_input;
    end
    else
        amp_input <= amp_input;
end




debounce u_judge(
    .clk(clk),
    .rst(rst_n),
    .key(judge_control),
    .key_pulse(judege_input_pulse)
);
debounce u_output(
    .clk(clk),
    .rst(rst_n),
    .key(output_control),
    .key_pulse(output_control_pulse)
);
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        judge_control_state<=1'b0;
    else if (judege_input_pulse)
        judge_control_state<=~judge_control_state;
    else
        judge_control_state<=judge_control_state;
end
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        output_control_state<=1'b0;
    else if (output_control_pulse)
        output_control_state<=~output_control_state;
    else
        output_control_state<=output_control_state;
end

endmodule

其中旋转编码器的原理及实现未列出,参考:

https://www.stepfpga.com/doc/%E6%97%8B%E8%BD%AC%E7%BC%96%E7%A0%81%E5%99%A8%E6%A8%A1%E5%9D%97

按键消抖的原理及实现,参考:

https://www.stepfpga.com/doc/7._%E6%8C%89%E9%94%AE%E6%B6%88%E6%8A%96

最终实现效果:

FiXoAzQBvvrg51s5634dg9RJ4r85

 

五、遇到的主要难题及解决方法

主要难题①:

128*64的显示问题,参考为128*32的oled直接移植也能显示,但中间有空行。

解决难题①:

阅读SSD1306的芯片手册,通过阅读寄存器表,更改初始化参数向量,使得屏幕能够显示为128*64

主要难题②:

8行的以PAGE寻址的刷屏方式,如何操作16行的中文数据。

解决难题②:

通过大量修改代码,增加dis_type寄存器,分类刷新PAGE。

主要难题③:

如何将内部维护的二进制数据,映射为屏幕可以显示的数字。

解决难题③:

通过引入bin2bcd模块,将二进制数转化为BCD码用以映射对应的数组位置。

 

六:未来的计划及建议:

1.别的都还不错,除了我这个板子是纯FPGA的,如果可以结合STM32硬核嵌入式开发会更好。

2.Lattice diamond有bug,我用ip核的时候特别不方便,在我的机器上会出现ip和窗口紊乱的情况,使用IP核不方便。遇到上述情况我只有重新启动才能解决问题。如果可以在在线编程工具中也提供IP核的接口就更好了。

3.仿真工具结合Modelsim需要引入lattice对应的库文件,而这样的仿真环境可以被放在在线编程调试工具中,这样会大大减少我们的开发学习时间。

 

附件下载
OLED_DAC.zip
工程代码
团队介绍
我是来自北京理工大学的学生,曹广川。
团队成员
curious
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号