在模拟通信系统中,基带信号通过对载波波形幅度,相位,频率的调制以达到将信号在载波上传输信息的目的。根据基带信号的类型可分为:模拟调制 & 数字调制。

调制原理

本文所述AM(Amplitude Modulation)调制,即是模拟信号对载波幅度进行调制,通过与载波信号相乘来进行频谱搬移。其中第一个公式为时域表达式,第二个公式为频域表达式。

$$ S_{AM}=(A_0+m(t))*cos(w_ct+\phi_c) $$

$$ S_{AM}(\omega)=\frac{A_0}{2}*[M(\omega+\omega_c)+M(\omega-\omega_c)] $$

在上文的公式中$m(t)$为基带信号(一般为低频信号),$A_0$为直流偏置,用来将基带信号叠加到正半轴,使得在调制后信号仍然保持着$m(t)$的包络。其中$cos(w_ct+\phi_c)$为载波信号,$\omega_c$为载波角频率。

在描述AM信号的调制深度时,常定义为$\frac{m(t)_{max}}{A_0}$,当调制深度大于1时为过调幅现象,此时因为存在相位突变,调制后的信号包络无法反映原始信号$m(t)$。

MATLAB仿真

在开始之前,先阐述一下各项参数:

  • 采样率:10M
  • 基带信号频率:100K
  • 载波信号频率:1M
  • 采样点数:1000点(为了对齐一个周期的正弦波,避免FPGA仿真时出现相位的突变)

调制

在MATLAB调制的仿真中,我们需要将基带信号输出到TXT文档,供FPGA仿真时读取,作为AD采样的数值。同时绘制调制后的频域波形与时域波形,便于直观对照。

close all;clear;clc;
fs=10e6;
f1=100e3;
f2=1e6;
n=1000;
t=0:1/fs:(n-1)/fs;
s1=sin(2*pi*f1*t); %基带信号
s=(s1+max(s1)).*sin(2*pi*f2*t); %调制后的信号
s_fft=fft(s);s_fft_abs=abs(s_fft);
subplot(311);plot(s1(1:500));title("基带信号时域波形");
subplot(312);plot(s(1:500));title("调制信号时域波形");
subplot(313);plot(s_fft_abs(1:length(s_fft)/2));title("调制信号频域波形");

Q=8; %此处是量化位数,为了与ADC对应此处设置为8位
s1=s1/max(abs(s1));
Q_s=round(s1*(2^(Q-1)-1));
Q_s=Q_s+(2^(Q-1)-1);
fid=fopen("am_mod.txt","w");
for i=1:length(Q_s)
    Q_data=dec2bin(Q_s(i),Q); %与ADC对应,输出为无符号二进制数,注意在新版MATLAB中此函数有变化
    fprintf(fid,"%s\r\n",Q_data);
end
fprintf(fid,";");
fclose(fid);

其绘制的图片如下,可以观察到在调制后的频域上有三个尖峰,分别对应0.9,1.0,1.1三个频点:

image-20211008090850756

解调

其实不用写代码的

在MATLAB信号分析其中拖入变量s,分析器中选择包络即可提取信号包络,然后再经过一个低通滤波器即可。提取上包络后的波形如下图:

image-20211008150157310

FPGA仿真

本仿真在Vivado 2018.2上测试,元件选择为Xilinx XC7Z010CLG400,需要用到的IP核介绍如下。

IP核

Clocking Wizard

该IP核通过MMCM与PLL来输出特定频率到所需的模块,其中PLL可看作MMCM的子集。该IP核较为简单,介绍一个容易出问题的地方。

image-20211008091817238

如上图所示,在Input Clock Information的Source一栏中可以选择时钟来源,分别为:

  • 单端时钟:在后续的引脚选择上,必须与器件的专用时钟端口对应。
  • 差分时钟:类似于HDMI中的CLK+,CLK-,通过差分来抵消传输过程中的干扰。
  • 全局时钟:当该IP核的输入来自工程内的其他模块时,需要选择此。
  • 不缓冲输入:当从普通IO口引入时钟时,由于已经添加了一个Buffer,故此处不必再添加Buffer。

