1. 从VPI到Matlab:接收端DSP的协同仿真闭环
上次我们聊了怎么用Matlab生成发射端的信号,然后把文本文件扔给VPI去跑光纤传输。很多朋友跑完仿真,拿到VPI输出的那一大坨数据文件,可能就有点懵了:这接下来该怎么办?今天,我们就来把这个环给闭上,重点讲讲信号经过光纤“蹂躏”之后,在接收端怎么用Matlab给它“治病”——也就是数字信号处理。
你可以把这个过程想象成看病。VPI里的光纤传输模拟,就像是让信号去跑了一场极限马拉松,过程中会累(衰减)、会喘(色散)、会眼花(偏振模色散)、还会喝醉(非线性效应)。等信号跑到接收端,已经是“面目全非”了。我们Matlab里的DSP算法,就是一位全科医生,它的任务是把这位“伤员”识别出来,给它矫正视力(时钟恢复)、分清左右手(偏振解复用)、醒酒(载波相位估计),最后再判断它到底说了啥(解调、误码率计算)。最妙的是,这位“医生”的诊断报告(比如误码率),我们还能拿回VPI里去,验证整个仿真链路的性能到底靠不靠谱。这就形成了一个完美的“仿真-处理-验证”闭环。
我刚开始玩这套流程的时候,最大的感受就是“割裂感”消失了。以前要么纯用VPI里的模块搭DSP,虽然方便但像个黑盒,调参数都不知道为啥;要么纯用Matlab写算法,但又得自己费劲去模拟光纤损伤,不够真实。现在好了,VPI负责把物理世界的光纤效应模拟得尽可能真实,Matlab则用我们最熟悉的数学工具和编程环境去处理信号,两边各司其职,强强联合。这套方法特别适合做算法研究和原型验证,比如你想试试一种新的载波相位恢复算法行不行,用这个流程跑一遍,结果一目了然。
那么,这个闭环具体怎么跑起来呢?核心就是数据接力。VPI仿真结束后,会把接收到的光信号(通常已经过光电转换变成电信号)的I路和Q路数据,以文本文件的形式保存下来。这个文件里,通常每一行是一个采样点,第一列是时间,后面两列分别是I和Q的电压值。我们的Matlab脚本,就是读取这个文件,把数据加载进内存,然后开始一系列DSP操作。处理完后,我们可以直接算出误码率,也可以把处理后的“干净”符号再保存成文件,甚至可以反馈给VPI的误码率分析仪模块去读。下面,我们就一步步拆解这个DSP“诊疗”流程。
2. 诊疗第一步:时钟恢复——把凌乱的脉搏捋整齐
信号从VPI出来,第一个问题就是采样点不对齐。VPI的仿真器是以一个固定的、非常高的采样率(比如8倍或16倍于符号速率)来运行和输出数据的。但是,这个采样时钟和发射端符号的时钟不一定是同步的,而且经过光纤传输后会有时间抖动。这就好比我们用高速摄像机去拍一个按固定节奏跳动的心脏,但摄像机的快门时机和心跳时机没对上,拍出来的画面里,心跳的峰值可能落在两帧画面之间,我们看到的峰值就不是最大值了。
所以,时钟恢复(Clock Recovery)的目标,就是从这个过采样的数据流里,准确地找到每个符号的最佳采样时刻。这个“最佳时刻”,通常是眼图张开最大、受噪声和码间干扰影响最小的时候。
2.1 经典算法:Gardner定时误差检测
在工程上,最常用、也最 robust 的算法之一就是 Gardner 算法。它妙就妙在只需要每个符号2个采样点(二倍采样)就能工作,对载波频率偏移不敏感,特别适合我们这种还没做载波恢复的“盲”状态。
它的核心思想很简单:观察一个符号周期内,中间点和边界点的关系。在最佳采样点附近,中间点(符号判决点)的幅度应该最大,而前后两个边界点(过渡点)的幅度应该大致相等。如果不等,就说明采样时刻偏了,需要调整。
我们用Matlab来实现一下这个感觉。假设我们从VPI读进来的数据是 rx_signal,它是一个复数序列(I + j*Q),采样率是符号速率 Rs 的 sps(Samples Per Symbol)倍,比如 sps = 8。
% 假设 rx_signal 是过采样的接收信号,sps 是每符号采样数
sps = 8;
% 首先进行简单的重采样,降到2倍采样(Gardner算法所需)
rx_signal_2x = resample(rx_signal, 2, sps); % 现在每符号2个点
% 初始化变量
n_symbols = floor(length(rx_signal_2x) / 2) - 1; % 能处理的符号数
tau = 0; % 定时误差初始值
interp_out = zeros(1, n_symbols); % 存放恢复出的符号
mu = 0.01; % 环路滤波器的步进,控制收敛速度
% Gardner 算法核心循环
for k = 1:n_symbols
% 获取三个点:y[k-1], y[k-0.5], y[k]
% 注意索引,因为我们是2倍采样,所以符号索引和采样索引关系为:
% 第k个符号的判决点索引是 2*k,过渡点索引是 2*k-1
y_km1 = rx_signal_2x(2*k - 2); % 前一个符号的判决点
y_kh = rx_signal_2x(2*k - 1); % 当前符号的过渡点(半符号时刻)
y_k = rx_signal_2x(2*k); % 当前符号的判决点(目标点)
% Gardner 定时误差计算
error = real(y_kh) * (real(y_k


490

被折叠的 条评论
为什么被折叠?



