系列导航 :目录 | 上一章:时域与频域 | 下一章:业余无线电入门(编写中)
本章目标:读完本章,你将能够
- 说出一台接收机必须完成的几件事:选择、解调、还原;
- 推导二极管包络检波器时间常数的上下限,解释"对角切割失真"和纹波;
- 理解真实二极管的平方律检波,说清楚矿石机为什么要用锗二极管;
- 动手做一台不用电池的矿石收音机,并估算它从电波里能得到多大的电压;
- 理解软件无线电(SDR)的基本结构,装好 RTL-SDR 并收听 FM 广播;
- 用 Python 实现 FM 广播的鉴相解调和去加重,解码一条真实格式的 ADS-B 飞机报文。
前置知识:第 01 章的 AM 与平方反比、第 03 章的 RC 电路和调谐回路、第 04 章的频谱与 FFT。
前四章我们准备了足够多的理论,现在该听到声音了。
本章分两半。前一半回到一百年前,做一台矿石收音机 :一个线圈、一个可变电容、一个二极管、一副耳机,不用电池,靠电台发来的电波本身的能量就能发声。它是理解"接收机到底在做什么"的最好起点。后一半跳到今天,用一个两百元左右、U 盘大小的 RTL-SDR 接收器,把一大段频谱直接变成数字,交给电脑和 Python 处理。我们会用 Python 解调 FM 广播,再解码一条飞机发出的 ADS-B 报文。
一、接收机要做的三件事

图 1:一台最简单的 AM 接收机。上:功能方框;下:每一级的信号波形。
不论简单还是复杂,接收机都要完成三件事:
- 选择:天线收到的是所有电台信号的叠加(图 1 左下)。接收机要从中选出一个。矿石机靠第 03 章的 LC 调谐回路,现代接收机靠超外差(第 15 章)或数字滤波(第 20 章);
- 解调 :选出的信号是已调载波,信息藏在它的幅度、频率或相位里。接收机要把信息取出来。对 AM 来说,就是取出包络;
- 还原:把解调出的信号变成人能感知的东西,比如声音、图像或文字。
真正的接收机通常还要做第四件事:放大。矿石机没有任何放大,所以只能收听强信号。
二、包络检波
1. 工作原理
AM 信号的信息在包络里,所以只要能"描出"包络就行。最简单的电路是一个二极管加一个 RC 并联网络:

图 2:左:二极管包络检波器。中:检波输出跟随 AM 信号的包络。右:放大看,每个载波周期的峰值附近二极管导通、给电容充电,其余时间电容经电阻放电。
- 输入电压高于电容电压时,二极管导通,电容迅速充电到输入的峰值;
- 输入电压低于电容电压时,二极管截止,电容经电阻 R 缓慢放电,时间常数 τ=RC。
于是电容电压"骑"在每个载波周期的峰值上,大致描出了包络,只是带有锯齿状的纹波。
2. 时间常数的上下限
RC 的选择有两个相互矛盾的要求。
**下限:要远大于载波周期。**否则电容在两个载波峰之间就放电完了,输出跟着载波起落,纹波很大。所以要求 RC≫1/fc。
**上限:放电要跟得上包络的下降。**包络 1+μcosωmt 下降时,电容电压必须至少以同样快的速度下降,否则输出会脱离包络,变成一段指数曲线。电容放电的速率是 v/RC,包络下降的速率是 μωmsinωmt,要求在任何时刻
RC1+μcosθ≥μωmsinθ,θ=ωmt
即 RC≤μωmsinθ1+μcosθ 对所有 θ 成立。对 θ 求右边的最小值:令导数为零,得到 cosθ=−μ,代回去,
RC≤μωm1−μ2
违反这个条件产生的失真称为对角切割失真。调制度越深、音频频率越高,上限越严格。

图 3:三种时间常数下的检波输出(载波 100 kHz,音频 1 kHz,调制度 0.8)。
以载波 100 kHz(为了仿真方便,按比例缩小)、音频 1 kHz、调制度 0.8 为例,上限是 1−0.64 /(0.8×2π×1000)=119.4 μs,载波周期是 10 μs。附录代码用理想二极管模型逐点仿真了三种时间常数:
| RC | 音频幅度(理想 0.8) | 总谐波失真 | 100 kHz 纹波幅度 |
|---|---|---|---|
| 3 μs(太小) | 0.373 | 1.00 % | 0.3907 |
| 100 μs(合适) | 0.764 | 0.03 % | 0.0302 |
| 1 ms(太大) | 0.375 | 36.21 % | 0.0043 |
RC 太小时纹波很大,输出幅度只有一半;RC 太大时纹波最小,但对角切割让失真高达 36%;100 μs 时两者兼顾。剩下的纹波频率很高(100 kHz),耳朵听不到,耳机也跟不上,所以无关紧要。
3. 真实的二极管:小信号时是平方律
上面的仿真假设二极管是理想开关。真实二极管的电流由肖克利方程描述:
i=Is(ev/(nVT)−1)
其中 VT=kT/q≈25.85 mV 是热电压(300 K), Is 是反向饱和电流, n 是理想因子。当输入 Acosωt 加在二极管和负载 R 上,稳态时检波输出是一个直流电压 V(这里只看载波,不加调制)。在一个载波周期内,二极管的平均电流必须等于流过负载的电流 V/R:
Is2π1∫02πe(Acosθ−V)/(nVT)dθ−1=RV
括号里的积分恰好是零阶修正贝塞尔函数 I0(nVTA) 乘以 e−V/(nVT)。附录代码用二分法解出 V。小信号时 I0(x)≈1+x2/4,可以推出 V∝A2:输出与输入振幅的平方成正比 ,这叫平方律检波 。只有信号足够大时,输出才接近理想的 V≈A。

