
在上一节《阶段性总结:从 bit 流到 QPSK 点簇——基于 P201Pro 和 GNU Radio 的简单收发系统》中,我们大致走通了从发射端 bit 流到接收端点簇的整条链路,但并没有说明发射端到底怎么把 bit 映射成符号、接收端又怎么把符号解回 bit。
这一节把这两个环节补全:用一组可验证的发送 bit 流,完整走一遍 QPSK 点簇 → 硬判决 → 符号编号 → 串行 bit 流,并解决中间那个最容易被忽略的坑——点簇明明已经收敛得很好,恢复出来的 bit 却是错的。
上一节结束时,接收端已经能把杂乱的星座轨迹收敛成四个清晰的 QPSK 点簇。但点簇只是几何形状,系统还没有输出任何可读的数字信息。要真正完成通信,还需要三步:
| 步骤 | GNU Radio 模块 | 作用 |
|---|---|---|
| 硬判决 | Constellation Decoder | 把带噪的复数采样点判给最近的理想星座点,输出符号编号 |
| 解映射 | Repack Bits | 把每个 QPSK 符号(编号 0~3)按 MSB First 拆成 2 个 bit |
| 相位校正 | Multiply Const | 消除载波相位模糊造成的象限错位 |
要让接收结果可验证,发送的数据就必须是已知的。这里用 MATLAB 把字符串直接转成 bit 流:
info = 'Hello,World'; bitsM = int2bit(double(info), 8); % 每个字符转成 8 bit bits = reshape(bitsM, [], 1); % 拉平成串行 bit 流
转换过程如下:
Hello,World → ASCII : 72 101 108 108 111 44 87 111 114 108 100
→ bit : 01001000 01100101 01101100 01101100 01101111
00101100 01010111 01101111 01110010 01101100
01100100
把上面的 bit 连起来,得到本次实验的发送 bit 流:
0100100001100101011011000110110001101111001011 000101011101101111011100100110110001100100
将这串 bit 流写入 bits.dat,再用 File Source 模块读出来。为了能直接看到发送端到底送出了什么,这里使用一个自研的 bit 流观察 GUI 模块——它可以把发送 bit 显示出来,并附带搜索功能,方便随时核对接收端是否收到了同一串数据。

QPSK 每个符号携带 2 个 bit,因此共有 4 种符号状态,对应星座图上相隔 90° 的四个点。本次实验采用的映射表如下:
| bit 组合 | 符号编号 | QPSK 符号 | 所在象限 |
|---|---|---|---|
00 |
0 | e^(jπ/4) |
I > 0,Q > 0 |
01 |
1 | e^(j3π/4) |
I < 0,Q > 0 |
11 |
3 | e^(j5π/4) |
I < 0,Q < 0 |
10 |
2 | e^(j7π/4) |
I > 0,Q < 0 |

把上一节生成的串行 bit 流每两位一组切开,逐组映射到星座点上,就从一维的 bit 序列得到了二维的复数符号序列:

发射端仍然沿用总结篇中的流图,但做了一处改动:把成型滤波器换成了 RC 滤波器。原因是本次实验两天线距离很近,信号很强,不用匹配滤波器反而显示得更清楚。发射设备为 P201Pro。

接收端同样相对总结篇做了简化:因为发射端未使用 RRC 成形,接收端也就不做匹配滤波。实测效果相当好——星座图上的四个点簇非常干净。


这一节值得单独记一笔,因为它踩了坑:早期使用棒状天线时接收一直很差,一度以为是频偏和定时不对,浪费了不少时间;后来偶然动了一下天线,效果突然变好。再往后为了方便就不再用那根天线,换成另一款之后,接收效果直线上升——也就是上面那种点簇非常干净的状态。
经验:在排查同步算法之前,先确认天线的物理状态(连接、朝向、匹配)。本节的现象说明,一根没接好或选型不合适的天线,会表现得非常像「频偏/定时没调对」,从而把排查方向带偏。




