
1. 项目概述与核心挑战拆解做北斗B1频点信号捕获这个项目我前后折腾了大概两个月。最开始以为就是照搬GPS L1 C/A码的捕获思路把载波频率换成1561.098MHz把码率换成2.046Mcps然后跑一遍搜索就完事了。真正上手才发现B1频点的NH码跳变问题差点让我把整个捕获方案推倒重来。先说清楚这个项目到底是干什么的。北斗B1频点B1I是北斗二号和北斗三号都在播发的公开服务信号中心频率1561.098MHz码速率2.046Mcps测距码码长为2046个码片周期1ms。这些参数和GPS的C/A码很像但多了一个东西——NH码。NH码的全称是Neumann-Hoffman码北斗B1I信号里用的是NH码长度为20bit码速率为1kbps调制在测距码之上每个NH码码片持续1ms正好覆盖一个完整的测距码周期。NH码跳变带来的直接问题就是如果你按照GPS C/A码捕获的思路做1ms的相干积分那么测距码的自相关特性是好的但NH码的存在会让导航电文比特翻转问题变得更复杂。GPS L1 C/A码也有比特翻转但bit周期是20ms而NH码是20个1ms码片的组合每个码片都可能发生电平翻转这就导致在进行跨毫秒相干累积时信号极性可能不一致累积结果会被抵消。这个项目的核心目标就是解决这个问题在存在NH码跳变的条件下稳定可靠地完成B1I信号的捕获包括码相位搜索、多普勒频移估计以及后续的比特同步、NH码同步。项目适合做卫星导航接收机开发的工程师、做北斗信号处理算法研究的同学以及想搞懂“安卓盒子为什么能支持北斗导航”这类应用底层原理的硬件爱好者。我实测下来如果不处理NH码直接用GPS那套10ms或20ms相干累积策略去捕获B1I灵敏度会损失得非常厉害有时候甚至根本捕获不到信号。我后面会详细讲原因也会给出我自己验证过的几种应对方案包括分段匹配滤波加非相干累积、基于FFT的循环相关结合NH码相位搜索以及一种更实用的“两段式捕获”方法。2. 信号模型与NH码底层原理2.1 B1I信号结构拆解要搞定NH码跳变首先得把B1I信号的构成彻底吃透。北斗B1I信号的射频表达式可以写成S(t) A·C(t)·D(t)·NH(t)·cos(2π·f_carrier·t φ)其中A是信号幅度C(t)是测距码码率2.046Mcps码长2046周期1msD(t)是导航电文速率50bps即每个bit持续20msNH(t)是NH码速率1kbps每个码片持续1msf_carrier是1561.098MHz载波频率。注意看NH码和导航电文的关系NH码周期是20ms导航电文的一个bit也是20ms二者是同步的一个导航电文bit正好对应20个NH码码片。NH码的作用是提高导航电文的抗干扰能力和比特同步性能但代价就是让信号捕获阶段多了一个不确定因素。实际的NH码序列是固定的B1I信号使用的那组20bit序列是00001010011011011111从第一个码片开始依次排列。我之所以强调这一点是因为后面做相干累积补偿时可以直接用本地预存的NH码序列去消除跳变影响而不需要实时估计。信号经过下变频和采样后数字中频信号可以表示为x(n) A·C(n - τ)·D(n - τ)·NH(n - τ)·cos(2π·(f_IF f_d)·n·T_s φ) w(n)其中τ是码相位延迟f_d是多普勒频移f_IF是中频频率T_s是采样间隔w(n)是噪声。这里最关键的一点是测距码周期为1msNH码码片周期也是1ms导航电文bit周期是20ms。三者之间存在严格的倍数关系这是设计捕获算法的突破口。如果你理解了这三者的对齐关系NH码跳变问题就有了解决的理论基础。2.2 NH码跳变为什么“致命”很多初学者会有个疑问NH码码率才1kbps周期20ms看起来不算快啊为什么会对捕获造成那么大影响问题出在捕获算法通常会做“跨毫秒相干累积”。现有接收机普遍采用1ms数据做相关运算然后通过多次累积提高信噪比。GPS C/A码的捕获就是典型的例子单次1ms相关的信噪比不够就做10次甚至20次相干累积把积分时间延长到10~20ms灵敏度能提升10dB以上。但北斗B1I信号上叠加了NH码而NH码的电平不是恒定的。我上面给出的序列里前几个码片是0、0、0、0、1、0、1、0……这意味着信号极性在1ms边界处可能发生翻转。如果直接做10ms相干累积相当于把这10个1ms的相关结果直接相加但其中某些1ms区间的信号极性是反的相加后正负抵消累积增益大幅下降。具体算一笔账假设NH码序列中10ms内有6个码片和本地参考极性一致、4个不一致那么理想情况下的10dB增益会变成10·log10((6-4)²)≈6dB损失了4dB左右。如果NH码序列正好是前10个码片和后10个码片极性相反实际上B1I的NH码确实存在类似长串连续翻转的区域那么10ms相干累积的结果几乎为零信号被完全抵消。这就是NH码跳变被称为捕获“隐形杀手”的原因它不是噪声不会通过滤波消除它是确定性的信号调制但如果不了解它的规律就会在累积阶段把信号“自己和自己抵消掉”。2.3 本地复现与捕获目标定义在介绍应对策略前我需要先明确捕获阶段到底要估计什么。B1I信号捕获的核心输出有两个一个是码相位τ̂测距码在一个周期内的延迟一个是多普勒频移f̂_d。成功捕获的标志是在τ̂和f̂_d处相关峰明显高于噪声基底且可通过后续的比特同步和NH码同步验证。需要注意的是捕获阶段一般不需要估计NH码的相位。因为NH码同步可以在捕获之后、跟踪环路启动之前完成。捕获阶段要做的是保证“无论NH码处于什么相位都能检测到信号”或者“通过某种方式消除NH码相位的影响”。所以整个算法的设计核心就变成了如何在不知道NH码当前相位或者说不愿意花太多搜索代价去遍历NH码相位的情况下把相关增益做上去同时把NH码跳变的影响降到最低。我最终采用的方案兼顾了这两点。这里先说结论与GPS接收机常用的“预检测积分时间10~20ms”不同B1I捕获的预检测积分时间不宜超过1ms或者最多做到2ms配合NH码补偿然后通过非相干累积来弥补灵敏度损失。3. 捕获策略的整体设计与选型思路3.1 为什么不能照搬GPS方案我最初给这个项目定的方案是1ms相关20ms相干累积然后做门限判决。理由很简单——GPS L1 C/A码就是这么干的PN码周期也是1ms20ms内导航电文比特最多翻转一次影响可控。拿到B1I信号实测数据后这个方案立刻暴露了问题。因为我没考虑NH码直接用20ms相干累积结果相关峰几乎消失了。用Matlab跑了一下不同NH码相位下的累积增益发现最差情况下20ms累积的增益只有理论值的不到10%。问题就是我在2.2节说的极性抵消。有同学可能会说GPS C/A码捕获也有比特翻转问题啊业界不是用“半比特交替法”或者“双块零填充”来解决吗确实有但GPS的bit周期是20ms翻转时刻最多出现在20ms边界而B1I的NH码翻转可能出现在任何一个1ms边界。也就是说可能直接翻转在用作相干累积的那段数据的中间位置——GPS的经典解法在这个场景下不够用。3.2 三种可行技术路线对比我调研和实测了三种主流应对方案这里直接列一个对比表格方案基本原理灵敏度实现复杂度适用场景1ms相干非相干累积单ms相关后取模平方做多次非相干平均中等约35~38dB·Hz可捕获低通用捕获策略信号强度尚可1ms相关NH码相位遍历对NH码20个相位分别做相干累积选最大输出高约30~33dB·Hz可捕获中高弱信号捕获但搜索时间×20分段匹配滤波FFT多组1ms相关输出做FFT利用信号谱特性累积高高高动态、弱信号场景软件接收机常用我最终选用的是“1ms相干非相干累积”作为主方案另外在弱信号场景下辅以“已知NH码序列补偿”的增强方法。原因有两点第一我的主要应用场景是室内外过渡区域和普通城市环境信号强度基本在-130dBm以上1ms相干非相干累积的灵敏度足够用没必要为了极端弱信号场景把搜索时间扩大20倍。第二非相干累积方案有一个隐性的好处它天然对NH码跳变“免疫”。因为每个1ms块是独立取模后相加的极性翻转只会让单个块的相关值降低到接近噪声水平但其他块不受影响整体的检出概率依然稳定。这在工程上非常稳健。3.3 非相干累积的损耗计算非相干累积也有代价——平方损耗。相干累积10ms的增益是10log10(10/1)10dB而非相干累积10次的增益按理论是10log10(10)10dB但由于平方运算引入了噪声×噪声项实际增益小于理论值。这个损耗叫“平方损耗”squaring loss可以用下面这个近似公式估算L_sq (1 1/(2·SNR·N))²其中SNR是单次相关的输出信噪比线性值N是非相干累积次数。举个例子如果单次1ms相关的输出SNR为0dB线性值1做10次非相干累积平方损耗约为(11/(2·1·10))² ≈ 1.10也就是约0.4dB损耗可接受。但如果单次SNR是-5dB线性值0.316N10时平方损耗约1.55对应1.9dB损耗。所以在中等信噪比下非相干累积损失的dB并不多。我最后配置的参数是1ms相关20次非相干累积总积分时间20ms。相比GPS方案的20ms相干累积灵敏度大约损失3~5dB但把NH码跳变的“灾难性抵消”问题彻底规避了工程上非常值得。4. 实战捕获流程与关键参数配置4.1 整体流程框架我实现的B1I捕获模块整体分五个阶段信号预处理下变频、低通滤波、采样率调整输出数字中频信号。粗捕获1ms相干相关非相干累积完成码相位和频率的粗搜索。精细频率估计在粗捕获结果附近做精细频率扫描提升频率分辨率。NH码同步利用捕获到的码相位信息估计当前NH码的相位从20bit序列的哪个位置开始。结果验证检查相关峰是否稳定、是否通过“峰值与次大值比”的门限验证。前两步是重头戏后面三步是保证跟踪环路能正常接管信号的关键。4.2 捕获参数计算与配置先说采样率和中频频率选择。我用的中频是4.092MHz采样率是16.368MHz正好是码率2.046Mcps的8倍这样每个码片有8个采样点码相位分辨率约为0.125码片够用。多普勒搜索步进的设置需要重点说说。捕获阶段的频率搜索步进一般取1/(2·T_coherent)其中T_coherent是相干积分时间。T_coherent1ms时频率搜索步进为500Hz。为什么是这个值因为相关输出的幅频响应是一个sinc函数主瓣零点在1/T_coherent处。如果频率误差超过500Hz相关损失就会超过3.9dB步进取500Hz能保证最大频率误差不超过250Hz对应损耗约0.9dB可以接受。我实测的多普勒搜索范围是±10kHz对应40个频率格点。每个频率格点做1ms×20次的非相干累积总计算量在普通的x86平台上是毫秒级的移植到ARM Cortex-A7上大约是几百毫秒完全可接受。以下是我代码里实际使用的关键参数表参数名数值说明中频频率4.092 MHz下变频后的模拟中频采样率16.368 MHz4倍中频便于数字下变频码率2.046 McpsB1I测距码速率码长2046 chips一个完整测距码周期采样点/码片8码相位搜索分辨率0.125chip相干积分时间1 ms单次相关运算长度非相干累积次数20总积分20ms多普勒搜索范围±10 kHz覆盖接收机运动带来的多普勒频率搜索步进500 Hz由1ms相干时间决定捕获门限峰值/次大值 2.5虚警概率约1e-64.3 核心捕获算法伪代码这一节我给出实际运行的捕获主流程伪代码。这里用FFT-based循环相关做码相位搜索这是软件接收机最常用的高效实现。import numpy as np from numpy.fft import fft, ifft fs 16.368e6 # 采样率 code_freq 2.046e6 # 码率 samples_per_code int(fs * 1e-3) # 1ms内采样点数16368 # 生成本地测距码1ms重采样到采样率 local_code generate_b1i_code() # 2046 chips local_code_upsampled np.repeat(local_code, 8) # 16368 samples local_code_fft fft(local_code_upsampled) # 非相干累积缓冲 accum np.zeros(samples_per_code) for freq_idx, doppler in enumerate(doppler_search_list): # 生成本地载波中频多普勒 t np.arange(samples_per_code) / fs carrier np.exp(-1j * 2 * np.pi * (if_freq doppler) * t) accum.fill(0.0) for block_idx in range(20): # 取第block_idx个1ms数据块 block signal_data[block_idx * samples_per_code : (block_idx 1) * samples_per_code] # 载波剥离 baseband block * carrier # FFT循环相关 spec fft(baseband) corr ifft(spec * np.conj(local_code_fft)) # 非相干累积取模平方 accum np.abs(corr) ** 2 # 检测峰值与门限判定 peak_idx np.argmax(accum) peak_value accum[peak_idx] second_peak find_second_peak(accum, peak_idx, exclusion_zone20) if peak_value / second_peak threshold: code_phase peak_idx % 8 # 码片内相位 code_chip peak_idx // 8 # 码片序号 # 保存粗捕获结果退出搜索这段代码的核心思想非常简单每个1ms块独立做循环相关然后取模平方累加。关键是第二步的循环相关中用到了FFT和IFFT把每次相关运算的复杂度从O(N²)降到了O(NlogN)其中N16368。20次累积完成后直接找峰值如果峰值与次大值的比值超过2.5就认为捕获成功。我在实际工程中并没有用Python而是用C语言重写了一遍。FFT库用的是开源的KissFFT在ARM平台上比FFTW更轻量。如果要移植到FPGA则可以直接用Xilinx的FFT IP核原理完全一致。4.4 NH码同步流程详解粗捕获完成后我们知道了码相位和多普勒频率。但此时还不知道NH码的起始相位。NH码同步的目的是确定当前1ms数据块对应NH码20bit序列中的哪一个位置。NH码同步有两种常见做法。第一种是能量法对捕获后的1ms数据进行相关运算连续处理20个1ms块观察每个块的信号极性相关结果的正负号与本地NH码序列比对找出最匹配的相位偏移。第二种是差分检测法对相邻1ms块的相关结果做差分运算提取NH码的跳变沿从而确定NH码相位。我采用了能量法因为实现简单且可靠。伪代码如下# 假设已经完成码相位和多普勒的粗捕获 corr_results [] for block_idx in range(20): block signal_data[block_idx * samples_per_code : (block_idx 1) * samples_per_code] # 载波剥离 码相关使用捕获到的码相位和多普勒 corr correlate_with_code(block, carrier_freq, code_phase) corr_results.append(corr) # 复数相关结果 # 提取极性 polarity np.sign(np.real(corr_results)) # 取实部符号 # 与本地NH码所有可能的20个循环移位比对 nh_code np.array([0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1]) # 注意NH码通常用0/1表示映射到电平为1/-1 nh_bipolar 1 - 2 * nh_code # 0 - 1, 1 - -1 best_shift 0 best_score -1 for shift in range(20): shifted np.roll(nh_bipolar, shift) score np.sum(polarity * shifted) # 相关打分 if score best_score: best_score score best_shift shift # best_shift 就是NH码的起始相位这里有一个关键细节必须提醒在捕获阶段估计的多普勒频率可能有几百Hz的残余误差这会导致复相关结果的相位在20ms内缓慢旋转。所以在做极性提取时我建议先用相邻1ms块的相位差分来估计残余频率然后对20个相关结果做相位补偿再取实部符号。否则如果残余频率过大实部的正负可能受相位旋转影响导致极性误判。我实测在残余频率不超过100Hz时不做相位补偿也能正确同步超过200Hz时正确率会明显下降。因此捕获阶段的频率搜索步进不能太粗500Hz是一个合理的上限如果你把步进取到1kHzNH码同步的可靠性会打折扣。4.5 峰值判定与虚警控制捕获的最终判决是看峰值与次大值之比是否超门限。我用的门限是2.5约8dB这个值怎么来的呢在只有噪声的情况下20次非相干累积的噪声输出近似服从高斯分布中心极限定理其峰值与次大值的比值分布可以通过蒙特卡洛仿真得到。我跑了一万次纯噪声仿真发现峰值与次大值比超过2.5的概率约为1e-6即单频点虚警概率约百万分之一。40个频率格点全部搜索下来总虚警概率约4e-5可接受。如果想把门限调得更严可以取3.0此时单频点虚警概率降到约1e-8但代价是灵敏度损失大约1dB。在信号质量好的场景用2.5就行在强干扰场景可以动态调高门限到3.0。5. 常见问题与排查技巧实录5.1 NH码跳变导致的捕获失败特征我在这个项目里踩了不少坑挑几个典型的记录下来希望对后来者有帮助。第一个坑相干累积输出全部接近于零但单1ms相关输出能看到微弱峰值。这个现象几乎可以肯定是NH码跳变导致的极性抵消。排查方法是把非相干累积次数改为1次直接看单ms相关结果如果单ms能看到峰就说明信号没问题问题出在累积策略。我最初就是用这个办法定位到NH码的。第二个坑捕获结果的码相位和多普勒频率在短时间内跳动不稳定。这种情况往往是NH码同步失败导致的。如果NH码相位没对齐后续跟踪环路的码环虽然能锁定码相位但载波环会受NH码跳变的调制影响出现周期性的相位跳变。解决方案是仔细检查4.4节的NH码同步逻辑尤其是残余频率补偿部分。第三个坑在强信号下可以捕获但弱信号下捕获概率骤降。这个和门限设置、非相干累积次数都有关系。我建议先把非相干累积次数从20提到50看看灵敏度有没有提升。如果提升了说明是累积增益不够如果没提升可能是前端噪声系数太大需要检查射频链路。5.2 实测数据与问题速查表我整理一张问题速查表方便大家对照排查现象可能原因排查方法解决方案20ms相干累积无峰NH码极性抵消改为1ms相关观察改用非相干累积单ms有峰累积后消失频率残余过大检查频率步进步进降到250Hz码相位抖动NH码同步失败检查极性提取增加残余频率补偿弱信号捕获概率低非相干累积次数不足逐步增加NN调到50~100捕获成功但跟踪失锁NH码同步错误查看NH码打分修正相位补偿逻辑多普勒搜索耗时过长搜索范围/步进不合理统计搜索时间双层搜索粗搜细搜5.3 一个值得分享的优化小技巧做非相干累积时我建议把每个1ms块的相关结果先乘以一个窗函数再取模。我在项目里用了汉明窗发现它能有效抑制FFT循环相关的旁瓣泄漏让主峰更尖锐对峰值/次大值比的门限判定有帮助。具体做法是在频域做循环相关后对相关输出的幅度乘以一个长度为16368的汉明窗。但要注意这么做会稍微降低主峰的幅度约0.5dB所以如果信号本来就弱可以不加窗或者改为只对峰值附近的若干个点加窗。这个技巧是我在反复试验中总结出来的常规教材里不会写。另一个技巧是NH码序列本身可以“半查表”。B1I的NH码虽然是20bit的固定序列但不同卫星PRN号可能使用相同的NH码序列。我最初以为不同卫星有不同NH码后来查阅资料确认B1I的NH码是统一的不需要按卫星区分。这节省了很多实现上的麻烦。6. 从捕获到应用的周边配套6.1 北斗天线工作原理简介做捕获实验时很多人会忽略天线的影响导致实际捕获灵敏度和理论值差很多。北斗B1频点天线的工作原理本质上是一个带通滤波器加一个低噪声放大器LNA。天线接收1561.098MHz附近的电磁波通过陶瓷贴片或螺旋结构谐振放大再经过LNA放大后送入接收机前端。实际测试中发现天线放在窗边和放在室内桌面捕获灵敏度可以差到10dB以上。这是因为B1I信号经过墙体衰减后强度可能降到接收机灵敏度以下。如果你用安卓盒子或者手机做北斗导航实验不要指望天线性能能和外接有源天线比。外接天线最好选择带26dB增益的型号且供电电压要和接收机匹配多数有源天线用3.3V或5V供电接反了会烧LNA。一个容易出现的问题是有源天线的供电电流不足。有些接收机板卡的供电能力只有50mA但某些有源天线的工作电流需要80mA会导致天线增益下降甚至不工作。排查方法是测量天线供电端的电压如果从5V掉到4V以下说明供电不足需要外接偏置器bias tee供电。6.2 安卓盒子支持北斗导航的原理“安卓盒子怎样支持北斗导航”这个热词很有意思。安卓盒子本身不带GNSS接收机要支持北斗导航必须外接一个USB或蓝牙的GNSS接收机模块比如常用的Ublox M8N、中科微AT6558模块等。模块捕获北斗B1信号后通过NMEA协议把定位信息输出给安卓系统。安卓系统对GNSS的支持主要有两种方式一种是使用系统级的GNSS HAL接口把外接模块当作系统内置定位设备另一种是使用第三方App直接读取串口或蓝牙数据在App内部解析NMEA并显示位置。前者体验好但需要系统权限通常是root或者系统签名后者实现简单但只能在特定App内使用定位。我曾在某款RK3328芯片的安卓盒子上试过用USB转串口接一个中科微的模块配合开源的“GPS Test”App显示定位结果。一开始串口读取正常但看不到卫星排查后发现是模块默认的串口波特率是9600而我配置成了115200导致解析数据全部乱码。改回9600后马上就能看到北斗卫星BDS和GPS卫星的列表了。如果你也想在安卓盒子上折腾北斗导航建议直接用蓝牙模块因为很多盒子只有1~2个USB口插了鼠标键盘就没位置了。蓝牙模块配对后系统会自动创建一个虚拟串口读取方式跟USB串口一样。6.3 北斗CORS账号设置方法CORS连续运行参考站系统账号和捕获算法无关但属于北斗应用里大家问得比较多的问题这里也顺便整理一下基本流程。CORS账号主要用于高精度定位通过接收参考站的差分改正数来提高定位精度。CORS账号设置一般分三步第一步是获取账号信息通常包括服务器IP/域名、端口号、用户名、密码和挂载点mount point第二步是在接收机或软件中配置NTRIP客户端输入这些参数第三步是连接成功后接收机会输出差分改正后的高精度定位结果。我踩过的坑主要有两个一个是挂载点选错导致接入成功但差分数据不更新另一个是网络类型选错有的CORS服务要求走TCP有的要求走UDP配置错了就收不到数据流。如果用的是安卓盒子可以用支持NTRIP协议的App来接收差分数据再通过蓝牙或WiFi转发给接收机。注意CORS账号一般是按时间收费的而且不同地区使用的坐标框架可能不同。如果定位结果和实际位置偏了几米很可能是没有做坐标转换需要在软件里设置正确的坐标系统参数。6.4 天线摆放与捕获性能的实测对比我在项目里实际测过几种天线摆放方式对B1I捕获灵敏度的影响结果很能说明问题天线摆放位置捕获可用卫星数平均捕获时间说明窗外阳台外置有源天线12~141s信号最好窗边外置有源天线8~101~2s可用窗边内置天线设备4~62~5s灵敏度下降明显室内桌面内置天线1~35~15s经常捕获失败室内铁皮柜旁边0捕获失败信号被屏蔽如果你的捕获模块在室内始终失败先别急着调算法大概率是天线位置的问题。把一个几百块的有源天线装在窗外比优化算法带来的收益高得多。拿我自己的经验来说在室内环境调试NH码同步算法时如果信号本身太弱你很难分辨是算法问题还是信号问题。后来我把天线固定在窗外信号强度稳定在-125dBm左右才把算法的调试效率提上来。建议所有做北斗信号处理开发的朋友先把天线工程搞扎实再做算法迭代不然真的会陷入“算法改了没什么效果”的迷茫期。7. 最后说点实在的做完这个项目我最大的感悟是对付NH码跳变思路比代码重要。最开始我一头扎进相干累积的细节里试图用各种“补相位”的技巧去消除跳变影响绕了不少弯路。后来退一步想明白了——既然NH码跳变在1ms边界处不可预测那就别硬碰硬用非相干累积绕开它就行。工程上“绕开”往往比“硬刚”更优雅也更稳健。具体到实现上我给后来者的建议是第一步先用1ms相关20次非相干累积跑通整个捕获流程确保能稳定捕获到信号第二步再加NH码同步和残余频率补偿让捕获结果能顺利交接给跟踪环路第三步如果还有余力再考虑用NH码相位遍历去提升弱信号灵敏度。先跑通再优化这个顺序不要反。如果你也在做类似的北斗信号处理项目欢迎验证一下我给的NH码序列确保你用的序列和实际发射信号一致。我第一次就吃了亏——用了网上流传的一组不完全正确的NH码序列导致同步算法一直不对。建议以北斗官方接口控制文件为准不要轻信二手资料。最后再分享一个小技巧调试时尽量把中间结果每个1ms块的相关值、极性序列、NH码打分打印出来用文本日志的方式记录。我调试NH码同步时就是在日志里发现相关结果的相位在缓慢旋转才意识到需要做残余频率补偿。很多看似神秘的bug把中间量打出来一看原因马上就清楚了。