简介:一项基于C语言实现的GPS信号模拟器源码,面向软件定义无线电(SDR)开发者、GPS接收机测试人员及定位算法研究者。它能够生成GPS基带IQ信号数据流,配合常见SDR平台即可上变频为射频信号,用于室内外GPS信号回放与接收机性能验证。压缩包大小约4.28MB,内含完整的C源码与跨平台构建说明,支持Windows/Visual Studio和Linux/GCC环境下编译;同时提供用户运动文件接口,能以10Hz采样模拟动态接收场景,并通过调整最大样本容量扩展更长的运动轨迹。生成的数据流可作为后续RF转换的输入,便于在实验室环境中模拟不同运动状态下的GPS信号,从而降低外场测试门槛。目前已有2383人学习下载,通过研读该项目可掌握GPS基带信号生成原理、SDR发射端配置流程,且适合作为GNSS相关课程设计或二次开发的基础框架。 gps-sdr-sim 是我这几年做接收机测试时用得最顺手的软件定义GPS信号模拟器,没有之一。这个项目用纯软件的方式生成 GPS L1 C/A 基带信号,再配合几十块钱到几百块钱的 SDR 硬件转成射频,就能让手机、车载导航模块、RTK 接收机在室内或者屏蔽环境里“看到”完整的卫星信号。很多人从入门到放弃,多半是卡在星历数据、时间同步和发射链路这三道坎上。这篇文章把原理、实操和排查方法一起讲清楚,适合正在做 GNSS 接收机开发、产线测试、算法验证,或者单纯想把 GPS 信号生成流程摸透的工程师。
1. 软件定义在 GPS 信号模拟里的意义
1.1 为什么不用硬件模拟器
传统的 GPS 信号模拟器是专门的硬件盒子,能输出多通道、多星座、高动态场景,价格从十几万到上百万不等。对多数研发团队和实验室来说,这个成本很难承受,而且改一个场景参数往往要通过厂商的专用界面,灵活性有限。gps-sdr-sim 这类项目把信号生成全部搬到 PC 上,只要 CPU 算力够,就能按你指定的时间、位置、轨迹生成对应的数字基带信号,再由通用 SDR 设备负责上变频和发射。这样整套环境成本降到几千块,而且所有参数都在命令行和配置文件里,改起来非常方便。
软件定义带来的另一个好处是完全可控。硬件模拟器内部对信号波形、噪声、多径等细节的暴露程度有限,而软件方案里你能直接看到 IQ 数据,能分析信道结构,能对导航电文做定制。比如你想验证接收机的某颗星捕获算法,可以直接生成只包含特定 PRN 号的信号;想测试微弱信号下接收机的灵敏度,可以调节信号幅度和加噪声。这些操作在硬件模拟器上往往是被锁定或者需要额外授权的高端功能,在 gps-sdr-sim 里就是改几个参数的事。
1.2 典型应用场景与适合人群
这个方案最常见的落地场景有三类。第一类是接收机研发和回归测试,比如你在写一个定位算法,需要大量不同卫星分布、不同运动状态下的信号输入,用 gps-sdr-sim 批量生成 IQ 文件就能把测试自动化跑起来。第二类是产线功能测试,很多物联网模组本身就带 GNSS,像 EC200A 这类模组在产线上不可能等真实天空信号,用模拟信号源判定位功能是否正常,速度快还稳定。第三类是教学和原理验证,把 GPS 信号从生成到解调完整走一遍,学生能直观看到 C/A 码、导航电文、多普勒频移这些抽象概念到底长什么样。
适合读这篇文章的人,我默认你已经会用命令行,知道 GPS 大概是怎么定位的,但不一定清楚卫星信号的具体结构。如果你只是想拿手机 GPS 工具箱导点数据玩一玩,也能从里面找到操作步骤。下面内容都基于 Linux 环境,Windows 用户用 WSL 或者 MinGW 编译的思路也完全一样。
2. 核心原理:模拟器到底在模拟什么
2.1 C/A 码、导航电文与载波的关系
GPS L1 频点的载波频率是 1575.42 MHz,上面调制着两部分东西:C/A 码和导航电文。C/A 码是 1023 个码片组成的 Gold 码,码速率 1.023 MHz,所以一个码周期正好 1 毫秒。每颗卫星分配一个唯一的 PRN 号,对应不同的 Gold 码序列。导航电文的速率只有 50 bps,内容包含星历、时钟参数、电离层改正信息等。实际发射的信号可以理解成“载波 + C/A 码 + 导航电文”三层的乘积:先用 50 bps 的导航电文和 1.023 MHz 的 C/A 码做异或,再用异或结果对 1575.42 MHz 载波做 BPSK 调制。
接收机要完成定位,至少要做两件事。第一是捕获:在本地生成各颗卫星的 C/A 码,去和接收信号做相关运算,找到码相位和载波多普勒频移;第二是跟踪和电文解调:锁定之后持续跟踪,从信号里剥离 C/A 码,再把 50 bps 的导航电文解出来。gps-sdr-sim 生成信号的本质,就是在一堆数字样本里把这些层次精确地叠加出来。你指定一个位置和时刻,它先算当前可见卫星,再根据卫星星历计算每颗星的伪距、多普勒、功率延迟,然后逐样本生成 C/A 码和导航电文并调制到数字载波上。最终输出的 IQ 文件,就是接收机天线口之前那个“虚拟世界”的完整采样。
2.2 星历文件与 GPS 时间基准
生成信号必须有星历,否则接收机解不出卫星位置,也就谈不上定位。gps-sdr-sim 使用 RINEX 格式的广播星历文件,这种文件在公开数据源每天更新,文件名类似brdc3540.14n,表示 2014 年第 354 天的 GPS 导航文件。星历里包含了卫星开普勒轨道参数、钟差参数、轨道摄动修正项等。需要注意的是,星历有有效期,一般从参考时刻开始几小时内有效,过期的星历不是不能用,而是算出来的卫星位置误差会快速增大,轻则定位误差变大,重则完全无法定位。所以我实际使用时都会去找信号模拟时段当天或最近几天的星历文件。
GPS 时间和 UTC 不是一回事。GPS 时是连续时标,从 1980 年 1 月 6 日零点开始按周和秒累计,没有闰秒;UTC 由于有闰秒插入,两者之间会差一个整秒数。平时做数据转换,经常要把 GPS 时间转成儒略日,公式是 JD = 2444244.5 + GPS周数 * 7 + 周内秒 / 86400。某些版本命令行里可以直接给 UTC 时间,但如果只给了起始 GPS 周和秒,你就得自己确认这个换算关系,否则信号里的时间基准和星历参考时间对不上,接收机拿到一堆互相矛盾的时间信息,定位会失败。这也是很多人拿到 gps-sdr-sim 之后第一个栽跟头的地方。
2.3 多普勒频移和动态场景
卫星绕地球飞行的速度很快,地面接收机看到的 L1 载波频率并不是正好 1575.42 MHz,而是叠加了一个正负几千赫兹的多普勒频移。对静止接收机来说,卫星升起来和落下去时多普勒变化规律是确定的;对运动中的接收机来说,还要叠加上自身运动产生的多普勒。gps-sdr-sim 需要根据星历精确计算发射时刻每颗卫星的速度矢量、位置矢量和接收点的相对运动,才能生成正确的多普勒。这也是为什么模拟动态场景比静态复杂得多,绝不是简单把轨迹点连起来就行。
动态场景输入文件通常是用一个文本文件描述接收机的位置随时间的变化,常见格式是每一行写纬度、经度、高度,有的版本还支持时间列。这里有个很好用的数据来源:用手机上的 GPS 工具箱这类 App 采集一段真实 GPS 轨迹,导出 NMEA 文件,再写一个小脚本把 GGA 语句里的经纬度和高程抽出来生成轨迹文件。这样你在室内模拟的信号,完全复现了之前在某条路上真实跑过的卫星几何关系和多普勒变化,特别适合做回溯测试。我后面会给出一个简单的转换方式。
3. 完整实操:从源码编译到射频输出
3.1 编译与依赖
gps-sdr-sim 是 C 语言写的开源项目,依赖很少。在 Ubuntu/Debian 系统上,先确认有 git、gcc、make 这些基础工具,然后直接拉源码编译:
git clone https://github.com/osqzss/gps-sdr-sim.git cd gps-sdr-sim make编译过程一般不会报错,依赖的只是标准 C 库。唯一可能要注意的是,如果你之后想直接调用 HackRF 的库做实时流式输出,需要在编译前装好 libusb 和 hackrf 开发头文件,不过更通用的做法是先用 gps-sdr-sim 生成文件,再交给 HackRF 配套的 hackrf_transfer 工具去发射,这样依赖更少,流程也更清晰。编译完成后目录下会生成gps-sdr-sim可执行文件,用./gps-sdr-sim -h看看帮助信息,确认版本和参数。
3.2 生成静态和动态场景 IQ 文件
先跑通最简单的静态场景。假设目标位置是北京某点,使用刚下载好的星历文件生成 300 秒信号:
./gps-sdr-sim -e brdc3540.14n -l 39.904,116.407,50 -b 8 -d 300-e指定 RINEX 导航文件,-l指定纬度、经度、高程,-b 8表示每个 I/Q 用 8 bit 量化,-d 300表示时长 300 秒。默认输出文件是gpssim.bin,采样率默认 2.6 MHz。这个文件的大小可以提前估算:2.6 MHz 采样率乘以每样本 2 字节(I 和 Q 各 1 字节),每秒大约 5.2 MB,一分钟约 312 MB,10 分钟约 3.1 GB。如果你只需要分析算法,希望信号质量更好,可以把-b改成 16,文件体积直接翻倍;如果你打算实时发射而不是落盘,就不用关心体积问题。
动态场景用-u替代-l,指定轨迹文件:
./gps-sdr-sim -e brdc3540.14n -u user_motion.txt -b 8 -d 600轨迹文件可以从手机 GPS 工具箱导出的 NMEA 里提取。下面这段 Python 就是简单地从 GGA 语句里抽位置:
import re out = [] with open('nmea.log', 'r') as f: for line in f: if line.startswith('$GPGGA'): parts = line.split(',') lat = float(parts[2]) / 100.0 lon = float(parts[4]) / 100.0 height = float(parts[9]) # 简单换算:dddmm.mmmm 转十进制度 lat = int(lat) + (lat - int(lat)) * 100 / 60 lon = int(lon) + (lon - int(lon)) * 100 / 60 out.append(f"{lat:.7f},{lon:.7f},{height:.1f}") with open('user_motion.txt', 'w') as f: f.write('\n'.join(out))注意抽完之后的点位不要太密,一般每秒 1 个点或者每 2 秒 1 个点就够了,太密会让生成速度变慢,而且对结果没有实质提升。
3.3 用 HackRF 把基带信号发射出去
gps-sdr-sim 生成的是数字基带 IQ 数据,必须通过 SDR 设备搬到射频上去,接收机才能收到。我实测最顺手的是 HackRF One,它的工作频率范围覆盖 GPS L1 频段,带宽足够。先确认 HackRF 驱动和工具已装好,然后执行:
hackrf_transfer -t gpssim.bin -f 1575420000 -s 2600000 -a 1 -x 40-t指定 IQ 文件,-f设置中心频率,这里必须和 GPS L1 的 1575.42 MHz 一致,-s采样率要和生成信号时一致,-a 1开启射频放大器,-x 40是发射 VGA 增益。把接收机天线放在距离 HackRF 天线十几厘米到一两米的位置,正常情况下几秒内就能看到卫星锁定。实测下来这个距离范围内的信号强度非常充足,不需要刻意加大增益,增益开太高反而容易造成前级饱和,让接收机误判信号质量变差。如果你要做长时间连续发射,也可以把 gps-sdr-sim 的 stdout 直接管道给 hackrf_transfer 的 stdin,避免先生成几十 GB 文件,但我建议第一次调试还是老实生成文件,出了问题好定位。
3.4 参数选择背后的考量
采样率选 2.6 MHz 看起来随意,其实是折中过的。GPS C/A 码速率为 1.023 MHz,经过 BPSK 调制后信号主瓣带宽约 2.046 MHz,按带通采样理论,采样率必须大于这个数值才能不混叠。2.6 MHz 留了足够的过渡带余量,同时 USB 传输和文件体积都可接受。如果你用的是某些只支持整 MHz 采样率的设备,那选 4 MHz 或 5.7 MHz 也没问题,只是信号里会多一点带外噪声,接收机照样能捕获。
IQ 位数的选择,8 bit 对大多数接收机测试足够。GPS 接收机内部通常用 2 bit 或 3 bit 量化就能正常工作,8 bit 已经是过剩的。只有当你研究的场景涉及极弱信号或者高精度测距,才需要 16 bit 来保证信噪比。增益选择上,HackRF 的发射功率本身不大,正常测试场景下接收机贴近天线即可,没必要追求“覆盖整个房间”。信号一旦过强,接收机前端 AGC 会压增益,反而导致量化噪声增大,这是不少人忽略的细节。
4. 常见问题与排查技巧实录
4.1 接收机锁不上星、定位慢
最典型的现象是接收机一直显示“正在搜索卫星”或者卫星一直在捕获但无法锁定。排查顺序一般是这样的:先确认星历文件时间对不对,这是最高频的坑。你看一眼 RINEX 文件头里的参考时刻,再想清楚自己模拟的起始时间,两者必须能对上。其次是采样率,生成文件时的-s参数和发射时-s参数不一致,接收机看到的信号频谱位置全错,必然锁不上。然后是天线位置和增益,天线离太远、中间有金属遮挡,信号衰减严重;增益开太高导致饱和,信号波形失真,同样锁不上。
我遇到过一次特别隐蔽的问题:星历和起始时间都没错,但接收机就是搜不到信号,折腾半天才发现是 HackRF 的频偏没有校准。HackRF 板载振荡器精度一般,如果没做过校准,实际发射频率可能偏了几千赫兹,这正好超出接收机捕获搜索范围。解决办法是先用 HackRF 自带工具测量并校正频率,或者在软件里补偿载波偏移。这个检查项我建议放到首位,因为它比排查星历还要快。
| 现象 | 可能原因 | 解决思路 |
|---|---|---|
| 完全搜不到卫星 | 采样率不匹配 | 核对生成与发射的 -s 参数 |
| 搜到但无法锁定 | 星历过期或时间错位 | 换当天星历,核对起始时间 |
| 信号断断续续 | USB 供电不足/带宽不够 | 换独立供电 Hub,降低采样率 |
| 锁星后定位漂移 | 增益过高导致饱和 | 调低 TX VGA 增益 |
4.2 时间同步与 chronyc 的配合
很多接收机在测试时需要输出 1PPS 脉冲或者做时间比对,这时候系统时间精度就直接影响测试结论。如果你用 GPS 接收机作为授时源,给电脑提供 NMEA 和 PPS,一套典型的验证流程是用 chronyc 确认时间源是否锁定。运行chronyc sources -v,如果看到时间源状态是^*,表示已经和 GPS 时间同步;如果一直是^?,说明 PPS 信号没有正确识别。这里常见问题是串口波特率不对、PPS 线接错引脚,或者/dev/ttyS0权限不够。
在 gps-sdr-sim 的场景里,时间同步有两个层面的意义。第一,生成信号时内部的 GPS 时间必须准确映射到导航电文里,如果你手动指定了起始 GPS 时间,偏差超过容限,接收机解出的位置时间会整体偏移。第二,长时间发射时,PC 系统时钟如果漂移,SDR 设备的采样时钟参考也会受影响。所以我的习惯是测试前一天先把系统时间校准好,用 chronyc 连着跑一晚上,确认漂移在微秒级,再开始做长时测试。短时间测试可以不管,但凡是涉及 PPS 精度验证的项目,这个步骤不能省。
4.3 坐标转换与数据后处理
用模拟信号定位出来的原始坐标是 WGS84 经纬度,但在国内常见地图上叠加时你会发现位置偏了几百米,这是坐标系的差异,不是模拟器出了问题。天地图、高德用的 GCJ-02 会对 WGS84 做一次加密偏移,百度地图又套了一层 BD-09。所以正确姿势是:拿模拟信号定位结果做算法指标分析,就直接用 WGS84;要在地图上画轨迹,先用公开的转换库把 WGS84 转到 GCJ-02 或 BD-09。这个问题在真实 GPS 信号里一样存在,只是很多人第一次把接收机搬到室内测的时候,才会注意到“电脑连接手机 GPS 数据”“原生 GPS 坐标在天地图上偏移”这类讨论。
如果你需要把真实轨迹转成 gps-sdr-sim 轨迹文件,也要注意坐标基准统一。手机 GPS 工具箱导出的坐标默认就是 WGS84,可以直接用;但如果你的轨迹源来自地图 API,那已经是加密坐标了,得先逆转换回 WGS84,再喂给模拟器。否则模拟出来的信号,接收机解算的位置会偏离你期望的路线。
4.4 长时间运行与资源占用
gps-sdr-sim 的算法复杂度不算低,尤其是轨迹点数多、可见卫星多的时候。实测下来,一台普通 i5 处理器生成单频信号,实时率通常没问题,但如果用 16 bit、多频点、高采样率,就可能赶不上实时。最直接的方法是降低采样率到 2.6 MHz,不影响接收机正常工作。更激进的做法是分段生成:先按小时生成,每一段之间保持时间连续,再拼接文件或者在发射端循环播放。注意循环播放时如果导航电文里的时间不连续,接收机会重新搜索,所以长测场景我一般直接用管道模式连续流式生成,不要循环同一个文件。
另一个容易被忽视的问题是文件系统。生成 1 小时 8 bit 信号大概要 18 GB 空间,如果你的磁盘是慢速机械盘,写入速度可能跟不上生成速度,导致信号中断。建议用 SSD 或者直接使用实时管道发射。HackRF 长时间 USB 传输偶尔会掉线,排查顺序是换线、换 USB 口、检查供电,最终不行就调低采样率到 2 MHz 左右,实测稳定很多。
最后再分享一个我自己的经验:第一次跑通 gps-sdr-sim 之后,先别急着接真实接收机,拿它生成的 IQ 文件喂给 GNSS-SDR 这类开源软件接收机,在电脑上就能看到捕获、跟踪、电文解调的完整流程,这样调试起来比直接看射频链路简单得多。等软件接收机能把位置解出来,再上 HackRF 和实际模块联调,你基本不会遇到什么鬼打墙的问题。这套“先生成文件、再软件解调、最后射频闭环”的顺序,我每次带人入门都推荐,能省下大量排查时间。
本文还有配套的精品资源,点击获取