前面两篇文章分享了我对数字基带信号与成型滤波的理解、测试、验证,本文将分享DPSK在AD9361上的实现。思路是这样的,首先在Matlab上验证DPSK调制后的频谱,然后在FPGA中采用伪随机序列作为原始数据,经上采样、成型滤波后送入AD9361,由AD9361完成IQ调制并最终发射出去。在本文中,最终的已调DPSK信号,其物理层速率是2Mbps,占用带宽为2.5MHz,物理层与占用带宽是联动的关系。
DPSK在Matlab中的实现
读入上一篇文章中生成的随机序列,进行4倍上采样,然后使用filter函数对上采样后的序列进行成型滤波(滤波器系数也是在上一篇文章中生产的),成型滤波后输出的数据与载波相乘得到 DPSK调制信号,最后绘制DPSK已调信号的频谱。Matlab代码如下:
clear;clc;
ps=1*10^6; %码速率为1MHz
Fs=4*10^6; %采样速率为4MHz
fc=1*10^6; %载波频率为1MHz
N=20000; %仿真数据的长度
coe_int=importdata('D:\Temp\matlab\coe_int.txt');
s=importdata('D:\Temp\matlab\rand_data.txt');
t=0:1/Fs:(N*Fs/ps-1)/Fs; %产生长度为N,频率为fs的时间序列
%以Fs频率采样
Ads=upsample(s',Fs/ps);
rcos_Ads=filter(coe_int,1,Ads);
%产生载频信号
f0=sin(2*pi*fc*t);
%产生DPSK已调信号
dpsk=rcos_Ads.*f0;
%绘制成形滤波后信号频谱、DPSK信号频谱、DPSK信号时域波形
m_dpsk=20*log10(abs(fft(dpsk,1024)));
m_dpsk=m_dpsk-max(m_dpsk);
%m_dpsk=mean(m_dpsk,1);
%设置幅频响应的横坐标单位为MHz
x_f=[0:(Fs/length(m_dpsk)):Fs/2];x_f=x_f/10^6;
%只显示正频率部分的幅频响应
mdpsk=m_dpsk(1:length(x_f));
plot(x_f,mdpsk);
legend('DPSK已调信号频谱');
xlabel('频率(MHz)');ylabel('幅度(dB)');grid on;
最终绘制的DPSK已调信号频谱如下图,可见,信号频谱被限制在1.25MHz左右,与预期是相符的。
FPGA生成伪随机数
这段代码用于在FPGA中生成伪随机数作为源,事实上FPGA中不可能真的生成随机数,只是这些数的周期很长而已。有一点需要注意的是,用于DPSK调制的数字序列必须具有随机性,否则频谱会出现单独的峰。伪随机数模块的时钟速率是2MHz。代码如下:
module pn2(clk,rst,data_out);
input clk;
input rst;
output data_out;
reg [7:1] c;
//reg[15:0] cnt;
always @(posedge clk)
begin
if (rst==0)
begin
c<=8'b1010111;
end
else
begin
c[2]<=c[1];
c[3]<=c[2];
c[4]<=c[3];
c[5]<=c[4];
c[6]<=c[5];
c[7]<=c[6];
c[1]<=c[2]^c[3]^c[4]^c[7];
end
end
assign data_out = c[7];
endmodule
星座映射
在我看来,星座映射就是个简单的组合逻辑,但是听着很高大上,所以教科书里面都这样写,在这个系列的第一篇文章中,已经说明了要采用-1 1这样的序列来替代0 1序列,所以需要把FPGA生成的随机的0 1序列变为随机的-1 1序列,在这里,我采用通常的二进制补码来表示-1,以便与其他模块对接。星座映射的代码如下:
module code_convert(
input code_convert_clk,
input code_convert_din,
input code_convert_rst,
output [7:0] dout
);
reg pre_code;
reg pre_code_temp;
reg [7:0] code_convert_dout;
always @(posedge code_convert_clk)
begin
if(code_convert_rst==0)
pre_code_temp <= code_convert_din;
else
begin
if (code_convert_din == pre_code_temp)
begin
code_convert_dout=8'b11111111; //Change to bi-polar code -1.
pre_code_temp <= 1'b0;
end
else
begin
code_convert_dout=8'b00000001; //Change to bi-polar code +1.
pre_code_temp <= 1'b1;
end
end
end
assign dout=code_convert_dout;
endmodule
上采样与成型滤波
这部分我直接用了Vivado的FIR IP核,将其配置为4倍内插方式,并使用上一篇文章中生成的滤波器系数,如下图
这里我们再来看看数据速率的变化。原始数字序列的速率是2Mbps,经过星座映射后仍是2Mbps,但是经过FIR的4倍内插之后,速率变为了8Mbps,由于FIR内插的都是0,在这期间不能让FIR的输入端获得有效数据,于是这里有个很重要的操作,就是保证FIR的输入端在8MHz时钟的驱动下,每4个时钟周期使能一次FIR的m_tvalid信号,这样才能保证结果的正确性。代码如下:
reg [1:0] axis_cnt=0;
always @(posedge clk_8m)
begin
if(!Hard_rstn)
axis_cnt <= 'd0;
else if(s_data_tvalid)
axis_cnt <= axis_cnt + 1'b1;
end
wire m_tvalid;
assign m_tvalid = (s_data_tvalid==1'b1) && (axis_cnt==3'd3);
AD9361的配置
早在2016年的时候一个朋友就跟我说AD9361内部有2000多个寄存器,看着头疼,在我这次的软件无线电工程实践过程中,也同样感觉到很头疼,好在ADI官方有个小软件,可以图形化操作,用起来很方便。本项目配置过程重点要考虑AD9361内部的滤波器,如下图。
AD9361内部集成了4个数字滤波器包括1个可编程FIR滤波器和3个半带滤波器,AD9361内部还集成了2个可编程的模拟滤波器。我本想把前面生成的系数写入AD9361的寄存器,这样可以减少占用的FPGA资源,可是AD9361的FIR滤波器只能写入128个系数,而我用Matlab生成的系数确是129个,实在没想明白怎么办。所以在我的配置中,bypass掉了AD9361的FIR滤波器,后面的3个半带滤波器则正常使用,2个模拟滤波器也正常配置。配置过程如下:
生成初始化脚本后,使用一个convert.exe的小软件,将脚本转换为verilog代码,如下图
本项目中使用FPGA模拟SPI接口直接对进行AD9361配置,用了别人编写好的代码^_^,我只是把其中的寄存器参数替换成了刚刚生成的。ADI公司还提供了no-OS代码,可以移植到单片机上或者FPGA的软核,这里就不展开叙述了。
还有二点值得注意的是:FIR的输出直接给到AD9361,位宽是不匹配的,我做了个简单粗暴的操作,直接把FIR的高12位输出给到AD9361,毕竟低位数据影响不大;DPSK方式,IQ支路只用一路就好,我的代码中I路对应FIR输出的高12位,Q路始终为0。代码如下:
assign tx_i_ch1=srrc_out[16:5];
assign tx_q_ch1=12'b0;
assign tx_i_ch2=srrc_out[16:5];
assign tx_q_ch2=12'b0;
至此,已经完成了DPSK调制的所有功能模块,如下图:
使用Vivado编译完整工程,将最终得到的bitstream文件写入FPGA,就在频谱仪上看到了预期的波形,这是最让人激动的时刻,如下图。
至此,关于DPSK调制的完整实验都已经完成,我又尝试了几种不同的采样率和信道带宽,都能与预期的波形相符。回想这些年的研发经历,这次试验是我唯一能够了解到底层机制的一次,甚至可以知道每一个bit是怎么来的,也是我第一次把数学与工程实践相结合,领略到数学之美的同时也感受到了数字基带开发的乐趣:可计算、可仿真、可实际验证。其实这3篇文章提炼出来的都是我在学习实践过程中总结出来的要点,我自己深入学习差不多有半年的时间才搞出来DPSK的波形,而且这仅仅是发射部分,有个高手说接收比发射复杂10倍,未来还有很长的路要走。