ZYNQ学习笔记(七):超详细!数字信号处理—数字上变频的FPGA实现(FIR+CIC+混频)


前言

在上一篇文章中,我们学习和实现了数字上变频系统中非常关键环节 —— 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实现

总结

综合六七篇的学习笔记,终于完整的实现了数字上变频的实现,自己又往前了小小一步、道阻且长、还在路上~谢谢各位。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星空lg

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值