项目介绍
1 通过板上的高速DAC(10bits/125Msps)配合FPGA内部DDS的逻辑,
生成波形可调(正弦波、三角波、方波)、频率可调(DC-)、幅度
可调的波形
2 生成模拟信号的频率范围为DC-20MHz,调节精度为1Hz
3 生成模拟信号的幅度为最大1Vpp,调节范围为0.1V-1V
4 在OLED上显示当前波形的形状、波形的频率以及幅度
5 利用板上旋转编码器和按键能够对波形进行切换、进行参数调节
设计思路
本次实验中使用的模块包括OLED显示模块,DDS模块,旋钮模块,分频器模块,锁相环模块。
硬件介绍
本次使用的是硬禾学堂提供的基于小脚丫FPGA的电赛训练平台。使用的综合软件是Lattic Diamond。
由于疫情原因对于波形检测使用了硬禾学堂提供的便携示波器
实现的功能以及图片展示
主要代码片段及说明
顶层模块
通过顶层代码连接各个模块并传递参数。Verilog部分代码如下:
module TOP_1(
input clk_in, //系统时钟
input rst_n_in, //系统复位,低有效
input key_a, //旋转编码器A管脚
input key_b, //旋转编码器B管脚
input change, //切换按钮 频率 幅度
input way, //切换按钮 波形图像
output oled_rst, //OLCD液晶屏复位
output oled_dcn, //OLCD数据指令控制
output oled_clk, //OLCD时钟信号
output oled_dat, //OLCD数据信号
output dac_clk,
output [9:0] dac_data
);//////略///
dds_main u1(
.clk(clkop),
.dac_data(dac_data),
.dac_clk(dac_clk),
.check(check_1),
.frequence(F),
.range(range)
);
posedge_check posedge_check_u5(
.clk(clk_in),
.rst_n(rst_n_in),
.check(way),
.pos_check(pos)
);
a120M a120M_u3 (
.CLKI(clk_in),
.CLKOP(clkop)
);
OLED_12864 OLED_12864_u1 (
.clk (clk_in) ,
.rst_n (rst_n_in),
.data(oleddata),
.data1(data1),
.state1(state1),
.way(way_1),
.oled_csn(oled_csn),
.oled_rst(oled_rst),
.oled_dcn(oled_dcn),
.oled_clk(oled_clk),
.oled_dat(oled_dat)
);
TEMP_1 TEMP_1_u2 (
.clk(clk_in),
.res(rst_n_in),
.indata1(key1),
.indata2(key2),
.change(sum),
.data(oleddata),
.state1(state1),
.data1(data1)
);
XUANNIU_1 XUANNIU_1_u3(
.clk(clk_in), //系统时钟
.rst_n(rst_n_in), //系统复位,低有效
.key_a(key_a), //旋转编码器A管脚
.key_b(key_b), //旋转编码器B管脚
.clk_500us(clk_500us),
.key1(key1),
.key2(key2),
.L_pulse(L_pulse),
.R_pulse(R_pulse)
);
DIVIDE_1 #(.WIDTH(32),.N(6)) u4 (
.clk(clk_in),
.rst_n(rst_n_in),
.clkout(clk_500us)
);
endmodule
OLED模块
通过SPI协议向OLED屏幕书写数据,OLED模块中连接着ASCII码模块,可以通过旋钮模块进行动态调节数据,显示生成波形的频率和幅度。Verilog部分代码如下(部分代码参考电子森林开原代码):
module OLED_12864
(
input clk, //12MHz系统时钟
input rst_n, //系统复位,低有效
input [3:0] sw,
input key_a,
input key_b,
input [63:0] data,
input [7:0] data1,
input state1,
input [1:0] way,
output reg oled_csn, //OLCD液晶屏使能
output reg oled_rst, //OLCD液晶屏复位
output reg oled_dcn, //OLCD数据指令控制
output reg oled_clk, //OLCD时钟信号
output reg oled_dat //OLCD数据信号
);////// 略///
MAIN:begin
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 <= " ";state <= SCAN; end
6'd2 : begin y_p <= 8'hb1; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end
6'd3 : begin y_p <= 8'hb2; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end
6'd4 : begin y_p <= 8'hb3; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";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 <= " ";state <= SCAN; end
6'd9 : begin y_p <= 8'hb0; x_ph <= 8'h10; x_pl <= 8'h00; mem_hanzi_num <= 8'd0; state <= CHINESE; end
6'd10: begin y_p <= 8'hb0; x_ph <= 8'h11; x_pl <= 8'h00; mem_hanzi_num <= 8'd2; state <= CHINESE; end
6'd11: begin y_p <= 8'hb0; x_ph <= 8'h12; x_pl <= 8'h00; mem_hanzi_num <= 8'd4; state <= CHINESE; end
6'd12: begin y_p <= 8'hb0; x_ph <= 8'h13; x_pl <= 8'h00; mem_hanzi_num <= 8'd6; state <= CHINESE; end
6'd13: if(way == 2'b00) begin
y_p <= 8'hb0; x_ph <= 8'h15; x_pl <= 8'h00; mem_sin_num <= 8'd0; state <= SIN;
end
else if(way == 2'b01) begin
y_p <= 8'hb0; x_ph <= 8'h15; x_pl <= 8'h00; mem_sin_num <= 8'd8; state <= SIN;
end
else if(way == 2'b10) begin
y_p <= 8'hb0; x_ph <= 8'h15; x_pl <= 8'h00; mem_sin_num <= 8'd16; state <= SIN;
end
else if(way == 2'b11) begin
y_p <= 8'hb0; x_ph <= 8'h15; x_pl <= 8'h00; mem_sin_num <= 8'd24; state <= SIN;
end
6'd14: begin y_p <= 8'hb3; x_ph <= 8'h10; x_pl <= 8'h00; mem_hanzi_num <= 8'd8; state <= CHINESE; end
6'd15: begin y_p <= 8'hb3; x_ph <= 8'h11; x_pl <= 8'h00; mem_hanzi_num <= 8'd10; state <= CHINESE; end
6'd16: begin y_p <= 8'hb5; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd10; char <= {data,"HZ"};state <= SCAN; end
6'd17: begin y_p <= 8'hb6; x_ph <= 8'h10; x_pl <= 8'h00; mem_hanzi_num <= 8'd12; state <= CHINESE; end
6'd18: begin y_p <= 8'hb6; x_ph <= 8'h11; x_pl <= 8'h00; mem_hanzi_num <= 8'd14; state <= CHINESE; end
6'd19: if(state1 == 1) begin
y_p <= 8'hb7; x_ph <= 8'h12; x_pl <= 8'h00; num <= 5'd4; char <= {"0.",data1,"v"};state <= SCAN;
end
else if(state1 == 0)begin
y_p <= 8'hb7; x_ph <= 8'h12; x_pl <= 8'h00; num <= 5'd4; char <= {" ",data1,"v"};state <= SCAN;
end
6'd21: begin cnt_main <= 6'd9; end
endcase
ASCII码转换模块
通过旋钮进行自加自减,最后通过assign进行拼接,完成OLED动态显示过程中的变量参数设计。Verilog部分代码如下:
module TEMP_1
(
clk,
res,
indata1,
indata2,
change,
data,
state1,
data1
);////// 略///
always@(posedge clk or negedge res) begin
if(!res)begin
temp[0] <= 8'b0011_0000;
temp[1] <= 8'b0011_0000;
temp[2] <= 8'b0011_0001;
temp[3] <= 8'b0011_0010;
temp[4] <= 8'b0011_0000;
temp[5] <= 8'b0011_0000;
temp[6] <= 8'b0011_0000;
temp[7] <= 8'b0011_0000;
temp1 <= 8'b0011_0001;
state1 <= 1;
end
else if (change == 1)begin
if(indata2 == 1'b1)begin
if(temp[7] > 8'b0011_0000)begin
temp[7] <= temp[7] - 1'b1;
end
else if(temp[7] == 8'b0011_0000)begin
temp[7] <= 8'b0011_1001;
if(temp[6] > 8'b0011_0000)begin
temp[6] <= temp[6] - 1'b1;
end
else if(temp[6] == 8'b0011_0000)begin
temp[6] <= 8'b0011_1001;
if(temp[5] > 8'b0011_0000)begin
temp[5] <= temp[5] - 1'b1;
end
else if(temp[5] == 8'b0011_0000)begin
temp[5] <= 8'b0011_1001;
if(temp[4] > 8'b0011_0000)begin
temp[4] <= temp[4] - 1'b1;
end
else if(temp[4] == 8'b0011_0000)begin
temp[4] <=8'b0011_1001;
if(temp[3] > 8'b0011_0000)begin
temp[3] <= temp[3] - 1'b1;
end
else if(temp[3] == 8'b0011_0000)begin
temp[3] <= 8'b0011_1001;
if(temp[2] > 8'b0011_0000)begin
temp[2] <= temp[2] - 1'b1;
end
else if(temp[2] == 8'b0011_0000)begin
temp[2] <= 8'b0011_1001;
if(temp[1] > 8'b0011_0000)begin
temp[1] <= temp[1] - 1'b1;
end
else if(temp[1] == 8'b0011_0000)begin
temp[1] <= 8'b0011_1001;
if(temp[0] > 8'b0011_0000)begin
temp[0] <= temp[0] - 1'b1;
end
end
end
end
end
end
end
end
end////// 略///
assign data1 = temp1;
assign outdata1 = temp[0];
assign outdata2 = temp[1];
assign outdata3 = temp[2];
assign outdata4 = temp[3];
assign outdata5 = temp[4];
assign outdata6 = temp[5];
assign outdata7 = temp[6];
assign outdata8 = temp[7];
assign data = {outdata1,outdata2,outdata3,outdata4,outdata5,outdata6,outdata7,outdata8};
DDS模块
通过相位累加器完成频率的控制Verilog代码如下(部分参考电子森林开源代码):
module dds_main(
clk,
frequence,
check,
range,
dac_data,
dac_clk
);input clk;input [1:0] check;output [9:0] dac_data; output dac_clk;input [31:0] frequence;input [3:0] range;
wire [3:0] range_1;
assign range_1 = range;
wire [31:0] next_phase;
wire [7:0] phase;
reg [31:0] a;
// 相位累加器
assign next_phase = (32'h00000024 + frequence * 32'h24) + a;
always@(posedge clk)
a <= next_phase;
assign phase = a[31:24];
wire [9:0] sine_data;
lookup_tables u_lookup_tables(phase,check,range_1,sine_data);
assign dac_data = sine_data;
assign dac_clk = ~clk;
endmodule
正弦波 三角波 方波的波表以及幅度调节模块
通过制作的波表使DAC可以输出想要的波形,此模块中还含有波形幅度控制模块,可以配合旋钮进行波形幅度的控制。Verilog部分代码如下(部分参考电子森林开源代码):
module lookup_tables(
phase,
check,
range,
sin_out
);////// 略///
always @(sel or sine_table_out or phase)
begin
if(check == 2'b00) begin
case(sel)
2'b00: begin
sine_onecycle_amp = 9'h12C+sine_table_out[8:0];
address = phase[5:0];
end
2'b01: begin
sine_onecycle_amp = 9'h12C+sine_table_out[8:0];
address = ~phase[5:0];
end
2'b10: begin
sine_onecycle_amp = 9'h12C-sine_table_out[8:0];
address = phase[5:0];
end
2'b11: begin
sine_onecycle_amp = 9'h12C-sine_table_out[8:0];
address = ~ phase[5:0];
end
endcase
end
else if(check == 2'b01) begin
case(sel)
2'b00: begin
sine_onecycle_amp = sine_table_out[8:0];
address1 = phase[7:0];
end
2'b01: begin
sine_onecycle_amp = sine_table_out[8:0];
address1 = phase[7:0];
end
2'b10: begin
sine_onecycle_amp = 9'd315 + sine_table_out[8:0];
address1 = phase[7:0];
end
2'b11: begin
sine_onecycle_amp = 9'd315 + sine_table_out[8:0];
address1 = phase[7:0];
end
endcase
end
else if(check == 2'b10) begin
case(sel)
2'b00: begin
sine_onecycle_amp = sine_table_out[8:0];
address2 = phase[7:0];
end
2'b01: begin
sine_onecycle_amp = 9'd315 + sine_table_out[8:0];
address2 = phase[7:0];
end
2'b10: begin
sine_onecycle_amp = 9'd315 + sine_table_out[8:0];
address2 = phase[7:0];
end
2'b11: begin
sine_onecycle_amp = sine_table_out[8:0];
address2 = phase[7:0];
end
endcase
end
end////// 略///
module sin_table(address,address1,address2,sin,check);
output [8:0] sin;
input [5:0] address;
input [7:0] address1;
input [7:0] address2;
input [1:0] check;
reg [9:0] state;
reg [8:0] sin;
localparam SIN = 10'h1, Triangle = 10'h2, Square = 10'h4;
always @(address)
begin
if(check == 2'b00)
state <= SIN;
else if(check == 2'b01)
state <= Square;
else if(check == 2'b10)
state <= Triangle;
case(state)
SIN:begin
case(address)
6'd0: sin=9'd 0 ;
6'd1: sin=9'd 7 ;
6'd2: sin=9'd 15 ;
6'd3: sin=9'd 3 ;
6'd4: sin=9'd 29 ;
6'd5: sin=9'd 36 ;
6'd6: sin=9'd 44 ;
6'd7: sin=9'd 51 ;////// 略///
endmodule
旋钮模块以及分频模块
借鉴电子森林开源代码,这里就不过多描述了。
资源占用
遇到的主要难题及解决方法
在DDS模块中,频率的控制和幅度的控制需要统一,所以要同步控制三种波形,需要提前计算或者一步一步尝试。
在OLED模块部分,一开始没有什么思路,通过学习电子森林开源代码了解了SPI协议的书写,完善了动态输出过程中ASCII转换的问题,在后面的学习中发现可以通过左移加三算法进行快速计算,相比我自己写出来的部分要好很多,但由于时间较紧张就没有进行完善。
未来的计划
希望在将来可以多参加FPGA此类活动,增加设计思路和经验,熟悉Verilog的各种使用,用更少的资源完成需要的任务。