# 用USRP解码飞机的ADS-B信号 ## 0. 前言 USRP(Universal Software Radio Peripheral,通用软件无线电外设)是Ettus Research公司的一款高性能软件定义无线电(Software Defined Radio, SDR)硬件平台,广泛应用于通信原型验证与信号处理研究。 捕获与解码飞机的ADS-B信号是学习SDR应用的一个经典入门级项目。ADS-B(Automatic Dependent Surveillance – Broadcast,广播式自动相关监视)是现代航空监视的核心技术,飞机以1090MHz频率广播其身份、位置、高度等信息,信号格式公开,非常适合用于SDR技术实践。 本文将介绍如何基于USRP设备完成ADS-B信号的接收与解析。先讲一讲ADS-B信号的报文格式与解码原理,然后实现一个基于GNU Radio的从信号捕获到报文解析的接收机。我用的硬件是入门级的USRP B200,开发环境是~~WSL下的Ubuntu 22.04系统+GNU Radio+UHD~~ *Radioconda环境下的GNU Radio + UHD*,环境配置会在附录里介绍。 ## 1. ADS-B介绍 ADS-B是现代航空监视的核心技术。与传统依赖地面雷达询问、飞机被动应答的模式不同,ADS-B是飞机主动、定期广播的。它通过Mode S(S模式) 扩展电文格式,将自身的身份、位置、高度、速度等关键信息以非加密的明文形式向外界广播,任何具备接收设备的地面站或其他飞机都能直接接收,从而实现更精确、高效的空中交通管理。著名的飞行轨迹追踪网站[FlightAware](https://www.flightaware.com/)就是通过解码ADS-B包来获得飞机的位置信息。 *我们的应用仅限于被动接收这些公开的ADS-B广播信号用于学习与研究,不涉及任何信号发射,因此是完全合法的。* ### 调制方式 Mode S采用的是PPM调制(Pulse Position Modulation)。在一个时钟周期里,用脉冲出现的位置来传递信息。在一些光通信和UWB (Ultra Wide Band)应用里,一个时钟周期会被划分为4份或8份,称为4PPM,8PPM,一个符号能传递2个或3个比特。但是Mode S用的比较简单,一个时钟周期只分为2份:高电平出现在前半段表示1,出现在后半段表示0。 如图所示,从8us开始是数据包,每1us传递1比特,总共持续56us或112us。在开始传输数据之前,从0us到8us是前导码,前导码包括4个0.5us宽的脉冲,分别位于第0us,第1us,第3.5us和第4.5us。因为正常的数据包不会出现这样的形状,所以接收机可以侦测有没有收到前导码,以便告诉后续的信号处理模块“数据包要来了,准备好接收”。 ![ADS-B调制方式](adsbsignalscheme.png) ### 解码 ADS-B其实是Mode S格式的数据的一个子集,那些56us长的数据包就是Mode S格式但非ADS-B的,是飞机收到地面站询问之后回复的各种应答消息,而非主动发出的ADS-B广播。 一个112位的ADS-B包包括下面这些信息,其中前5个比特表示数据格式DF,DF=17,18,19是“长的包”,即112位。其中DF=17最常见的ADS-B广播数据,解码器会继续解析后面的TC等字段,提取身份、位置、速度等信息。绝大多数商用客机和通用航空飞机都会使用全球唯一的 24位 ICAO 地址来标识一架飞机。DF=18和19来自军用和匿名物体,不会有ICAO身份标识。 其他DF则是“短的包”,即56位,后续包含的信息各不相同。我们暂时只关注DF=17。 ![ADS-B数据格式](adsbfield.png) ## 2. 基于USRP的接收机实现 ### 系统框图 为了实现一个接收机,硬件上需要一根1090MHz的天线,一台USRP,一台PC。基于GNU Radio的软件解码器会从USRP收到的基带信号里解析出飞机的数据,并通过TCP传输给客户端。 ![adsbrx](adsbrx.png) ### gr-adsb 开源社区里有很多现成的ads-b的解码器,比如dump1090,可以从接收到的基带信号恢复出时钟,进行PPM解调,并解码飞机信息。我这里选用的是gr-adsb (https://github.com/mhostetter/gr-adsb.git),因为它可以作为模块直接在GNU Radio里调用。 安装很简单,直接编译就行。 ``` bash $ cd gr-adsb/ $ mkdir build $ cd build/ $ cmake ../ $ make $ sudo make install $ sudo ldconfig ``` 它最核心的模块是`framer.py`,`demod.py`,`decoder.py`这三样。 * Framer根据用户设定的阈值,判断有没有接收到前导码,如果接收到会输出一个flag给Demodulator。 * 在收到前导码后,Demoulator利用概率函数判断后续的比特是0还是1。 * Decoder解析整个包得到速度、高度、位置等信息。 ### 流图 在GNU Radio Companion的GUI界面里,绘制这样的流图。ADS-B信号严格一定发射在1090MHz,所以接收频率设定在1090M。采样率需要是2MHz的整数倍,2M效果就很好。我在Complex to Mag之前还放了一个DC Blocker用来消除DC Offset。最后解析出ADS-B信息后通过TCP端口发布消息。 ![adsb_rx](adsbrxflowgraph.png) ### TCP接收端 我写了一个简单的Python接收端用来从TCP端口接收消息并打印。 ``` {code-block} python :class: scrollable-code import zmq import pmt def zmq_pdu_subscriber(): context = zmq.Context() socket = context.socket(zmq.SUB) # 连接到GNU Radio的ZMQ Pub Sink # 地址和端口需要与GNU Radio流图中的配置一致 socket.connect("tcp://localhost:5001") socket.setsockopt_string(zmq.SUBSCRIBE, "") print("等待接收ADS-B PDU消息...") try: while True: # 接收二进制PDU数据 pdu_bin = socket.recv() # 反序列化PDU pdu = pmt.deserialize_str(pdu_bin) # 提取数据部分(pmt.car获取PDU的数据) plane = pmt.to_python(pmt.car(pdu)) # 处理ADS-B飞机数据 print("收到飞机数据:") print(f" ICAO地址: {plane.get('icao', 'N/A')}") print(f" 时间: {plane.get('datetime', 'N/A')}") print(f" 经度: {plane.get('longitude', 'N/A')}") print(f" 纬度: {plane.get('latitude', 'N/A')}") print(f" 高度: {plane.get('altitude', 'N/A')} 英尺") print(f" 速度: {plane.get('speed', 'N/A')} 节") print(f" 航向: {plane.get('heading', 'N/A')}°") print("-" * 50) except KeyboardInterrupt: print("\n接收端停止") except Exception as e: print(f"处理数据时出错: {e}") finally: socket.close() context.term() if __name__ == "__main__": zmq_pdu_subscriber() ``` ### 实验结果 同时运行流图和接收端,时域和频域的图像可以帮助调试,通过观察有没有收到长度在120us左右的burst,确认硬件是否连接良好。如果硬件ok并顺利解析,Python接收端就会打印出收到的飞机数据。 ![adsbresult](adsbresult.png) ## 3. 总结 解析ADS-B是个很好的软件无线电入门项目,可以从物理层开始一层一层把想要的数据解出来。下一步的学习方向可以包括: * 优化Framer和Demodulator的算法,让它对噪声更robust * 利用USRP和GNU Radio做更多有意思的事 --- ## 参考资料 **环境配置** 1. [在Ubuntu子系统中安装GNU Radio使用USRP](https://cloud.tencent.com/developer/article/2196076) 2. [使用USRP探索无线世界 Part 1 | USRP从入门到追踪飞机飞行轨迹](https://cloud.tencent.com/developer/article/1041016) 3. [Windows下调试USRP B210](https://zhuanlan.zhihu.com/p/616174707) 4. [Windows下GNURadio安装的高效替代方法——radioconda](https://www.cnblogs.com/penggeon/p/-/installing-gnuradio) **ADS-B原理** 4. [ADS-B Guide](https://blog.exploit.org/ads-b-guide-demodulation-and-decoding/) 5. [The 1090 Megahertz Riddle](https://mode-s.org/1090mhz/content/ads-b/1-basics.html) **文档手册** 6. [USRP Hardware Driver and USRP Manual](https://files.ettus.com/manual/index.html) 7. [GNU Radio - Main Page](https://wiki.gnuradio.org/index.php?title=Main_Page) --- ## 附录A 环境配置 我在配环境时踩了一些坑,包括但不限于:1)MacOS没有合适的安装包 2)Ubuntu 24.04的各种不兼容 3)把设备挂载到WSL而不是Windows。 ~~其实Windows下安装是挺顺利的,见下文,但是我喜欢WSL那种工作环境和娱乐环境分开的设计,所以都装在WSL里了。~~ ~~踩完坑后发现Ubuntu 22.04是用着最舒服的,安装流程也是最丝滑的,所以下面统一基于Ubuntu 22.04来介绍。~~ **【更新: 不要用WSL!!!】** 我后来在试图解决连接USRP时数据溢出的问题的时候折腾了好久,最后发现瓶颈在于WSL的虚拟USB接口的传输速率,所以要避免在虚拟机里运行GNU Radio。 **最佳方案是在conda环境里装radioconda,直接装好GNU Radio + UHD 以及 gr-adsb,gr-ieee802.11等扩展模块。** 不管在Windows本体还是Mac本体还是Ubuntu本体里,都可以在装了miniconda以后: ``` bash conda create -n radioconda -c conda-forge -c ryanvolz --only-deps radioconda ``` **本附录剩下的内容仅供参考,以防偶尔需要手动管理UHD和GNU Radio的安装,或用来快速验证硬件是否正常工作。** ### Ubuntu驱动 UHD (USRP Hardware Driver)是Ettus Research官方出品的硬件驱动,参考[官方文档] (https://files.ettus.com/manual/page_install.html),用`apt-get`安装即可。 ``` bash sudo add-apt-repository ppa:ettusresearch/uhd sudo apt-get update sudo apt-get install libuhd-dev uhd-host ``` 装完UHD第一次使用之前要下载给设备的FPGA用的固件,下载完的固件会保存在`/usr/share/uhd/images`: ``` bash sudo uhd_images_downloader ``` ### Windows驱动 然而这是WSL特有的一个坑,只装ubuntu版的UHD是不够的,Windows上也需要安装UHD,否则没法[把usb设备挂载到WSL](../cat1_iot/esp-idf-oled.md#wsl),会识别不出USRP设备。 准确地说Windows上需要安装的是LibUSBx驱动,用来支持USB3,但是这个驱动自从UHD4.8开始已经集成进了UHD,所以直接去[这里](http://files.ettus.com/binaries/uhd/latest_release)下载官方的UHD安装包是最简单的。 安装完成后在`C:\Program Files\UHD\bin`目录下启动命令行,运行`uhd_usrp_probe`,就会检测有没有已经连接的设备,输出如下: ``` {code-block} text :class: scrollable-code C:\Program Files\UHD\bin>uhd_usrp_probe [INFO] [UHD] Win32; Microsoft Visual C++ version 1944; Boost_108500; UHD_4.9.0.0-release [INFO] [B200] Loading firmware image: C:\Program Files\UHD\share/uhd\images\usrp_b200_fw.hex... [INFO] [B200] Detected Device: B200 [INFO] [B200] Loading FPGA image: C:\Program Files\UHD\share/uhd\images\usrp_b200_fpga.bin... [INFO] [B200] Operating over USB 3. [INFO] [B200] Detecting internal GPSDO.... [INFO] [GPS] No GPSDO found [INFO] [B200] Initialize CODEC control... [INFO] [B200] Initialize Radio control... [INFO] [B200] Performing register loopback test... [INFO] [B200] Register loopback test passed [INFO] [B200] Setting master clock rate selection to 'automatic'. [INFO] [B200] Asking for clock rate 16.000000 MHz... [INFO] [B200] Actually got clock rate 16.000000 MHz. _____________________________________________________ / | Device: B-Series Device | _____________________________________________________ | / | | Mboard: B200 | | revision: 5 | | product: 1 | | name: MyB200 | | serial: 31DFF0E | | FW Version: 8.0 | | FPGA Version: 16.0 | | | | Time sources: none, internal, external, gpsdo | | Clock sources: internal, external, gpsdo | | Sensors: ref_locked | | _____________________________________________________ | | / | | | RX DSP: 0 | | | | | | Freq range: -8.000 to 8.000 MHz | | _____________________________________________________ | | / | | | RX Dboard: A | | | _____________________________________________________ | | | / | | | | RX Frontend: A | | | | Name: FE-RX1 | | | | Antennas: TX/RX, RX2 | | | | Sensors: temp, rssi, lo_locked | | | | Freq range: 50.000 to 6000.000 MHz | | | | Gain range PGA: 0.0 to 76.0 step 1.0 dB | | | | Bandwidth range: 200000.0 to 56000000.0 step 0.0 Hz | | | | Connection Type: IQ | | | | Uses LO offset: No | | | _____________________________________________________ | | | / | | | | RX Codec: A | | | | Name: B200 RX dual ADC | | | | Gain Elements: None | | _____________________________________________________ | | / | | | TX DSP: 0 | | | | | | Freq range: -8.000 to 8.000 MHz | | _____________________________________________________ | | / | | | TX Dboard: A | | | _____________________________________________________ | | | / | | | | TX Frontend: A | | | | Name: FE-TX1 | | | | Antennas: TX/RX | | | | Sensors: temp, lo_locked | | | | Freq range: 50.000 to 6000.000 MHz | | | | Gain range PGA: 0.0 to 89.8 step 0.2 dB | | | | Bandwidth range: 200000.0 to 56000000.0 step 0.0 Hz | | | | Connection Type: IQ | | | | Uses LO offset: No | | | _____________________________________________________ | | | / | | | | TX Codec: A | | | | Name: B200 TX dual DAC | | | | Gain Elements: None ``` 接着用`usbipd`把设备挂载到WSL下,在WSL里同样运行 ``` bash uhd_usrp_probe ``` 会输出一样的结果,这就说明UHD硬件驱动已经装好了。 ### GNU Radio安装 GNU Radio是一款开源软件定义无线电工具包,可以通过其图形化界面(GUI)以拖放“流图”方式快速搭建通信系统,支持Python自定义模块;既能进行离线仿真验证,也能直接驱动USRP等硬件设备进行实时信号收发,大幅简化从设计到实现的流程。 按照[官方指示](https://wiki.gnuradio.org/index.php/InstallingGR),同样用`apt-get`安装即可。 ``` bash sudo apt-get install gnuradio ``` ### 验证 GNU Radio自带的fft频谱仪是个很有用的小工具: ``` bash uhd_fft --freq 2402M ``` 其中`freq`用来指定频率,还有一些其他参数可以用来调整采样率、窗口等,这些参数在GUI下也都可以调,几乎就是个虚拟频谱仪。 把频率调到2402MHz,也就是蓝牙的Channel 1,就能捕捉到电脑发出的蓝牙低功耗(BLE)广播的Beacon信号。 ![蓝牙频谱](ble_spectrum.jpg) ## 附录B Downlink Format 虽然我们只关注DF=17,但是gr-adsb的decoder模块其实对各种DF都能解析,在顶层流图里可以设定只解析“长的包” (Extended Squitter Only)或是所有包。其他DF的简介请看Amazon Q的解释。 ``` {code-block} markdown :class: scrollable-code The decoder handles different DF values by routing them through specific decoding paths in the `decode_message()` method. Here's what happens for each DF value: ## Key DF Values and Their Processing: **DF 0 & 16** - Air-Air Surveillance (ACAS): - Decodes vertical status, reply information, and altitude - DF 0: Also decodes crosslink capability - DF 16: Also decodes 56-bit message value (MV) **DF 4, 5, 20, 21** - Surveillance and Comm-B Messages: - Decodes flight status, downlink request, and utility message fields - DF 4/20: Altitude replies - extracts 13-bit altitude code - DF 5/21: Identity replies - extracts 13-bit identity code - DF 20/21: Also decode 56-bit Comm-B message **DF 11** - All-Call Reply: - Decodes capability field and ICAO address - Updates plane dictionary with basic aircraft info **DF 17** - Extended Squitter (ADS-B): - Decodes capability and ICAO address - Calls `decode_me()` to process the 56-bit ADS-B message containing: - Aircraft identification/callsign - Position data (airborne/surface) - Velocity information - Status messages **DF 18** - Extended Squitter/Non-Transponder: - Decodes CF (Control Field) to determine message type - Routes to appropriate decoder based on CF value: - CF 0,1,6: Standard ADS-B messages - CF 2,3,5: TIS-B messages - CF 4: Management messages **DF 19** - Military Extended Squitter: - Decodes Application Field (AF) - AF 0: Standard ADS-B messages - AF 1-7: Reserved for military use ## Message Filtering: The decoder respects the `msg_filter` setting: - "All Messages": Processes all DF types - "Extended Squitter Only": Only processes DF 17, 18, 19 ## Error Handling: - Unknown DF values are logged as debug messages - Unsupported formats publish "unknown" PDUs - CRC validation varies by DF type (some use address/parity, others use parity/interrogator ID) The decoder essentially acts as a router, using the DF value to determine which specific decoding algorithm to apply to extract the relevant aviation data from each message type. ```