前言
在上一篇文章中,我们学习和实现了数字上变频系统中非常关键环节 —— CIC插值滤波器的FPGA实现,感兴趣的小伙伴可以再看一下~文章链接:ZYNQ学习笔记(六):数字信号处理—CIC插值滤波器的FPGA实现
在CIC插值滤波器实现后,那这篇文章就可以学习如何完整的构建数字上变频系统了~ 掌握数字基带信号到射频信号的完整处理流程~
一、数字上变频(DUC)
数字上变频的基本功能是将基带信号上变频到载波频率上,数字上变频DUC实现了信号从基带信号到带通信号的转换。因为基带信号的采样率较低,不能满足射频发射的要求,所以基带信号往往经过滤波和内插转换成高采样率,从而调制到中频载波频率上。
经典的DUC流程框架~
可总结为:I 和 Q 基带信号输入。内插滤波,提高采样率。成形滤波,调整信号频谱形状。DDS 模块,生成混频所需的正弦和余弦信号。混频,将 I 和 Q 信号上变频到目标频率。DAC,将数字信号转换为模拟信号。
这里就延伸出来几个问题:
假设我想把一个采样率25MHZ,频率1MHZ的单音信号,为了满足后续射频发射的需求,我们需要把信号先搬移到中频,即40Mhz或者60MHZ的位置,那么该如何处理原始信号?
1、首先需要确定的是,根据奈奎斯特定理,40MHZ频率对应的采样率应不低于80MHZ,但是为了稳妥起见,我们一般设置为频率的3-5倍,因此这里我们把最终的采样率设置为200MHZ~这也就需要CIC内插滤波模块实现8倍上采样。
2、DDS载波频率如何确定?正交调制这个过程的原理是什么?
这里我们先做一下假设,假设I为sin wt,Q为cos wt,载波频率为其10倍,即分别为cos 10wt,sin 10wt;那么按图中混频、正交调制后,最终输出结果是多少?
由此可见,虽然其频率保持不变,但相位和符号发生变化。
如果我们把I、Q的值调换一下,I为cos wt,Q为cos wt,最终输出结果是多少?
即:
由此我们可以看出,如果想实现单音信号的频谱搬移,我们可以把I设置为余弦,Q设为正弦;其次,为实现把1MHZ信号先搬移到中频,即40Mhz的位置,载波频率应设置为39MHZ。
那假设单音信号换成一个带宽6MHZ的信号呢?这个上变频过程又是什么样的?
首先:
那么对于混频信号 𝑦(𝑡),我们来具体分析其频谱。
即原始基带信号的频谱 [-3MHz, 3MHz] 被搬移到以载波 𝑓𝑐 为中心频率的频带范围内[𝑓𝑐−3MHz,𝑓𝑐+3MHz]。
二、DUC的FPGA实现
实验环境:vivado2018.3、黑金7020开发板。
在详细的实现步骤前,先给出完整系统视图:
首先是由DDS产生一个采样率25MHZ,频率1MHZ的单音信号来充当数字基带信号(频率字由VIO2控制),DDS配置如下:
在这里可以看到sin与cos位宽信息:
由SIice IP核把正余弦信号分离,我们把I设置为余弦,Q设为正弦;之后分别送入FIR Compiler IP。
至于FIR Compiler IP如何使用,这里还要额外说一下。
在matlab命令行输入指令唤起Filter Desinger:
这里我们设置了一个80阶的FIR低通滤波器~
在这里将参数设置定点数才能导出参数的.coe文件,字长一般选择最佳长度。
然后导出配置文件:
回到vivado中来,添加FIR IP核,按图中配置:
这样我们的FIR IP核就配置好啦。
后面把经过FIR低通滤波的数据送入咱们上篇文章设计的CIC插值滤波器,进行8倍上采样。为了适应工程需要,本文对CIC的参数做了一些改变,这里给出CIC模块的完整代码。具体原理不在阐述,可以自行翻阅上一章节~
module clc
(
clk_in, // 输入数据时钟
clk_out, // 输出数据时钟(Ntimer倍于输入数据时钟)
reset, // 复位信号
data_in, // 输入数据
data_out // 输出数据
);
parameter STAGES = 4; // 滤波器阶数
parameter DATA_WIDTH = 40; // 数据宽度
parameter INDATA_WIDTH = 49; // 中间数据宽度
parameter Ntimer = 8; // 插值倍数
input clk_in; // 输入数据时钟
input clk_out;// 输出数据时钟(Ntimer倍于输入数据时钟)
input reset; // 复位信号
input signed [DATA_WIDTH-1:0] data_in; // 输入数据
output reg signed [15:0] data_out; // 输出数据
// 积分器的寄存器
reg signed [INDATA_WIDTH-1:0] integrator [0:STAGES-1];
// 梳状器的寄存器
reg signed [INDATA_WIDTH-1:0] comb [0:STAGES-1];
reg signed [INDATA_WIDTH-1:0] combd [0:STAGES-1];
// 插值的寄存器
reg signed [INDATA_WIDTH-1:0] interpolation = 0;
reg [7:0] cont;
// 输出缓冲
reg signed [INDATA_WIDTH-1:0] output_buffer = 0;
integer i;
// 将输出缓冲的值映射到输出端口
always @(posedge clk_out) begin
data_out <= output_buffer[INDATA_WIDTH-1:INDATA_WIDTH-16]; // 调整以适应实际的位宽和动态范围
end
// 梳状器(由输入时钟驱动)
always @(posedge clk_in or posedge reset) begin
if (reset) begin
for (i = 0; i < STAGES; i = i + 1) begin
comb[i] <= 0;
combd[i] <= 0;
end
end
else begin
// 梳状器操作
comb[0] <= {{(INDATA_WIDTH-DATA_WIDTH){data_in[DATA_WIDTH-1]}}, data_in};
for (i = 1; i < STAGES; i = i + 1) begin
combd[i-1] <= comb[i-1];
comb[i] <= comb[i-1] - combd[i-1];
end
combd[STAGES-1] <= comb[STAGES-1];
end
end
// 插值器(输出时钟驱动)
always @(posedge clk_out or posedge reset) begin
if (reset) begin
interpolation <= 0;
cont <= 0;
end
else begin
cont <= cont + 1;
if (cont == Ntimer-1) begin // N倍插值
interpolation <= comb[STAGES-1];
cont <= 0;
end
else
interpolation <= 0;
end
end
// 积分器逻辑(由输出时钟驱动)
always @(posedge clk_out or posedge reset) begin
if (reset) begin
for (i = 0; i < STAGES; i = i + 1) begin
integrator[i] <= 0;
end
output_buffer <= 0;
end
else begin
integrator[0] <= interpolation;
for (i = 1; i < STAGES; i = i + 1) begin // 积分器
integrator[i] <= integrator[i] + integrator[i-1];
end
output_buffer <= integrator[STAGES-1];
end
end
endmodule
I、Q信号通过CIC插值滤波进行处理后。接下来,信号通过乘法器与DDS产生的载波信号(200MHZ采样率、39MHZ频率)相乘,这些载波信号是经过SIice IP核分离出的正弦和余弦信号。以此来实现:
之后通过Addar/Subtracter IP核把两者相减:
至此我们就得到了上变频后的信号,实现了:
后面其实也可以加个DAC模块来进行实际的输出~这里可以自行加入,在我第二篇博客里也有AN108对应的使用方法。
三、功能验证
因为关于CIC插值滤波的结果上篇文章已经观测过了,比如采样率、插值数量等等,这里也不再啰嗦、不再进行细致的观测~我们这里直接观测最终的输出结果就好。
首先在ILA观测数据:
存为.CSV文件,导入matlab分别观测CIC_0输出的信号与信号y(t)频谱:
close all;
clc;
clear all;
% 从CSV文件加载信号数据
data = readmatrix('iladata2.csv');
x = data(:, 6); % 提取信号向量,CIC_0输出的信号换为5
fs = 200000000; % 设置采样频率为200MHz
% 绘制信号的时域图像
figure;
time_vector = (0:length(x)-1) / fs; % 生成时间轴向量
plot(time_vector, x);
title('DDS信号波形');
xlabel('Time (s)');
ylabel('Amplitude');
xlim([0 max(time_vector)]); % 设置x轴范围
grid on;
% 调用 spectrum_plot 函数来绘制频谱图
tt_str = '频谱图'; % 设置图表标题
spectrum_plot(x, fs, tt_str);
% spectrum_plot 函数定义
function spectrum_plot(x, fs, tt_str)
% 使用 Hamming 窗函数
vec_win = hamming(length(x));
% 增加傅里叶变换的长度
NFFT = 2^nextpow2(length(x));
y = fftshift(fft(x .* vec_win, NFFT));
y_dB = 20*log10(abs(y));
N_f = length(y);
f_idx = (0:N_f-1).' / N_f * fs - (fs / 2);
figure;
plot(f_idx / 1E6, y_dB); % 将频率转换为MHz
xlabel('MHz');
ylabel('Magnitude (dB)');
title(tt_str);
grid on;
end
程序运行结果:
由此可见我们上变频的目的已经完全达到~
最后、附上工程的整体链接~数字上变频的FPGA实现
总结
综合六七篇的学习笔记,终于完整的实现了数字上变频的实现,自己又往前了小小一步、道阻且长、还在路上~谢谢各位。