从电磁波到比特:用具体数字读懂 FM 与 WiFi 的 IQ 解调
一篇面向 SDR 初学者与爱好者的文章。通篇用"具体数字 + 手算"代替抽象公式,把 FM 收音、I/Q、PCM,以及 WiFi beacon / QAM / 5G 这几件看似不相干的事,串成同一条主线:原始 IQ 是通用货币,解调是把这串数字翻译成含义。
摘要
软件定义无线电(SDR)最常见的困惑是:明明天线收到了信号,为什么还要额外的"解码器"才能听懂?本文以一个 HackRF 为例,从最基本的"频率是什么"讲起,用一组傻瓜数字手算:
- FM 发射端如何把音频变成电磁波(调制);
- 接收端如何把 IQ 样本变回声波(解调、PCM);
- WiFi 的 beacon 为何能被"听"出来,以及它和 FM 的本质异同;
- 一个 IQ 点
(0.8, 0.2)在 BPSK 与 16-QAM 下为何解出完全不同的比特; - 5G 为何是同一套思想的翻版;
- 最关键的一点:HackRF 是"哑"的,它只产 IQ,从不解析协议。
读完你应能建立一句核心直觉:IQ 文件 ≈ 原始采样流(像 PCM/WAV),但解码出的产物取决于你用什么协议去解释它。
一、先建立直觉:频率、混频与 I/Q
1.1 频率是什么
一个正弦波 s(t) = cos(2π f t)。频率 f 决定"每秒转多少圈"。相位 φ(t) = 2π f t 是转过的总角度;频率就是相位对时间的导数:
f(t) = (1 / 2π) · dφ/dt
记住这句:"频率 = 相位变化有多快"。后面 FM 调制与解调全靠它。
1.2 本振(LO)与混频:把"中心频点搬到 0"
真实电台在 100 MHz 这种高频,ADC 采样不了那么快。SDR 的做法是用一个本振(LO) 产生同频余弦/正弦,与接收信号相乘(混频),把高频"搬"到基带(中心频点附近 ≈ 0 Hz)。
- 混频后得到 I(同相,乘 cos) 和 Q(正交,乘 sin) 两路。
- 复数基带信号就是
x = I + jQ,这就是 IQ 样本。
HackRF 每采样一次,就吐出一对 (I, Q) 浮点数。这一对数字就是后面一切的起点。
1.3 负频率为啥存在
基带里会出现"负频率",它只是复数表示下的数学产物:一个旋转向量顺时针转,频率记负;逆时针转记正。对理解解调不碍事,记住"IQ 平面上的一个点 = 当前这一刻的波形状态"即可。
二、FM 调制:音频如何"爬"上载波(发)
2.1 核心公式
调频(FM)的本质:瞬时频率随音频 m(t) 上下摆动。
f_inst(t) = f_c + k · m(t)
f_c:载波中心频率(如 103 MHz)k:灵敏度m(t):音频,范围约[-1, +1]
因为频率是相位的导数,对时间积分得到相位:
φ(t) = 2π f_c t + 2π k ∫ m(t) dt
s(t) = cos( φ(t) ) # 这就是 antennae 上发出的电磁波(基带复数形式)
关键直觉 :音频 m(t) 是 sin,进到相位里变成 -β·cos(sin 积分是 -cos)。这就是"音频的瞬时值影响频率大小"的数学来源。
2.2 手算示例(常量音频,看疏密)
为便于手算,下面把"频率"定义为每采样步推进的圈数(cycles/sample) ,不是 Hz。设 f_c = 0.15、k = 0.05。
- 音频
m = +1→f = 0.20圈/步 → 每步相位前进360° × 0.20 = 72° - 音频
m = -1→f = 0.10圈/步 → 每步前进36°
从相位 0 开始累加,算波形 s = cos(φ):
| 步 | 音频 m | 每步相位前进 | 累计相位 φ | 波形 s = cos φ |
|---|---|---|---|---|
| 0 | +1 | 72° | 0° | 1.000 |
| 1 | +1 | 72° | 72° | 0.309 |
| 2 | +1 | 72° | 144° | -0.809 |
| 3 | +1 | 72° | 216° | -0.809 |
| 4 | +1 | 72° | 288° | 0.309 |
| 5 | +1 | 72° | 360° | 1.000 ← 5 步一圈(密) |
同一个表,但音频 m = -1(每步只前进 36°):
| 步 | 音频 m | 每步相位前进 | 累计相位 φ | 波形 s = cos φ |
|---|---|---|---|---|
| 0 | -1 | 36° | 0° | 1.000 |
| 1 | -1 | 36° | 36° | 0.809 |
| ... | -1 | 36° | ... | ... |
| 10 | -1 | 36° | 360° | 1.000 ← 10 步一圈(疏) |
结论:音频大 → 频率高 → 波形一圈只用 5 步(密);音频小 → 频率低 → 一圈用 10 步(疏)。"调制"就是把声音的"大小"翻译成电波的"疏密"。
2.3 正弦音频的逐步手算
让音频是个正弦 m = [0, +0.7, +1.0, +0.7, 0, -0.7, -1.0, -0.7],仍用 f_c=0.15, k=0.05:
| 步 | 音频 m | 瞬时频率 f=0.15+0.05m | 每步相位前进(=360f) | 累计相位 | 波形 cos(累计) |
|---|---|---|---|---|---|
| 0 | 0.0 | 0.150 | 54.0° | 54° | 0.588 |
| 1 | +0.7 | 0.185 | 66.6° | 120.6° | -0.515 |
| 2 | +1.0 | 0.200 | 72.0° | 192.6° | -0.976 |
| 3 | +0.7 | 0.185 | 66.6° | 259.2° | -0.190 |
| 4 | 0.0 | 0.150 | 54.0° | 313.2° | 0.687 |
| 5 | -0.7 | 0.115 | 41.4° | 354.6° | 0.997 |
| 6 | -1.0 | 0.100 | 36.0° | 390.6° | 0.771 |
| 7 | -0.7 | 0.115 | 41.4° | 432.0° | 0.809 |
数一数波形的"摆动快慢":第 1~3 步(音频为正,频率 0.185~0.200)相位每步跳 ~70°,波形猛变 → 频率高;第 5~6 步(音频为负,频率 0.10~0.115)相位每步只跳 ~40°,波形慢变 → 频率低。
2.4 一张图看疏密
把上面这种波形画出来(蓝线是电波 s(t),橙线是音频 m(t)):

