
简介这份MATLAB资源围绕ADS-B广播式自动相关监视信号收发与解码展开面向航空通信、信号处理方向的初学者和研究者旨在帮助读者理顺ADS-B报文结构、解码流程以及误码性能评估的完整思路。压缩包内共6个文件包括4个.m格式脚本、1份PDF项目报告和1个自述说明文件整体大小仅1.16MB小巧轻便便于快速下载和本地查阅。其中脚本分别对应信号仿真、解码实现、误码率计算等关键环节PDF报告对系统架构与测试结果进行了整理自述文件则给出基本运行指引。已有417人学习下载。读者可对照源码与报告逐步掌握从ADS-B基带信号处理到性能验证的主流方法并借助误码率脚本自行设计对比实验也可作为课程设计或毕业设计阶段的有益参考。1. ADS-B 收发器项目的真实定位能发、能收还要能校验拿到 ADS-B-Transmitter-Receiver-master 这个项目名先把“master”放一边它只是 Git 仓库的主分支名跟航空协议本身没有任何关系。真正把项目定位区分开的是 Transmitter 和 Receiver 两个词并列出现只做接收一个 RTL-SDR 加解码脚本就够了能把 1090 MHz 上的 Mode S 112 位短报文自己编出来、调制成波形发出去再收回来正确解析才算把 ADS-Bcode 这条链路完整掌握。闭环能力对 SDR 工具链开发、防碰撞算法仿真和报文协议教学都直接有用。这篇按“帧结构 → 接收解码 → 发射编码 → 回环验收”的顺序展开命令和代码都能在本地直接落地。2. ADS-B 的 112 位短报文帧格式、PPM 与 CRC 先立起来2.1 Mode S ES 帧的位分配哪一段是数据哪一段是校验Mode S Extended SquitterES是所有 ADS-B 广播的承载格式固定 112 位。前 88 位是消息本体后 24 位是校验位但 ADS-B 的校验位里又混入了飞机地址所以不能简单把后 24 位当普通 CRC 处理。发送顺序一律高位在前没有字节对齐代码里看到的十六进制只是为了便于阅读做的转换。位区间字段典型值作用0–4DF17二进制 10001Downlink Format17 表示 ADS-B ES 长报文5–7CA/NC5机载能力标识接收端可粗略判断平台状态8–31ICAO240xABCDEF全球唯一航空器地址发射端填入自己的标识32–87ME长度 56 位载荷区内含类型码 TC 和位置、速度、识别信息88–111PI长度 24 位CRC-24 计算结果再异或 ICAO 地址后的校验字ME 内部没有固定字段表前 5 位是 Type Code接收端根据 TC 决定剩余 51 位的解释方式。TC1 到 4 是识别码TC9 到 18 是位置TC19 是空速与地速TC28 是状态向量。理解了这一点再看报文就不会把“这 56 位都是什么”当成一个整体去背而是先按 TC 分支再逐位拆。2.2 PPM 脉冲位置调制与 8 微秒前导码ADS-B 的基带调制是 PPM每一比特占 1 微秒。比特 0 在前半个周期拉高、后半个周期拉低比特 1 反过来前半个周期低、后半个周期高。接收端只需要比较相邻两个半周期的幅度就能恢复比特流不需要相干载波同步这正是 SDR 接收 ADS-B 的便利之处。每帧数据前还有一个 8 微秒的前导码它在 0、1.0、3.5、4.5 微秒位置各放一个脉冲用于让接收端找到数据位的起始点。以 2 MS/s 采样率为例1 微秒对应 2 个采样点前导码脉冲落在采样偏移 0、2、7、9 处。下面这段代码把前导码的判定条件写出来后续 3.2 节会直接复用def detect_preamble(power, th0.15): idxs [] i 8 while i len(power) - 240: p power[i:i10] peaks p[0] p[2] p[7] p[9] guards p[1] p[3] p[4] p[5] p[6] p[8] if peaks th * 6 and guards th * 2: idxs.append(i) i 240 else: i 1 return idxs这里的power是 IQ 幅度序列th是归一化阈值。peaks表示四个理论脉冲位置的幅度和guards是它们之间的间隙幅度和。脉冲位置强、间隙位置弱才判定为前导码否则帧起点不可信。找到起点后跳过 16 个采样点8 微秒就进入 112 个数据位。2.3 CRC-24 的反馈多项式与“校验位异或地址”Mode S 使用生成多项式 0x1FFF409对应从 x^24 到低次的反馈结构。实现时把最高次 x^24 抽掉反馈项就是 0xFFF409。下面这个逐位版本适合教学和移植输入是 0/1 列表输出是 24 位整数def mode_s_crc(bits: list, poly_low: int 0xFFF409) - int: reg 0 for bit in bits: top (reg 23) 1 # 取当前最高位决定要不要反馈 reg ((reg 1) | bit) 0xFFFFFF if top: reg ^ poly_low return regtop保存移位前的最高位移位后新的输入位进入低位。如果最高位为 1说明多项式除法当前商位为 1需要异或反馈项。这里只保留 24 位结果最高次项通过反馈自然消掉。发射端把这一段计算方法叫 CRC 计算但 ADS-B 还有一层特殊处理后 24 位不是直接放 CRC而是将 CRC 与 ICAO 地址异或后再发。接收端不知道对方地址时可以把收到的 PI 与重算出的 CRC 异或恢复出发射端 ICAO再与报文第 8 到 31 位比对。这个“自校验”设计是 ADS-B 报文的精髓也是后面接收代码里判断帧有效性的依据。3. RTL-SDR 接收链路从 I/Q 文件里提取 112 位的完整过程3.1 数据采集参数先把 1090 MHz 的 I/Q 存下来接收链路最稳的做法是先用 RTL-SDR 采集一段原始 I/Q再用 Python 离线解析。这样调试阈值和改代码时不必反复对着实时频段抓信号回放同一份数据能保证对比公平。采集命令rtl_sdr -f 1090000000 -s 2000000 -g 40 -n 20000000 cap.bin参数含义-f设中心频率 1090 MHz-s设采样率 2 MS/s-g设接收增益 40 dB-n控制共采集 2000 万个采样点约 10 秒时长。输出文件是交错 int8 格式的 IQ 数据每个采样两个字节。采样率选 2 MS/s 而不是 RTL-SDR 常见的 2.4 MS/s是因为 2 MS/s 下每个数据位正好两个采样前导码位置、PPM 半周期比较都能用整数偏移表达省去重采样步骤。参数数值说明中心频率1090 MHzADS-B 下行链路 ES 频道采样率2 MS/s每位 2 采样便于整数索引增益30–48 dB低于 30 灵敏度不足高于 48 易饱和采样格式int8 交错 I/Q直接读入后转复数数组读取时把两个 int8 合并成复数再除以 127.5 归一化到 [-1,1]避免不同设备的满幅差异影响后续阈值。如果采集时窗口内有多个信号连续到达-n可以扩大到 1 亿点文件约 200 MB对离线调试仍然友好。3.2 前导码检测与 PPM 比特流提取核心流程三件事归一化幅度、检测前导码、按半周期比较恢复 112 位。下面的函数接收归一化后的复数数组返回每个候选帧的起始位置然后进入比特流提取import numpy as np def read_capture(path): raw np.fromfile(path, dtypenp.int8).astype(np.float32) iq (raw[0::2] 1j * raw[1::2]) / 127.5 return iq def extract_bits(iq, start, spb2): seg iq[start 8 * spb : start 8 * spb 112 * spb] bits [] for k in range(112): bits.append(1 if abs(seg[k * spb 1]) abs(seg[k * spb]) else 0) return bitsread_capture将 int8 交错序列拆成实部虚部并转成复数数组。extract_bits从检测到的起点向后跳过 8 微秒前导码然后逐比特比较两个半周期的幅度。这里没有做载波频偏补偿ADS-B 信号带宽窄、报文短幅度比较对残余频偏不敏感这也是 PPM 调制工程上的实际好处。3.3 CRC 校验与 ICAO 复核把“看起来像帧”变成“确定是帧”拿到 112 位后还不能直接信必须过校验关def bits_to_int(bits): v 0 for b in bits: v (v 1) | b return v def parse_frame_adsb(bits): df bits_to_int(bits[0:5]) icao bits_to_int(bits[8:32]) calc mode_s_crc(bits[:88]) pi bits_to_int(bits[88:112]) addr calc ^ pi return df, icao, addr, addr icaoaddr icao成立时说明报文通过自校验。calc ^ pi是把发射端做的“CRC 异或地址”还原成真实地址这个地址必须和帧内 ICAO 字段一致否则就是有位翻转、检错失败或前导码起点偏差。用这个方法做统计时我一般只看通过率不单独追求单帧成功。起点的 1 个采样偏移会影响后续所有比特但 CRC 几乎总会暴露这种错位。4. 发射端编码把 ADS-Bcode 从字段拼到 1090 MHz 波形4.1 组帧顺序与可选报文先做 TC4 识别码别一上来就拼 CPR发射端最容易踩的坑是直接挑 TC11 位置报文做起步因为位置报文包含 CPR 压缩编码需要奇偶两帧交替接收才能解出全局经纬度单帧无法闭环验证。我一般先用 TC4 识别码报文作为最小可验证对象字段短、不涉及 CPR、全球坐标解算接收端只要校验通过就能确认链路通。TC4 帧只依赖 ICAO、类型码和 8 个字符的飞行呼号。组帧顺序是固定的5 位 DF、3 位 CA、24 位 ICAO、56 位 ME、24 位 PI。DF 固定为 17CA 固定为 5ICAO 选一个测试地址比如 0xABCDEF。ME 前 5 位是 TC4随后 3 位保留置 0剩下 48 位用 6 位字符编码写入呼号。4.2 ME 载荷的 6 位字符编码以“TST0001”为例Mode S 的呼号编码不是 ASCII而是 6 位 ICAO 字符集A 到 Z 对应 1 到 26空格对应 32数字 0 到 9 使用 ASCII 低 6 位。编码函数如下def char_to_code(ch): if A ch Z: return ord(ch) - 64 # A1, B2 ... Z26 if ch : return 32 if 0 ch 9: return ord(ch) 0x3F # 数字保持 ASCII 低 6 位 return 0 def callsign_to_bits(callsign): cs callsign.upper().ljust(8)[:8] bits [] for ch in cs: code char_to_code(ch) bits.extend([(code k) 1 for k in range(5, -1, -1)]) return bitsord(ch) - 64把 ASCII 的 A 从 65 变成了 1正好落在 ICAO 字符集区间。数字走 0x3F是因为 ASCII 数字的高 3 位固定是 011取低 6 位刚好得到 0x30 到 0x39。长度不足 8 位时右侧补空格超过 8 位直接截断。组装完整帧并计算 PIdef build_test_frame(callsignTST0001, icao0xABCDEF): df_bits [1,0,0,0,1] # DF17 ca_bits [1,0,1] # CA5 icao_bits [int(b) for b in f{icao:024b}] me_bits [0,0,1,0,0] [0,0,0] callsign_to_bits(callsign) msg df_bits ca_bits icao_bits me_bits # 前 88 位 crc mode_s_crc(msg) pi crc ^ icao return msg [int(b) for b in f{pi:024b}]pi crc ^ icao是 ADS-B 发射端的关键一步没有这一步接收端按 2.3 节的自校验逻辑比对时永远对不上。CALLSIGN 建议用TST0001这种不会和真实航班呼号冲突的测试标识。4.3 生成 PPM 波形并送入 SDR得到 112 位后需要把它变成基带波形。按 2 MS/s 采样率每个比特 2 个采样点前导码 16 个采样点总长 240 个采样点。波形生成器def ppm_wave(bits, spb2, amp1.0): total 8 * spb len(bits) * spb wave np.zeros(total, dtypenp.complex64) wave[0:1] amp # 前导码 0us wave[spb:spb1] amp # 前导码 1.0us wave[7:8] amp # 前导码 3.5usspb2 时 wave[9:10] amp # 前导码 4.5us base 8 * spb for i, b in enumerate(bits): pos base i * spb if b 1: wave[pos1:pos2] amp # 前半低后半高 else: wave[pos:pos1] amp # 前半高后半低 return wave前导码的 3.5 和 4.5 微秒在 2 MS/s 下对应采样偏移 7 和 9所以直接写死。数据位的 PPM 判断用两个半周期的能量差异发射端自然按同一规则构造比特 1 只填充后半采样点比特 0 只填充前半采样点。把波形写入文件后交给 HackRF 发射python3 -c import numpy as np; from adsb_tx import build_test_frame, ppm_wave; wppm_wave(build_test_frame()); w.tofile(tst0001.cf32) hackrf_transfer -f 1090000000 -s 2000000 -x 20 -i tst0001.cf32-x 20是发射增益 20 dB-i从文件读取基带 IQ。这个命令会不断循环播放同一帧方便接收端做统计验证。4.4 闭环测试的射频链路配置测归测别对着空气发射1090 MHz 属于航空监视频段没有无线电执照时不允许对空辐射。实验室闭环验证请用同轴线加衰减器把发射端和接收端直连不接天线。推荐一套我常用的链路环节配置目的发射端HackRFTX 增益 10–20 dB输出功率约 -10 到 0 dBm衰减器40 dB 同轴固定衰减保护接收端 LNA 不饱和接收端RTL-SDR增益 20–30 dB与衰减匹配留出调整余量连接SMA 同轴线尽量短减少线缆引入的反射和驻波接收增益不要照搬直收空中信号时的 40 dB衰减器后面信号幅度已可控增益过高反而让 PPM 波形削顶造成解调错误。闭环先通了再谈外场。5. 回环验收技巧用已知 ICAO 统计通过率再用 dump1090 交叉验证5.1 三组数据判断“能发不能收”闭环测试只看一帧成功没有说服力至少要统计几百帧。我习惯用三组数据定位问题前导码触发数、CRC 通过数、ICAO 恢复正确数。三者关系如下现象可能原因调整方向触发数很少衰减过大或发射增益不足提高 TX 增益检查衰减器标称值触发数多但 CRC 全挂接收饱和削顶或起点偏移降低 RX 增益检查前导码检测阈值CRC 通过但 ICAO 不一致帧拼接错位或发射端 PI 计算漏异或对比addr与icao两个字段输出第三类现象最隐蔽。如果parse_frame_adsb返回的addr和icao不一致多半是build_test_frame里漏了pi crc ^ icao接收端自校验必然失败。5.2 验收脚本的一个可执行片段把前面的函数拼成统计脚本强制跑满 300 帧python3 adsb_check.py cap.bin --icao 0xABCDEF --min-frames 300脚本内部逻辑是循环扫描detect_preamble返回的所有起点逐个extract_bits和parse_frame_adsb累计三类计数。通过标准触发数大于 300CRC 通过率高于 95%且恢复地址全部等于 0xABCDEF。低于 95% 时优先怀疑采样率设置确认rtl_sdr的-s 2000000和ppm_wave的spb2完全一致。5.3 和 dump1090 对照的现场做法闭环验证只能证明链路自洽不能证明实现与通行协议完全兼容。做法是拿第二台 RTL-SDR 接天线收真实信号让 dump1090 和你的脚本同时跑对比同一班机的 ICAO 和高度字段dump1090 --device-index 1 --net --net-http-port 8080 --aggressive然后打开http://127.0.0.1:8080观察实时航班用其中一个飞机的 ICAO 回查自己的解析结果。重点不是要求高度完全一致而是确认 DF、ICAO、CRC 三段逻辑和 dump1090 对齐。真实接收环境有干扰和多径通过率只要比闭环低一截就说明协议栈本身没问题剩下的差异来自射频信道。本文还有配套的精品资源点击获取