项目介绍
简介
该项目来源于硬禾学堂2022年寒假在家一起练活动,使用基于小脚丫FPGA的电赛训练平台实现DDS任意波形发生器。
项目要求
DDS任意波形发生器/本地控制
- 通过板上的高速DAC(10bits/125Msps)配合FPGA内部DDS的逻辑,生成波形可调(正弦波、三角波、方波)、频率可调(DC-)、幅度可调的波形
- 生成模拟信号的频率范围为DC-20MHz,调节精度为1Hz
- 生成模拟信号的幅度为最大1Vpp,调节范围为0V-1V
- 在OLED上显示当前波形的形状、波形的频率以及幅度
- 利用板上旋转编码器和按键能够对波形进行切换、进行参数调节
环境配置
- 基于小脚丫FPGA的电赛训练平台
- Lattice Diamond 3.12.0.240.2
- 多功能调试助手
设计思路和框图
我将项目整体分成了三个部分:控制模块,显示模块,波形输出模块。三者通过Top连接到一起,在Top中进行逻辑运算和控制信息传递。
主要工作是实现使用旋转编码器和按键来改变波形的种类和频率幅度参数,然后通过OLED进行可视化并通过DAC模块进行波形的模拟输出。
硬件介绍和功能
旋转编码器
改变参数大小
旋转编码器的向左向右旋转可以调节所选参数的大小,向右是增大,向左是减小。
改变参数步进值
当按下编码器时可以改变调节参数的步进值。
频率一共有8个步进值,从1Hz开始每次提高十倍;幅度一共有两种步进值:0.1V和1V。
训练板大按键
左侧:波形切换按键
按下这个按键可以切换波形,一共有4种波形:正弦波、矩形波、三角波和直流波形。
右侧:光标切换按键
按下这个按键可以使得光标在频率和幅度之间切换,我们通过它选择要调节的参数。
彩色LED灯
这上下两个灯表示光标所在的参数。在同一时刻只会亮起一盏灯,上面的灯亮表示光标在频率上面,下面的灯亮时表示光标在幅度上面。
单色LED灯排
这个灯排显示的是频率参数调节的步进值,也可以理解为是频率调节的数字的位置。举个例子,当从右往左数第三个红灯亮时,此时调节频率的步进值就是100。
复位键:核心板上的最右侧按键
按下复位键可以让程序恢复到初始状态。
OLED屏幕
波形形状显示
中部Waveform右侧显示了当前波形的形状,一共有四种图案:正弦波、矩形波、三角波和直流波形。
波形频率显示
下方Frequency下一行显示了8位数字组成的频率值,后方单位为Hz。
波形幅度显示
下方Amplitude后方显示了由个位和十分位组成的幅度值,后方单位为V。
DAC输出模块
DAC输出模块的输入端口有9位数字输入端口和1个频率输入端口,有1个模拟信号输出端口。通过该模块实现数字量到模拟量的转换,在右侧引脚可以使用多功能调试助手的示波器功能对其输出波形进行测量。
DDS的实现
DDS信号发生器采用直接数字合成,英语名Direct Digital Synthesis,简称为DDS。DDS的实现包括查找表、相位控制器和DAC、外部滤波器。
DDS技术是根据奈奎斯特取样定律,从连续信号的相位出发,将正弦信号取样,编码,量化,形成一个正弦函数表,存在EPROM中,合成时,通过改变相位累加器的频率字来改变相位增量,也就是我们所称的步长。相位增量的不同导致一个周期内取样点的不同,在时钟频率即采样频率不变的情况下,通过相位的改变来改变频率。
本项目使用120MHz主频,目的是为了提高输出信号的最大频率。而核心板的时钟频率为12MHz,这里使用Lattice中的锁相环IP核将DAC的输入时钟频率提高到120MHz。
相位累加寄存器是DDS的核心,在我的设计中相位寄存器的字长为 28 bit,可以使得频率分辨率达到 120*10^6 / 2^28 = 0.447Hz.
输出信号的最大频率与主频的关系:fo=(M x fc )/(2^n)。其中fo为DDS信号源的输出频率,M为相位步进的步长,fc为DDS的输入时钟频率,n为累加器总位宽。当相位步进约为20*(2^28)/120=44739时即可达到20MHz的输出信号频率。
DDS系统中的幅度调制可以通过在查找表和DAC输入之间放置数字乘法器来实现。幅度的字长的是4bit,可以实现0-15的数字表示,这里代表0-10个0.1V,因此是幅度输出是0-1V,调节分辨率为0.1V。
图片展示
- 波形形状和参数的显示
- 多种波形可供切换
- 波形的频率和幅度可调,频率范围0-20000000Hz,调节精度1Hz;幅度范围0-1V,调节精度1V。
- 灯光显示光标位置和频率步进
- 控制旋钮和按键
- DAC模块
主要代码片段及说明
OLED屏幕上的参数显示
OLED模块代码基于硬禾学堂网站的代码。主要利用了其中MAIN函数显示字母的功能,进行了一系列的修改。首先需要注意的是每个字符串的单个ASCII字符都是用8位二进制数表示的,所以传入其中的数值需要将每个数字都编码成8位BCD码的形式。
5'd8: begin y_p <= 8'hb7; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= {"Amplitude ", disp_amptitude[8*2-1:8], ".", disp_amptitude[7:0]," V"}; state <= SCAN; end
OLED屏幕上的波形形状显示
波形显示同样是通过MAIN函数中的ASCII打印方式。将正弦波和矩形波等图案的8*5点阵信息增加到ASCII查找表的末尾,即可通过对应数值表示该图案。每个波形图案都是由两个字符位组成。
case(wave_type)
2'd0: char <= {"Waveform ", 8'd123, 8'd124, " "}; // sin
2'd1: char <= {"Waveform ", 8'd125, 8'd126, " "}; // square
2'd2: char <= {"Waveform ", "/", 8'd127, " "}; // triangle
default: char <= {"Waveform ", "--", " "}; // direct
endcase
多步进的频率调节
这里的frequency使用了用于oled显示的编码形式,也就是以十进制显示的每个数字都编码为8位BCD码,当调节每个数字的大小使用下面的代码凭借步进状态的不同就可以实现不同的增加量。
if (frequency[frequency_step_state*8 +: 8] < 9) begin
frequency[frequency_step_state*8 +: 8] = frequency[frequency_step_state*8 +: 8] + 1;
end
正弦波的查找表
以下模块借鉴了Chores的博客,在其基础上修改。
实现正弦输出需要使用累加器寻址然后配合使用正弦函数查找表,下面显示了正弦波具有对称性的四个状态,可以节约查找表使用的存储大小。
case(section)
2'b00: begin
lut_address = address[5:0];
cos = 9'h1ff + lut_cos;
end
2'b01: begin
lut_address = ~address[5:0];
cos = 9'h1ff + lut_cos;
end
2'b10: begin
lut_address = address[5:0];
cos = 9'h1ff - lut_cos;
end
2'b11: begin
lut_address = ~address[5:0];
cos = 9'h1ff - lut_cos;
end
endcase
其他波形的相位函数
对于矩形波和三角波,他们的波形和相位之间的函数关系简单,可以通过表达式来完成。
//Code below is designed to generate Square wave
assign square_dac = {10{dds_phase[27]}};
//Code below is designed to generate Trig wave
assign trig_dac = dds_phase[27] ? ~dds_phase[26:17] : dds_phase[26:17];
//Code below is designed to generate Saw wave
assign dc_dac = 28'd0 - 1'd1;
// assign dc_dac = dds_phase[27:18];
光标和步进显示灯
简单的实现了显示当前光标状态和频率步进值的功能。
assign logs = ~(8'd1 << frequency_step_state);
assign led2 = cursor_state? 3'd111: 3'd110;
assign led1 = cursor_state? 3'd110: 3'd111;
遇到的主要难题及解决方法
BCD和BIN转换资源占用超量
由于在oled模块中参数必须使用8位BCD编码来表示,而在DDS模块中使用的是BIN编码,两者之间必须要进行转换。第一次使用累除的方法将BIN转换为BCD,似乎做循环除法占用了大量资源,导致报错无法生成下载文件。后来改换为从BCD转换为BIN编码,尽管做乘法消耗也比较大但还是比循环除法要省事。
多种波形都使用查找表很复杂
该项目需要能够输出多种波形,如果每一个波形对应一个查找表的话,编码量大且资源占用高。后来在网上查找到了使用相位函数公式的方法,极大的简化了矩形波、三角波和直流波形的生成方式。
OLED显示功能异常
平台上的oled代码中又很有冗余的部分,增加新功能时也需要进行大量修改。前期未能理解字符串的显示过程,后查阅资料看到其编码方式,才修改变量实现动态参数的显示。在调试功能的时候,使用led灯来显示变量数值,可以有效的进行异常的分析排查。
资源占用情况
Design Summary:
Number of registers: 387 out of 4635 (8%)
PFU registers: 387 out of 4320 (9%)
PIO registers: 0 out of 315 (0%)
Number of SLICEs: 1348 out of 2160 (62%)
SLICEs as Logic/ROM: 1348 out of 2160 (62%)
SLICEs as RAM: 0 out of 1620 (0%)
SLICEs as Carry: 415 out of 2160 (19%)
Number of LUT4s: 2527 out of 4320 (58%)
Number used as logic LUTs: 1697
Number used as distributed RAM: 0
Number used as ripple logic: 830
Number used as shift registers: 0
Number of PIO sites used: 36 + 4(JTAG) out of 105 (38%)
Number of block RAMs: 0 out of 10 (0%)
Number of GSRs: 1 out of 1 (100%)
EFB used : No
JTAG used : No
Readback used : No
Oscillator used : No
Startup used : No
POR : On
Bandgap : On
Number of Power Controller: 0 out of 1 (0%)
Number of Dynamic Bank Controller (BCINRD): 0 out of 6 (0%)
Number of Dynamic Bank Controller (BCLVDSO): 0 out of 1 (0%)
Number of DCCA: 0 out of 8 (0%)
Number of DCMA: 0 out of 2 (0%)
Number of PLLs: 1 out of 2 (50%)
Number of DQSDLLs: 0 out of 2 (0%)
Number of CLKDIVC: 0 out of 4 (0%)
Number of ECLKSYNCA: 0 out of 4 (0%)
Number of ECLKBRIDGECS: 0 out of 2 (0%)
未来的计划或建议
- 优化显示功能,使得光标和步进值可以直接在OLED屏幕上可视化
- 增加频率和幅度的精度