盯着看:橙线往上爬(音频大)时,蓝线挤成一簇一簇(密 = 频率高);橙线往下掉(音频小/负)时,蓝线拉开变疏(频率低)。这就是 FM 调制的全部秘密。
三、FM 解调:IQ 如何变回音频(收)
接收端先把射频下变频成复数基带 x = cos φ + j sin φ,然后解调。核心模块 quadrature_demod_cf 干的事,就是下面这行:
Δφ[n] = arg( x[n] · conj(x[n-1]) ) # 相邻样本的相位差
音频 = Δφ[n] × gain
conj(x[n-1]) 是取共轭;两复数相乘的辐角 恰好等于"从 xn-1 转到 xn 转过的角度",即相位差。arg 就是 atan2。
3.1 手算:相邻样本相位差 → 音频
用第 2.3 节那组 IQ(取 I=cosφ, Q=sinφ),对每对算 x[n]·conj(x[n-1]) 再取 arg:
例 n=2:x[2]=(cos120.6°, sin120.6°)=(−0.515, 0.857),x[1]=(0.588, 0.809) 的共轭 (0.588, −0.809)
乘积 = (−0.515·0.588 − 0.857·(−0.809)) + j(0.857·0.588 − (−0.515)·0.809)
= (−0.303 + 0.693) + j(0.504 + 0.417)
= (0.390, 0.921)
arg = atan2(0.921, 0.390) ≈ 67° → 音频为正 ✓(对应 m=+0.7 附近)
全部结果(节选过渡):
| 过渡 | arg(Δφ) | 还原的音频趋势 |
|---|---|---|
| 0→1 | +67° | 正(音频上升) |
| 1→2 | +72° | 正(峰值) |
| 2→3 | +67° | 正 |
| 3→4 | +54° | 回落到 0 |
| 4→5 | +41° | 负(音频转负) |
| 5→6 | +36° | 负(谷值) |
还原出的相位差序列,和你发射前喂进去的音频 m 完全同向------这就是解调。
3.2 相位差 → 频率 → PCM(真实参数)
上面是玩具"圈/步",现在接回真实 FM 链路。设:
- 采样率
samp_rate = 240000Hz(resampler 输出的 quad rate) - 最大频偏
max_dev = 75000Hz - 灵敏度
k = 75000(PCM=±1 时频偏 ±75k)
两向公式:
发: Δf = k · m (m 即归一化 PCM,范围 ±1)
Δφ = 2π · Δf / samp_rate
收: Δf = Δφ · samp_rate / 2π
m = Δf / k
取音频 PCM m = [0, +0.5, +0.5, 0, −0.5, −0.5, 0],算成数字:
| 音频 PCM m | 频偏 Δf=75000·m | 相位差 Δφ=2π·Δf/240k | 收端还原 Δf | 还原 m | 16-bit PCM=round(m·32767) |
|---|---|---|---|---|---|
| 0 | 0 Hz | 0° | 0 Hz | 0.0 | 0 |
| +0.5 | +37500 Hz | +56.25° | +37500 Hz | +0.5 | +16383 |
| +0.5 | +37500 Hz | +56.25° | +37500 Hz | +0.5 | +16383 |
| 0 | 0 Hz | 0° | 0 Hz | 0.0 | 0 |
| −0.5 | −37500 Hz | −56.25° | −37500 Hz | −0.5 | −16383 |
| −0.5 | −37500 Hz | −56.25° | −37500 Hz | −0.5 | −16383 |
| 0 | 0 Hz | 0° | 0 Hz | 0.0 | 0 |
那个 gain 现在也对上了:quadrature_demod_cf 的增益 gain = samp_rate/(2π·max_dev) = 240000/(2π·75000) ≈ 0.5093。
还原 PCM m = Δφ(弧度) × 0.5093 = (2π·37500/240000) × 0.5093 = 37500/75000 = +0.5 ✓
3.3 音频即 PCM:float 与 int16
解调出来的"音频数据"就是 PCM------一串按采样时刻排列的振幅数字。
- GNU Radio 的
audio_sink吃[-1, 1]的 float PCM; .wav文件里存[-32768, 32767]的 int16 PCM,就是 float 乘以 32767;- 所以"音频数据" = 这串 PCM 数字。
完整闭环:
[发射] WAV(int16) ─/32767→ m(PCM ±1) ─FM调制→ IQ → 电波
[接收] 电波 → IQ ─quadrature_demod(gain)→ m(PCM ±1) ─×32767→ WAV(int16)
四、音色与音高:同一结论的两个视角
两个人音高相同、音色不同,频谱图一看就懂:

