搞SDR的人总有一天会动“自己造一个GPS卫星信号”的念头。GPS-SDR-SIM就是那个让你能把手机、车载导航、甚至一块u-blox模块“骗”得团团转的开源工具。它是一个C语言写的小程序不需要任何额外依赖输入一份GPS广播星历文件就能生成一段能在SDR设备上播放的IQ基带数据配合HackRF这类能发射的SDR拉一根短线当天线你的手机GPS接收机真的会认为自己收到了来自太空的L1卫星信号然后稳稳报出你设定的经纬度。这东西适合谁如果你是SDR玩家想体验一把“软件定义一切”的威力它是最经典的上手项目如果你是搞GPS接收机开发、物联网定位测试的它又是一个成本几乎为零的实验室信号源。不需要几十万的GPS模拟器一台一百多块的HackRF加一台电脑就能完成大多数基础测试。这篇博文就把我从编译、选星历、生成信号、播放到最终定位成功的完整过程拆开揉碎讲清楚顺便把我踩过的坑都摆到台面上。1. 先搞清楚它在干什么核心原理与整体设计1.1 GPS L1信号到底长什么样不提点背景直接敲命令后面出了问题会一头雾水。GPS L1频点是1575.42MHz民用信号采用BPSK调制在这1MHz多一点的带宽里同时混着三样东西C/A码、导航电文和载波。C/A码是GPS的“扩频码”一颗卫星分配一个唯一的1023位伪随机序列码速率1.023MHz每个码片持续约1微秒整段码长1毫秒所以它每毫秒重复一次。接收机就是靠相关性把淹没在噪声里的信号捞出来并计算出码相位。导航电文则是50bit/s的慢速数据流广播卫星星历、时钟修正、历书、电离层参数等等接收机需要解码出这些内容才能算出自己位置。GPS-SDR-SIM干的事情就是把这些要素在PC上全部合成出来把多颗卫星的信号叠加在一起写到文件里。1.2 GPS-SDR-SIM的工作链路与文件格式完整链路是从NASA或者其它数据中心下载当天的RINEX广播星历文件文件名类似brdc0100.23n喂给gps-sdr-sim程序会读取星历算出当前可见卫星、每颗卫星的多普勒、码相位、导航电文比特然后用数字方式生成一个复数基带信号按你指定的采样率输出成一个二进制IQ文件。最后用hackrf_transfer之类的工具把文件读出来通过HackRF的DAC变成模拟信号上变频到1575.42MHz发射出去。这里要理解一个关键点gps-sdr-sim输出的IQ文件是“时域采样点”每个采样点由I分量和Q分量组成。默认参数-b 8表示I/Q各8bit即每个采样点占2字节。按默认采样率2.6MHz算一秒钟信号就是5.2MBytes300秒大约是1.5GB。如果你用-b 16文件体积直接翻倍到3GB但动态范围更好对接收机来说区别不大。1.3 为什么是“预先生成IQ文件”而不是实时信号源很多人刚接触时会问为什么不能像GNU Radio那样实时流式输出两个原因。第一GPS信号本身是慢变的卫星位置、多普勒在几秒内变化很小而C/A码又是周期性的所以提前计算好一整段采样是完全可行的非实时方案逻辑简单、调试方便。第二文件可以反复使用生成一次动态场景想播多少次播多少次对测试接收机一致性反而更友好。代价就是文件占空间、生成时间长。生成300秒信号在普通PC上通常只需要几秒到十几秒比实时快得多。这个“预算”逻辑决定了后面所有参数选择你要测试多久就得准备多大的磁盘。2. 编译安装与环境准备2.1 编译比想象中简单GPS-SDR-SIM的源码仓库在GitHub上搜索关键词gps-sdr-sim就能找到项目很老但维护得还可以。它没有任何第三方库依赖只需要gcc和make。Linux下操作最顺git clone https://github.com/takafuminaka/gps-sdr-sim.git cd gps-sdr-sim make编译完当前目录就会出现一个gps-sdr-sim可执行文件。运行一下./gps-sdr-sim如果打印出参数说明就说明环境OK。整个过程不到一分钟相比现在动辄几百兆依赖的软件工程这个老项目简直是一股清流。我建议在Linux或者WSL下操作。Windows原生编译需要自己搞定C环境不是不行但没必要WSL下生成的二进制在Ubuntu子系统里跑得非常好。2.2 所需硬件与天线准备播放端也就是发射端需要一块能覆盖1.5GHz频段并支持发射的SDR设备。社区用得最多的就是HackRF One频率范围1MHz到6GHz发射能力虽然弱但覆盖GPS L1绰绰有余。此外bladeRF、USRP、ADALM-Pluto理论上都可以只是命令行工具不同。RTL-SDR不行它只有接收功能不能发射。接收端就很随意了。一部安卓手机带GPS L1、一块u-blox NEO-M8N/M6M模块、或者任何支持GPS的接收机都行。iPhone也能收到信号但iOS不开放卫星列表查看调试观察不方便所以我强烈建议用安卓手机装一个“GPS Test”或“GPSTest”应用能直观看到卫星数量和信噪比。天线这块是低成本玩家最容易忽略的地方。HackRF板载SMA接口你不需要买昂贵的GPS天线直接截一段约4.8cm长的铜丝焊到SMA内芯上作为1575.42MHz的1/4波长单极子天线就够了。因为发射功率极低距离也只有几十厘米到几米这种简陋天线完全够用。注意铜丝长度不要差太多4.8cm对应波长整数倍辐射效率最高。2.3 RINEX广播星历文件获取没有星历文件gps-sdr-sim寸步难行。这个程序需要通过RINEX格式的广播星历文件来还原卫星轨道和时钟。最权威的下载渠道是NASA的CDDIS数据中心。路径大致为https://cddis.nasa.gov/archive/gnss/data/daily/2023/brdc/需要注册一个免费账号登录后下载对应日期的文件。文件名格式是brdcDDD0.YYn.gz比如2023年1月1日的文件就是brdc0010.23n.gz。这里的DDD是年积日YY是年份n代表导航文件中的GPS导航数据。下载后解压即可。注意一定要下载“和测试当天日期接近”的星历文件。GPS广播星历的有效期一般是星历参考时刻前后约4小时超出太远接收机即使收到信号也不容易定位。用当天或最近两三天的brdc文件最保险。如果实在不想注册NASA账号还有一些高校或机构提供历史星历镜像但不如CDDIS稳定。作为常规使用一次下载一年的brdc文件存着也不占多少空间几十MB而已。3. 核心参数与静态场景生成3.1 常用参数速查表编译好后先花一分钟熟悉参数。下表是日常使用频率最高的几个选项参数含义举例-e指定RINEX导航星历文件-e brdc0010.23n-l静态位置纬度,经度,高度-l 31.2304,121.4737,50-u动态轨迹文件(CSV)-u trace.csv-t模拟起始时间格式YYYY/MM/DD,HH:MM:SS-t 2023/01/01,00:00:00-o输出IQ文件名-o gpssim.bin-s采样率默认2.6MHz-s 2600000-b量化位数8或16-b 8-d信号时长单位秒默认300-d 600-n模拟卫星数量默认自动-n 8注意-l和-u是互斥的一个表示接收机静止一个表示接收机运动。-t不指定时程序取当前系统时间作为模拟起点但这里有个先后问题如果系统时间和星历实际日期差太远生成的导航电文时间会和星历对不上接收机就会“看不懂”。后面我会详细讲时间匹配。3.2 生成静态定位信号一个完整实例假设我要模拟一台接收机静止在上海外滩某坐标持续10分钟。命令就是这样./gps-sdr-sim -e brdc0100.23n \ -l 31.2304,121.4737,50 \ -b 8 \ -s 2.6e6 \ -d 600 \ -o shanghai_static.bin没有指定-t程序会取当前时间所以只要当前时间和brdc文件对应日期误差在两三天内正常都能用。跑完之后会看到类似这样的输出程序自动选了8颗可视卫星计算了每颗卫星的多普勒、码相位然后把IQ数据写入了shanghai_static.bin。这里解释下“自动选星”逻辑程序会根据你给定的经纬度和高度计算当前时刻所有可见卫星的仰角默认挑选可见卫星中信号质量最好的若干颗。如果你想强制只模拟某几颗卫星用-n限制数量即可。GPS定位最少需要4颗卫星但测试时8到10颗效果更好收敛更快。3.3 文件大小、采样率与位宽的取舍文件大小公式很好记文件大小(字节) 采样率 × 2字节(I/Q各1字节) × 时长(秒)2.6MHz采样率、10分钟就是2,600,000 × 2 × 600 3,120,000,000 字节 ≈ 2.9GB一个测试文件快3GB了所以很多人首次跑完都会吓一跳。解决方案就三条路缩短时长、降低采样率、减小位宽。缩短时长最简单但GPS接收机首次定位可能需要1分钟测试时长建议不低于300秒。降低采样率确实省空间但低于2.4MHz后C/A码主瓣开始被截掉接收灵敏度下降。所以一般就在2.4MHz到2.6MHz之间权衡。8bit就是最优性价比16bit只在你怀疑量化噪声影响星座图时使用。我个人的经验是日常调试用2.6MHz、8bit、300秒文件约1.5GB够用也还好。只有需要长时间稳定性测试时才生成600秒以上文件。4. 动态轨迹模拟给你“移动”的卫星4.1 CSV轨迹文件怎么组织静态定位测试只是热身GPS-SDR-SIM真正黑科技的地方是模拟移动场景。只需要准备一个CSV文件每行包含三个字段纬度、经度、高度例如31.2304,121.4737,5 31.2310,121.4740,5 31.2315,121.4748,5程序会按照每行数据点之间的顺序插值生成一条连续的移动轨迹。配合-u参数使用./gps-sdr-sim -e brdc0100.23n \ -u trace.csv \ -d 300 \ -b 8 \ -o moving.bin注意当指定-u后就不要再用-l了。高度单位是米坐标是WGS84经纬度。CSV里点的密度决定了移动速度如果程序按等时间间隔插值点与点之间距离越远代表移动越快。具体每行间隔多少秒请以你下载的版本README为准我用的版本是每行代表一秒。动态模式对于测试接收机的定位速度、轨迹平滑算法、多普勒跟踪能力非常有用。你甚至可以模拟一个人站在环形路口转圈看接收机画出来的轨迹是不是也在绕圈。4.2 用脚本生成一条测试路线手工在CSV里写几十个坐标太痛苦我一般用Python脚本生成测试路线。比如生成一条绕外滩建筑群逆时针走的路线import math lat0, lon0 31.2304, 121.4737 radius 0.001 # 约110米 steps 120 with open(circle.csv, w) as f: for i in range(steps): theta 2 * math.pi * i / steps lat lat0 radius * math.cos(theta) lon lon0 radius * math.sin(theta) f.write(f{lat:.6f},{lon:.6f},5\n)这个脚本生成120个点形成一个圆形路线。如果你想让速度快一点就减少点数但保持经纬度跨度不变想模拟行人点间距控制在0.0001度约11米以内比较合理。4.3 动态场景调试时的观察点动态场景下接收机定位结果应该跟着预设轨迹走。用手机的话观察地图上的轨迹点是否按照路线移动用u-blox模块的话可以看NMEA语句中经纬度随时间的变化。尤其是速度和航向字段能反映多普勒模拟是否准确。有一个常见误区很多人觉得动态模拟就是给接收机连续变化的坐标其实不止。GPS-SDR-SIM在动态模式下会同时调整每颗卫星的多普勒频移、码相位变化率以及导航电文的帧定时这些才是接收机能稳定跟踪的关键。如果你只是简单改坐标而不更新这些参数接收机会因为多普勒突变而反复失锁。所以动态场景务必用-u参数不要在静态模式下手动改位置。5. 用HackRF把信号播放出来5.1 把文件变成射频信号信号文件生成好接下来就是用HackRF把它变成电磁波。先确认HackRF驱动装好hackrf_info能看到设备信息。然后执行hackrf_transfer -t shanghai_static.bin \ -f 1575.42e6 \ -s 2.6e6 \ -a 1 \ -x 20参数逐个解释-t指定要发送的IQ文件-f设置中心频率为GPS L1的1575.42MHz-s设置DAC采样率必须和生成文件时一致否则播放速度不对、频率也会偏-a 1选择HackRF的第二个天线口TX/RX口另一个口是纯RX-x设置发射增益范围0到47dB我这里用20dB是室内近场实验比较平衡的值。注意如果你用的是-s 2.4e6生成的信号HackRF播放时也必须-s 2.4e6。两者不一致轻则频率偏移重则接收机完全收不到。这个命令会从文件头播放到文件尾结束后自动停止。300秒的文件刚好5分钟测试绰绰有余。如果想循环播放可以加-R参数但循环带来的相位不连续会让接收机周期性失锁所以我一般不用而是直接生成足够长的文件。5.2 手机或者GPS模块怎么验证播放前把安卓手机调成飞行模式关掉Wi-Fi和蜂窝数据只保留GPS接收。原因很简单飞行模式能避免基站辅助定位的干扰让你看到的定位结果纯粹来自你模拟的GPS信号。手机放到HackRF天线旁边打开“GPS Test”这类App。正常情况下10到60秒内就能看到卫星列表里出现绿色信号条信噪比通常在30dBHz以上。再过几秒App会显示定位成功经纬度和高度与你设置的坐标非常接近。用u-blox模块更严谨。把NEO-M8N连接USB转串口用u-center软件打开串口看$GPGGA或者$GPRMC语句。如果模块输出经纬度和设定坐标一致就代表整条链路完全打通。模块天线和HackRF天线之间建议间隔20厘米以上中间不要放金属物体。5.3 功率、天线距离和接收饱和的博弈GPS地面接收的正常信号功率约为-130dBm而HackRF的发射功率大概在-10dBm到5dBm之间。听着差距巨大但近距离情况下问题不是信号弱而是信号过强导致接收机前端饱和。实测下来HackRF增益调到20dB时手机离天线的合适距离大概是30到80厘米。如果距离只有5厘米你反而会看不到卫星因为接收机LNA被强信号推到饱和前端失真严重。所以正确的做法是先调低增益比如10dB把手机放在20厘米外观察卫星信噪比。如果完全没有信号再慢慢增加增益、缩短距离。用“GPS Test”看信噪比数值比看定位成功更有利于判断因为这个过程能看到趋势变化。经验不要把HackRF天线和手机天线贴在一起。很多人以为贴得越近越好实际上是贴得越近越容易饱和。我踩过一次把天线怼在手机背面结果一个卫星都搜不到拉远到30cm后秒定位。6. 常见问题排查与避坑实录6.1 典型问题排查速查表现象可能原因解决办法手机完全搜不到卫星HackRF没有选对天线口、增益过低、天线断连确认-a 1检查SMA接头提高-x增益搜到卫星但一直不定位接收机时间与模拟时间偏差太大星历文件太旧使用与当天接近的brdc文件必要时用-t指定时间定位成功但坐标偏差很大天线距离过近/过远、多径干扰调整天线距离和增益将接收机远离墙面和金属物体定位后几秒就丢失播放到文件末尾停止循环播放导致相位突变生成更长文件尽量不用-R循环手机靠近天线时反而丢失信号过强导致前端饱和拉远距离降低-x增益文件播放很快结束默认只有300秒用-d 600或更长或生成更大的文件HackRF播放时USB掉线供电不足换一根高质量USB线插直连主板的口避免USB Hub6.2 几个我踩过的坑第一个坑是RINEX文件格式。最初我下载了一个RINEX 3格式的.rnx文件gps-sdr-sim直接报错。后来老老实实找RINEX 2.x的brdc文件才跑通。如果你的版本比较新可能支持RINEX 3但没必要在格式上折腾下载经典的brdc文件最省事。第二个坑是时间匹配。我拿了一个半年前的星历文件自信满满地生成信号结果手机怎么都定位不了。后来用-t指定模拟时间到星历对应的时间段同时把手机GPS缓存清掉才成功。原理是接收机会用导航电文里的时间戳和自身RTC时间做比对时间差太远就会认为星历不可用。清缓存的操作因手机而异一般是在GPS Test里重置或者重启手机。第三个坑是播放进程和生成采样率不一致。有次生成时用了2.4MHz播放时忘了改默认的2.6MHz结果手机能搜到极弱的卫星信号但定位极慢检查半天才发现这个低级错误。把两条命令的参数写在一块就永远不会错。还有一个不能忽视的事情合法合规。室内近场模拟GPS信号用于测试教学完全没问题但不要拿着它去室外干扰真实设备更不要蓄意遮挡无人机或者别人的定位系统。GPS模拟器在专业领域是合法工具但用错了场合会惹麻烦。7. 动手做一次完整的室内定位测试说再多不如跑一遍。这里我给一套我测试时固定使用的操作流程你照着做基本能一次成功。第一步下载当天brdc文件解压到工作目录比如brdc1040.23n。第二步编译gps-sdr-sim。第三步生成静态信号./gps-sdr-sim -e brdc1040.23n \ -l 31.2304,121.4737,10 \ -d 300 \ -b 8 \ -o test.bin第四步连接HackRF把一根4.8厘米铜丝天线接到天线口1。第五步播放hackrf_transfer -t test.bin -f 1575.42e6 -s 2.6e6 -a 1 -x 15第六步把安卓手机调成飞行模式打开GPS Test放在HackRF天线旁30厘米处。等待一分钟看到卫星信号条亮起来然后经纬度稳定显示。到这里你就完成了从数字世界到射频世界的完整闭环。我通常会在第六步保持手机不动继续观察两三分钟确认定位没有抖动。如果一切正常下一步就可以把-l换成动态CSV体验一下“移动定位”的乐趣。真心建议每个玩SDR的人都把GPS-SDR-SIM跑通一次。它不挑硬件、不挑系统、不依赖重型软件无线电框架却把GPS信号生成中最核心的原理全部体现了一遍。跑通之后你对GPS接收机为什么能看到卫星、为什么需要4颗星、为什么多普勒会影响跟踪都会有一个全新的直观认知。这也是我每次给新人推荐SDR入门项目时始终把它排在第一位的原因。