图 4:两种二极管的检波特性(负载 100 kΩ,双对数坐标)。
附录代码对比了两种二极管模型( Is 取数量级上的典型值):
| 输入振幅 A | 低阈值二极管( Is=10−7 A) | 普通硅二极管( Is=10−12 A) |
|---|---|---|
| 10 mV | 0.270 mV | 3.8×10−6 mV |
| 30 mV | 2.43 mV | 3.7×10−5 mV |
| 100 mV | 26.4 mV | 9.1×10−4 mV |
| 300 mV | 170 mV | 1.24 mV |
| 1000 mV | 815 mV | 529 mV |
Is 大的二极管(锗管、肖特基管)在几十毫伏时就能检出可用的信号;普通硅管在 300 mV 以下几乎没有输出,这就是人们常说的硅管"导通电压高"。矿石机收到的信号只有几十到几百毫伏,所以必须用锗二极管。
平方律检波还有一个代价:对 AM 信号 (1+μcosωmt) 取平方,会产生二倍音频的分量。附录代码算出 μ=1 时二次谐波失真为 25.0%,理论值是 μ/4。所以弱信号在矿石机里听起来会有些失真。
4. 数字包络检波
在软件无线电里,包络检波变得异常简单。SDR 给出的是复基带信号 I+jQ(第 19 章详细讲),AM 信号的包络就是它的模:
m^(t)=∣I(t)+jQ(t)∣=I2+Q2
附录代码对任意载波相位的复基带 AM 信号直接取模,得到的音频幅度正好是 0.800,总谐波失真只有 7.7×10−14%,也就是计算机的舍入误差。没有纹波,也没有时间常数的取舍,因为复基带信号里已经没有载波了。
三、动手做一台矿石收音机
1. 电路

图 5:矿石收音机电路。左半部分是第 03 章设计的调谐回路,右半部分是本章的包络检波器。
| 元件 | 规格 | 作用 |
|---|---|---|
| 天线 | 10~30 m 的导线,尽量高、尽量远离房屋 | 接收电波 |
| 地线 | 接地棒,或者自来水管(金属管) | 为天线电流提供回路 |
| 线圈 L | 240 μH:直径 50 mm 的骨架上用 0.3 mm 漆包线密绕 67 匝(第 03 章) | 与 C 组成调谐回路 |
| 可变电容 C | 单联,约 15~365 pF | 调谐,覆盖 530~1600 kHz |
| 二极管 | 锗二极管,如 1N34A、2AP9 | 检波 |
| 电容 | 1 nF | 与 100 kΩ 组成 RC = 100 μs 的检波负载,同时旁路载波 |
| 电阻 | 100 kΩ | 电容的放电通路 |
| 耳机 | 高阻耳机(晶体耳机) | 发声 |
几点说明:
- 为什么要 100 kΩ 电阻 :晶体耳机本身几乎就是一个电容,没有直流通路。如果不并联电阻,检波电容充上电后无处放电,几秒钟后就没声音了。 RC=100 kΩ×1 nF=100 μs,正好满足第二节的条件;
- 为什么要高阻耳机:普通的 32 Ω 耳机需要毫瓦级的功率才能听到声音,而且会严重加重调谐回路的负载,让 Q 值大大下降,选择性变差。晶体耳机几乎不消耗功率;
- 安全:天线不要靠近任何电力线;雷雨天气要把天线断开并接地;地线不能接燃气管道。
2. 能量从哪里来
矿石机没有电池,耳机里的每一点声音都来自电台发出的电波。能有多少呢?
由第 01 章,各向同性辐射的功率密度 S=PtG/(4πd2),又 S=E2/η0, η0≈120π,所以电场强度
E=4πd2η0PtG =d30PtG
附录代码以 50 km 外一座 100 kW 的中波电台为例(按自由空间粗略估算, G=1): E=34.6 mV/m。天线的开路电压近似等于场强乘以天线的有效高度 ,对于一根挂在十来米高处的导线,有效高度取 5 m,开路电压约 173 mV。
这个电压再经过调谐回路(谐振时有一定的电压增益)送到二极管上,正好落在图 4 中锗二极管能工作、硅管几乎不工作的区间。这也说明了为什么矿石机需要一根又长又高的天线:天线越高,有效高度越大,收到的电压越高。
需要说明的是,中波主要靠地波传播,经过地面时有额外的损耗,实际场强通常低于自由空间的估算;离电台越近,效果越好。在大城市里,附近的大功率电台很容易收到;在偏远地区,晚上靠天波可能听到远方的电台。
3. 矿石机的局限
- 没有放大:只能听强台;
- 选择性差:只有一个调谐回路,而且天线和耳机都会降低它的 Q。第 03 章练习 5 算过,Q = 150 的回路对相邻 10 kHz 的电台只衰减 2 dB 多。强台常常会"盖住"整个波段;
- 只能收 AM。
这些局限,正是后面几章要一一解决的问题:放大器(第 16 章)、超外差接收机(第 15 章)、FM 与 SSB 的解调(第 12、13 章)。不过,我们也可以绕过这些模拟电路,直接走到最前沿:软件无线电。
四、软件无线电
1. 把一段频谱变成数字