- 200 Hz 那根基频杆一样高 → 基频相同 → 大脑判"音高一样"(音高 = 基频位置)。
- 400/600 Hz 谐波杆高度不同 → 音色不同(音色 = 谐波的相对能量,即波形包络)。
时域看"由哪些频率组成摆动"(音色),频域看"这些频率怎么随时间摆"(音高+音色),FM 把时域摆动原样搬上载波、解调再原样搬回。
五、转到 WiFi:同样的 IQ,不同的故事
5.1 频谱监听与 beacon
HackRF 调到 2.4 GHz(如信道 6 = 2.437 GHz),采样率 20 MHz(刚好罩住一个 20 MHz WiFi 信道),用 QT GUI 的频谱+瀑布图就能看到 WiFi 活动:突发传输在瀑布图里是一条条亮纹。beacon 是 AP 明文广播的管理帧(SSID 就写在里面)。
5.2 IQ 是原料,不携带调制标签
你存的 IQ 文件(wifi_iq.cfile)只是长串 (I, Q) 数字,不记录用了哪种调制。它和 raw PCM/WAV 是同一类东西------都是"还没解码的采样流"。调制方式既不写在文件里,HackRF 也不知道。
5.3 WiFi 数据不是 PCM,是比特/帧
| FM 音频 | WiFi | |
|---|---|---|
| 原始 IQ 文件 | 复数采样流 | 复数采样流(同一类原料) |
| 解调第一脚 | 相位差 → 频率 | 同步 → FFT(OFDM) → 均衡 |
| 一个采样代表啥 | 频率偏移 ∝ 一个音频振幅(PCM) | 星座点(QAM) ∝ 几个比特 |
| 解码最终输出 | PCM 数字(声音振幅) | 比特 / 802.11 帧(SSID 等) |
WiFi 用 QAM 星座把比特映射到 IQ 平面。以 QPSK 为例,一个符号(一个 IQ 点)携带 2 比特:
(0.707, 0.707) → 00 (−0.707, 0.707) → 01
(−0.707,−0.707) → 11 (0.707,−0.707) → 10
FM:一个 IQ 样本 → 一个音频振幅数字(PCM);WiFi:一个 IQ 符号 → 几个比特。 根本差别在这。
六、WiFi 怎么知道用哪种调制:前导码 + SIGNAL + 自适应
6.1 帧结构(802.11a/g OFDM)
┌──────────┬──────────┬───────────────────┐
│ 前导码 │ SIGNAL │ 数据正文 │
│ STF+LTF │ 字段 │ (按字段里的速率解) │
└──────────┴──────────┴───────────────────┘
已知序列 固定 BPSK 变化(QPSK/16-QAM/64-QAM)
(同步/估信道) (速率/长度)
- 前导码(STF+LTF):收发双方约定好的已知序列,用于同步、信道估计,是"标尺",不携带速率。
- SIGNAL 字段 :固定用 BPSK、编码率 1/2,里面写着"本帧正文用哪种调制、多长"。
6.2 为什么 SIGNAL 必须固定 BPSK
帧一开始,接收端还不知道这帧的调制。所以"速率说明"本身必须用一种双方永远约定好、无需先验知识的最稳调制来发 → BPSK。否则接收端连"该用什么调制解 SIGNAL"都不知道,会死锁。
6.3 具体例子:(0.8, 0.2) 用 BPSK 解 vs 用 16-QAM 解
假设解码器同步好,知道这是 SIGNAL 的一个 BPSK 符号。BPSK 星座只有 (+1,0) 与 (-1,0)。收到 (0.8, 0.2) 是带噪声的 (+1,0):
到 (+1, 0):√((0.8−1)² + (0.2−0)²) = √(0.04+0.04) = √0.08 ≈ 0.283
到 (−1, 0):√((0.8+1)² + 0.2²) = √(3.24+0.04) = √3.28 ≈ 1.811
近 (+1,0) → 解出 bit = 0。
同一个 (0.8, 0.2),若这帧正文是 16-QAM(星座 16 点,坐标在 ±0.316、±0.949 附近):
离 (0.8,0.2) 最近的是 (0.949, 0.316) → 对应某个 4-bit 模式(如 1010)
于是 (0.8, 0.2) 在 16-QAM 下解出 4 个比特,不是 1 个。

