FPGA实现全数字高阶锁相环
本人通信专业大三学生,非常感谢杜勇老师的《数字通信同步技术》和《锁相环技术原理》,带领小白的我从零开始一点点了解数字同步技术。
第一次写文章,记录一下用FPGA实现二阶、三阶锁相环解调PSK信号
原理推导较多,设计过程也很详细,记录的时候也顺便重新梳理了一遍设计流程,码字不易。
锁相环原理及FPGA实现
锁相环技术原理
相位锁定环路简称锁相环(PLL),它是以锁定输入载波信号的相位为目标的一种载波环形式。它是一个产生、输出周期信号的电子控制环路,通过不断地调整其输出信号的相位,使输出信号与输入信号相位保持一致,当输入信号、输出信号相位保持一致时称锁相环进入锁定状态,此时表现出锁相环的稳态特性;当输入、输出信号相位未达到一致但趋向于一致的时候成为捕获状态,此时表现锁相环的暂态特性,如果最终锁相环未能达到锁定状态,则称锁相环失锁,丢失信号。
锁相环技术被广泛应用在各种通信系统和仪器设备中,任何需要调制出稳定频率的系统基本上都用到了锁相环。
基本工作原理
首先是模拟锁相环的技术原理,之后是模拟锁相环的数字化。
锁相环由三部分组成,分别是相位鉴相器、环路滤波器、压控振荡器构成。结构如图所示:
从图中可以看出,锁相环是一个负反馈环路,
是输入到锁相环的信号,
是输出信号,锁相环任务就是使得这两个信号的相位保持一致,或者说,输出信号是输入信号的复制品。接下来对各个模块进行数学公式推导。
鉴相器
鉴相器用来鉴别输入信号和产生的复制信号间的相位差异,鉴相器的种类有很多种,最简单的就是一个乘法器。将输入信号和输出信号相乘,即:
其中
是鉴相器得增益
如果输出信号与输入信号非常相似(最终他们是要一样的),那么公式最后一个等式第一项就是两倍于
的高频信号,第二项是低频信号。这时候将乘法器的结果通过一个低通滤波器,那么高频信号就会被滤除出去,而低频信号被保留下来,也就是拿到了输入信号和输出信号的相位差。
环路滤波器
环路滤波器输入信号 包含了输入输出信号相位差的信息,而环路滤波器通常也是一个低通滤波器,但不仅仅是一个低通滤波器,我感觉环路滤波器是整个锁相环的核心,它控制着整个锁相环的性能。
锁相环一阶、二阶、三阶指的是整个系统的阶数(系统函数中分母包含关于s的最高阶次),具体不同阶的作用会在后面具体讨论分析。由于压控振荡器那里存在一阶,所以环路滤波器对应的阶数为常数、一阶、二阶等(锁相环系统阶数=环路滤波器阶数+1),设环路滤波器的系统函数是F(s),具体表达式如下:
一阶锁相环
一阶环路的环路滤波器是一个常数,其传递函数表达式如下:(分母包含关于s的最高阶次为0)
此时环路滤波器相当于一个常数,或者说环路滤波器不存在
二阶锁相环
二阶锁相环环路滤波器传递函数表达式为:(分母包含关于s的最高阶次为1)
定义特征频率和阻尼系数
,
,表达式为:
这两个参数完全决定了锁相环性能,参数选取不同带来的影响会在后面分析不同阶次锁相环作用时一起分析。
根据参数定义式,从而可以将二阶环路传递函数写成以下的形式:
令
,则表达式化简为:
三阶锁相环
三阶锁相环传递函数为二阶,其表达式为:(分母包含关于s的最高阶次为2)
同理,我们也可以将其简化为以下的表达式:
其中
压控振荡器
鉴相器的输出经过环路滤波器之后送入压控振荡器,压控振荡器基本功能是产生一定周期频率的信号,输入信号和产生信号关系为:
为压控振荡器的增益,
是输出信号的瞬时角频率,我们还可以得到振荡器输出信号的瞬间初始相位为:
只要锁相环输入与输出信号之间存在一个相位差,压控振荡器就会根据信号相位的差异不断调整输出信号频率,使得最终相位保持一致。
通过对输出信号表达式进行拉普拉斯变换,可以得到压控振荡器的传递函数为:
由此证实压控振荡器的阶数为1。
三个模块传递函数均写出之后,可以得到锁相环系统的传递函数为:
为环路滤波器的传递函数,K为系统总增益,
即鉴相器增益乘以压控振荡器的增益。
模拟锁相环数字化
现在所有的输入信号均是采样信号,所以我们要设计的是全数字锁相环,要对每个模块进行数字化。
从S域到Z域
之上讨论传递函数和系统函数时均对时域进行拉普拉斯变换到s域(连续),信号与系统课学过,离散的s域便是Z域,即我们要对系统时域做Z变换。
Z变换也可以看作从拉普拉斯变换的采样,在设计数字环路滤波器的时候可以采用双线性变换积分器和矩形波积分器两种方式,将s离散化,具体形式如下:
上式中T为采样周期。
稳态响应
从系统函数H(z)分析,可以看出锁相环阶数对其跟踪能力的影响。
由Z变换终值定理可知,输出信号与输入信号相位差
在n趋向于无穷时(锁相环稳定后的输出信号与输入信号相位差)应该等于其Z变换后Z趋近于1的值,即:
H(z)为系统函数,
为误差信号系统函数
总结一二三阶锁相环系统函数,可以给出N阶锁相环系统函数为:
从而给定输入信号,可以求出稳态时锁相环的相位误差
以频率阶跃激励为例,
,
为频率阶跃幅值,从而根据终值定理,我们可以求出
由此可见,一阶锁相环稳态响应时相位误差为一个常数,二阶以上的锁相环稳态响应相位误差为0。
同理,可以计算当输入信号为频率斜升激励时,一阶锁相环稳态时会失锁,二阶锁相环稳态时有恒定相位跟踪误差,三阶以上锁相环稳态响应为0
接下来用matlab对锁相环稳态响应进行仿真。
当输入信号频率恒定时,即相位与时间成线性关系,用二阶锁相环对输入信号进行捕获,稳态时信号波形和相位结果如下:
上图中红色为输入信号波形和相位变化曲线,蓝色为锁相环输出信号波形和相位变化曲线。从图中可以看出,输入信号相位随时间增长线性增加,信号频率保持不变。用二阶锁相环进行捕获时,稳态相差为零,可以完全复制输入信号。
接下来将输入信号变为线性调频信号,即输入信号相位与时间成平方关系,仍然用二阶锁相环进行捕获,结果如下:
这回可以发现,当输入信号为线性调频信号,即相位随时间平方倍增加时,锁相环输出相位会与输入信号存在一个稳态相差。
改用三阶锁相环对信号进行捕获:
可以看出,三阶锁相环可以完美捕获线性调频信号。
通过公式推导,我们可以得出结论,一个N阶锁相环可以最多准确无误地跟踪相位按照时间(N-1)次方变化的信号,否则会出现稳态相差甚至失锁。
接下来分析锁相环的暂态性能。对于二阶锁相环, 和 决定着锁相环的跟踪速度和灵敏程度,结果如下:
越小,锁相环越灵敏,
越大,锁相环跟踪速度越快。
数字环路滤波器
根据变换表达式,我们可以得到数字环路滤波器的Z域表达式,以二阶、三阶为例:
二阶锁相环环路滤波器:
三阶锁相环环路滤波器:
根据其Z域表达式可以反推出时域表达式,从而进行FPGA设计。二阶、三阶时域表达式可以写为:
二阶锁相环:
三阶锁相环:
数控振荡器
数控振荡器(NCO)输出一个阶梯形周期信号,通过正弦、余弦查表将输出转换成为正弦、余弦信号。
输出频率为
M为相位增量控制字,N为数控振荡器的位数。
根据时钟频率,确定了输出信号频率后,可以求出控制字大小。
相关参数计算
设计要求
现在开始设计一个二阶、三阶锁相环,相关性能参数如下:
1、输入PSK信号量化位宽:10bit
2、数控振荡器输出复制载波信号位宽:10bit
3、系统时钟60Mhz
4、输入PSK信号码率4Mhz,载波66Mhz
5、数控振荡器位宽32bit
6、鉴相器低通滤波器量化位宽为12bit
输入信号
输入信号PSK调制到载波以后中频为66Mhz,若要满足二倍采样频率无失真复原信号则需要一百多M的采样速率,消耗极大的硬件资源。
因此利用带通采样定理,取原始信号的低频镜像分量,即进行数字下变频。
输入原始信号码速为4Mhz,由于输入信号时域有限,故频率带宽无限。因此在调制到66Mhz载波之前需要对其进行成形滤波。采用升余弦滤波器对其进行成形滤波,
,故成形滤波器截至频率为
故调制以后信号中心频率为66Mhz,带宽范围为66-3.6Mhz~66+3.6Mhz
鉴相器
首先计算鉴相器增益。输入信号为PSK调制信号,故鉴别相位时采用同相-正交环来解调恢复载波信号。数控振荡器同时产生sine和cosine波来作为I路和Q路输出,分别与输入信号相乘。鉴相器增益由两部分构成,输入输出信号相乘带来的幅度增益和相乘结果通过低通滤波器的滤波增益。首先计算幅度增益。
输入信号为10bit有符号数,输出信号为10bit有符号数,根据公式,相乘后的结果为
故输出的最大值为
即乘法器带来的幅度增益为
得到I路和Q路与输入信号相乘结果后,有以下几种方法可以通过IQ路信号鉴别相位差异。
采用最后一种方法不增加位宽,因此采用了自后一种方法来通过IQ两路计算信号相位差。
根据系统时钟为60Mhz,设计采样频率为30Mhz,因此数字下变频后零频附近镜像中心频率为6Mhz。根据带通采样定理,A/D镜像频率最小间隔为4.8Mhz,因此滤波器通带为3.6Mhz至8.4Mhz。用MATLAB设计低通滤波器核心代码如下;
fs = 30e6;
fc = [3.6e6 8.4e6];
mag=[1 0];
dev=[0.04 0.01];
[n,wn,beta,ftype] = kaiserord(fc,mag,dev,fs);
fpm = [0 fc(1)*2/fs fc(2)*2/fs 1];
magpm = [1 1 0 0]
format long;
h_pm = firpm(n,fpm,magpm);
而后对h_pm进行12bit量化,并将滤波器参数导出,方便在设计FPGA时直接导入。
qm = 12;
q = round(h_pm/max(abs(h_pm))*(2^(qm-1)-1));
s_q = sum(abs(q));
scale = max(abs(q))/max(abs(h_pm))
scale求出了量化带来的增益。求滤波器的幅频响应曲线为:
滤波器量化增益为5144.
综上,鉴相器带来的总增益为:
数控振荡器
而后设计数控振荡器,采样频率为30Mhz,振荡器位宽为32bit,数控振荡器更新周期为Tnco,先设置其为一个周期,即采样频率分之一。增益与控制字为:
总增益为:
总增益约为1
环路滤波器
取
,
根据公式,计算
的值。
由于需要在FPGA里进行乘法操作,需要调用乘法器IP核,故将结果进行近似,使得在FPGA里乘法操作变成移位操作
即分别右移6位、右移12位
FPGA实现二阶、三阶锁相环
计算完参数以后,接下来进行FPGA设计。我采用的是vivado2017.4的版本进行仿真测试的
鉴相器部分
首先是乘法器部分,调用乘法器IP核,将输入信号din分别和输出信号的IQ两路(sine、cosine)相乘
mult_gen_0 mq(
.clk(clk),
.a(din),
.b(cosine),
.p(mdq)
);
mult_gen_0 mi(
.clk(clk),
.a(din),
.b(sine),
.p(mdi)
);
滤波器环节调用滤波器IP核,设置采样频率为30Mhz,量化位宽为12bit,输入信号为乘法器鉴相后的的输出结果。位宽为19bit(第一位为符号位,舍去)
将matlab产生的滤波器系数导入,vivado给出的滤波器幅频特性为:
将滤波器ip核实例化,滤波器输出为32位(IQ两路分别实例化一个ip核):
fir_compiler_0 fq(
.aclk(clk),
.s_axis_data_tdata(mdq[18:0]),
.s_axis_data_tready(m_axis_data_tvalid_q),
.s_axis_data_tvalid(1'b1),
.m_axis_data_tdata(dq),
.m_axis_data_tvalid(m_axis_data_tvalid_q)
);
fir_compiler_0 fi(
.aclk(clk),
.s_axis_data_tdata(mdi[18:0]),
.s_axis_data_tready(m_axis_data_tvalid_i),
.s_axis_data_tvalid(1'b1),
.m_axis_data_tdata(di),
.m_axis_data_tvalid(m_axis_data_tvalid_i)
);
接下来是通过IQ两路鉴别相位差异:
module phase_detect(rst,clk,di,dq,pd);
input rst;
input clk;
input signed[31:0]di;
input signed[31:0]dq;
output signed[31:0]pd;
reg signed [31:0]pdout;
always@(posedge clk or posedge rst)
if(!rst)
pdout <= 32'd0;
else
begin
if(di[31])
pdout <= -dq;
else
pdout <= dq;
end
assign pd = pdout;
endmodule
环路滤波器部分
module filter_loop(rst,clk,pd,frequency_df);
input rst;
input clk;
input signed[31:0]pd;
output signed[31:0]frequency_df;
reg signed[31:0]sum_d;
wire signed[31:0]pd_c2,pd_c1,sum;
wire signed[31:0]df;
assign pd_c1 = {{6{pd[31]}},pd[31:6]};
assign pd_c2 = {{12{pd[31]}},pd[31:12]};
always@(posedge clk or posedge rst)
if(!rst)
sum_d <= 0;
else
sum_d <= sum;
assign sum = pd_c2+sum_d;
assign df = sum_d+pd_c1;
assign frequency_df = df;
endmodule
数控振荡器部分
数控振荡器调用DDSip核产生正弦和余弦信号,设置频率控制字为可编程状态,通过设置芜杂散动态范围来控制输出正余弦的位宽。
实例化DDSip核如下:
assign carrier = 32'd809332900;
assign s_axis_config_tdata = frequency_df + carrier;
dds_compiler_0 dd1(
.aclk(clk),
.aresetn(rst),
.s_axis_config_tdata(s_axis_config_tdata),
.s_axis_config_tvalid(1'b1),
.m_axis_data_tdata(m_axis_data_tdata),
.m_axis_data_tvalid(m_axis_data_tvalid),
.m_axis_phase_tdata(m_axis_phase_tdata),
.m_axis_phase_tvalid(m_axis_phase_tvalid)
);
wire signed[9:0]cosine;
wire signed[9:0]sine;
assign cosine = m_axis_data_tdata[9:0];
assign sine = m_axis_data_tdata[25:16];
总模块
总模块将其他模块实例化:
module PLL_TWO(
rst,clk,din,datai,dataq,pd,frequency_df
);
input rst;
input clk;
input signed[9:0]din;
output signed[31:0]datai;
output signed[31:0]dataq;
output signed[31:0]pd;
output signed[31:0]frequency_df;
wire signed[31:0] s_axis_config_tdata;
wire signed[31:0] m_axis_data_tdata;//sine and cosine
wire m_axis_data_tvalid;
wire m_axis_phase_tvalid;
wire signed[31:0] m_axis_phase_tdata;
wire signed[31:0] carrier;
assign carrier = 32'd809332900;
assign s_axis_config_tdata = frequency_df + carrier;
dds_compiler_0 dd1(
.aclk(clk),
.aresetn(rst),
.s_axis_config_tdata(s_axis_config_tdata),
.s_axis_config_tvalid(1'b1),
.m_axis_data_tdata(m_axis_data_tdata),
.m_axis_data_tvalid(m_axis_data_tvalid),
.m_axis_phase_tdata(m_axis_phase_tdata),
.m_axis_phase_tvalid(m_axis_phase_tvalid)
);
wire signed[9:0]cosine;
wire signed[9:0]sine;
assign cosine = m_axis_data_tdata[9:0];
assign sine = m_axis_data_tdata[25:16];
wire signed[19:0]mdq;
wire signed[19:0]mdi;
mult_gen_0 mq1(
.CLK(clk),
.A(din),
.B(cosine),
.P(mdq)
);
mult_gen_0 mi1(
.CLK(clk),
.A(din),
.B(sine),
.P(mdi)
);
wire signed[31:0]dq;
wire signed[31:0]di;
wire m_axis_data_tvalid_q;
wire m_axis_data_tvalid_i;
fir_compiler_0 fq(
.aclk(clk),
.s_axis_data_tdata(mdq[18:0]),
.s_axis_data_tready(m_axis_data_tvalid_q),
.s_axis_data_tvalid(1'b1),
.m_axis_data_tdata(dq),
.m_axis_data_tvalid(m_axis_data_tvalid_q)
);
fir_compiler_0 fi(
.aclk(clk),
.s_axis_data_tdata(mdi[18:0]),
.s_axis_data_tready(m_axis_data_tvalid_i),
.s_axis_data_tvalid(1'b1),
.m_axis_data_tdata(di),
.m_axis_data_tvalid(m_axis_data_tvalid_i)
);
wire signed[31:0]pd;
phase_detect ph1(
.rst(rst),
.clk(clk),
.di(di),
.dq(dq),
.pd(pd)
);
wire signed[31:0]frequency_df;
filter_loop f1(
.rst(rst),
.clk(clk),
.pd(pd),
.frequency_df(frequency_df)
);
assign datai = di;
assign dataq = dq;
endmodule
仿真测试
用matlab产生输入信号并导出。
添加并编写vivado仿真文件进行仿真测试,导入产生的输入信号。仿真结果如下:
pd为鉴相器的输出,即输出信号与输入信号的相位差异。
frequency_df为环路滤波器的输出。从图中可以看出,在第九百个周期左右锁相环达到锁定状态。
三阶锁相环
三阶锁相环与二阶锁相环只有环路滤波器不一样,其余完全一致,根据环路滤波器公式:
取
可以求出三阶锁相环的环路滤波器参数
,从而在FPGA中进行相对应的移位操作,具体原理公式上面有写,计算结果如下:
更改环路滤波器代码如下:
module filter_loop(rst,clk,pd,frequency_df);
input rst;
input clk;
input signed[31:0]pd;
output signed[31:0]frequency_df;
reg signed[31:0]sum_c2,sum_c3,sum_c3_2;
wire signed[31:0]pd_c2,pd_c1,pd_c3,sum_2,sum_3;
wire signed[31:0]df;
assign pd_c1 = {{5{pd[31]}},pd[31:5]};
assign pd_c2 = {{12{pd[31]}},pd[31:12]};
assign pd_c3 = {{18{pd[31]}},pd[31:18]};
always@(posedge clk or posedge rst)
if(!rst)
begin
sum_c2 <= 0;
sum_c3 <= 0;
sum_c3_2 <= 0;
end
else
begin
sum_c2 <= sum_2;
sum_c3 = sum_c3_2;
sum_c3_2 = sum_3;
end
assign sum_2 = pd_c2+sum_c2;
assign sum_3 = pd_c3-sum_c3+2*sum_c3_2;
assign df = sum_c2+pd_c1+sum_c3;
assign frequency_df = df;
endmodule
仿真结果如下:
最后
第一次写博客,水平有限,笔误肯定很多,希望以后越来越好吧!