图 6:RTL-SDR 的结构。硬件只负责把一段频谱搬下来、变成数字,剩下的工作全部由电脑完成。
软件无线电(Software Defined Radio,SDR)的思想是:让硬件只做最少的事,即放大、把一段频谱搬到低频、数字化,然后把数字样本交给电脑;选择、解调、解码全部用软件完成。想收 FM 就运行 FM 解调程序,想收飞机信号就运行 ADS-B 解码程序,硬件完全不用改。
RTL-SDR 本来是一种 DVB-T 数字电视接收棒。2012 年前后,爱好者们发现它的芯片 RTL2832U 有一个可以直接输出原始 IQ 样本的模式,于是开源社区为它写了驱动程序。从此,花两百元左右就能拥有一台覆盖几十 MHz 到上千 MHz 的宽带接收机。
2. 关键参数
| 参数 | 数值 | 含义 |
|---|---|---|
| 频率范围 | 约 24~1766 MHz(R820T2 调谐器) | 覆盖 FM 广播、航空波段、业余 2 米和 70 厘米波段、ADS-B 等。部分改进型号(如 RTL-SDR Blog V3、V4)可通过直接采样或内置上变频器接收 HF |
| ADC | 8 位 | 理想信噪比 6.02×8+1.76=49.9 dB,动态范围有限,强信号附近容易过载(第 18 章) |
| 采样率 | 稳定工作约 2.4 MS/s(复数) | 一次能看到 2.4 MHz 宽的频谱 |
| 数据率 | 2.4 MS/s × 2 字节 = 4.8 MB/s | 经 USB 2.0 传给电脑 |
| FFT 分辨率 | 2.4 MS/s ÷ 2048 = 1171.9 Hz | 用 2048 点 FFT 画瀑布图时每个频点的宽度(第 04 章) |
3. 安装与第一次开机
- 驱动 :Windows 上用 Zadig 工具把接收棒的驱动换成 WinUSB;Linux 上安装
rtl-sdr软件包; - 软件:推荐跨平台的 SDR++,Windows 上也可以用 SDR#,Linux 和 macOS 上可以用 GQRX;
- 天线:接收棒附带的拉杆天线就能用。收 FM 广播时把两根振子各拉到约 75 cm(100 MHz 的 λ/4,第 01 章),水平或竖直放置都试一试;
- 收听 FM 广播:把频率调到 87~108 MHz 之间,解调方式选 WFM(宽带调频),带宽约 200 kHz。在瀑布图上,每个电台是一条约 200 kHz 宽的亮带;
- 调增益:8 位 ADC 很容易过载。增益太高时,瀑布图上会冒出很多原本不存在的"假信号",这时要把增益降下来。
收听业余 2 米波段的中继台(144~148 MHz,NFM 窄带调频),以及航空波段(118~137 MHz,AM),步骤类似,只是解调方式不同。请遵守无线电管理规定,不要录制、传播收听到的非公开通信内容。
五、用 Python 解调 FM 广播
现在我们用 Python 实现 SDR 软件里的 WFM 解调器。为了让没有硬件的读者也能运行,附录代码先仿真一个 FM 广播信号,再对它进行解调;最后给出接上 RTL-SDR 时需要替换的几行代码。
1. FM 广播的参数
| 参数 | 数值 |
|---|---|
| 频段 | 87~108 MHz |
| 最大频偏 | ±75 kHz |
| 音频带宽 | 15 kHz |
| 预加重时间常数 | 50 μs(我国和欧洲;美国为 75 μs) |
| 卡森带宽 2(Δf+fm) | 2×(75+15)=180 kHz |
仿真中的音频是两个单音:1 kHz(幅度 0.6)和 6 kHz(幅度 0.15)。复基带采样率取 240 kHz。
2. 鉴相解调
FM 信号的复基带形式是 x(t)=ejφ(t),其中相位 φ(t)=2πΔf∫m(τ)dτ。瞬时频率是相位的导数:
fi(t)=2π1dtdφ=Δf⋅m(t)
数字实现时,用相邻两个样本的相位差代替导数。一个巧妙的写法是:
Δφn=arg(xnx∗n−1),m^n=2πΔfΔφnfs
xnx∗n−1 的辐角正好是两个样本的相位之差(第 02 章:复数相乘,辐角相加;乘以共轭,辐角相减),而且结果自动落在 (−π,π] 内,不用担心相位卷绕。前提是相邻样本间的相位差不能超过 π,所以采样率必须足够高(练习 6)。
3. 预加重与去加重
FM 解调后的噪声有一个特点:频率越高,噪声越大(第 13 章会推导出噪声幅度谱随频率线性上升)。而音乐和话音的能量主要在低频,高频分量本来就弱,于是高频部分的信噪比特别差。
对策是:发射端先把高频提升 (预加重 ),接收端再把高频衰减 回来(去加重 )。信号经过两次处理后恢复原状,而接收机引入的高频噪声只经过了去加重,被压低了。去加重就是第 03 章的 RC 低通滤波器,时间常数 50 μs,转折频率 1/(2π×50 μs)=3183 Hz。数字实现是一个单极点滤波器:
yn=ayn−1+(1−a)xn,a=e−1/(fsτ)
4. 仿真结果