用哪种调制解 (0.8, 0.2) |
得到 |
|---|---|
| BPSK | 1 比特:0 |
| 16-QAM | 4 比特:1010(举例) |
这就是为什么调制必须先在 SIGNAL 里"自报":解码器先老实用 BPSK 解 SIGNAL,读到"正文用 16-QAM",才切换过去解正文。
七、5G 也如此:换名字,同原理
5G NR 完全同一思想,只是更名、更复杂:
| WiFi (802.11) | 5G NR | 作用 |
|---|---|---|
| 前导码 STF/LTF | PSS / SSS | 小区搜索、同步 |
| SIGNAL 字段(BPSK) | PBCH(在 SSB 内,QPSK) | 发 MIB:最基础系统参数 |
| 数据正文(自适应 QPSK→64-QAM) | PDSCH(自适应 QPSK→256-QAM) | 真正数据 |
| (速率在 SIGNAL 写明) | DCI(PDCCH 上) | "你这段数据用 256-QAM、占哪些资源块" |
5G 的"beacon" = SSB(Synchronization Signal Block),周期广播,里面含 PSS+SSS+PBCH。PBCH 用固定 QPSK 发 MIB,手机先读到 MIB,才知道后续怎么收。之后正文按信道质量自适应选 QPSK / 16 / 64 / 256-QAM------和 WiFi 自适应调制一模一样。
不同点:5G 在授权频段(sub-6G、甚至 24G+ 毫米波),HackRF 撑不住其带宽与 mmWave;结构上有可变子载波间隔、波束赋形,复杂得多。
八、谁来决定"怎么解析":HackRF 是哑的,软件才懂
回到最常见的误解:HackRF 不知道、也不决定用什么解析 beacon。
| 角色 | 干什么 | 知道调制吗 |
|---|---|---|
| 你(操作员) | 把 HackRF 设到 2.437G/20M 存 IQ;再选"用 802.11 解码器" | 你告诉软件"当 WiFi 解" |
| HackRF(硬件) | 调频 + 混频 + 采样 → 输出 I/Q | 完全不知道,不关心 |
| gr-ieee802-11(软件) | 跑 802.11 PHY,解帧 | 逐帧从 SIGNAL 字段自己读出 |
- HackRF 只把波形点原样记成
(0.8, 0.2)(这是采样不是解析)。 - "这是 WiFi"是你告诉软件的(你跑的是 802.11 解码器,不是 FM 解调器)。
- 软件也不是开头就知道每帧调制------它先拿已知前导码同步,再用固定 BPSK 读 SIGNAL,才知道正文调制。
类比:HackRF 是麦克风/录音笔(录下波形,听不懂语言);gr-ieee802-11 是懂 802.11 的翻译官(你请它来,它按 WiFi 语法翻译成文字)。麦克风永远不会自己说"这是 WiFi"。
九、动手实验与踩坑(真实可复现)
用 HackRF + GNU Radio(gr-soapy)可完整复现上述链路,几个关键经验:
- FM 接收端 :
soapy_hackrf_source(1M) →rational_resampler_ccc(25/6 → 240k) →analog_wfm_rcv(240k/5 → 48k) →audio_sink。频率用variable_qtgui_range做成滑块(88--108 MHz)。 - FM 发射端 :
wav_source(48k) →analog_wfm_tx(→240k) →rational_resampler_ccc(25/3 → 2M) →soapy_hackrf_sink(2M @ 103 MHz)。 - 踩坑(重要) :gr-soapy 的 HackRF 发射在 1 MHz 采样率下会持续
Soapy sink error: TIMEOUT------这是 soapy 封装层在 1M 不把 TX key 起的 bug,与硬件无关(hackrf_transfer在 1M 正常)。解决办法:把发射采样率提到 2 MHz 即可 ,接收端仍可用 1M。另需给 sink 的stream_args加buffers=64(gr-soapy 的.grc块不暴露该参数,需改生成的.py)。 - WiFi 监听 :
soapy_hackrf_source(20M @ 2.437G, LNA 增益 ~32) →qtgui_sink_x(频谱+瀑布) +file_sink(存wifi_iq.cfile)。用blocks_head限制样本数可实现"采 N 秒自动停"。 - WiFi 解码 :
wifi_iq.cfile交给gr-ieee802-11(需另行编译安装),其wifi_phy块读 IQ → 解出 beacon 的 SSID/BSSID。
合法边界:接收/观测 ISM 频段与广播 beacon、SSB/MIB 属被动频谱观察;解密他人加密流量、deauth 攻击、握手包破解等不在本文范围。
十、结语:IQ 是通用货币
把全篇收成一句:
- IQ 样本 = 电磁波在基带的原始采样,和 PCM/WAV 一样是"一串数字",但它不标协议、不标调制。
- FM 解调:IQ 的相位差 → 频率 → PCM(一串能播放的振幅数字)。
- WiFi 解码:IQ 的星座点 → 最近调制点 → 比特 → 帧(能解析的文字字段,不是声音)。
- 谁懂:HackRF 只产 IQ(哑);软件解码器按协议规范,从帧头固定的已知/稳健调制(BPSK/QPSK)逐帧读出后续调制,才解得对。
无论 FM、WiFi 还是 5G,链路都是"哑硬件采 IQ → 智能软件按协议解释" 。你存的每一份 cfile,都是同一类原料;最后长出声音还是长出 beacon,取决于你请来哪位"翻译官"。
参考配图(与本文同目录)
fm_modulated_wave.png--- FM 调制波形:音频(橙)与电波疏密(蓝)fm_spectrum_compare.png--- 音高相同、音色不同的频谱对比constellation_bpsk_16qam.png--- 同一 IQ 点(0.8,0.2)在 BPSK 与 16-QAM 下解出不同比特
参考实现
- GNU Radio 3.10 + gr-soapy(
soapy_hackrf_source/soapy_hackrf_sink) gr-ieee802-11(Bastian Bloessl):802.11 接收机,用于从 IQ 文件解码 beaconhackrf_transfer:原生 libhackrf 工具,用于发射自检(验证硬件正常)