用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就是通过解码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调制方式

解码

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数据格式

2. 基于USRP的接收机实现

系统框图

为了实现一个接收机,硬件上需要一根1090MHz的天线,一台USRP,一台PC。基于GNU Radio的软件解码器会从USRP收到的基带信号里解析出飞机的数据,并通过TCP传输给客户端。

adsbrx

gr-adsb

开源社区里有很多现成的ads-b的解码器,比如dump1090,可以从接收到的基带信号恢复出时钟,进行PPM解调,并解码飞机信息。我这里选用的是gr-adsb (https://github.com/mhostetter/gr-adsb.git),因为它可以作为模块直接在GNU Radio里调用。

安装很简单,直接编译就行。

$ cd gr-adsb/
$ mkdir build
$ cd build/
$ cmake ../
$ make
$ sudo make install
$ sudo ldconfig

它最核心的模块是framer.pydemod.pydecoder.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

TCP接收端

我写了一个简单的Python接收端用来从TCP端口接收消息并打印。

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

3. 总结

解析ADS-B是个很好的软件无线电入门项目,可以从物理层开始一层一层把想要的数据解出来。下一步的学习方向可以包括:

  • 优化Framer和Demodulator的算法,让它对噪声更robust

  • 利用USRP和GNU Radio做更多有意思的事


参考资料

环境配置

  1. 在Ubuntu子系统中安装GNU Radio使用USRP

  2. 使用USRP探索无线世界 Part 1 | USRP从入门到追踪飞机飞行轨迹

  3. Windows下调试USRP B210

  4. Windows下GNURadio安装的高效替代方法——radioconda

ADS-B原理

  1. ADS-B Guide

  2. The 1090 Megahertz Riddle

文档手册

  1. USRP Hardware Driver and USRP Manual

  2. GNU Radio - 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以后:

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安装即可。

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:

sudo uhd_images_downloader

Windows驱动

然而这是WSL特有的一个坑,只装ubuntu版的UHD是不够的,Windows上也需要安装UHD,否则没法把usb设备挂载到WSL,会识别不出USRP设备。

准确地说Windows上需要安装的是LibUSBx驱动,用来支持USB3,但是这个驱动自从UHD4.8开始已经集成进了UHD,所以直接去这里下载官方的UHD安装包是最简单的。

安装完成后在C:\Program Files\UHD\bin目录下启动命令行,运行uhd_usrp_probe,就会检测有没有已经连接的设备,输出如下:

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里同样运行

uhd_usrp_probe

会输出一样的结果,这就说明UHD硬件驱动已经装好了。

GNU Radio安装

GNU Radio是一款开源软件定义无线电工具包,可以通过其图形化界面(GUI)以拖放“流图”方式快速搭建通信系统,支持Python自定义模块;既能进行离线仿真验证,也能直接驱动USRP等硬件设备进行实时信号收发,大幅简化从设计到实现的流程。

按照官方指示,同样用apt-get安装即可。

sudo apt-get install gnuradio

验证

GNU Radio自带的fft频谱仪是个很有用的小工具:

uhd_fft --freq 2402M

其中freq用来指定频率,还有一些其他参数可以用来调整采样率、窗口等,这些参数在GUI下也都可以调,几乎就是个虚拟频谱仪。

把频率调到2402MHz,也就是蓝牙的Channel 1,就能捕捉到电脑发出的蓝牙低功耗(BLE)广播的Beacon信号。

蓝牙频谱