DDS Compiler

该IP核可以用于输出特定频率的正余弦波以及相位变化,可以通过AXI4接口配置其初始相位以及相位增量(改变输出频率),相关介绍如下:

Configuration

  • Configuration Options:配置为相位生成或正余弦波生成,或二者兼有。
  • System Requirements:配置输入频率,通道数与参数设置,这里一般选择为System Parameters,便于在后面直接配置输出频率。
  • System Parameters:可配置杂散范围与频率分辨率的数值,此配置将会决定相位宽度。

Implementation

  • Phase Increment Programmability:配置相位增量是否可调,可用来改变输出频率。
  • Phase Offset Programmability:配置相位偏移是否可调。
  • Output:配置输出波形(正,余弦或二者兼有),输出极性(是否反转,幅度范围),是否有相位输出(我经常不勾选)。

Output Frequencies

此处可用于配置各个通道的频率,前提是在Configuration中勾选到System Requirements。

本设计所用到的IP核Summary如下:

image-20211008083826319

Multiplier

该IP核用于将两个输出相乘,最后输出的位宽等于两个输入位宽之和,即$P=A*B$。在使用该IP核时,一定要注意输入位宽与是否为有符号数匹配。有符号与无符号数在该IP核内的处理方式不同,可能会造成乘法错误。

FIR Compiler

该IP核用于将输入信号进行FIR滤波,系数可从MATLAB的FDATOOL中导入,注意导入的数据需要在FDATOOL中量化为定点。

Filter Options

  • Filter Coefficients:此处可以手动输入抽头系数,或者由MATLAB导出。
  • Filter Specification:配置多速率FIR滤波器,出于性能问题在实际设计中常常使用CIC滤波器做多速率转换。

Channel Specification

本页面需要关注Hardware Oversampling Specification,该选项可以指定时钟频率与采样率的关系。

Implementation

  • Coefficient Type:输入抽头系数的数据类型,从MATLAB导入后保持默认即可。Coefficient Structure,此项用来指定抽头系数的类型:由系统推断,非对称与对称。
  • Data Path Options:设置输入输出数据的位宽,输出数据有一个Rounding Mode需要注意。Truncate LSBs可用于直接将低位舍去,保留高位,但我更倾向于仿真后手动截位。

image-20211008103433205

本设计所用到的IP核Summary如下:

image-20211008103639198

调制源文件

`timescale 1ns / 1ps

module am_mod(
    input sys_clk,
    input sys_rst_n,
    input [7:0] adc_data_unsigned,
    output adc_clk,
    output [15:0] am_mod
    );

wire clk_10m,locked,valid;
assign valid = sys_rst_n & locked;
assign adc_clk = valid ? clk_10m : 1'b0; //clk_10m时钟有效时输出到adc_clk,以10M的ADC时钟采样到adc_data_unsigned
ip_pll inst0(
    // Clock out ports
    .clk_10m(clk_10m),     // output clk_10m
    // Status and control signals
    .resetn(sys_rst_n), // input resetn
    .locked(locked),       // output locked
   // Clock in ports
    .clk_in(sys_clk)
);      // input clk_in

wire signed [7:0] carry_data;
wire carry_valid;
ip_dds inst1 (
  .aclk(sys_clk),                              // input wire aclk
  .aresetn(sys_rst_n),                        // input wire aresetn
  .m_axis_data_tvalid(carry_valid),  // output wire m_axis_data_tvalid
  .m_axis_data_tdata(carry_data)    // output wire [7 : 0] m_axis_data_tdata
);

parameter depth_inv = 2; //用来配置调制深度
wire signed [15:0] mult_data;
wire signed [7:0] adc_data;
assign adc_data = ((adc_data_unsigned - 8'd128) + depth_inv * 8'd128)/(depth_inv + 1);
assign am_mod = mult_data;
ip_mult inst2 (
  .CLK(sys_clk),  // input wire CLK
  .A(adc_data),      // input wire [7 : 0] A
  .B(carry_data),      // input wire [7 : 0] B
  .CE(valid),   // input wire CE
  .P(mult_data)      // output wire [15 : 0] P
);
    
endmodule

调制TestBench

注意此处的文件读写操作,用来输入基带信号,输出调制信号,输出到TXT的数据可在MATLAB中进一步的验证正确性。此外,fdisplay这个函数会根据wire或者reg在程序中定义的类型进行输出,此处定义为signed,默认为unsigned。注意将所需要的调试文件设为顶层设计。

`timescale 1ns / 1ps

