任务要求
本项目选择2022寒假练项目9:2005年A题-正弦信号发生器,完成其基础部分。同时实现2022寒假练项目3的大部分要求,作为在完成项目基础上的补充
2005年A题-正弦信号发生器要求:
- 正弦波输出频率范围:1kHz~10MHz;
- 具有频率设置功能,频率步进:100Hz;
- 输出信号频率稳定度:优于10-4;
- 输出电压幅度:在50Ω负载电阻上的电压峰-峰值V opp≥1V;
- 失真度:用示波器观察时无明显失真。
根据项目3补充:
- 生成波形可调(正弦波、三角波、方波)、频率可调(DC-)的波形
- 生成模拟信号的频率范围为DC-20MHz,调节精度为1Hz
- 在OLED上显示当前波形的信息
设计思路
整体思路:
信号发生器要实现频率范围是DC-20MHz,外部晶振提供的12MHz是不够用的,利用Diamond中的PLL将频率上升为120MHz,当作DAC使用的时钟。在该程序中,所有的波形都使用了查找表的方式实现,并利用镜像生成,减少占用的存储空间,只需要存储1/4的波形数据就可生成完整的波形。在主程序中,为了提升波形在高频率时的频率精准度,采用了bcd码的10进制各位乘对应的频率调谐字,从而可以将误差控制在1Hz内(具体可看代码部分)。同时bcd码用来在OLED上进行频率的显示,同时进行了消零操作。
波表可以通过EXCEL等简单的方式获得
项目分为以下几个模块:
- 主程序模块(包含输入操作的采集与频率的设定)
- 波形查找表模块(包含各波形的数据)
- 二进制转8421BCD码(将频率转化为8421BCD码的形式)
- OLED驱动模块(利用小脚丫的例程进行修改和利用)
- 消抖模块(利用延时消抖的原理)
- PLL升频模块(利用Lattice内部的IP核)
各模块之间的关系框图如下:
硬件介绍
本项目使用的是基于小脚丫FPGA的电赛训练平台
其系统框图如下:
采取虚拟U盘的方式进行程序下载
工具介绍
本实验平台FPGA使用的是Lattice公司的MXO2-4000HC芯片,使用Lattice Diamond软件进行程序编写,同时也提供了小脚丫Web IDE的编写功能。
主要代码片段与说明
- 查找表相位确定
//-------------------------设定频率调谐字(下称步进)----------
/*1Hz 步进00000024 1Hz的步进累加到100Hz时,和100Hz步进相差21<36,小于1Hz,所以将1Hz的累加最大值为100Hz
10Hz 00000166
100Hz 步进00000DFB 100Hz累加步进到1000Hz时,步进值相差1<36,小于1Hz所以可以将1000Hz作为另一个阶段
1kHz 00008BCF
10kHz 0005761A
100kHz 00369D03
1MHz 02222222
*/
always@(posedge clk_120M) cnt <= cnt + 1'b1;
always @(negedge clk_in)
begin
step_num =
32'h15555555 *freq_bcd[31:28]//*10MHz
+ 32'h02222222 *freq_bcd[27:24]//*1MHz
+ 32'h00369D03 *freq_bcd[23:20]//*100kHz
+ 32'h0005761A *freq_bcd[19:16]//*10kHz
+ 32'h00008BCF *freq_bcd[15:12]//*1kHz
+ 32'h00000DFB *freq_bcd[11:8] //*100Hz
+ 32'h00000166 *freq_bcd[7:4] //*10Hz
+ 32'h00000024 *freq_bcd[3:0]; //*1Hz
end
assign next_phase = step_num +accumulator;
always @(posedge clk_120M) accumulator<=next_phase;
assign phase = accumulator[31:24]; //相位phase是高八位的数据
本项目要实现频率从DC-20MHz的范围,通过频率来设定频率调谐字,但是固定的一个频率的频率调谐字实现的只是非常接近这一频率的值,存在一定的误差。当我们要实现1Hz可调节,最高到达20MHz时,误差也会累加,产生很大的频率误差。所以采用这种分段计算的方法,使用频率的bcd码,计算得到给定频率下的频率调谐字。但是同时,这种方式也占用了比较多的逻辑门资源。
- 二进制转8421bcd码
begin
if(!rst) //复位时清空所有寄存器
begin
state <= 0;
bcd <= 0;
regdata <= 0;
regdata1 <= 0;
w1 <= 0;
w2 <= 0;
w3 <= 0;
w4 <= 0;
w5 <= 0;
w6 <= 0;
w7 <= 0;
w8 <= 0;
q <= 0;
end
else
case(state)
0: //初始状态,给寄存器赋初始值
begin
regdata <= bin;
regdata1 <= bin;
state <= 1;
w1 <= 0;
w2 <= 0;
w3 <= 0;
w4 <= 0;
w5 <= 0;
w6 <= 0;
w7 <= 0;
w8 <= 0;
q <= 0;
end
1: //移位状态,每移位1次计数器q值加1。
begin
q <= q + 1;
regdata[24:0] <= (regdata[24:0] << 1);
w1 <= {w1[2:0],regdata[24]};
w2 <= {w2[2:0],w1[3]};
w3 <= {w3[2:0],w2[3]};
w4 <= {w4[2:0],w3[3]};
w5 <= {w5[2:0],w4[3]};
w6 <= {w6[2:0],w5[3]};
w7 <= {w7[2:0],w6[3]};
w8 <= {w8[2:0],w7[3]};
if(q == 24)
begin
state <= 3; //转换完成后跳至状态3输出结果并等待
end
else
state <= 2; //未完成则跳至状态2判断每一位是否大于等于5
end
2: //判断每一位是否大于等于5,是则自加3,并跳回状态1进行下一次移位
begin
state <= 1;
if(w1 >= 5)
w1 <= w1 + 3;
else
w1 <= w1;
if(w2 >= 5)
w2 <= w2 + 3;
else
w2 <= w2;
if(w3 >= 5)
w3 <= w3 + 3;
else
w3 <= w3;
if(w4 >= 5)
w4 <= w4 + 3;
else
w4 <= w4;
if(w5 >= 5)
w5 <= w5 + 3;
else
w5 <= w5;
if(w6 >= 5)
w6 <= w6 + 3;
else
w6 <= w6;
if(w7 >= 5)
w7 <= w7 + 3;
else
w7 <= w7;
if(w8 >= 5)
w8 <= w8 + 3;
else
w8 <= w8;
end
3: //完成状态,输出转换完成的BCD码并等待输入的变化
begin
bcd <= {w8,w7,w6,w5,w4,w3,w2,w1};//将符号位、个位~万位拼起来
if(regdata1 != bin) //regdata1不等于bin说明输入发生变化
state <= 0; //跳回初始状态,以进行下一次转换
else
state <= 3; //输入没变化则停留在此状态等待
end
endcase
end
为了进行更为准确的频率调谐字设置和OLED的频率显示,都需要获得频率值在十进制下的各位数字,直接想法是采用除法,但是除法在硬件中占用的资源过多,所以选择更好的二进制转bcd码,直接从各位取出想要的值即可。
采用的是左移加三算法,利用有限状态机进行控制。状态0:初始化状态;状态1:左移状态;状态2:加三状态;状态3:比较与等待状态。在状态三中,出现输入的二进制数发生变化时,重新返回状态0,开始下一次的转化
- lookup_tables的镜像处理
begin
case(sel)
2'b00: begin
sine_onecycle_amp = 9'h1ff + sine_table_out[8:0];
address = phase[5:0];
end
2'b01: begin
sine_onecycle_amp = 9'h1ff + sine_table_out[8:0];
address = ~phase[5:0];
end
2'b10: begin
sine_onecycle_amp = 9'h1ff - sine_table_out[8:0];
address = phase[5:0];
end
2'b11: begin
sine_onecycle_amp = 9'h1ff - sine_table_out[8:0];
address = ~ phase[5:0];
end
endcase
end
- OLED显示频率
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 <= wave_decide;state <= SCAN; end
5'd2: begin y_p <= 8'hb1; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end
5'd3: begin y_p <= 8'hb2; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "Frequency ";state <= SCAN; end
5'd4: begin y_p <= 8'hb3; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end
5'd5: begin y_p <= 8'hb3; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16;
char <= {2'b0,ww,2'b0,qw,2'b0,bw,2'b0,sw,2'b0,gw,8'd46,4'b0,sfw,4'b0,bfw,4'b0,qfw,8'd32,8'd107,8'd72,8'd122,8'd32,8'd32,8'd32};
state <= SCAN; end
顶层RTL视图
该程序资源占用报告如下
实现的功能及图片展示
波形显示使用的是之前在某购物平台购买的DSO150贝壳示波器,参数如下:最高实时取样率1Msps,模拟带宽0-200KHz,取样缓冲深度1024字节,输入阻抗1MΩ,
正弦信号输出
展示100Hz正弦波信号输出
方波信号输出
展示100Hz方波
三角波信号输出
展示100Hz三角波
DC输出
直流信号输出
频率可调
正弦信号调整位1000Hz
波形信息显示
遇到的主要难题及解决方法
问题1:无法有效的调用OLED显示信息
在系统的了解了OLED显示的具体方式后,明白写内容时是按照固定的位置进行,写固定的位数,对例程进行针对性的修改后实现了信息的显示。
问题2:使用除法获取频率各位数字占用资源过多
转变思路,从之前使用FPGA制作的简易计算器获取思路,可以转化为二进制转bcd码,可以大大的减少资源占用,时间也会大大减少
问题3:对DDS等知识的首次接触
多利用平台上的资料和例程进行理解,利用示波器看到波形显示可以更快的理解
未来的计划或建议
通过继续完成其他项目来熟练运用开发板上的元件,包括旋转编码器和ADC。
同时能够更加透彻的理解各元件内部运作的逻辑。
本项目中的三角波也是使用查找表实现的,但是这种较为简单的波形可以通过函数实现,占用的寄存器会更少,可以进行改进。频率调谐字的设定也占用比较多的资源,应该可以通过计算比对,找到满足精度的计算方式,比如可以每百位作为一个分段。