图 7:左:接收到的 FM 信号的频谱(载噪比 20 dB)。右:解调后的音频频谱,红色为不用预加重和去加重,蓝色为使用。
附录代码在载噪比 20 dB 的条件下,分别不用和使用预加重/去加重,解调出音频,再低通滤波到 15 kHz、抽取到 48 kHz 采样率:
| 不用预加重 / 去加重 | 使用预加重 / 去加重 | |
|---|---|---|
| 调制信号峰值(满偏 = 1) | 0.750 | 0.944 |
| 解调出的 1 kHz 幅度(原 0.6) | 0.5999 | 0.5999 |
| 解调出的 6 kHz 幅度(原 0.15) | 0.1500 | 0.1500 |
| 音频信噪比 | 44.6 dB | 54.3 dB |
两种方式都准确还原了两个单音的幅度,而预加重/去加重把信噪比提高了约 10 dB。代价是预加重让调制信号的峰值变大(0.750 → 0.944),广播电台要相应控制高频的音量,以免超出允许的频偏。
附录代码还测出这个 FM 信号的 99% 功率带宽为 144.0 kHz,比卡森公式 2×(75+6)=162 kHz 略窄。卡森公式只是一个保守的估计,第 13 章会用贝塞尔函数精确计算 FM 的频谱。
5. 接上真实的 RTL-SDR
如果你有 RTL-SDR,安装 Python 库 pyrtlsdr(python -m pip install pyrtlsdr),把附录代码中生成仿真信号的部分换成从接收棒读取样本即可。下面是一个最小的例子,它把解调出的音频保存成 WAV 文件:
python
import wave
import numpy as np
from rtlsdr import RtlSdr
FS, DEV, TAU = 240e3, 75e3, 50e-6
sdr = RtlSdr()
sdr.sample_rate = FS # RTL-SDR 支持 225~300 kS/s 和 0.9~3.2 MS/s 两个区间
sdr.center_freq = 97.4e6 # 改成你所在地区一个 FM 电台的频率
sdr.gain = "auto"
x = sdr.read_samples(int(FS * 5)) # 读 5 秒
sdr.close()
d = np.angle(x[1:] * np.conj(x[:-1])) * FS / (2 * np.pi * DEV) # 鉴相
a = np.exp(-1 / (FS * TAU))
y = np.empty_like(d)
acc = 0.0
for i, v in enumerate(d): # 去加重
acc = a * acc + (1 - a) * v
y[i] = acc
k = np.arange(101) - 50
h = 2 * 15e3 / FS * np.sinc(2 * 15e3 / FS * k) * np.hanning(101)
audio = np.convolve(y, h / h.sum(), mode="same")[::5] # 低通并抽取到 48 kHz
pcm = (np.clip(audio / np.abs(audio).max(), -1, 1) * 32767 * 0.8).astype(np.int16)
with wave.open("fm.wav", "wb") as w:
w.setnchannels(1)
w.setsampwidth(2)
w.setframerate(48000)
w.writeframes(pcm.tobytes())
说明 :这段代码需要接上 RTL-SDR 才能运行。编写本文时手边没有硬件,它没有经过实际运行,只是附录代码中已验证的解调算法加上 pyrtlsdr 的读取接口。如果遇到问题,可以先检查驱动是否装好、center_freq 是不是本地真实存在的电台频率。
六、解码一条 ADS-B 报文
1. 什么是 ADS-B
民航飞机通过 ADS-B(广播式自动相关监视)在 1090 MHz 上不停地广播自己的身份、位置、高度和速度,每秒好几次。这些信号没有加密,任何人都可以接收,用 RTL-SDR 加上 dump1090 之类的软件,就能在地图上看到周围几百公里内的飞机。
1090 MHz 的波长是 27.5 cm,一根 6.9 cm 长的 λ/4 竖直天线(第 01 章)就能收得很好。飞机在万米高空,无线电视距很远(练习 8),接收距离常常超过 300 km。
2. 信号格式
ADS-B 用脉冲位置调制(PPM),速率 1 Mbit/s:
- 前导码:8 μs,在 0、1.0、3.5、4.5 μs 处各有一个 0.5 μs 的脉冲,用来让接收机发现报文的开头;
- 数据位:每位 1 μs,脉冲在前半个微秒表示 1,在后半个微秒表示 0。
扩展电文(DF17)共 112 位:
| 字段 | 位数 | 内容 |
|---|---|---|
| DF | 5 | 下行格式,17 表示 ADS-B |
| CA | 3 | 应答机能力 |
| ICAO | 24 | 飞机的全球唯一地址 |
| ME | 56 | 消息内容:前 5 位是类型码 TC,1~4 表示身份识别(航班号),9~18 表示空中位置...... |
| PI | 24 | CRC-24 校验 |

