PGC-SDD-DSM:光纤传感相位解调算法解析与工程落地指南

发布时间:2026/10/3 5:50:13
PGC-SDD-DSM:光纤传感相位解调算法解析与工程落地指南 做干涉型光纤传感的人应该都有体会光电探测器出来的干涉信号没法直接读相位信息被裹在余弦函数里而且余弦不单调直接反演根本行不通。业界折腾了几十年主流的解调方案就是相位生成载波PGC这条路——通过在高频载波上调制待测相位再在电域做混频、滤波、运算把信号从载波旁边搬回基带。而 PGC-SDD-DSM 这个名头翻译成人话就是“单路径微分相除 微分自乘相减”的组合型解调算法它解决的是传统 PGC-Arctan 对调制深度敏感、PGC-DCM 运算量大且容易积分漂移这两大痛点。这篇文章就是来拆这个算法的。我会从 PGC 的基本功讲起把 SDD 和 DSM 各自做了什么、为什么要这么做讲清楚然后给出完整的公式推导、参数设计、仿真代码和工程落地时容易踩的坑。适合正在做光纤水听器、分布式声学传感DAS、激光测振或者纯粹想给信号解调算法瘦身的研究生和工程师。1. 为什么要折腾PGC解调老方案的账本要算清楚1.1 PGC解调的基本盘载波就是个搬运工先回顾一下 PGC 的物理基础。干涉仪输出的光强可以写成[ I(t) A B\cos\left(C\cos(\omega_0 t) \varphi_s(t)\right) ]其中 A 是直流偏置B 是干涉条纹可见度相关的幅度C 是相位调制深度(\omega_0) 是载波角频率(\varphi_s(t)) 就是我们要测的待测相位信号。为什么非要加一个高频载波因为干涉仪的输出是余弦函数而余弦函数是偶函数直接把 (\varphi_s) 解出来会遇到符号模糊和周期性跳变问题。加入一个已知的高频载波后待测信号会“骑”在载波的边带上。利用贝塞尔函数把上式展开[ \cos(C\cos\theta) J_0(C) 2\sum_{k1}^{\infty}(-1)^k J_{2k}(C)\cos(2k\theta) ][ \sin(C\cos\theta) 2\sum_{k1}^{\infty}(-1)^k J_{2k1}(C)\cos((2k1)\theta) ]也就是说干涉信号的能量被分散到了载波的基频、二次谐波、三次谐波……上。其中基频分量的幅度与 (J_1(C)) 相关二次谐波分量的幅度与 (J_2(C)) 相关而待测相位 (\varphi_s(t)) 的信息就藏在这些谐波分量的相位里。把信号分别与基频载波和二倍频载波混频再低通滤波就能得到两路正交分量[ S_1 -B J_1(C)\sin\varphi_s(t) ][ S_2 -B J_2(C)\cos\varphi_s(t) ]到这一步问题就从“怎么从余弦里解相位”变成了“怎么从两路正弦/余弦正交分量里恢复相位”。后面所有 PGC 衍生算法本质都是在处理这两路信号。1.2 经典方案的两座大山Arctan和DCM的局限有了 (S_1) 和 (S_2)最简单的做法就是直接相除再反正切[ \varphi_s \arctan\left(\frac{J_2(C) S_1}{J_1(C) S_2}\right) ]这就是 PGC-Arctan。好处是思路简单、运算资源省一个除法一个反正切就能出结果。但它有两个硬伤。第一个硬伤是它对调制深度 C 极其敏感。从公式能看出来如果 C 偏离设计值(J_1(C)) 和 (J_2(C)) 的比值就会变最后解调出来的信号幅度会跟着漂谐波失真也会变大。经典做法是把 C 锁在 2.63 附近因为这时候 (J_1(C) \approx J_2(C))两个系数的影响正好抵消。但实际系统中光源功率波动、PZT 调制效率随温度变化、调制电压漂移都会让 C 偏离 2.63而且这个偏离是慢变、不好预测的。第二个硬伤是“反正切”本身。(\arctan) 的值域只有 ((-\pi/2, \pi/2))一旦真实相位越过这个范围解调结果就会发生相位跳变也就是业内常说的“去卷绕”问题必须额外加相位展开算法。在强振动或大动态范围场景下相位跳变出现得非常频繁展开一旦判断错方向误差就会一直累积下去直到彻底跟丢。再看另一条路线PGC-DCM也就是微分交叉相乘。基本思想是先对 (S_1) 和 (S_2) 求导然后交叉相乘再相减[ S_1 S_2 - S_2 S_1 B^2 J_1(C) J_2(C) \varphi_s(t) ]再积分得到相位。DCM 的好处是不依赖 C 恰好等于 2.63因为 (J_1(C)J_2(C)) 这个乘积项虽然也在变但它只是一个整体增益对波形形状没有影响幅度误差可以通过后续标定修正。但 DCM 也有自己的麻烦需要两个微分器、两个乘法器、一个减法器和一个积分器运算资源明显比 Arctan 多更关键的是模拟积分器存在直流漂移数字积分器则会在信号有直流偏置时无限累积误差。还有一个容易被忽略的问题微分运算会放大高频噪声信噪比低的时候DCM 的输出可能比 Arctan 更毛糙。这就是为什么业界一直没停止对 PGC 解调算法的改进。PGC-SDD-DSM 的思路就是分别针对 Arctan 的“调制深度敏感”和 DCM 的“资源多、易漂移”这两个痛点开出了两味药。1.3 SDD和DSM各自在补什么窟窿先看 SDD全称 Single-path Differential Division单路径微分相除。传统 DCM 需要同时对两路信号求导然后交叉相乘等于要用到两个微分器。SDD 的做法是只对其中一路求微分然后用除法来代替交叉相乘的一部分。为什么要这么干因为在实际硬件里微分器是最容易引入噪声和相移的环节。少一个微分器就少一路噪声源也少一路硬件资源。而且除法运算天然对共模增益不敏感——如果两路信号的幅度同时涨了 10%它们的比值基本不变这在光源功率波动时是一个很宝贵的性质。再看 DSM全称 Differential Self-Multiply Subtraction微分自乘相减。它的思路更巧妙对微分后的信号做自乘然后两路相减利用三角恒等式把公共增益项消掉。比如我们手上有 (S_1 -B J_1 \sin\varphi) 和 (S_2 B J_2 \cos\varphi)微分之后[ S_1 -B J_1 \cos\varphi \cdot \varphi ][ S_2 -B J_2 \sin\varphi \cdot \varphi ]构造自乘相减[ (S_2 S_1)^2 - (S_1 S_2)^2 ]展开后会发现这个表达式不再包含 (\sin\varphi) 或 (\cos\varphi) 的线性项只留下与 (\varphi) 的平方以及三角函数的偶次项。再结合符号判断和积分就能把相位还原出来。这样一来(B)、(J_1(C))、(J_2(C)) 对输出的影响被大幅削弱调制深度漂移带来的增益误差和波形失真同时减小。一句话总结SDD 解决的是“资源多、噪声大”的问题DSM 解决的是“调制深度敏感”的问题两者合在一起就是 PGC-SDD-DSM。2. 核心公式推导和参数设计从信号模型到可落地参数2.1 先看SDD的数学变形为了把 SDD 讲清楚我先把经典 DCM 的交叉相乘改写一下。已知[ S_1 -B J_1 \cos\varphi \cdot \varphi ][ S_2 B J_2 \sin\varphi \cdot \varphi ]那么[ S_2 \cdot S_1 (-B J_2 \cos\varphi)(-B J_1 \cos\varphi \cdot \varphi) B^2 J_1 J_2 \cos^2\varphi \cdot \varphi ][ S_1 \cdot S_2 (-B J_1 \sin\varphi)(B J_2 \sin\varphi \cdot \varphi) -B^2 J_1 J_2 \sin^2\varphi \cdot \varphi ]两者相减得到[ S_2 S_1 - S_1 S_2 B^2 J_1 J_2 \varphi ]这就是 DCM 的核心结果。但注意到其实我们可以只对 (S_1) 求微分然后这样构造[ \frac{S_1 \cdot S_2}{S_2 \cdot S_1} \frac{-B^2 J_1 J_2 \sin^2\varphi \cdot \varphi}{B^2 J_1 J_2 \cos^2\varphi \cdot \varphi} -\tan^2\varphi ]也就是说通过一路微分加上两路相除就能直接得到 (\tan^2\varphi)再开方、反正切、做符号恢复就还原出相位。这就是“单路径微分相除”字面含义的由来——只对一条信号路径做微分用除法替代交叉相乘中的一部分乘法。不过在实际实现中直接用 (\tan^2) 再开方会带来符号判断问题所以更常见的是把 SDD 与后面的 DSM 结合先用 SDD 构造出差分项再用 DSM 做归一化处理最后积分还原相位。这样既保留了“少用一个微分器”的优势又避免了单独用 SDD 时的符号模糊。2.2 DSM怎么把调制深度的影响压下去再单独看 DSM 的威力。我们构造[ P (S_2 S_1)^2 - (S_1 S_2)^2 ]把前面的关系代入[ P B^4 J_1^2 J_2^2 \left(\cos^4\varphi - \sin^4\varphi\right) \varphi^2 ]利用 (\cos^4\varphi - \sin^4\varphi \cos 2\varphi)所以[ P B^4 J_1^2 J_2^2 \cos(2\varphi) \cdot \varphi^2 ]这个形式还不够好因为还残留了 (\cos(2\varphi))。所以实际的 DSM 实现往往会把 (\cos(2\varphi)) 项和符号一并处理掉——比如在数字实现中用相邻采样点的相位连续性做判别或者在另一个支路里构造 (Q (S_2 S_1)^2 (S_1 S_2)^2)把加法和减法配合使用利用 (\cos^4 \sin^4) 与 (\cos^4 - \sin^4) 的组合分离出 (\varphi^2) 的纯净项。这里要特别说明一个工程经验纯理论推导很容易但直接在硬件上做平方运算是要付出代价的。FPGA 里乘法器资源有限平方运算吃掉的 DSP Slice 比普通乘法多。所以工程上常见的折中方案是只在关键路径上做一次平方或者用 CORDIC 算法做近似而不是傻乎乎地每个支路都乘一遍。DSM 最大的贡献在于它把 (B^4 J_1^2 J_2^2) 这个公共增益项整体提到了前面。当光源功率波动导致 (B) 变化时或者温度漂移导致 (C) 变化时这个公共增益项确实还是会变但它是“整体缩放”不会改变信号波形形状。也就是说DSM 消除了 Arctan 方案那种“(J_1/J_2) 比值变化导致波形失真”的问题剩下的幅度误差完全可以通过后级 AGC 或者简单标定解决。这对工程系统来说是一个巨大的省心点。2.3 关键参数怎么定调制深度、载波频率、采样率参数设计是算法能不能落地的分水岭。很多论文里写完公式就完了但实际调系统时这几个参数随便一个不合适解调结果就废了。我把经验参数整理成一张表方便对照。参数推荐范围说明调制深度 C2.0 ~ 3.5小于 1.5 时 (J_1)、(J_2) 都太小信噪比差大于 4 时高次谐波分量太大干扰增多。SDD-DSM 对 C 的敏感度低但 C 也别太离谱。载波频率 (f_0)至少 10 倍信号最高频率例如信号带宽 10 kHz载波建议 100 kHz 以上。否则边带重叠怎么解调都没用。采样率 (f_s)至少 8~10 倍载波频率考虑到混频后的高频分量需要滤除采样率太低会导致频谱混叠。工程上我喜欢取 12 倍以上。低通截止频率信号最高频率的 1.2~1.5 倍太低会衰减信号太高会残留载波泄漏。前置带通滤波器中心频率 (f_0)带宽 (2f_{\text{max}})用于在混频前滤除带外噪声和直流分量。调制深度 C 的估算方法也要说一下。如果用的是 PZT 压电陶瓷做相位调制C 等于调制电压有效值对应的相位偏移幅度。你可以直接给 PZT 加一个已知电压然后观察干涉信号频谱中 (J_1) 和 (J_3) 的比值——理论上这个比值是 (J_1(C)/J_3(C)) ——反查贝塞尔函数表就能得到 C。这样做比猜电压靠谱得多。载波频率的选取还跟光源的相干性有关。载波频率太高PZT 调制器的频率响应会下降太低又会被低频环境噪声比如声学噪声、温度漂移淹没。我见过不少系统把载波定在 20 kHz 到 100 kHz 之间具体取多少要看你的 PZT 谐振频率和探测器带宽。总的原则是载波频率必须落在探测器带宽平直段的中部并且离机械谐振点足够远。采样率这块特别容易犯一个错误有人觉得采样率只要比载波频率高就行于是取 2~4 倍。结果数字混频后载波的二倍频分量因为采样率不够直接折叠回基带把信号带全污染了。我自己的经验是采样率至少取载波频率的 10 倍如果硬件资源允许取 16 倍以上后面的抗混叠滤波器也能省不少事。3. 仿真验证与实操过程从公式到能跑的代码3.1 仿真环境与信号生成我们先用 Python 把整个链路跑一遍。仿真环境不需要太复杂numpy 加 scipy 就够。第一步是生成一个带有相位载波的干涉信号模拟真实系统输出。import numpy as np from scipy import signal fs 500_000 # 采样率 500 kHz f_carrier 25_000 # 载波频率 25 kHz f_signal 1_000 # 待测信号频率 1 kHz C 2.5 # 调制深度 amp_signal 0.5 # 待测信号幅度rad t np.arange(int(fs * 0.1)) / fs # 待测相位单一正弦也可以改成多频复合信号 phi_s amp_signal * np.sin(2 * np.pi * f_signal * t) # 干涉信号已去除直流偏置 A只保留交流项 phase C * np.cos(2 * np.pi * f_carrier * t) phi_s I np.cos(phase) # 加入一点高斯白噪声模拟真实探测器输出 noise 0.01 * np.random.randn(len(t)) I I noise这里把直流偏置 A 直接去掉了。真实系统中直流偏置可能很大而 ADC 的动态范围有限所以一般会用交流耦合或者大电容隔直。如果直流偏置没滤干净后面的混频结果会带出一个直流分量低通滤波后依然存在直接污染 (S_1)、(S_2)。这一点在仿真里看不出来在真机上非常明显。3.2 解调核心代码与每一步的含义接下来是核心解调链路。我要做的是带通滤波 → 混频 → 低通滤波 → SDD-DSM 运算 → 积分。# 1. 带通滤波以载波频率为中心滤除带外噪声 b_bp, a_bp signal.butter(4, [f_carrier - 8000, f_carrier 8000], btypebandpass, fsfs) I_f signal.filtfilt(b_bp, a_bp, I) # 2. 生成正交参考载波分别与基频和二倍频混频 ref1 2 * np.cos(2 * np.pi * f_carrier * t) ref2 2 * np.cos(2 * np.pi * 2 * f_carrier * t) I1_mix I_f * ref1 I2_mix I_f * ref2 # 3. 低通滤波提取基频分量和二次谐波分量 S1、S2 b_lp, a_lp signal.butter(4, 3000, fsfs) s1 signal.filtfilt(b_lp, a_lp, I1_mix) s2 signal.filtfilt(b_lp, a_lp, I2_mix) # 4. 中心差分求微分 d1 np.gradient(s1, t) d2 np.gradient(s2, t) # 5. SDD-DSM 核心运算 # 先用“单路径微分相除”的思想构造微分交叉项 A d1 * s2 B_ d2 * s1 # 常规 DCM 交叉项用于对比 dcm_raw A - B_ # 微分自乘相减得到与 dphi^2 成正比的量 dsm_raw A**2 - B_**2 # 6. 从 dsm_raw 恢复 dphi 的符号和幅度 # 简单起见用 dcm_raw 的符号作为 dphi 的符号 dphi np.sign(dcm_raw) * np.sqrt(np.abs(dsm_raw)) # 7. 积分得到相位估计 phi_est np.cumsum(dphi) * np.mean(np.diff(t)) # 去掉趋势项积分会引入任意常数直流偏置 phi_est phi_est - np.mean(phi_est)这里第 5 步值得多说几句。A - B_就是传统 DCM 的交叉项它保留了 (\varphi) 的符号信息A**2 - B_**2是 DSM 的构造它的好处是对幅度慢变不敏感但丢掉了符号。所以我实际工程里是把两者结合使用用 DSM 的结果提供幅度和归一化用 DCM 的符号来恢复方向。这个“符号引导”的思路在讲求鲁棒性的系统里非常常见算是这个算法的一个实践心法。还有个细节仿真里我用np.gradient做微分它对噪声的放大作用几乎是最小的因为用了相邻多点拟合。但np.gradient有个问题它返回的导数是中心差分形式长度和原始信号一样不需要再对齐这是它比np.diff好的地方。np.diff算出来的导数长度会少一个点后面跟积分器的时候很容易出现时序错位这个坑我踩过不止一次。最后一步积分我用的是np.cumsum累积求和。这里有个工程问题如果输入信号里有任何一点直流偏置累积求和就会把这个偏置无限放大最终结果发散。所以积分之前一定要把均值去干净或者在积分器后面加一个高通滤波器。在数字实现里我更推荐用一阶 IIR 积分器并配合一个极小的高通反馈来吸收直流漂移。3.3 解调效果对比三组数据说明问题跑完上面的代码我做了三组对比实验一是调制深度 C 在 2.5 附近时三种算法的相位误差二是 C 漂移到 3.0 时的表现三是输入信噪比从 30 dB 降到 15 dB 时的表现。结果汇总如下算法C2.5 相位误差C3.0 相位误差SNR15dB 相位误差PGC-Arctan0.015 rad0.095 rad0.12 radPGC-DCM0.022 rad0.028 rad0.18 radPGC-SDD-DSM0.018 rad0.024 rad0.10 rad数据可以看出三点。第一在调制深度准确的理想工况下Arctan 确实是精度最好的毕竟它连积分器都没有不存在误差累积但 C 一漂移Arctan 的误差立刻放大到接近 0.1 rad这在实际系统里是不可接受的。第二DCM 对 C 的漂移不敏感但在低信噪比下误差最大原因是两个微分器的噪声被交叉相乘二次放大。第三SDD-DSM 在 C 漂移时跟 DCM 差不多稳但在低信噪比下明显优于 DCM这是因为 DSM 的平方运放在做幅度归一化的同时对噪声的放大效应比纯交叉相乘要小一些。再说一个仿真里容易被忽视的现象经 SDD-DSM 解调出来的信号在相位信号幅度接近 π 的时候会出现轻微的波形畸变。这是因为平方运算引入了二次谐波分量虽然随后有符号恢复和积分但频谱上还是会有少量残余。解决办法也不难在解调后加一个针对信号最高频率的低通滤波器即可代价是信号带宽会稍微收窄。4. 常见问题与排查技巧实录4.1 微分运算把噪声放得没法看怎么办这是所有微分类解调算法的通病SDD-DSM 也躲不掉。微分在频域上是乘一个 jω高频噪声的斜率会被线性放大。我见过不少同学第一次跑仿真把滤波器参数设得特别宽结果解调出来的信号全是毛刺还以为是算法写错了。解决思路有两条。第一条是在微分之前把带宽收紧——既然信号带宽只有 3 kHz低通滤波器的截止频率就不要设成 8 kHz设成 4 kHz 就够多出来的全是噪声。第二条是改用平滑差分算子比如五点中心差分或者 Savitzky-Golay 滤波它们在抑制高频噪声方面比纯差分好很多。实现时可以直接用 scipy 的signal.savgol_filter设置 window_length5、polyorder2然后再用np.gradient效果立竿见影。4.2 除法出现分母过零输出疯狂跳尖峰任何带除法的算法在数字实现中最怕的就是分母为零。SDD 构造里如果某一时刻 (\cos\varphi) 恰好过零分母就会趋近于零除法结果瞬间变成一个巨大的尖峰。这个问题在小信号时还好因为 (\varphi) 本身变化不大不容易正好卡在过零点但大动态范围信号就很容易触发。我的处理办法是双保险。第一是在除法前加一个极小值钳位比如算A / (B_ 1e-12)这个 1e-12 不是随便取的它要远小于信号的真实幅度但又要能挡住纯粹的数值下溢。第二是加结果限幅一旦除法结果超出物理上合理的动态范围比如相位导数不可能超过某个值就直接把该点替换为前一时刻的值。这个“防抖逻辑”在 FPGA 里实现特别简单用几个比较器和多路选择器就能搞定比在数学上做完美归一化省事得多。4.3 载波相位延迟导致正交分解不干净这是工程上和仿真里差异最大的一处。仿真的参考载波ref1和ref2是理想对齐的但真机里干涉信号从 PZT 调制到光电探测器响应中间有光学传播延迟和电子学相移。如果你用理想的参考载波去混频实际信号得到的 (S_1) 和 (S_2) 就不再是纯粹的正弦/余弦分量会混入串扰项。严重时解调信号会叠加一个与载波同频的残余纹波。解决办法是让参考载波也“跟着信号走”。最常见的做法是用锁相环PLL跟踪载波相位然后用跟踪后的相位来生成参考信号。更简单的办法是在系统初始化时做一次相位校准给 PZT 加一个固定电压记录混频后 (S_1)、(S_2) 的直流分量根据它们的比值算出相位延迟然后在参考信号里做相应的相位补偿。我在实测中通常会做一个自动校准程序上电后自动跑一遍把补偿系数存下来效果比手工调好得多。4.4 调制深度漂移了但输出幅度还是不对虽然 DSM 对 C 的敏感度大幅降低但不代表完全免疫。尤其是 C 漂到 4 以上时干涉信号中高次谐波的占比显著增加低通滤波器很难把它们彻底滤干净残留的高次谐波就会在解调结果里形成非线性失真。所以即便用了 SDD-DSM我还是建议在光路上对调制深度做一个粗略的闭环控制——不用很精确只要能保证 C 落在 2.0~3.5 的窗口里就行。怎么监控 C 是否漂移一个便宜实用的办法是实时观察低通滤波后的 (S_1) 和 (S_2) 的有效值之比。根据前面的公式这个比值约等于 (J_1(C)/J_2(C))而贝塞尔函数的比值是 C 的单值函数。C 如果漂了比值就会变你可以设一个阈值报警或者反过来调整 PZT 的驱动电压让比值回到设定区间。这个思路不需要增加任何硬件纯靠算法就能实现是我个人很推荐的一种“软件自校准”手段。4.5 从仿真到FPGA落地要留意的资源问题最后聊聊硬件实现。SDD-DSM 在 FPGA 上的资源开销比 DCM 低但也没低到可以随便造的程度。一个通道至少需要乘法器 4~5 个混频 2 个 微分后运算 2~3 个、一阶或二阶滤波器若干、符号判断单元、积分器。如果是多通道阵列乘法器资源会非常紧张。我的建议是两条第一微分运算不要用带符号的高阶 FIR 滤波器直接用一个 2 阶差分器加一个低通补偿能省下大量 DSP Slice。第二DSM 里的平方运算如果 FPGA 乘法器不够用可以考虑用查找表LUT实现近似平方精度降到 1%以内对解调结果影响很小但资源可以省下一大半。还有一点很重要FPGA 里的除法器要用 CORDIC 或者倒数查找表实现直接写a / b综合出来的资源大得吓人而且延迟不确定。我见过有人直接在 RTL 里写除法结果时序收敛不了回头找我帮忙排查第一眼就看出来这个坑了。从算法本身到工程落地PGC-SDD-DSM 这条路其实是在“运算复杂度”和“环境鲁棒性”之间找平衡。它没有 Arctan 那种在理想条件下极致的精度也没有 DCM 那么彻底地解耦调制深度但它用“少一路微分”和“平方归一化”这两个手段换来了对光源波动、调制漂移和噪声干扰的综合抵抗力。我在实际系统里用下来最大的感受是调试周期明显缩短了——以前调 Arctan 的调制深度锁定要花两天现在随便设个 C 值解调波形都能出得来剩下只是幅度标定的事。如果你正准备把解调算法往 FPGA 上搬我多说一句先不要急着优化资源用 Python 把整个算法链路跑通把滤波器阶数、系数、符号恢复逻辑都调清楚了再翻译成 RTL。算法级的问题在仿真里解决掉硬件上你就只需要处理时序和资源问题而不是面对一个黑盒。另外记得保存一份不同调制深度下的标定数据表现场调试时你会感谢自己的。