经过 Costas Loop 之后,星座图已经形成四个点簇,但此时输出仍然是带有噪声的复数采样值,并不是可以直接读取的数字符号。Constellation Decoder 的作用,就是把每个接收点判给距离它最近的理想星座点:
ŝ_k = argmin_m | r_k − s_m |²
其中 r_k 是接收点,s_m 是第 m 个理想 QPSK 星座点。对本文使用的映射而言,判决边界就是 I = 0 和 Q = 0:
| 判决区域 | 输出符号 | 对应 bit |
|---|---|---|
| I > 0,Q > 0 | 0 | 00 |
| I < 0,Q > 0 | 1 | 01 |
| I > 0,Q < 0 | 2 | 10 |
| I < 0,Q < 0 | 3 | 11 |

需要注意,Constellation Decoder 实际输出的是符号编号 0~3,并不是串行 bit。因此后面接一个 Repack Bits 模块,按照 MSB First 把每个 QPSK 符号拆成两个 bit,这样才得到真正的串行 bit 流。
接上 Repack Bits 之后,接收 bit 流就出来了。但这里立刻出现了一个问题:接收星座图明明收敛得那么好,可是拿发送的 bit 流去搜索,却完全搜不到。
发送 bit 流(用于搜索比对) 0100100001100101011011000110110001101111001011 000101011101101111011100100110110001100100

原因不是同步没做好,而是 相位旋转。
这就是 QPSK 的载波相位模糊(carrier phase ambiguity)。此时点簇已经稳定,说明载波同步基本完成,但星座象限与 bit 之间的对应关系可能仍然是错的——也就是整体被旋转了 0°、90°、180° 或 270°。旋转后的星座仍然表现为四个清晰点簇,但每个象限代表的 bit 已经改变。
发射端和接收端分别使用各自的本地振荡器,两者的初始相位并不完全一致,同时无线信道和收发硬件也会引入额外相位偏移。因此接收到的 QPSK 信号通常可以表示为:
r_k = s_k · e^(jθ) + w_k
其中 s_k 为发送符号,θ 为收发载波之间的相位差,w_k 为噪声。相位差会使所有星座点围绕原点整体旋转。Costas Loop 可以估计并消除连续的载波相位误差,但 QPSK 星座每旋转 90° 后形状完全相同,因此环路无法只根据星座形状判断哪一个方向才是发送端规定的参考方向,最终可能稳定锁定在四种方向之一:
Δθ = m · π/2 , m = 0, 1, 2, 3
如果此时仍按原来的象限划分去判决,就会产生整块的误判。

解决办法是在 Constellation Decoder 之前加入 Multiply Const,把星座反向旋转到发送端规定的方向。对应关系如下:
| 接收星座偏转 | Multiply Const 取值 |
|---|---|
| 0° | 1 |
| +90° | −1j |
| 180° | −1 |
| −90° | 1j |
由于此时并不知道实际偏移了多少度,就把每种旋转都做一次校正,逐个观察哪种相位补偿能得到正确的 bit 流。校正之后,接收 bit 流与发送完全一致:

到这一步,接收与解映射已经成功,恢复出的 bit 流与发送完全一致。但要注意:这里的相位旋转问题仍然是手动解决的——这一点将放在后续「前导码」的内容里实现自动识别。
收发设备仍然沿用如下结构,收发过程见实测视频:


至此,接收机已经完成了从 QPSK 点簇、符号硬判决到串行 bit 流恢复的完整过程:
射频信号 → 下变频 → QPSK 点簇(Costas Loop)
→ Constellation Decoder(象限硬判决,输出符号 0~3)
→ Multiply Const(相位模糊校正)
→ Repack Bits(MSB First 拆成 2 bit/符号)
→ 串行 bit 流(与发送一致)
当前实验通过手动尝试四种相位方向解决 QPSK 相位模糊,后续将结合前导码完成自动相位识别与校正。这也是从「能收到」走向「能稳定收」的关键一步。