图 8:一条 ADS-B 报文的开头。灰线是发射的脉冲,蓝色柱是 2 MS/s 采样得到的幅度(噪声标准差 0.2),上方的 0/1 是逐位判决的结果。
3. 解码过程
附录代码使用的报文是 ADS-B 解码教材中常用的一个例子:8D4840D6202CC371C32CE0576098。代码先把它变成 PPM 波形,加上噪声,然后像真正的接收机一样处理:
- 找前导码:用前导码的波形模板和接收信号做相关,相关峰的位置就是报文的开头;
- 判决每一位:比较每一位前半和后半两个采样的大小;
- 校验 CRC :把 112 位当作一个二进制多项式,除以生成多项式 0x1FFF409(25 位,模 2 运算),余数为零表示报文正确(第 26 章会讲 CRC 的原理);
- 解析字段 :取出 DF、ICAO 地址、类型码,航班号的每个字符用 6 位编码,查表
#ABCDEFGHIJKLMNOPQRSTUVWXYZ##### ###############0123456789######得到字符。
运行结果:
| 项目 | 结果 |
|---|---|
| 无噪声时的 CRC 余数 | 0x0(正确) |
| 噪声 σ = 0.2 | 前导码位置 123(真值 123),误码 0 位,校验通过 |
| 噪声 σ = 0.8 | 前导码位置 190(找错了),误码 57 位,校验失败,丢弃 |
| 翻转第 50 位 | CRC 余数 0x6159c0,错误被检出 |
| 解码内容 | DF = 17,ICAO 地址 4840D6,类型码 TC = 4(身份识别),航班号 KLM1023 |
CRC 的作用在这里体现得很清楚:噪声太大时,接收机可能连报文开头都找错,解出一串乱码;CRC 保证了这样的报文会被可靠地丢弃,而不会在地图上显示一架"幽灵飞机"。哪怕只错一位,CRC 也能发现。
飞机的位置用一种叫 CPR 的紧凑编码,需要两条报文配合才能解出经纬度,留到第 21 章的 SDR 实战再讲。
小结
- 接收机要完成选择、解调、还原三件事,通常还要放大。矿石机只靠调谐回路选择,没有放大。
- 包络检波器 是二极管加 RC。下限 RC≫1/fc,上限 RC≤1−μ2 /(μωm);太小纹波大,太大产生对角切割失真。
- 真实二极管 小信号时是平方律 检波,输出 ∝ 输入²,并带来 μ/4 的二次谐波失真;锗管、肖特基管的 Is 大,在几十毫伏就能工作,硅管需要几百毫伏。
- 数字包络检波 就是复基带信号取模 ∣I+jQ∣,没有纹波也没有时间常数的问题。
- 矿石机的能量全部来自电波:100 kW 中波电台 50 km 外的场强约 34.6 mV/m,5 m 有效高度的天线约 173 mV。需要高阻耳机和并联电阻,天线要长、要高。
- 软件无线电让硬件只负责放大、变频和数字化,选择和解调交给软件。RTL-SDR 覆盖约 24~1766 MHz,8 位 ADC,约 2.4 MS/s。
- FM 鉴相解调 : m^n∝arg(xnx∗n−1)。预加重/去加重(50 μs)在仿真中把音频信噪比从 44.6 dB 提高到 54.3 dB。
- ADS-B:1090 MHz,PPM,1 Mbit/s,112 位报文,CRC-24 校验。解码出 ICAO 地址 4840D6、航班号 KLM1023。
下一章,我们暂时放下技术,看看怎样成为一名合法的业余无线电爱好者:执照考试、呼号、频率规划和通联规范。
练习题
- (检波器设计) 广播的调制度最大取 0.5,音频最高 3 kHz。检波负载电阻为 100 kΩ 时,电容最大可以取多少?中波载波频率约 1 MHz,下限条件满足吗?
- (平方律失真) 调制度 μ=0.3 的 AM 信号经过平方律检波,二次谐波失真是多少?
- (场强) 一座 50 kW 的电台在 20 km 外的场强约是多少(自由空间估算)?有效高度 5 m 的天线上开路电压是多少?
- (ADC 位数) 如果把 8 位 ADC 换成 12 位(一些更高档的 SDR 采用 12 位或 14 位 ADC),理想信噪比提高多少 dB?
- (去加重) 50 μs 和 75 μs 两种去加重网络的转折频率各是多少?
- (鉴相的采样率) FM 广播的最大频偏是 75 kHz。复基带采样率为 240 kHz 时,相邻样本之间的最大相位差是多少弧度?如果采样率降到 120 kHz 会怎样?
- (编程:门限效应) 修改附录代码第 5 部分,把载噪比依次改为 15、10、6、3 dB(使用预加重/去加重),音频信噪比各是多少?你观察到了什么?
- (ADS-B 的接收距离) 一架飞机在 10000 m 高度,你的接收天线架在 10 m 高处。用第 01 章的公式估算无线电视距。
- (手工解码) 航班号字段中三个 6 位编码分别是 001011、110001、100000,查表得到哪三个字符?
- (耳机) 为什么矿石机不能用普通的 32 Ω 耳机?说出至少两个原因。
参考答案
- RC≤0.5×2π×30001−0.25 =91.9 μs, C≤91.9 μs/100 kΩ= 919 pF 。载波周期 1 μs, RC=91.9 μs 远大于它,下限满足。图 5 中取 1 nF,对应 100 μs,在这个最苛刻的条件下略微超出,实际广播很少同时出现深调制和 3 kHz 的高音,影响不大;也可以改用 820 pF。
- μ/4= 7.5%。调制越浅,平方律失真越小。
- E=30×5×104 /(2×104)= 61.2 mV/m ,开路电压约 306 mV。
- 6.02×12+1.76=74.0 dB,比 8 位的 49.9 dB 提高 24.1 dB(每多一位提高约 6 dB)。
- 50 μs: 1/(2π×50×10−6)= 3183 Hz ;75 μs:2122 Hz。
- 最大相位差 2π×75/240= 1.96 rad ,小于 π,没有问题。采样率降到 120 kHz 时为 3.93 rad ,超过 π, arg(⋅) 会把它卷绕成 3.93−2π=−2.36 rad,解调出完全错误的结果。所以鉴相解调要求采样率大于最大瞬时频率的 2 倍。
- 附录代码改参数后的结果:载噪比 15 dB → 49.3 dB ,10 dB → 44.1 dB ,6 dB → 24.9 dB ,3 dB → 14.8 dB 。载噪比在 10 dB 以上时,每降低 5 dB,音频信噪比也只降低 5 dB;降到 10 dB 以下后,音频信噪比急剧恶化,从 10 dB 到 6 dB,载噪比只降了 4 dB,音频信噪比却降了 19 dB。这就是 FM 的门限效应:信号弱到一定程度后,噪声会引起相位的整周跳变,在耳机里听到"喀喀"的噪声,第 13 章会详细分析。
- d≈4.12×(10000 +10 )=4.12×(100+3.16)≈ 425 km。
- 001011 = 11 → K ;110001 = 49 → 1 ;100000 = 32 → 空格。
- (a) 32 Ω 耳机需要毫瓦级的功率才能发出可听的声音,矿石机提供的功率远远不够,而晶体耳机几乎只需要电压;(b) 低阻负载会严重降低调谐回路的 Q,使选择性变得极差;(c) 低阻负载使检波器的时间常数和工作点完全改变,检波效率大大降低。
参考资料
- ARRL. The ARRL Handbook for Radio Communications. 最新版. "Receiving" 与 "Software Defined Radio" 相关章节。
- W. Hayward, R. Campbell, B. Larkin. Experimental Methods in RF Design. ARRL, 2003. 检波器与接收机相关章节。
- M. Lichtman. PySDR: A Guide to SDR and DSP using Python . pysdr.org
- J. Sun. The 1090 Megahertz Riddle: A Guide to Decoding Mode S and ADS-B Signals . 2nd ed. TU Delft OPEN Publishing, 2021. mode-s.org/decode
- RTL-SDR Blog. www.rtl-sdr.com
- Osmocom rtl-sdr 项目. osmocom.org/projects/rt...
附录:完整代码
下面的代码复现了本章所有数值:包络检波在三种时间常数下的失真与纹波、数字包络检波、二极管检波特性的数值求解与平方律失真、矿石机的场强估算、RTL-SDR 的几个参数、FM 广播的仿真与解调(含预加重和去加重的对比、99% 带宽),以及 ADS-B 报文的 PPM 波形生成、前导码检测、CRC-24 校验和字段解析,最后画出 FM 解调前后的频谱。只依赖 numpy 和 matplotlib,保存为 example.py 后运行 python example.py 即可,几秒钟就能运行完。
python
"""无线电自学教程 05:第一台收音机------从矿石机到 SDR ------ 附录代码
1. 包络检波:理想二极管 + RC,三种时间常数下的失真与纹波;数字包络检波(取 IQ 的模);
2. 二极管的检波特性:小信号时平方律,大信号时线性;
3. 矿石机的"能源":远处中波电台在天线上感应出多大的电压;
4. RTL-SDR 的几个数字:8 位 ADC 的动态范围、带宽、数据率;
5. 用 Python 解调 FM 广播:仿真发射端(含预加重)与接收端(鉴相、去加重、抽取);
6. 解码一条 ADS-B 报文:PPM 波形、前导码检测、CRC-24 校验、航班号。
最后画出 FM 解调前后的频谱。
运行:python example.py
"""
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams["font.sans-serif"] = ["Microsoft YaHei", "SimHei", "DejaVu Sans"]
plt.rcParams["axes.unicode_minus"] = False
def thd(x, fs, f0, n_harm=10):
"""总谐波失真:2~n_harm 次谐波的有效值与基波之比。x 须包含整数个 f0 周期。"""
X = np.abs(np.fft.rfft(x - x.mean()))
k0 = int(round(f0 * len(x) / fs))
harm = np.sqrt(sum(X[h * k0] ** 2 for h in range(2, n_harm + 1)))
return harm / X[k0], X[k0] * 2 / len(x)
# ---------------- 1. 包络检波 ----------------
print("=== 1. 包络检波:载波 100 kHz,音频 1 kHz,调制度 0.8 ===")
FC, FM, MU = 100e3, 1e3, 0.8
fs = 10e6
t = np.arange(int(12e-3 * fs)) / fs
am = (1 + MU * np.cos(2 * np.pi * FM * t)) * np.cos(2 * np.pi * FC * t)
rc_max = np.sqrt(1 - MU**2) / (MU * 2 * np.pi * FM)
print(f"不产生对角切割失真的条件:RC <= sqrt(1-μ^2)/(μ ω_m) = {rc_max * 1e6:.1f} μs;载波周期 {1e6 / FC:.0f} μs")
def envelope_detector(x, rc, fs):
"""理想二极管(正向导通电阻为零)+ 并联 RC:输入高于电容电压就充电到输入,否则放电。"""
decay = np.exp(-1 / (fs * rc))
v = np.empty_like(x)
vc = 0.0
for i, xi in enumerate(x):
vc = xi if xi > vc else vc * decay
v[i] = vc
return v
seg = slice(int(2e-3 * fs), None) # 去掉前 2 ms 的建立过程,剩下 10 个音频周期
for rc in (3e-6, 100e-6, 1e-3):
v = envelope_detector(am, rc, fs)[seg]
d, a1 = thd(v, fs, FM)
V = np.abs(np.fft.rfft(v - v.mean())) * 2 / len(v)
ripple = V[int(FC * len(v) / fs)]
print(f"RC = {rc * 1e6:6.0f} μs:音频幅度 {a1:.3f}(理想 {MU}),总谐波失真 {d * 100:6.2f} %,"
f"100 kHz 纹波幅度 {ripple:.4f}")
fs_bb = 100e3
tb = np.arange(int(10e-3 * fs_bb)) / fs_bb
iq = (1 + MU * np.cos(2 * np.pi * FM * tb)) * np.exp(1j * 0.7) # 复基带 AM,任意载波相位
d, a1 = thd(np.abs(iq), fs_bb, FM)
print(f"数字包络检波 |I + jQ|:音频幅度 {a1:.3f},总谐波失真 {d * 100:.2e} %")
# ---------------- 2. 二极管的检波特性 ----------------
print("\n=== 2. 二极管检波特性:输入 A cos(ωt),负载 100 kΩ ===")
VT = 0.02585 # 热电压 kT/q(300 K)
R_LOAD = 100e3
def detector_dc(A, Is, n=1.0, R=R_LOAD):
"""解 Is[I0(A/nVt) exp(-V/nVt) - 1] = V/R,得到检波输出的直流电压 V。"""
x = A / (n * VT)
def f(V): # A <= 1 V 时 x < 40,np.i0 不会溢出
return Is * (np.i0(x) * np.exp(-V / (n * VT)) - 1) - V / R
lo, hi = 0.0, A
for _ in range(200):
mid = (lo + hi) / 2
lo, hi = (mid, hi) if f(mid) > 0 else (lo, mid)
return (lo + hi) / 2
amps = np.array([0.003, 0.01, 0.03, 0.1, 0.3, 1.0])
for name, Is in (("低阈值二极管 Is = 1e-7 A", 1e-7), ("普通硅二极管 Is = 1e-12 A", 1e-12)):
V = np.array([detector_dc(A, Is) for A in amps])
print(name)
for k, (A, v) in enumerate(zip(amps, V)):
slope = np.log(V[k] / V[k - 1]) / np.log(amps[k] / amps[k - 1]) if k else float("nan")
print(f" A = {A * 1e3:6.0f} mV:输出 {v * 1e3:10.3e} mV,V/A = {v / A:.2e},"
f"对数斜率 {slope:.2f}")
th = np.linspace(0, 2 * np.pi, 4096, endpoint=False)
sq = (1 + 1.0 * np.cos(th)) ** 2 # 平方律检波,μ = 1
S = np.abs(np.fft.rfft(sq)) * 2 / len(th)
print(f"平方律检波 μ = 1:基波 {S[1]:.3f},二次谐波 {S[2]:.3f},二次谐波失真 {S[2] / S[1] * 100:.1f} %(理论 μ/4)")
# ---------------- 3. 矿石机的"能源" ----------------
print("\n=== 3. 100 kW 中波电台,相距 50 km(自由空间估算) ===")
P, d_km, h_eff = 100e3, 50.0, 5.0
E = np.sqrt(30 * P) / (d_km * 1e3) # E = sqrt(30 P G)/d,G = 1
print(f"场强 E = {E * 1e3:.1f} mV/m,有效高度 {h_eff:.0f} m 的天线开路电压 {E * h_eff * 1e3:.0f} mV")
# ---------------- 4. RTL-SDR 的几个数字 ----------------
print("\n=== 4. RTL-SDR ===")
bits, rate = 8, 2.4e6
print(f"{bits} 位 ADC 的理想信噪比 6.02N + 1.76 = {6.02 * bits + 1.76:.1f} dB")
print(f"采样率 {rate / 1e6} MS/s(复数 IQ):可同时看到 {rate / 1e6} MHz 宽的频谱,"
f"2048 点 FFT 的分辨率 {rate / 2048:.1f} Hz,USB 数据率 {rate * 2 / 1e6:.1f} MB/s")
# ---------------- 5. FM 广播的解调 ----------------
print("\n=== 5. FM 广播:频偏 75 kHz,预加重 50 μs,复基带采样率 240 kHz ===")
FS, DEV, TAU = 240e3, 75e3, 50e-6
rng = np.random.default_rng(5)
n = int(0.5 * FS)
t5 = np.arange(n) / FS
audio = 0.6 * np.cos(2 * np.pi * 1000 * t5) + 0.15 * np.cos(2 * np.pi * 6000 * t5)
a = np.exp(-1 / (FS * TAU)) # 单极点去加重 y[n] = a y[n-1] + (1-a) x[n]
def deemphasis(x):
y = np.empty_like(x)
acc = 0.0
for i, xi in enumerate(x):
acc = a * acc + (1 - a) * xi
y[i] = acc
return y
def preemphasis(y):
"""去加重的逆运算(假设 y 在开始之前一直保持 y[0],避免开头出现阶跃)。"""
return (y - a * np.r_[y[0], y[:-1]]) / (1 - a)
def lowpass_taps(cutoff, fs, n_taps=101):
m = np.arange(n_taps) - (n_taps - 1) / 2
h = 2 * cutoff / fs * np.sinc(2 * cutoff / fs * m) * np.hanning(n_taps)
return h / h.sum()
taps = lowpass_taps(15e3, FS)
noise = (rng.standard_normal(n) + 1j * rng.standard_normal(n)) * np.sqrt(0.01 / 2) # 载噪比 20 dB
for use_emph in (False, True):
m = preemphasis(audio) if use_emph else audio
print(f"{'有' if use_emph else '无'}预加重:调制信号峰值 {np.abs(m).max():.3f}(满偏 = 1)")
x = np.exp(2j * np.pi * DEV * np.cumsum(m) / FS)
if use_emph:
X = np.abs(np.fft.fft(x)) ** 2
fr = np.fft.fftfreq(n, 1 / FS)
order = np.argsort(np.abs(fr))
cum = np.cumsum(X[order]) / X.sum()
print(f" 99% 功率带宽 {2 * np.abs(fr[order][np.searchsorted(cum, 0.99)]) / 1e3:.1f} kHz,"
f"卡森公式 2(75 + 6) = 162 kHz")
rx = x + noise
demod = np.angle(rx[1:] * np.conj(rx[:-1])) * FS / (2 * np.pi * DEV) # 相邻样本的相位差 → 频率
if use_emph:
demod = deemphasis(demod)
out = np.convolve(demod, taps, mode="same")[::5] # 低通 15 kHz,抽取到 48 kHz
out = out[2400:2400 + 19200] # 去掉开头 50 ms,取 0.4 s
O = np.abs(np.fft.rfft(out)) * 2 / len(out)
k1, k6 = int(1000 * len(out) / 48e3), int(6000 * len(out) / 48e3)
P = np.abs(np.fft.rfft(out)) ** 2
band = slice(1, int(15e3 * len(out) / 48e3))
sig = P[k1] + P[k6]
print(f" 解调输出:1 kHz 幅度 {O[k1]:.4f}(原 0.6),6 kHz 幅度 {O[k6]:.4f}(原 0.15),"
f"音频信噪比 {10 * np.log10(sig / (P[band].sum() - sig)):.1f} dB")
# ---------------- 6. ADS-B ----------------
print("\n=== 6. ADS-B:1090 MHz,PPM,1 Mbit/s,112 位报文 ===")
MSG = "8D4840D6202CC371C32CE0576098"
GEN = 0x1FFF409 # CRC-24 的生成多项式(25 位)
def hex_to_bits(h):
return np.array([int(b) for b in bin(int(h, 16))[2:].zfill(len(h) * 4)])
def crc24_remainder(bits):
"""对全部 112 位做模 2 除法,余数为 0 表示校验通过。"""
reg = int("".join(map(str, bits)), 2)
for shift in range(len(bits) - 1, 23, -1):
if reg >> shift & 1:
reg ^= GEN << (shift - 24)
return reg
def ppm_waveform(bits, fs=2e6):
"""前导码 8 μs(在 0、1、3.5、4.5 μs 处各一个 0.5 μs 脉冲)+ 每位 1 μs:1 = 先高后低,0 = 先低后高。"""
pre = np.zeros(16)
pre[[0, 2, 7, 9]] = 1
body = np.ravel([[1, 0] if b else [0, 1] for b in bits])
return np.r_[pre, body].astype(float)
CHARSET = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ##### ###############0123456789######"
bits = hex_to_bits(MSG)
print(f"报文 {MSG}:{len(bits)} 位,CRC 余数 = {crc24_remainder(bits):#x}")
template = ppm_waveform(bits)[:16]
for sigma in (0.2, 0.8):
rng = np.random.default_rng(6)
clean = np.zeros(1000)
clean[123:123 + 240] = ppm_waveform(bits)
iq = clean * np.exp(1j * 1.3) + sigma * (rng.standard_normal(1000) + 1j * rng.standard_normal(1000)) / np.sqrt(2)
mag = np.abs(iq) # 接收机只看幅度
corr = np.correlate(mag - mag.mean(), template - template.mean(), mode="valid")
start = int(np.argmax(corr))
data = mag[start + 16:start + 16 + 224].reshape(112, 2)
rx_bits = (data[:, 0] > data[:, 1]).astype(int)
rem = crc24_remainder(rx_bits)
print(f"噪声 σ = {sigma}:前导码位置 {start}(真值 123),误码 {np.sum(rx_bits != bits)} 位,"
f"CRC 余数 {rem:#x},{'校验通过' if rem == 0 else '校验失败,丢弃'}")
df = int("".join(map(str, bits[:5])), 2)
icao = int("".join(map(str, bits[8:32])), 2)
me = bits[32:88]
tc = int("".join(map(str, me[:5])), 2)
callsign = "".join(CHARSET[int("".join(map(str, me[8 + 6 * i:14 + 6 * i])), 2)] for i in range(8))
print(f"DF = {df},ICAO 地址 {icao:06X},类型码 TC = {tc}(身份识别),航班号 "{callsign.strip()}"")
flip = bits.copy()
flip[50] ^= 1
print(f"把第 50 位翻转后,CRC 余数 = {crc24_remainder(flip):#x}(不为 0,错误被检出)")
# ---------------- 画图:FM 解调前后的频谱 ----------------
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11, 4))
Xr = np.fft.fftshift(np.abs(np.fft.fft(rx)) ** 2)
ax1.plot(np.fft.fftshift(np.fft.fftfreq(n, 1 / FS)) / 1e3, 10 * np.log10(Xr / Xr.max()), linewidth=0.5)
ax1.set_xlabel("频率 / kHz")
ax1.set_title("接收到的 FM 信号(复基带)")
ax2.plot(np.fft.rfftfreq(len(out), 1 / 48e3) / 1e3, 20 * np.log10(O + 1e-9), linewidth=0.8)
ax2.set_xlabel("频率 / kHz")
ax2.set_title("解调并去加重后的音频")
for ax in (ax1, ax2):
ax.grid(True, color="#e6e5e1")
fig.tight_layout()
plt.show()
运行后的完整输出:
text
=== 1. 包络检波:载波 100 kHz,音频 1 kHz,调制度 0.8 ===
不产生对角切割失真的条件:RC <= sqrt(1-μ^2)/(μ ω_m) = 119.4 μs;载波周期 10 μs
RC = 3 μs:音频幅度 0.373(理想 0.8),总谐波失真 1.00 %,100 kHz 纹波幅度 0.3907
RC = 100 μs:音频幅度 0.764(理想 0.8),总谐波失真 0.03 %,100 kHz 纹波幅度 0.0302
RC = 1000 μs:音频幅度 0.375(理想 0.8),总谐波失真 36.21 %,100 kHz 纹波幅度 0.0043
数字包络检波 |I + jQ|:音频幅度 0.800,总谐波失真 7.74e-14 %
=== 2. 二极管检波特性:输入 A cos(ωt),负载 100 kΩ ===
低阈值二极管 Is = 1e-7 A
A = 3 mV:输出 2.428e-02 mV,V/A = 8.09e-03,对数斜率 nan
A = 10 mV:输出 2.699e-01 mV,V/A = 2.70e-02,对数斜率 2.00
A = 30 mV:输出 2.434e+00 mV,V/A = 8.11e-02,对数斜率 2.00
A = 100 mV:输出 2.638e+01 mV,V/A = 2.64e-01,对数斜率 1.98
A = 300 mV:输出 1.701e+02 mV,V/A = 5.67e-01,对数斜率 1.70
A = 1000 mV:输出 8.150e+02 mV,V/A = 8.15e-01,对数斜率 1.30
普通硅二极管 Is = 1e-12 A
A = 3 mV:输出 3.370e-07 mV,V/A = 1.12e-07,对数斜率 nan
A = 10 mV:输出 3.776e-06 mV,V/A = 3.78e-07,对数斜率 2.01
A = 30 mV:输出 3.661e-05 mV,V/A = 1.22e-06,对数斜率 2.07
A = 100 mV:输出 9.092e-04 mV,V/A = 9.09e-06,对数斜率 2.67
A = 300 mV:输出 1.238e+00 mV,V/A = 4.13e-03,对数斜率 6.57
A = 1000 mV:输出 5.289e+02 mV,V/A = 5.29e-01,对数斜率 5.03
平方律检波 μ = 1:基波 2.000,二次谐波 0.500,二次谐波失真 25.0 %(理论 μ/4)
=== 3. 100 kW 中波电台,相距 50 km(自由空间估算) ===
场强 E = 34.6 mV/m,有效高度 5 m 的天线开路电压 173 mV
=== 4. RTL-SDR ===
8 位 ADC 的理想信噪比 6.02N + 1.76 = 49.9 dB
采样率 2.4 MS/s(复数 IQ):可同时看到 2.4 MHz 宽的频谱,2048 点 FFT 的分辨率 1171.9 Hz,USB 数据率 4.8 MB/s
=== 5. FM 广播:频偏 75 kHz,预加重 50 μs,复基带采样率 240 kHz ===
无预加重:调制信号峰值 0.750(满偏 = 1)
解调输出:1 kHz 幅度 0.5999(原 0.6),6 kHz 幅度 0.1500(原 0.15),音频信噪比 44.6 dB
有预加重:调制信号峰值 0.944(满偏 = 1)
99% 功率带宽 144.0 kHz,卡森公式 2(75 + 6) = 162 kHz
解调输出:1 kHz 幅度 0.5999(原 0.6),6 kHz 幅度 0.1500(原 0.15),音频信噪比 54.3 dB
=== 6. ADS-B:1090 MHz,PPM,1 Mbit/s,112 位报文 ===
报文 8D4840D6202CC371C32CE0576098:112 位,CRC 余数 = 0x0
噪声 σ = 0.2:前导码位置 123(真值 123),误码 0 位,CRC 余数 0x0,校验通过
噪声 σ = 0.8:前导码位置 190(真值 123),误码 57 位,CRC 余数 0x87706,校验失败,丢弃
DF = 17,ICAO 地址 4840D6,类型码 TC = 4(身份识别),航班号 "KLM1023"
把第 50 位翻转后,CRC 余数 = 0x6159c0(不为 0,错误被检出)