前段时间调试一台自研接收机的冷启动流程,卫星列表里北斗卫星一颗颗出现,但信号状态永远停在“搜索中”。换用GPS信号源一切正常,换成北斗B1频点就抓不住。折腾了两天,最后定位到的问题不是射频前端,也不是捕获通道资源不够,而是B1I信号上那个很多人没太当回事的NH码——20ms周期内的次级码跳变,把相干积分输出直接“抹平”了。
这篇文章就把这个坑的来龙去脉和我的处理过程完整记录下来。内容围绕北斗B1频点信号捕获展开,重点讲NH码跳变为什么会影响捕获、影响有多大、工程上怎么应对,顺带把我调试过程中踩过的几个坑也一起交代。正在做北斗接收机算法、软件接收机接入B1信号,或者对GNSS信号处理刚入门的朋友,这篇文章应该能帮你少走不少弯路。
1. 北斗B1I信号的“隐藏关卡”:NH码跳变从哪来
1.1 B1I信号结构里为什么会有NH码
先说清楚本文讨论的对象:北斗B1频点目前有B1I和B1C两套信号,B1I是北斗二号时期就开始播发的信号,特征非常典型,伪码速率2.046Mcps、码长2046、周期刚好1ms。而NH码(Neumann-Hoffman码)是叠在伪码之上的一个次级码,速率1kbps、周期20ms,也就是说每20ms内有20个NH码片,每个NH码片正好对应一个伪码周期。
B1I信号的主要参数可以整理成下面这张表:
| 参数 | 数值 |
|---|---|
| 载波频率 | 1561.098 MHz |
| 伪码速率 | 2.046 Mcps |
| 伪码长度 | 2046 chips |
| 伪码周期 | 1 ms |
| NH码速率 | 1 kbps |
| NH码周期 | 20 ms(20个码片) |
| 电文速率 | 50 bps |
| 电文比特时长 | 20 ms |
NH码的作用主要有两个:一是抑制连续波干扰带来的频谱线,二是帮助接收机在后续跟踪阶段快速识别电文比特边界。理想情况下这些好处都在接收机可接受范围内,但代价就是捕获阶段的信号模型里多了一个未知的±1翻转序列。如果你调的是B1C信号,也有类似的次级码问题,本文的思路可以迁移过去,只是具体的码速率和周期不同。
1.2 NH码跳变如何破坏相关峰
捕获的本质是做一个二维搜索:在码相位-多普勒频率平面上找到相关峰。接收机本地只产生伪码副本,但接收信号里实际上是电文、NH码、伪码、载波四项相乘后的结果。写成公式就是:
s(t) = A · D(t) · NH(t) · C(t) · cos(2πft)
其中D(t)是电文、NH(t)是NH码序列、C(t)是伪码。捕获时本地只恢复C(t),D(t)和NH(t)都是未知的乘数。问题就出在这里:NH(t)会在码片边界发生符号翻转,而本地副本里没有这个翻转。
可以打个比方。你和对讲机另一头的人约好每秒钟对一次暗号,但对方临时在暗号里插入了一段停顿,你还是按原来的节奏对,结果后半段全乱套。NH码跳变对本地相关器干的就是这个事:伪码对齐了,但NH码符号在积分窗口内翻转,前半段积分和后半段积分正负抵消,最后累加出来的能量大幅衰减。
具体有多严重,取决于相干积分窗口开了多长。如果只做1ms积分,影响还相对有限;如果做10ms、20ms的相干积分且不补偿NH码,相关峰会直接被压到噪声水平附近,捕获通道怎么搜都搜不到峰。这是很多刚开始做北斗B1捕获的开发者最容易踩的坑。
2. 不同积分策略下NH码跳变的真实杀伤力
2.1 1ms短积分:影响有限,增益受限
B1I伪码周期是1ms,NH码片时长也是1ms,这两者天然对齐。所以如果1ms的相干积分窗口恰好和NH码片边界对齐,那么在这个1ms窗口内NH码不会发生跳变,相关积分完全不受影响。
但是在不知道NH码相位的情况下,积分窗口的起点是随机的。它很可能横跨两个NH码片,导致积分窗口内出现一次符号翻转。最坏的情况是翻转正好发生在窗口正中间,前面0.5ms正号、后面0.5ms负号,积分结果被抵消掉很大一部分。量化来看,最坏情况下相关幅度损失接近一半,换算成功率损耗约2.5dB,平均损耗大约0.9dB。
所以1ms短积分的经验结论是:NH码跳变有影响,但不会让捕获完全失效。很多接收机用1ms相干积分加非相干累加做粗捕获,本质上就是避开NH码跳变的长周期破坏,用短窗口换取稳定性。代价就是灵敏度有限,面对弱信号必须叠很多次非相干累加,而非相干累加本身有平方损耗,累加增益涨得慢。室外开阔场景还凑合,室内和城市峡谷就不太够用了。
2.2 10ms/20ms相干积分:相关峰直接“消失”
真正让NH码跳变变成“杀手”的,是很多人想当然地把相干积分时间从1ms拉到10ms、20ms。逻辑上看起来没问题:积分时间越长灵敏度越高,1ms信噪比不够就积20ms,这不就是标准做法吗?
问题是NH码在20ms内有约一半的码片是反相的。本地副本没有NH码补偿时,接收信号的NH码序列与本地副本不匹配,相关结果里的每个1ms子段都在正负之间来回切换。20ms积分相当于把20段有正有负的值强行加在一起,结果不是互相增强,而是互相抵消。极端情况下,正负各半的序列累加结果直接趋近于零。
这里有一个很多人会忽略的事实:北斗B1I的电文速率是50bps,一个电文比特就是20ms,而NH码周期正好也是20ms。所以在不知晓电文相位和NH码相位的情况下,捕获阶段的相干积分极限也就是20ms左右。超过20ms去积分,不仅要处理NH码,还得处理电文比特的跳变,复杂度完全不一样。
2.3 一组仿真对比,直观看到破坏程度
光说原理不够直观,我把我们项目里做过的一组对比结果放出来,条件是中频采样、C/N0约40dBHz、多普勒置零:
| 积分方式 | 是否补偿NH码 | NH码跳变影响 | 相关峰表现 |
|---|---|---|---|
| 1ms相干 + 10次非相干累加 | 否 | 每个1ms窗口可能有跳变,有均摊损耗 | 峰值可见,但底噪抬升明显 |
| 10ms相干积分 | 否 | 积分内多次翻转,正负抵消 | 相关峰基本消失,淹没在噪声中 |
| 20ms相干积分 | 否 | 正负码片各占约一半 | 峰值趋近于零,捕获失效 |
| 20ms相干积分 | 是(NH码相位已同步) | 无跳变影响 | 峰值显著,增益与理论相符 |
不做NH码补偿时,10ms和20ms相干积分的结果就是“看着噪声说话”——峰值可能出现在任意码相位上,捕获通道报出一堆假候选点。一旦在第二级里正确补偿了NH码,20ms相干积分的峰值一下子就立起来了。
这个对比的核心结论是:在B1I信号上,想靠盲目延长相干积分时间来提升灵敏度是行不通的,必须先解决NH码相位问题。这也是整个捕获策略设计的出发点。
3. 典型的工程应对:NH码相位搜索与二级捕获流程
3.1 为什么不能直接搞“三维搜索”
看到NH码跳变影响这么大,第一个念头往往是:把NH码相位也加进搜索维度不就行了?
理论上确实可以。传统捕获是“码相位 × 多普勒频率”二维搜索,加上NH码相位后变成三维搜索。NH码周期20ms,每个码片1ms,所以NH码相位一共有20种可能。看起来20倍计算量也不算离谱,但实际上这个思路在工程里走不通。
原因很简单:捕获本身就需要把这三维全部穷举才能保证不丢星。B1I伪码长度2046个码片,码相位搜索步进一般半个码片,也就是4092个相位点;多普勒搜索步进取决于相干积分时长,1ms积分时步进约500Hz,从-10kHz到+10kHz约40个频点;再乘上20个NH码相位,粗算就是4092 × 40 × 20 ≈ 327万次相关运算。这个量级在实时接收机里做一次冷启动,硬件资源完全吃不消。
更合理的思路是把搜索过程拆成两级:先用低增益的粗捕快速锁定候选码相位和多普勒,再用NH码相位搜索做精捕验证。计算量从“满屏穷举”降到了“只对少数候选点做精细处理”,硬件负担小得多。
3.2 二级捕获:先1ms粗捕,再精捕NH码相位
我推荐的流程分两级。
第一级是粗捕,用1ms相干积分加非相干累加,快速搜索码相位和多普勒频率。这一步不需要知道NH码相位,因为1ms窗口内跳变影响有限,检测到峰值虽然会有1dB量级的损耗,但峰值依然存在。粗捕输出的是若干候选点,每个候选点包含码相位、多普勒频率和对应的1ms相关值序列。
第二级是NH码相位精捕。对每个候选点,把之前缓存下来的1ms相关值按20ms分组,然后遍历20种NH码相位,分别做符号翻转累加,找出累加结果最大且超过门限的相位。这一步的关键是:粗捕阶段已经算过1ms相关,第二级只是把缓存的复数相关值拿来做乘加,计算量增加得非常有限。
这里给一段软件接收机风格的伪代码,方便理解整个逻辑:
# corr_1ms: 粗捕阶段缓存的1ms相关复数值序列 # nh_code: 20个NH码片对应的符号,+1 / -1 # total_ms: 参与精捕的1ms相关值总个数(建议至少40ms) acc = np.zeros(20, dtype=complex) for phase in range(20): # 按候选相位旋转NH码序列 code_rot = np.roll(nh_code, phase) blocks = corr_1ms.reshape(-1, 20) # 每20ms一组 acc[phase] = np.sum(blocks * code_rot)实际工程里用C写过一版,逻辑一模一样,只是把循环展开成数组运算。20个相位的累加结果里取模值最大的那个相位作为估计值,同时和噪声底做比较,超过门限就认为NH码相位同步成功。
这个二级流程的优势非常明显:粗捕阶段不增加计算量,精捕阶段只在少数候选点上做小规模搜索,整体复杂度比直接三维穷举低了两个数量级。对FPGA实现来说,第一级可以用并行相关器阵列,第二级用一个小的累加状态机就能搞定。
3.3 其他可选方案:DBZP与半比特法
除了二级捕获,还有两个方案在特定场景下有用。
一个是DBZP(Double Block Zero Padding,双块零拓展)。这个算法把接收数据和本地伪码按块做FFT相关,块与块之间做零填充,本质上是对NH码相位进行隐式搜索。它的优点是适用于NH码相位完全未知且信号动态范围大的场景,缺点是实现复杂度高,FFT块的内存和运算量都比普通捕获大一个量级。
另一个是半比特法。思路是利用NH码周期和电文比特周期相同这个特点,把积分窗口切成10ms的“半比特”,每个20ms电文比特内至少有一个半比特不包含数据跳变。但这种做法只能规避跳变,不能同时拿到NH码相位信息,最终还是要回到NH码相位搜索上来。所以半比特法更多用于辅助判决,很少单独作为捕获方案。
三个方案对比如下:
| 方案 | 计算量 | 适用的动态范围 | 实现复杂度 | 工程建议 |
|---|---|---|---|---|
| 二级捕获 | 低 | 中低动态 | 低 | 最推荐,大部分接收机够用 |
| DBZP | 高 | 高动态、弱信号 | 高 | 面向高端接收机 |
| 半比特法 | 中 | 中 | 中 | 作为辅助手段,不用单独依赖 |
4. 实现中的细节坑与调优经验
4.1 积分窗口与NH码边界要对齐
这是我踩过最深的一个坑。二级捕获跑通之后,我发现即便正确搜索了NH码相位,相关峰还是偶尔出现小幅抖动。排查到最后发现,问题不在算法,而在本地积分的启动时刻。
如果积分窗口的起点和NH码片边界存在一个固定偏移,那么即使是1ms的积分窗口,也会周期性跨过NH码跳变点。这种偏移会让粗捕阶段的非相干累加结果出现规律性衰减,严重时直接影响候选点的检测概率。
解决办法是让本地码NCO和NH码时钟从同一个时钟源分频产生,并在捕获流程初始化时做一次码NCO相位对齐,确保每个积分清除脉冲落在伪码周期边沿。这个对齐动作在FPGA里是几行RTL逻辑的事,在软件接收机里则是保证采样起始点位于整码片边界。不要觉得这是小问题,它对整个捕获链路的稳定性影响非常大。
4.2 多普勒残余的相位旋转问题
NH码相位搜索通过符号翻转把跳变消掉了,但20ms的相干积分里还有一个隐藏问题:多普勒残余造成的相位旋转。
粗捕阶段的多普勒搜索步进通常比较粗,残余多普勒可能还有几十赫兹到上百赫兹。20ms积分时间里,100Hz残余多普勒会带来约0.4个完整周期的相位旋转。如果不处理,积分结果里不同1ms段的向量方向不一致,即使NH码相位完全正确,累加出来的峰值也会打折。
处理方式很直接:对每段1ms相关值乘一个补偿旋转因子exp(j·2π·f_d·k·T),其中k是1ms序号、f_d是粗捕后估计的残余多普勒、T是1ms。这个操作可以在软件接收机里用复数乘法完成,在FPGA里用一个相位累加器和CORDIC旋转器实现。实测下来,对残余多普勒做补偿后的20ms积分增益比不做补偿多出2到3dB,效果非常明显。
4.3 多假设检验下的门限校正
NH码相位有20个假设,这个“20”本身也是一个统计问题。
粗捕得到候选点后,第二级会对20个NH码相位都做一次门限判决。如果严格按照单次检测的虚警概率来设门限,20次检验下来的整体虚警概率会显著高于预期。因为只要有一个假设偶然超过门限,就会被当成捕获成功,实际捕获的却可能是噪声峰。
我习惯的做法是把总虚警率指标分摊到20次检验上。比如希望总虚警率在10的负6次方量级,单次检验的门限就要按5×10的负8次方去设。另一个有效的补充判决是“峰值/次峰值比”:把20个NH码相位里最大累加值和第二大累加值做比值,只有比值超过一定阈值才判定捕获成功。强信号下,正确的NH码相位往往显著高于所有错误相位,这个比值能过滤掉大量虚警。
4.4 FPGA和软件接收机的实现差异
同样的NH码补偿逻辑,FPGA和软件接收机的处理风格差别很大。
FPGA里,NH码补偿最常见的方式是准备一个20位的循环移位寄存器,按候选相位生成对应的NH码符号序列,然后跟每个1ms积分结果相乘。要注意累加器位宽必须按“最大符号翻转次数”来设计,否则20ms积分过程中会出现溢出。之前我们第一版累加器位宽按8位设计,结果NH码全正时直接溢出,波形毛刺明显,后来改到16位才稳定。
软件接收机里则更适合用矩阵化方式实现:先做1ms相关得到复数矩阵,再做NH码相位组合的矩阵乘加。这种实现可以利用SIMD指令或BLAS库,20个相位的运算开销几乎可以忽略。但要注意内存布局,按20ms为单位连续存储1ms相关值,避免第二个维度跨缓存行访问导致性能下降。
5. 从仿真到板级:验证NH码补偿效果的完整流程
5.1 仿真阶段最容易漏掉的设置
我见过不少同行在MATLAB里做B1I捕获仿真,结果怎么调都和理论对不上,最后发现是信号源没有生成NH码。B1I信号源直接把伪码乘以电文就发出来了,NH码根本没加进去。用这种信号测捕获算法,当然什么问题都发现不了。
仿真阶段一定要确认信号生成链路里包含NH码调制,并且NH码的起始相位要设置成随机值。建议直接设置一个可配置的NH码相位参数,在仿真里人为制造0到19之间的任意跳变点。这样捕获算法才能在真实条件下接受检验,而不是在一个“假理想环境”里自嗨。
仿真指标我建议重点看两个:一个是第二级NH码相位估计的正确率,另一个是20ms相干积分补偿前后的峰值增益差。如果正确率低于99%,优先检查门限设置和多普勒补偿是否有问题。如果补偿后的峰值增益接近理论值10log10(20),说明整条链路基本正常。
5.2 板级调试:TTFF和捕获灵敏度对比
仿真跑通之后,还是要上真实信号验证。我习惯的做法是先在窗边用真实天线做静态测试,对比两种场景:一种是把NH码补偿旁路掉,只靠1ms粗捕加非相干累加来出定位结果;另一种是完整跑二级捕获NH码补偿流程。
实测数据很能说明问题。旁路NH码补偿时,室内窗边环境下冷启动捕获到的北斗B1卫星数量在4到6颗之间波动,定位时间常常拉到15秒以上;开启完整流程后,同样位置能捕获6到9颗卫星,冷启动定位时间可以压到8秒左右。弱信号场景下差异更明显,比如靠近建筑物遮挡的位置,不补偿NH码时B1频点基本抓不住,补偿后还能稳定锁定两到三颗星,这就保证了定位不会被完全打断。
另一个值得做的是把信号源输出的功率逐步降低,标定捕获灵敏度。我们实测下来,加入NH码补偿后B1频点的捕获灵敏度比单纯1ms非相干累加提升了大约6到8dB,代价只是第二级多了20个累加分支,这个性价比非常划算。
5.3 一点个人体会
在多个项目里做完B1I捕获之后,我的体会是NH码跳变本身不难处理,真正坑人的往往是很隐蔽的工程细节。积分窗口和NH码边界有没有对齐,多普勒残余有没有补偿,门限有没有按多假设校正,这些才是决定捕获性能稳定性的关键。
如果你的接收机目前遇到北斗B1搜不到星、或者冷启动时间不稳定的问题,建议先把捕获链路拆开,确认每一级输出是否合理。不要一上来就怀疑射频前端和天线,很多时候问题就出在NH码这个“看不见的次级码”上。
这个处理思路不仅适用于B1I。后续如果接入B1C或者其他带次级码的新体制信号,NH码相位搜索、二级捕获、多假设门限校正这套方法论可以直接复用,需要改动的只是码速率、周期和具体码型参数。从这个角度看,把NH码跳变的应对吃透,一劳永逸。