一、项目需求
- 通过板上的高速DAC(10bits/125Msps)配合FPGA内部DDS的逻辑,生成波形可调(正弦波、三角波、方波)、频率可调、幅度可调的波形
- 生成模拟信号的频率范围为DC-5MHz,调节精度为1Hz
- 生成模拟信号的幅度为最大1Vpp,调节范围为0.1V-1V
- 通过UART同PC连接,在PC上可以使用Matlab、Labview或其它调试工具来控制波形的切换、参数的改变
二、整体设计思路
- 在PC使用Matlab软件中设定幅度、频率、波形等参数。
- PC通过USB转TTL串口使用UART协议向FPGA传输数据。
- 在FPGA中使用DDS模块产生10位的波形数据。
- 将10位的波形数据发给DAC,产生电压。
三、系统框图
四、模块实现
4.1时钟模块
在该项目中,由于最大的输出频率为5MHz,又因为要保证一个正弦波观测时不发生明显失真,最少要一个周期20个点,因此最小需要100MHz的时钟信号。所以,在本项目中,使用IP核设置的输出时钟信号为120MHz,如果需要更加理想的波形,可以继续增大时钟信号的频率。
4.2UART_RX模块
在该项目中,由于需要PC上位机和FPGA通信,因此决定采用UART协议通信,同时由于只需要PC向FPGA发送信息,因此在该项目中只做了UART_RX模块。
在该项目中,设置波特率为115200,因此可以计算出在FPGA内部的一位的计数周期为
120M/115200=1041
module UART_RX(
input clk, //120MHz
input rst,
input uart_data_s, //串行数据
output reg [7:0] uart_data_p, //并行数据
output reg rx_done //接收完成信号
);
parameter B_CNT = 1042; //120M/115200 波特率计数器最大值
reg [1:0] uart_data_s_r1;
reg [1:0] uart_data_s_r2; //边沿检测
reg uart_data_s_r3;
reg uart_en; //使能信号
reg [23:0] time_cnt = 24'd0; //115200波特率计数器
reg [3:0] bit_cnt = 4'd0; //位计数器
reg [2:0] judge_cnt;//判决计数器
reg [7:0] rx_data; //接收数据寄存器
//消抖
always @ (posedge clk)
begin
uart_data_s_r1 <= {uart_data_s_r1[0] , uart_data_s};
end
//边沿检测
always @ (posedge clk)
begin
uart_data_s_r2 <= {uart_data_s_r2[0] , uart_data_s_r1[1]};
end
//使能信号控制
always @ (posedge clk or negedge rst)
begin
if (!rst)
uart_en <= 1'd1;
else
begin
if (uart_data_s_r2 == 2'b10)
uart_en <= 1'd1;
else if ((bit_cnt == 4'd9) && (time_cnt == B_CNT / 2))
uart_en <= 1'd0;
else
uart_en <= uart_en;
end
end
//波特率计数器
always @ (posedge clk or negedge rst)
begin
if (!rst)
time_cnt <= 24'd0;
else if (uart_en)
begin
if (time_cnt == B_CNT - 1)
time_cnt <= 24'd0;
else
time_cnt <= time_cnt + 1;
end
else
time_cnt <= 24'd0;
end
//位计数器
always @ (posedge clk or negedge rst)
begin
if (!rst)
bit_cnt <= 0;
else if (uart_en)
begin
if (time_cnt == B_CNT - 1)
bit_cnt <= bit_cnt + 1;
else
bit_cnt <= bit_cnt;
end
else
bit_cnt<=0;
end
//三次判决计数
always @ (posedge clk or negedge rst)
begin
if (!rst)
judge_cnt <= 4'd0;
else if (uart_en)
begin
if (time_cnt == B_CNT /4||time_cnt==B_CNT /2||time_cnt==3*B_CNT /4)
begin
if(uart_data_s==1)
judge_cnt<=judge_cnt+1;
else
judge_cnt<=judge_cnt;
end
else if(time_cnt==1)
judge_cnt<=0;
end
else
judge_cnt<=0;
end
//根据判决结果确定输入电压值
always @ (posedge clk or negedge rst)
begin
if (!rst)
uart_data_s_r3 <= 0;
else if (uart_en)
begin
if (time_cnt ==B_CNT-10)
begin
if(judge_cnt>=2)
uart_data_s_r3<=1;
else
uart_data_s_r3<=0;
end
end
else
uart_data_s_r3<=0;
end
//接收缓存
always @ (posedge clk or negedge rst)
begin
if (!rst)
rx_data <= 8'd0;
else if (uart_en)
begin
if (time_cnt == B_CNT-5)
begin
case (bit_cnt)
1:rx_data[0] <=uart_data_s_r3;
2:rx_data[1] <=uart_data_s_r3;
3:rx_data[2] <=uart_data_s_r3;
4:rx_data[3] <=uart_data_s_r3;
5:rx_data[4] <=uart_data_s_r3;
6:rx_data[5] <=uart_data_s_r3;
7:rx_data[6] <=uart_data_s_r3;
8:rx_data[7] <=uart_data_s_r3;
endcase
end
else
rx_data <= rx_data;
end
end
//接收
always @ (posedge clk or negedge rst)
begin
if (!rst)
uart_data_p <= 8'd0;
else if (bit_cnt == 4'd9)
uart_data_p[7:0] <= rx_data[7:0];
else
uart_data_p <= uart_data_p;
end
//接收完成标志
always @ (posedge clk or negedge rst)
begin
if (!rst)
rx_done <= 0;
else if (bit_cnt == 4'd9&& (time_cnt == B_CNT / 4))
rx_done <= 1'd1;
else
rx_done <= 0;
end
endmodule
4.2 DDS模块
该模块中,需要根据需要的频率输入频率控制字,频率控制字在相位累加器中不断累加直到计满低若干位后溢出,将高若干位传入相位调制器,相位调制器在此基础上加上相位控制字,然后,根据相位调制器输出的值从波形存储器中查找地址,获取输出的波形数据,然后进入幅度控制模块,在该模块中将波形数据与幅度控制字相乘,然后取高10位,得到输入DAC的波形数据。
在该模块中,设相位累加器的高位有M位,总位数为N位,fc为时钟频率,f_word为频率控制字,可以得到输出频率为
f=fc/(2^N)*f_word
由于频率调节范围为1Hz-5MHz,因此最小调节频率应该为1Hz,FPGA时钟信号为120MHz,因此可以知道N至少应该为27位,考虑到FPGA内部的存储以及传输,因此在该项目中,令N为28位。由于正弦信号地址长度为4096,因为5MHz信号一个周期应该要经过4096个地址长度,因此可以计算得到5M/4096=4882,因此低位至少应该要小于等于12位,因此M的值为16位。
DDS模块流程图
由于方波和三角波的波形较为简单,同时为了减少FPGA内部存储的使用,因此方波和三角波的波形是通过地址直接计算得到的。
其中方波可以用如下代码实现
else if(mode==1'd1)//方波
begin
if(phase_ctrl<2048)
wave_data<=amp+10'b1000000000;
else if(phase_ctrl<4096)
wave_data<=-amp+10'b1000000000;
end
三角波可以用如下代码实现
if(mode==2)//三角波
begin
if(phase_ctrl<1024)
address<=phase_ctrl;
else if(phase_ctrl<2048)
address<=2048-phase_ctrl;
else if(phase_ctrl<3072)
address<=phase_ctrl-2048;
else if(phase_ctrl<4096)
address<=4096-phase_ctrl;
end
4.3 sin_list模块
考虑到ICE40UP5K FPGA芯片中RAM的大小为80kB,同时方便程序中计算,因此选用了1024位地址长度,12位的数据的正弦波数据表,占用了1536B。实际上,如果需要更精细的波形,可以再适当加大地址长度,但是由于在该项目中差别非常小,几乎无法看出,因此选择了1024位地址长度。同时,由于正弦波信号波形每1/4周期都存在完全一致或者完全对称的图形,因此我们在该模块中只存储了1/4的波形。
4.4 MATLAB模块
在该模块中,使用了MATLAB中的APP设计工具,通过该工具设计相应的操作界面。在该界面中,主要包括了幅度、频率以及波形信息选择。由于幅度、频率控制字的计算涉及小数,在MATLAB中计算比较方便,因此在MATLAB中直接计算出幅度、幅度控制字。由于FPGA供电为3.3V,因此可以将幅度转换成相应的数字量。
幅度/1000=3.3/1024*幅度控制字
其中幅度的单位为mV。
因此有
幅度控制字=幅度*0.31
由于频率
f=fc/(2^N)*f_word
所以可以知道
频率控制字=2.24*频率
%模式选择
if(app.DropDown.Value == "正弦波")
mode = 0;
elseif(app.DropDown.Value == "方波")
mode = 1;
elseif(app.DropDown.Value == "三角波")
mode = 2;
else
mode = 0;
end
%幅值
amp=ceil(0.31.*app.mVEditField.Value);
%频率
freq=ceil(1.12.*app.HzEditField.Value);
%端口
com=app.DropDown_2.Value;
%连接端口
baud = 115200;
port = serialport(com,baud);
%发送数据
data = zeros(1,8);
data(1) = mode;
data(2) =amp;
data(3) = mod(freq,256);
data(4) = mod((freq-data(3))/256,256);
data(5) = mod(((freq-data(3))/256-data(4))/256,256);
data(6) = mod((((freq-data(3))/256-data(4))/256-data(5))/256,256);
write(port,data,"uint8");
五、实现功能截图
5.1 1V 1000Hz 正弦波
5.2 1V 1000Hz 方波
5.3 1V 1000Hz 三角波
5.4 0.1V 1000Hz 正弦波
5.5 0.1V 1000Hz 方波
5.6 0.1V 1000Hz 三角波
5.7 1V 10Hz 正弦波
5.8 1V 1MHz 正弦波
六、资源占用截图
七、主要难题
在该项目中,主要的难点在于DDS的实现,由于频率控制,以及如何使用地址来进行查表,这都花费了较长时间去理解并且实现。
此外对于FPGA的使用不够熟练,在计算频率控制字以及幅度控制字,以及实现UART的部分都遇到了一定的困难。
八、未来的计划建议
由于该模块输出最大频率为5MHz,相对较大,因此在实际输出时会存在关于时钟信号对称的高频部分,由于DAC的输出最大频率为120MHz,因此这些谐波成分会对最后输出的结果产生一些比较明显的失真,这些部分的信号可通过在输出口后面加入一个低通滤波器,考虑到滤波器的衰减造成的失真,因此该滤波器的截止频率应当至少为25MHz,具体截止频率及阶数可以通过ADIsimDDS仿真确定。
此外可以增加调节信号相位以及占空比的部分,相位可以通过DDS模块中的相位调制器中相位控制字来确定,占空比可以在通过在DDS模块中加入占空比控制字,与方波和正弦波的地址控制字相乘,从而就可以调节方波以及正弦波的占空比。
在该项目中,还可以添加相应的存储功能,然后通过与正弦波相似的方法,通过地址从中读取波形信息,从而即可实现能够产生幅度可调、频率可调的存储波形的信号发生器。
九、硬件接线
实际使用的时候需要将小脚丫板上的19号口,与TTL模块的TX相连,GND与TTL 的GND相连,18号口为复位信号口,在不使用时需要连接3.3V的引脚,例如1号口,需要复位时拔下该杜邦线即可