- 项目介绍
本次项目完成小脚丫寒假训练营的项目3- DDS任意波形发生器/本地控制。能够通过开发的K1,K2按钮与旋转编码器实现对振幅-频率的分别控制,核心板上的四个开关分别实现正弦波,三角波,方波,直流的输出。同时由核心板上的按键分别实现波形量程的控制。并将结果通过DAC输出,并同时由OLED显示输出结果。
2.设计思路
项目通过旋转编码器,按钮实现参数调节,开关实现波形调节。通过目标波形的数据控制相位累加器的累加值实现波形的频率稳定与可调。通过调幅因数实现幅度可调。其中正弦波波形采用了查找表的方式实现输出。
Diamond综合电路如下:
框图如下:
资源使用情况截图:
3.简单硬件介绍
DAC介绍:本次项目使用了10位,125Mps的高速DAC模块,实际使用中DAC_clk选取了120Mps的频率。而其中的核心部件则为3PD5651E 10-bit 125Mps CMOS数模变化器,输入信号为clk信号与10bit信号数据。
OLED介绍:本次项目采取128*64OLED显示屏驱动芯片则为SSD1306.。故而从使用角度说我们只需要与SSD1306进行通讯,而OLED显示屏则由SSD1306控制。在本项目中由于引脚已经预先配置好了,故而我们使用4线串行总线进行通信。通过DC管脚做模式选择(DATA = 1'b1, CMD = 1'b0;),SCL时钟,SDA数据输入,RES做清空。
4.功能实现
如图所示,在OLED屏幕上显示了当前的输出的波形种类,同时可以选择调节波形的幅值或频率,幅值的范围为0~1.00V,频率的范围则为0~20M,频率的读数方法为三个数量级数值之和,即为(**M+**K+***)Hz。通过旋转编码器控制大小,按键控制调整的数据种类,开关控制波形形状。
按键分配如下:
实际输出表现图示:
正弦波:
方波:
三角波:
5.主要代码片段分析
旋转编码器:通过AB间的90度相位差,可以知道若A上升沿时B为低电平或A下降沿时B为高电平,则顺时针转动,反之则逆时针转动。故可以得到旋转检测代码如下:
always@(posedge clk_in or negedge rst_n_in)begin
if(!rst_n_in)begin
Right_pulse <= 1'b0;
Left_pulse <= 1'b0;
end else begin
if(A_pos && B_state) Left_pulse <= 1'b1;
else if(A_neg && B_state) Right_pulse <= 1'b1;
else begin
Right_pulse <= 1'b0;
Left_pulse <= 1'b0;
end
end
end
通过这一段代码可以记录旋转编码器左旋或右旋的状态。同时根据此改变对应的数据。计数方式为用16进制数记录10进制数,举例若振幅数据为056(hex),我们需要的数据为56(dec),换言之,实际需要的振幅为0.56V。当每四位加满(减满)需要进位时通过如下代码逻辑实现模仿十进制的进位或退位。项目中的数据均按此法进行存储。
if(Right_pulse && num_A <= 25'd255) begin
if(num_A[3:0] != 9) begin
num_A<=num_A+25'd1;
end else begin
num_A[3:0] <= 0;
if(num_A[7:4] != 9) begin
num_A[7:4] <= num_A[7:4] + 1;
end else begin
num_A[7:4] <= 0;
num_A[11:8] <= num_A [11:8] + 1;
end
end
end else begin
if(Left_pulse && num_A >= 25'd1) begin
if(num_A[3:0] != 0) begin
num_A<=num_A-25'd1;
end else begin
num_A[3:0] <= 9;
if(num_A[7:4] != 0) begin
num_A[7:4] <= num_A[7:4] - 1;
end else begin
num_A[7:4] <= 9;
num_A[11:8] <= num_A [11:8] - 1;
end
end
end
end
DDS:
直接数字频率合成(DDS)实现信号发生,其中正弦波形的产生采用查找表的方法,通过相位累加实现任意频率,幅值调节则由调幅因数实现。
always @(posedge clk_120m or negedge rst) begin
if (!rst) begin
phase_acc<=0;
end else begin
phase_acc <= phase_acc + num_phase;
end
end
如上面的代码所示,相位累加器本质就是一个计数器,其截断输出用作正弦查找表的地址。查找表中的每个地址均对应正弦波的从0°到360°的一个相位点。由于正弦波的周期性,实际应用时只需记录1/4的数据即可。而由公式Fn=M*Fclk/2^N可知,应用时我们只需要改变M值即可改变频率,若M变为原来的两倍,则频率也为原来的两倍,所以我们需要确定一个基准频率ΔM,此时输出频率为1Hz,当我们需要NHz的频率时,将M变为N倍的ΔM即可。本程序采用的是120M时钟30位累加器,计算得ΔM为9,故按照9的倍率进行调节即可,相位步进确定代码如下:
always@(posedge clk) begin
num_phase_1 = 9*(100*num_f0[11:8] + 10*num_f0[7:4] + num_f0[3:0])
+ 9*(100*num_f1[11:8] + 10*num_f1[7:4] +num_f1[3:0])*1000
+ 9*(10*num_f2[7:4] +num_f2[3:0])*1000000;
end
对于波形而言,方波与三角波都能直接通过对频率地址进行处理得到,而正弦波可以通过相位累加器的截断输出用作正弦查找表的地址。查找表中的每个地址均对应正弦波的从0°到360°的一个相位点。由于正弦波的周期性,实际应用时只需记录1/4的数据即可。查找表位10位地址10位精度的查找表,既1024个数据,不过根据前面提到的对称性只用存储256个数据,故实际存储的是8位地址,9bit大小的数据。
方波:
wire square_tap=phase_acc[29];
assign square_data={10{square_tap}};
三角波:
assign triangle_data=phase_acc[29] ? ~phase_acc[28:19]:phase_acc[28:19];
正弦波:
assign sin_out = sine_onecycle_amp[9:0];
assign sel = phase[9:8];
sin_table u_sin_table(address,sine_table_out);
always @(sel or sine_table_out)
begin
case(sel)
2'b00: begin
sine_onecycle_amp = 9'h1ff + sine_table_out[8:0];
address = phase[7:0];
end
2'b01: begin
sine_onecycle_amp = 9'h1ff + sine_table_out[8:0];
address = ~phase[7:0];
end
2'b10: begin
sine_onecycle_amp = 9'h1ff - sine_table_out[8:0];
address = phase[7:0];
end
2'b11: begin
sine_onecycle_amp = 9'h1ff - sine_table_out[8:0];
address = ~ phase[7:0];
end
endcase
end
//查找表:
always @(address)
begin
case(address)
8'h0: sin=9'h000;
… …
8'hff: sin=9'h1FE;
endcase
end
OLED驱动:
if(cnt_main >= 5'd20) cnt_main <= 5'd4;
else cnt_main <= cnt_main + 1'b1;
case(cnt_main) //MAIN状态
5'd0: begin state <= INIT; end
5'd1: begin y_p <= 8'hb1; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end
5'd2: begin y_p <= 8'hb2; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "A: V ";state <= SCAN; end
5'd3: begin y_p <= 8'hb3; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "f: Hz ";state <= SCAN; end
... ...
5'd19: begin
... ...
end
default: state <= IDLE;
endcase
end
如上代码所示,OLED的驱动通过定位显示地址与内容后反复循环刷新的方式实现显示。每个case下对应某一种不同的显示数据,在这些case之间循环即可实现反复刷新,实时显示波形的状态。
到的主要困难与对未来的展望。
在编写程序的过程中花了一定的时间去研究了解OLED与DAC的运行模式与研读示例程序以实现自己的功能。同时在数字表示上没有采取BIN转BCD的算法,而是直接用16进制记录,这样做的好处是OLED显示时只用4个一组的取出即可直接用于显示,但相对的,我在计数上的逻辑就较相对为复杂,采用了模仿十进制进位的方式编写了逻辑。同时由于采用的是查找表法而主频120M最高频率要求为20M若不进行滤波则无法不失真的输出波形,为此我设计了滤波器以实现高频输出功能:
未来计划对开发板上暂未用到的ADC模块进行学习,同时实践一些难度更高的项目。
附件:见附件链接