module am_mod_tb(
    );

reg sys_clk;
reg sys_rst_n;  
reg [7:0] adc_data_unsigned;
wire adc_clk;
wire signed [15:0] am_mod;  
am_mod inst0(
    .sys_clk (sys_clk),
    .sys_rst_n (sys_rst_n),
    .adc_data_unsigned (adc_data_unsigned),
    .adc_clk (adc_clk),
    .am_mod (am_mod)
);
parameter clk_period = 20;
parameter data_num = 1000;

initial begin
    $readmemb("am_mod.txt",stimulus);
    sys_clk = 1'b0;
    sys_rst_n = 1'b0;
    adc_data_unsigned = 8'd0;
    #100 sys_rst_n = 1'b1;   
end

always #10 sys_clk = ~sys_clk;

integer pattern;
reg [7:0] stimulus [1:data_num];
always@(posedge adc_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        adc_data_unsigned <= 8'd0;
        pattern = 1; 
    end else if (pattern == data_num)begin
        adc_data_unsigned <= stimulus[pattern];
        pattern = 1;
    end else begin
        adc_data_unsigned <= stimulus[pattern];
        pattern = pattern + 1;
    end
end

integer file_out;
initial begin
    file_out = $fopen("am_mod_data.txt");
    if(!file_out) begin
        $display("Cloud open file!");
        $finish;
    end
end
wire write_clk;
assign write_clk = adc_clk & sys_rst_n;
always@(posedge write_clk) begin
    $fdisplay(file_out,"%d",am_mod);
end
    
endmodule

调制结果

该图为仿真结果,可以看到am_mod信号的波形幅度与输入信号幅度同步变化,其包络大致与基带信号相同。此外16位am_mod的14位和15位同步变化,在后文将am_mod截断为8位无符号时将会利用此波形进行变换。

image-20211008105707918

将调试后输出的数据导入到MATLAB中分析频谱既可以观察到在归一化频率的0.19、0.2、0.21附近有尖峰,满足设计预期。

image-20211008110042168

调制截位

由于我们使用的AD/DA模块是黑金的AN108,其输入输出均为8bit无符号数据,因此需要进行转换。注意此处因为第15位与14位相同,故此处直接将第15位作为符号位,以提高截位后的精度。

assign am_mod = {mult_data[15],mult_data[13:7]} + 8'd128;

解调源文件

AM信号的调制可以直接采用包络检波,在大信噪比的时候其性能与相干解调差距不大。使用Verilog来进行包络检波总体分为:整流&滤波。其中整流即是对原始信号取绝对值操作,本工程中通过判断am_mod最高位来实现。滤波即使用FIR IP核来进行,通过在FDATOOL中设计一个通带归一化频率为0.02的滤波器,将系数导入到Vivado即可。注意在仿真数据出来以后可以将其放入信号分析器观察频谱,调整通阻带增益来达到更好的解调效果。

`timescale 1ns / 1ps

module am_demod(
    input sys_clk,
    input sys_rst_n,
    input [7:0] am_mod_unsigned,
    output adc_clk,
    output [7:0] m
    );

wire clk_10m,locked,valid;
assign valid = sys_rst_n & locked;
assign adc_clk = valid ? clk_10m : 1'b0; //clk_10m时钟有效时输出到adc_clk,以10M的ADC时钟采样到adc_data_unsigned
ip_pll inst0(
    // Clock out ports
    .clk_10m(clk_10m),     // output clk_10m
    // Status and control signals
    .resetn(sys_rst_n), // input resetn
    .locked(locked),       // output locked
   // Clock in ports
    .clk_in(sys_clk)
);      // input clk_in    

wire signed [7:0] am_mod;
reg signed [7:0] am_abs;
assign am_mod = am_mod_unsigned - 8'd128;
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        am_abs <= 8'd0;
    end else if(am_mod[7]) begin
        am_abs <= -am_mod;
    end else begin
        am_abs <= am_mod;
    end
end

wire inst1_s_axis_data_tready,inst1_m_axis_data_tvalid;
wire [31:0] inst1_m;
assign m = inst1_m[26:19] + 8'd128;
ip_fir inst1 (
  .aclk(clk_10m),                              // input wire aclk
  .s_axis_data_tvalid(valid),  // input wire s_axis_data_tvalid
  .s_axis_data_tready(inst1_s_axis_data_tready),  // output wire s_axis_data_tready
  .s_axis_data_tdata(am_abs),    // input wire [7 : 0] s_axis_data_tdata
  .m_axis_data_tvalid(inst1_m_axis_data_tvalid),  // output wire m_axis_data_tvalid
  .m_axis_data_tdata(inst1_m)    // output wire [31 : 0] m_axis_data_tdata
);
    
endmodule

解调TestBench

`timescale 1ns / 1ps

module am_demod_tb(
    );
reg sys_clk,sys_rst_n;
reg [7:0] adc_data_unsigned;
wire adc_clk,inst1_adc_clk;
wire [7:0] am_mod;    
wire [7:0] m;
am_mod inst0(
    .sys_clk (sys_clk),
    .sys_rst_n (sys_rst_n),
    .adc_data_unsigned (adc_data_unsigned),
    .adc_clk (adc_clk),
    .am_mod (am_mod)
);    

am_demod inst1(
    .sys_clk (sys_clk),
    .sys_rst_n (sys_rst_n),
    .am_mod_unsigned (am_mod),
    .adc_clk (inst1_adc_clk),
    .m (m)
);

parameter clk_period = 20;
parameter data_num = 1000;

initial begin
    $readmemb("am_mod.txt",stimulus);
    sys_clk = 1'b0;
    sys_rst_n = 1'b0;
    adc_data_unsigned = 8'd0;
    #100 sys_rst_n = 1'b1;   
end

always #10 sys_clk = ~sys_clk;

integer pattern;
reg [7:0] stimulus [1:data_num];
always@(posedge adc_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        adc_data_unsigned <= 8'd0;
        pattern = 1; 
    end else if (pattern == data_num)begin
        adc_data_unsigned <= stimulus[pattern];
        pattern = 1;
    end else begin
        adc_data_unsigned <= stimulus[pattern];
        pattern = pattern + 1;
    end
end

integer file_out;
initial begin
    file_out = $fopen("am_demod_data.txt");
    if(!file_out) begin
        $display("Cloud open file!");
        $finish;
    end
end
wire write_clk;
assign write_clk = adc_clk & sys_rst_n;
always@(posedge write_clk) begin
    $fdisplay(file_out,"%d",m);
end
    
endmodule

解调结果

FPGA仿真后的波形如下图所示,FIR滤波器存在相位延迟,故在一段时间内输出的波形不正确。

image-20211008104710570

将输出的波形导入到信号分析器,既可以观察到在0(直流分量)与0.02(100KHz的基带信号)附近有频点。如果希望波形还原更加完善,可以在FDATOOL中提高阻带权重,以滤除0.4(二次谐波)与0.8附近的频率。

image-20211008105049483

其他

  • AM调制的调制效率最大为$\frac{1}{3}$,信号利用率较低,频带利用率也较低。但是由于其可通过包络检波直接提取原始信号,故其成本极低,现在仍被用于无线电广播。
  • 在FPGA仿真后,需要对仿真数据进行分析。此时建议使用MATLAB的“信号分析器”来对频谱进行分析,此APP的功能强大且易用。可用于查看信号频谱,滤波器初步设计,包络提取等功能,推荐。
  • FPGA的操作中受限于资源所限,常常会对数据进行截位,此时可以根据信号的仿真波形以及IP核的Implementation上的接口说明来撰写截位代码。

参考文献

《通信原理(第七版)》樊昌信 曹丽娜著

《数字调制解调技术的MATLAB与FPGA实现》 杜勇著

标签: MATLAB, DSP, FPGA

添加新评论