一个 GNSS 软件接收机:Qt6 + C++20,把 GPS / 北斗 / Galileo 十种信号全部打通
一个不依赖任何第三方 GNSS 库的离线软件接收机。21,000 行 C++20 从捕获、跟踪、导航电文译码一路写到多系统联合定位,配上完整的 Qt6 图形界面和命令行批处理工具。十种民用卫星导航信号全部形成实采闭环,包括 Galileo E5 AltBOC 这种 58 Msps 的宽带信号。
如果你是在读卫星导航 / 测绘 / 通信 / 航空航天专业、想把课本上的公式真的跑一遍的学生;如果你是用 RTL-SDR、HackRF 玩过一阵、想试试把天上的卫星抓下来的爱好者;或者你手上恰好有一份采集数据、想知道它到底能不能用——这个项目就是为你写的。
十种民用卫星导航信号全部形成实采闭环,包括 Galileo E5 AltBOC 这种 58 Msps 的宽带信号,具体为B1i,B1c,B2a,B2b,B3i,L1ca、E1、E5a、E5b、E5ab-altboc。每种信号都能独立捕获、跟踪、电文提取、测量、定位。
一、写给三类人:为什么值得自己动手做一遍
先说清楚这个项目是为谁做的。开源的 GNSS 软件接收机不算少,但大多停在「能跑通」;而这个项目从第一天就按「能当学习平台 + 能当验证工具」两条线来设计,因为它最初就是被这两个需求逼出来的。
1.1 卫星导航专业的同学:把教科书里的公式跑起来
如果你在读测绘、导航、通信、航空航天相关专业,大概经历过这种割裂:
课堂上讲扩频、讲相关峰、讲 DLL/PLL 环路带宽、讲最小二乘定位——公式都懂,考试也会做。但真想验证「PLL 带宽从 25 Hz 降到 15 Hz,载噪比门限会怎么变」这类问题时,发现手边没有一个能随手改、改完立刻看到曲线的工具。MATLAB 能算,但要自己把整条链路搭起来已经是另一个课题了。
这个项目就是那个工具。参数面板上几乎每一个数字都对应教科书里的一个公式:
| 你在课本上学到的 | 在这个项目里对应 |
|---|---|
| 相干积分时间 | 捕获页「相干积分」 |
| 多普勒搜索范围与步长 | 捕获页「多普勒上限 / 多普勒下限 / 搜索步长」 |
| 非相干累加次数 | 捕获页「非相干累加次数」 |
| 检测门限与虚警概率 | 捕获页「峰值比门限」 |
| DLL / PLL / FLL 环路带宽 | 跟踪页「DLL 带宽 / PLL 带宽 / FLL 带宽」 |
| 早迟码间距 | 跟踪页「E-L 间距」 |
| 锁定判据 | 跟踪页「通道状态」与「C/N0 (dB-Hz)」两列 |
改一个数、点「应用配置」、跑一遍,捕获率和载噪比曲线立刻给出反馈。这比读十页论文更容易建立直觉,而且每一条曲线背后都是真实的采样数据,不是仿真出来的理想波形。
而且系统对参数状态是有门控的:改动任何影响结果的参数,配置就会退回「未应用」状态,三个处理按钮随之禁用。这看似麻烦,实际上是在保护你的实验记录——**不会出现「参数改到一半就跑出一份说不清配置的结果」**这类事故:
更值得说的是那些只在真实系统里才会冒出来的问题——这些恰恰是课本很少讲、但工程里天天遇到的:
- 采样率不是码率的整数倍时,相关器怎么做分数间隔抽取
- 多普勒搜索步长怎么和相干积分时长互相约束:步长太细浪费时间,太粗会直接跨过主峰
- 低载噪比下环路为什么失锁,载波锁定指标怎么提前告诉你「快不行了」
- 导航电文为什么要交织、为什么要 CRC,误码是怎么被维特比译码纠回来的
- 多个系统的时间基准不一致,为什么必须额外估一个系统间偏差,不然位置会被偏差吃掉
因为整个工程是完整实现的,你会顺带看到这些问题的真实解法,而不是一句「此处做相应处理」。项目自带单元测试和一整套实采数据验收门,所以它同时也是一个现成的课程设计 / 毕业设计 / 论文实验平台——需要做对比实验时,批处理工具能保证每组结果都可复现、可追溯。
1.2 无线电与 SDR 爱好者:把天上的卫星真的抓下来
如果你玩过 RTL-SDR、HackRF、USRP,用dump1090抓过飞机、用rtl_433收过气象站,那下一步几乎必然是——卫星。
卫星信号的魅力在于它有一个「已知答案」:GPS L1 C/A 的扩频码是公开的,卫星在哪儿是可以算出来的,你解出来的位置对不对,地图上一看就知道。这种有真值可以对照的解码,比抓一堆不明所以的 433 MHz 脉冲要过瘾得多。
但这条路卡住了不少人:抓到一段 IQ 数据之后呢?装 GNSS-SDR 太重、编译半天还不一定过;自己写脚本又只能做到捕获,跟踪一失锁就不知道怎么往下调。
这个项目在这条路上给你一个从采样文件到经纬度的完整闭环,而且每一步都能看见:
- 相关能量热力图 —— 亲眼看到那条码相位 × 多普勒的亮线
- 八通道锁定状态表 —— 看每颗卫星什么时候从 PULL-IN 跳进 LOCK
- 载噪比与多普勒双曲线 —— 直观感受信号强弱和卫星相对运动
- 天空图 —— 解算出的卫星方位俯仰,和窗外的实际天空对得上
它对采集设备的宽容度也比预期高。项目实测数据里就包含了 8 位、4 位、2 位三种量化位宽,零中频和带中频两种频谱摆放,甚至还包括采集器启动瞬态(开头的 32 ms 是无效数据,靠配置里的「跳过」选项处理)。这些坑都已经踩过并写进了内置预设——同类采集器出来的文件,基本开箱即用。
1.3 做信号验证的人:一份陌生的采集数据,到底能不能用
这是最实用、也最少被开源项目认真对待的场景。
你手上可能有一份新前端采回来的数据、一个新信号制式的样本、或者一段别人给的、来历不明的 IQ 文件。在投入真正的开发之前,你需要先回答几个问题:
- 采样率、中频、量化位宽猜对了吗?
- 里面真的有信号吗?峰值比够不够?
- 能锁定吗?载噪比有多少?
- 导航电文能解出来吗?CRC 过不过?
- 最后能不能算出位置?
这个项目把这条体检流程做成了几行命令:
# 全星座扫描:这份文件里到底有什么,能不能定位gnss_batch.exe--acquire--signalsgps-l1ca --track-seconds40\--require-lock --self-check--unpaced-oout/probe data/unknown.bin关键在于结尾那一串--require-*断言:任何一个不满足,进程就以非零码退出。也就是说「这份数据能不能用」这个问题,可以直接由退出码回答,不需要人肉翻几万行日志——挂到流水线里就是一道自动门槛。
我们已经用这套流程做完了 Galileo 四种信号的数据验收,其中E5 AltBOC 是一份 58 Msps、约 23 GB 的宽带采集文件,最终结果是:8 颗卫星全部锁定、171 页电文 CRC 全部通过、组装出 7 组完整星历、14 个定位解。
而且验证结论是可追溯的。每次运行都会写一份manifest.json,记录:
- 软件版本与配置模式版本(
buildVersion/schemaVersion) - 本次构建支持哪些信号(
capabilities) - 对照的接口控制文件版本(
icd) - 输入文件路径、大小与时间戳
- 这次实际处理了哪些信号和卫星(
processingSignals/processingPrns) - 各阶段统计量
把这份 JSON 连同结果 CSV 一起归档,几个月后重新翻出来也能说清「这个结论是怎么来的」。写验收报告、复现同事的实验、排查「上次明明能跑」——靠的都是它。
1.4 共同的起点:MATLAB 原型和工程软件之间那道鸿沟
三类人最终都会撞上同一堵墙。
MATLAB 里调个xcorr就完事的捕获,到了工程里要考虑:采样率不是码率的整数倍、FFT 要不要补零、多普勒步长和积分时长怎么互相约束、跨码周的相关结果怎么累加对齐。跟踪环路更麻烦——教科书上的二阶环路公式很干净,真实数据里的失锁、周跳、多径、采样时钟偏差会让它变得很难缠。
这个项目做的事,就是把这道鸿沟完整走一遍,并把过程留下痕迹:
21,000 行 C++20,不用任何现成的 GNSS 库。码生成、相关器、环路滤波、维特比译码、CRC 校验、最小二乘——全部自己实现。同时配上一套能让人看见中间量的界面,和一套能让人自动回归的命令行工具。
学完能带走的东西很具体:一个能改、能跑、能验证的接收机,以及一份能解释每一个数字从哪来的能力。这大概是自己动手做一遍和直接用现成软件之间,最大的差别。
二、它到底能做什么
先给结论:一个学习平台该有的能力,它基本齐了——十种信号、完整三段式流程、参数全可调、中间量全可导出;一个验证工具该有的能力,它也齐了——命令行入口、断言门槛、结果缓存、可追溯清单。
下面按「信号覆盖 → 处理流程 → 实际操作」三层展开。
2.1 信号覆盖:十种民用信号,全部实采闭环
| 系统 | 信号 | 中心频率 | 状态 |
|---|---|---|---|
| GPS | L1 C/A | 1575.42 MHz | ✅ 全链路闭环 |
| 北斗 | B1I | 1561.098 MHz | ✅ 全链路闭环 |
| 北斗 | B1C | 1575.42 MHz | ✅ 全链路闭环 |
| 北斗 | B2a | 1176.45 MHz | ✅ 全链路闭环 |
| 北斗 | B2b | 1207.14 MHz | ✅ 全链路闭环 |
| 北斗 | B3I | 1268.52 MHz | ⚠️ 捕获/跟踪/电文/观测量已验证,定位链已接入 |
| Galileo | E1 | 1575.42 MHz | ✅ 全链路闭环 |
| Galileo | E5a | 1176.45 MHz | ✅ 全链路闭环 |
| Galileo | E5b | 1207.14 MHz | ✅ 全链路闭环 |
| Galileo | E5 AltBOC | 1191.795 MHz(中心) | ✅ 全链路闭环(宽带) |
| Galileo | E6-B/C | — | 🔬 实验源码,默认不编译 |
这里有个容易被忽略的设计点:这三个系统的频点是重叠的。GPS L1 C/A、北斗 B1C、Galileo E1 同在 1575.42 MHz;北斗 B2a 与 Galileo E5a 同在 1176.45 MHz;B2b 与 E5b 同在 1207.14 MHz。所以一份中频数据里往往同时躺着多个系统的信号,能不能把它们都抓出来、并联合解算,是衡量接收机灵活性的硬指标。
「全链路闭环」在这里不是「能捕获到就算」,而是指一段数据完整走完:
捕获 → 跟踪 → 导航电文译码 → 星历组装 → 观测量构造 → 定位解算2.2 处理流程:经典三段式,但每一段都留了口子
三段式处理本身是教科书标准做法,但真正让这个项目好用起来的,是每段之间都做了可观察和可干预:
- 捕获段:二维搜索(码相位 × 多普勒),输出峰值比、码相位、多普勒频移,并把相关能量热力图一起留盘。
- 跟踪段:每颗卫星一条独立通道,DLL/PLL/FLL 三环并联,实时输出 E/P/L 相关值、载噪比、锁相指标、相关峰比。
- 定位段:观测量构造 + 迭代最小二乘(Householder QR),输出位置、钟差、精度因子、残差。
2.3 五步操作,从打开文件到出定位
界面上的流程被刻意压到五步:打开数据 → 应用配置 → 开始捕获 → 进入跟踪 → 开始定位。
其中「应用配置」这一步是刻意设的:改任何影响结果的参数都会让配置回到「未应用」状态,三个处理按钮随之禁用。这不是麻烦,而是防止你在参数改了一半的状态下跑出一份说不清参数的结果——实验可复现的前提是参数状态明确。
三、核心亮点
亮点 1:P ⊇ T ⊇ S —— 把实验自由度做成了一套自洽的模型
这是整个项目里我个人最满意的设计。
通常的 GNSS 软件只有一层选择:处理哪些卫星。但这个项目把选择拆成了两层,并强制约束包含关系:
S ⊆ T ⊆ P- P(处理选择):决定哪些(系统,信号,卫星)进入捕获与跟踪流水线。
- T(跟踪成功集合):不是选出来的,是跑出来的——真正锁定并取得有效观测的那些。
- S(定位参与选择):在 T 的基础上,进一步挑哪些观测参与定位解算。
这样做的价值在哪?举个具体场景:
你手里有一份 GPS + 北斗 B1C 的数据。你想同时跟踪两套信号看它们的载噪比对比,但只想用 GPS 单系统算一个位置做基准,再用双系统算一个位置做对比。
传统软件里你得跑两遍。而在这里,P 填全部、S 只填 GPS,跑一遍就能拿到:T 里的全部通道数据(用于对比)+ S 限定的 GPS 单系统位置。换个 S 再点一次定位,不用重新跟踪。
而且系统会主动拒绝非法组合:如果你在 S 里填了一颗没跟踪成功的卫星,它不会静默忽略,而是明确报错告诉你为什么不合法。
界面上的呈现也很直观——P / T / S三个数一直显示在总览页,S 永远不可能大于 T:
亮点 2:多系统联合定位,待估参数随系统数自动增长
单系统定位解 4 个未知量(三维位置 + 接收机钟差)。但当你把多个系统的观测混在一起解算时,问题来了:每个系统的时间基准不一样。
解决方案是在待估参数里加一项系统间偏差(ISB):
| 参与系统数 | 待估参数个数 | 说明 |
|---|---|---|
| 1 个 | 4 | 位置 3 + 钟差 1 |
| 2 个 | 5 | 追加 1 个系统间偏差 |
| 3 个 | 6 | 追加 2 个系统间偏差 |
参考系统优先取 GPS(GPS 在场时),否则取参与解算的第一个系统——参考系统本身不估计偏差项。
实测中这一点得到了直接验证:在 GPS + 北斗 B1C + Galileo E1 的三系统数据上,70 个定位历元里有 2 个真正完成了三系统联合解,GPS ISB 与 Galileo ISB 两列同时非零,且 GDOP 2.634 / 2.637 是全部 70 个历元中最好的两个:
双系统是更常见的工作点,GPS + 北斗 B1C 的组合同样能稳定解出联合解,此时待估参数为 5 个:
亮点 3:E5 AltBOC 宽带处理 —— 一份数据,同时吃出两套电文
Galileo E5 AltBOC 是这个项目里技术含量最高的部分。
AltBOC(15,10) 调制在 1191.795 MHz 中心频率上,副载波 15.345 MHz,把信号能量推到中心频率两侧约 ±15 MHz 处,形成一个宽带信号。它有两个导频分量(E5a-Q、E5b-Q)和两个数据分量(E5a-I、E5b-I),而 E5a-I 承载 F/NAV 电文、E5b-I 承载 I/NAV 电文——是两套不同的导航电文。
这个项目的做法是:用 58 Msps 的完整宽带采样,把中频分别搬移到两侧边带(±15.345 MHz)各搜一次,然后把同一时延、同一多普勒上的两路非相干功率相加。理由是整带复现码的副载波相位未知,直接合成复现码会产生旁瓣歧义。
结果是 8 条跟踪通道,同时产出两套电文:
| 电文类型 | 页数 |
|---|---|
| I/NAV(来自 E5b-I) | 147 |
| F/NAV(来自 E5a-I) | 24 |
| 合计 | 171 页,CRC 全部通过 |
这一点很关键:它不是分别跑 E5a 和 E5b 再拼出来的结果,而是同一条通道在真实的宽带数据上同时解出两套电文,最终组装出 7 颗卫星的完整星历。
另外,E5 AltBOC 的四分量结构在跟踪时要处理两个额外的相位细节:副载波相位修正、以及数据分量 E5b-I 的半相位修正因子;调度块跨码周回绕时(1 ms 码周期)还要做两段累加对齐。
亮点 4:捕获与观测缓存 —— 改选择不用重跑
GNSS 数据处理最耗时的两步是捕获(二维搜索)和跟踪(长时间环路收敛)。这个项目把两者的结果都做成缓存落盘:
<结果目录>/acquisition/acquisition-cache.json # 捕获缓存 + 热力图 <结果目录>/tracking/observation-cache.json # 观测缓存缓存按输入数据指纹判断有效性。换文件、换采样率、换量化格式——缓存自然失效重跑;但只是改 S 集合、改定位组合、改精度因子阈值——直接读缓存,秒级出结果。
实测中这套机制的价值非常直观:一份 42 秒的 E5 AltBOC 数据(58 Msps),完整跑一遍捕获 + 跟踪 + 定位需要比实时慢约 7 倍;而缓存命中后,改定位组合重算几乎是瞬时的。
亮点 5:界面 + 批处理双入口,断言直接当 CI 门槛
图形界面适合探索和教学,命令行适合自动化和回归。这个项目两条路都做全了。
命令行工具gnss_batch.exe提供49 个选项,覆盖信号选择、卫星范围、跟踪时长、缓存输入输出,以及一整套验收断言:
# 要求 8 颗 GPS 卫星全部捕获并锁定,然后自洽定位gnss_batch.exe--acquire--signalsgps-l1ca--prns3,6,9,15,18,21,22,26\--track-to-end --require-acquired --require-lock --self-check\--unpaced-oout/gps data/GPSdata.bin# 三系统联合定位验收:断言必须解出联合解gnss_batch.exe--acquire--signalsgps-l1ca,bds-b1c,gal-e1\--prns4,8,11,32 --b1c-prns6,8,9,11,12,20,29,30,36,40 --e1-prns16,17,28,30\--track-seconds40--require-joint-pvt-oout/e1-3sys data/B1L1E1_80.bin断言不满足时进程以非零码退出,所以可以直接挂在流水线里当回归门槛——这一点在持续迭代算法时非常省心。
值得一提的是 Galileo 的验收方式:它没有专门的断言开关,而是用「--require-lock+--require-joint-pvt+ 脚本核对galileo_ephemeris.json的条目数」组合完成。这是有意为之——galileo_navigation.csv里每一页的 CRC 结果、星历有效标志、IODnav 都被如实写出来了,比一个布尔断言能说明的问题多得多。
亮点 6:结果可复现 —— manifest.json 记下全部上下文
每次处理都会在输出目录写一份manifest.json,内容包括:
buildVersion/schemaVersion(配置模式版本)capabilities(本次构建支持哪些信号)icd(对照的接口控制文件版本)inputPath/inputBytes/createdUtcprocessingSignals/processingPrns、positioningSignals/positioningPrnsstatistics(各阶段统计量)
也就是说,任何一份结果都能回溯到产生它的软件版本、配置和数据文件。做实验记录、写论文、报验收,这一份 JSON 就够了。
配置格式还支持 schema 版本自动迁移——v1 的旧配置能被 v2 的软件直接读入。
四、实测数据
全部数字来自真实采样文件的批处理运行,不是仿真或估算。结果明细(CSV / JSON)随每次运行留盘。
4.1 四个数据集的完整结果
| 数据集 | 信号与处理选择 | 处理范围 | 捕获/锁定 | 电文页 | 星历 | 定位历元 | GDOP |
|---|---|---|---|---|---|---|---|
| E5a-8.bin | Galileo E5a,SVID 10/21/28/29 | 52 s | 4 / 4 | 20(F/NAV) | 4 | 3 | 6.49 ~ 6.53 |
| E5b-8.bin | Galileo E5b,SVID 4/19/21/23/28 | 42 s | 5 / 5 | 86(I/NAV) | 4 | 9 | 16.73 ~ 16.83 |
| E5ab-4.bin | E5 AltBOC,8 颗 SVID | 42 s | 8 / 8 | 171(I/NAV 147 + F/NAV 24) | 7 | 14 | 3.70 ~ 8.99 |
| B1L1E1_80.bin | GPS 4 + 北斗 B1C 10 + Galileo E1 4 | 40 s | 18 / 18 | 76(E1 I/NAV) | 4 | 70 | 2.63 ~ 10.59 |
E5a、E5b、E5 AltBOC 三份数据的定位结果落在同一地点附近,水平坐标一致到百米以内、高程一致到 14 m 以内——这本身就是对 Galileo 三种信号处理链正确性的一个交叉验证。
E5a 用的是 F/NAV 电文,页结构、交织方式和校验长度都与 I/NAV 不同,界面上可以逐页看到 CRC 与星历有效标志:
4.2 三系统联合解的构成
B1L1E1_80.bin 的 70 个有效定位历元,按参与系统拆开看:
| 参与系统 | 历元数 | GDOP 范围 |
|---|---|---|
| GPS 单系统(L1 C/A) | 30 | 10.29 ~ 10.59 |
| 北斗单系统(B1C) | 18 | 4.13 ~ 4.14 |
| Galileo 单系统(E1) | 4 | 7.61 ~ 7.63 |
| GPS + 北斗(L1 C/A + B1C) | 16 | 3.26 ~ 3.26 |
| GPS + 北斗 + Galileo(三系统) | 2 | 2.63 ~ 2.64 |
这张表很能说明多系统联合的意义:从 GPS 单系统的 GDOP 10.3 一路降到三系统的 2.63。
三系统能成立的前提是三套信号都被干净地抓下来——18 项捕获全部通过峰值比门限:
4.3 一个有意思的观察:系统间偏差是 1 ms 码周期的整数倍
三系统联合解里,两个系统间偏差的估计值分别是:
- 北斗对 GPS:约2 698 129 m
- Galileo 对 GPS:约899 372 m
单看绝对值会觉得大得离谱。但注意这些信号的主码周期都是 1 ms,对应光速距离 299 792 m:
2 698 129 ÷ 299 792 ≈ 9.000 899 372 ÷ 299 792 ≈ 3.000两个偏差几乎正好是 1 ms 主码周期的整数倍——北斗 9 倍,Galileo 3 倍。
这说明它们根本不是物理上的距离偏差,而是系统时间基准之差换算成的距离(那个位置本来就被接收机钟差和整码周期吸收了,剩下的时基差落到了这一列)。所以判断解算是否正常,不能看绝对值大小,而要看它在一段数据内是否稳定:
- 北斗 ISB 在 18 个历元上的跨度:2.82 m
- Galileo ISB 在 2 个历元上的跨度:1.54 m
重复性都在米级以内——说明偏差项被稳定估计,而不是被位置分量错误吸收。
如果某一历元这一列突然跳变几百米以上,才说明对应卫星的码相位发生了整周期错位,或电文与观测的配对出了问题。这是排查多系统解算问题时一个非常实用的判据。
五、界面一览
界面采用「中央结果区 + 左右可停靠面板」布局,中间是七个结果页:
| 页面 | 内容 |
|---|---|
| 总览 | 配置摘要、三集合状态、运行状态、七个页签入口 |
| 捕获结果 | 每颗卫星的峰值比、码相位、多普勒、相关能量热力图 |
| 跟踪动态 | 通道状态表、载噪比曲线、多普勒曲线、载波锁定指标 |
| 导航电文 | 按信号分类的电文页表,CRC 与星历有效标志 |
| 观测量 | 每颗卫星最新历元的伪距、多普勒、钟差 |
| 定位结果 | 位置、精度因子、轨迹、天空图、定位历元表 |
| 处理与定位选择 | P / T / S 三集合的编辑与包含关系展示 |
各页实拍:
捕获结果页—— 峰值比门限判定一目了然,热力图能看出相关峰的形态:
多系统数据会按星座分块列出,同一份文件里 GPS 与北斗 B1C 各自成组:
跟踪动态页—— 八通道状态表 + 载噪比 / 多普勒双曲线:
导航电文页—— 每页的 CRC 与星历有效标志都如实显示:
观测量页:
定位结果页—— 位置、精度因子、轨迹与天空图:
单系统(GPS L1 C/A)的定位页,天空图上按星座配色的卫星分布一眼可辨:
界面支持亮色 / 暗色主题切换,星座配色是全站统一的:GPS 蓝、北斗红、Galileo 绿、GLONASS 橙。
六、工程实现
6.1 分层架构
| 层次 | 职责 |
|---|---|
| 界面层 | Qt6 窗口、结果页、图表、主题 |
| 应用控制层 | 状态机、界面与处理的编排、线程与心跳 |
| 处理层 | 捕获、跟踪、定位三段调度与结果组织 |
| 算法信号层 | 码生成、相关、环路滤波、电文译码、最小二乘 |
| 数据层 | 采样文件读取、缓存、CSV / JSON 导出 |
处理层与界面层通过信号槽和后台线程解耦——界面上的曲线长时间不刷新会触发运行指示器变黄,提示超过 500 ms 未收到心跳,而不是让界面卡死。
6.2 规模与技术栈
| 项 | 内容 |
|---|---|
| 语言 / 标准 | C++20 |
| 界面框架 | Qt 6.11 |
| 构建 | CMake + Ninja,MinGW 13.1 x64 |
| 代码规模 | 约 21,400 行 C++(含头文件与测试) |
| 第三方 GNSS 库 | 无 |
这是我认为最值得强调的一点:没有用任何现成的 GNSS 库。码生成、相关器、锁相环、维特比译码、CRC 校验、最小二乘——全部自己实现。码资源表从官方 ICD 内嵌附件生成后固化成.inc文件,运行时零外部依赖。
6.3 电文译码的几个实现细节
以 Galileo 的 I/NAV 与 F/NAV 为例,两种电文的参数并不相同:
| 项目 | I/NAV | F/NAV |
|---|---|---|
| 每页符号数 | 250 | 500 |
| 同步头 | 10 符号固定图案 | 12 符号固定图案 |
| 编码数据段 | 240 符号 | 488 符号 |
| 纠错编码 | 1/2 速率卷积码,约束长度 7,64 状态 | 同 I/NAV |
| 交织 | 30 列 × 8 行分块交织 | 61 列 × 8 行分块交织 |
| 维特比输出 | 120 比特 | 244 比特 |
| 校验 | CRC-24Q(多项式 0x864CFB) | CRC-24Q,校验前 238 比特 |
卷积码的生成多项式为 G1=171、G2=133(八进制),且第二支路输出取反。星历需要类型 1 ~ 4 的页面齐备后才组装,并记录 IODnav。
6.4 构建与发布
一键发布脚本会比对源码与上次发布记录、构建、跑单元测试与界面烟雾测试,然后把 Qt、MinGW 和插件依赖部署到发布目录:
.\scripts\publish-windows.ps1每次替换前的版本自动备份,版本号与源码指纹记录在release.json里——发出去的东西都能对上源码。
测试注册在 CTest 里,从核心单元测试、无界面 GUI 冒烟测试,到实采数据验收门(GPS PVT、B1C 锁定、实采导航子帧、GPS+BDS 联合定位),一条命令跑完。
七、快速上手
# 构建&'C:\Qt\Qt6.11\Tools\CMake_64\bin\cmake.exe'--preset windows-mingw-debug &'C:\Qt\Qt6.11\Tools\CMake_64\bin\cmake.exe'--build--preset windows-mingw-debug# 跑测试&'C:\Qt\Qt6.11\Tools\CMake_64\bin\ctest.exe'--preset windows-mingw-debug# 发布(部署 Qt 依赖到 out/windows-x64).\scripts\publish-windows.ps1然后直接双击out\windows-x64\gnss_app.exe就能用。命令行工具在同一目录:
.\out\windows-x64\gnss_batch.exe--help原始数据文件只读使用,不会被复制到构建或输出目录——这一点对动辄几十 GB 的采集文件很重要。
八、已知限制
写文档和写代码一样,把边界说清楚比夸大能力更有价值:
- GPS / 北斗 / Galileo 三大系统已完成全链路实采验证,北斗 B3I 的定位链已接入但受单文件可用星数限制,尚未形成单文件解算。
- 没有外部真值,因此项目不声明绝对定位精度,只声明内部自洽与多源一致性。
- Galileo 星历尚未与精密星历比对——计划纳入后续验证。
- Galileo E6-B/C 仅作实验源码,默认不编译,待有实测数据后再验证。
- 北斗 GEO 卫星按设计跳过——BDS GEO 相对地球静止、多普勒近零、仰角无连续变化,其捕获跟踪行为与 MEO/IGSO 差异显著,超出本阶段范围。界面会明确标出被忽略的 GEO 卫星及原因。
- Galileo 单系统可用星数偏少,几何条件不如 GPS,精度因子偏大。
九、小结
回到最开始的问题:为什么值得自己动手做一遍?
因为这个过程会把 GNSS 从**「一个黑盒函数」变成「一条你能看到每一步的流水线」**。做完之后,你对「为什么低仰角卫星容易失锁」「为什么多系统联合能改善精度因子」「为什么系统间偏差看起来大得离谱但其实正常」这些问题的理解,会和只看论文完全不同。
对三类人来说,它的价值分别落在这里:
| 你的身份 | 它给你的东西 |
|---|---|
| 卫星导航专业学生 | 一个把课本公式跑成曲线、能改能对比的实验平台;从捕获到定位的每一层都看得见、可复现,可直接用于课程设计、毕设与论文实验 |
| 无线电 / SDR 爱好者 | 从一段 IQ 采样文件到地图上经纬度的完整闭环;热力图、锁定状态、载噪比、天空图,每一步都有反馈,采集格式的坑已内置预设 |
| 做信号验证的工程师 | 一套由退出码回答「这份数据能不能用」的体检流程;断言可挂流水线,manifest.json让结论可追溯 |
这个项目把这件事做得比较完整:
- 十种民用信号全部实现,包括 58 Msps、23 GB 量级的宽带 E5 AltBOC
- 零第三方 GNSS 依赖,21,000 行 C++20 自己写到底
- P ⊇ T ⊇ S 三集合模型,把实验自由度做成了自洽的约束系统
- 多系统联合定位,待估参数随系统数自动增长,ISB 稳定可观测
- 缓存 + 断言 + manifest,让结果可复现、可回归、可追溯
- 界面与批处理双入口,教学探索和自动化验收两不误
如果你正在学 GNSS、做 SDR 相关的课题、在做信号验证,或者只是单纯对「手机是怎么知道自己在哪」这件事有过好奇——希望这个项目能帮你少走一点弯路。
欢迎在评论区聊聊你想拿它验证什么,或者卡在了哪一步。
本文所有实测数据均来自项目批处理程序的真实运行,结果明细留盘可复现。