













系列项目主页:https://beautifulzzzz.com/gnuradio/
这部分流程图展示了一个完整的 OFDM 接收机物理层同步与解复用网络。它的核心任务是将天线接收到的连续复采样信号,变成一个个整齐的、频率校准过的、区分了“报头”和“载荷”的数据包。

在无线传输中,信号会经历频率偏移(由于多径或晶振误差)和时间上的不确定性。这个电路的作用就像是一个自动对焦+自动裁剪的相机:
1)功能概述:这是整个系统的“眼睛”
2)原理详解
首先我们参考《[30]. GNU Radio 系列教程(三十)—— OFDM TX 详解:1.1.2 总结该块的功能》,了解到:
实际发送时,先在 64 个子载波上发送 Sync Words 1,然后再发送 Sync Words 2,然后再将 448 字节数据按照 Occupied Carriers 的顺序分配到每个子载波上,不足 48 的整数倍,进行补 0。
Schmidl & Cox 算法的本质是寻找时域上的重复性:
| 条目 | 细则 |
|---|---|
| 巧妙的同步字设计 | 我们在发送端设计的 sync_word1 = [0., 0., ..., 1.414, 0., -1.414, 0., ...] 在频域上是每隔一个子载波填充一个值,奇数位置全是 0。根据 IFFT 的特性,频域上的这种“空位填充”在时域上会产生两个完全相同的半周期(如果在频域每隔一个点取一个值(中间补0),那么在时域就会产生两个完全一样的重复周期)。 |
| 定时同步 | 模块维护一个滑动窗口,计算当前半个符号与后半个符号的互相关 \(P(d)\) 和能量 \(R(d)\): - 度量函数 \(M(d)\):\(M(d) = \frac{|P(d)|^2}{(R(d))^2}\)。 - 表现:当窗口滑到 sync_word1 时,\(M(d)\) 会出现一个明显的“平台区”(由于循环前缀 CP 的存在,平台宽度等于 CP 长度)。该模块通过检测这个平台的下降沿或中心点,在 detect 端口输出触发脉冲。 |
| 频率偏移估算 | - 原理:由于频率偏移的存在,时域上本应完全相同的两部分会产生相位差 \(\phi\)。 - 计算:\(\phi = \text{angle}(P(d))\)。 - 范围:这种方法(细频偏)只能检测到 \(\pm 1\) 子载波间隔内的偏移。如果偏移太大(比如偏了 2 个子载波),相位会发生 \(2\pi\) 翻转,导致失效(这就是为什么还需要 sync_word2 来做粗频偏补偿的原因,但在本模块中主要输出的是细频偏)。 |
备注:假设 \(L\) 是半个 OFDM 符号的长度(在我们 OFDM 的设置中,fft_len=64,所以 \(L=32\))。接收到的采样序列为 \(r(n)\)。\(P(d)\):互相关项 (Correlation),它计算的是“当前窗口”与“\(L\) 个采样前的窗口”之间的相似性:
\[P(d) = \sum_{m=0}^{L-1} (r_{d+m}^* \cdot r_{d+m+L}) \]
\(R(d)\):能量归一化项 (Energy),它计算的是窗口内信号的总能量,用来给 \(P(d)\) 做分母:
\[R(d) = \sum_{m=0}^{L-1} |r_{d+m+L}|^2 \]
3)块参数详解
| 参数名 | 我们的设置值 | 详细说明 |
|---|---|---|
| FFT length | 64 |
决定了处理窗口的大小。 |
| CP length | 16 |
决定了定时度量函数 \(M(d)\) 平台的长度。 |
| Threshold | 0.9 |
当 \(M(d) > 0.9\) 时认为抓到了包。我们的 sync_word1 功率较大且结构清晰,0.9 是合理的。 |
| Use Even Carriers | True |
必须设为 True。因为我们的的 sync_word1 确实只占用了偶数载波。 |
1)功能概述:这是系统的“修正器”
2)原理详解
a. 为什么要进行频率校正?
在无线传输中,由于发射端和接收端的晶振(Oscillator)不可能完全同步,或者由于移动产生的多普勒频移,接收到的信号会带有一个频率偏移 \(f_{off}\)。
在时域上,这意味着每一个采样点 \(n\) 都会比前一个采样点多出一个相位旋转:
\[r(n) = s(n) \cdot e^{j(2\pi \frac{f_{off}}{f_s} n + \theta_0)} \]
其中 \(s(n)\) 是理想信号,\(r(n)\) 是我们收到的带偏信号。如果不校正,进入 FFT 后,子载波之间会发生严重的正交性破坏(ICI),导致星座图变成一圈圆环,无法解调。
b. Frequency Mod 模块:产生“纠偏波”
这个模块的作用是将 S&C 输出的一个标量(频率值)转换成一个随时间旋转的复正弦波。该模块的核心是一个相位累加器,它将输入的标量频偏值 \(x[n]\) 积分还原为随时间演进的相位:
\[y[n] = \exp\left( j \cdot \text{Sensitivity} \cdot \sum_{k=0}^{n} x[k] \right) \]
在工程实现中,通常采用更高效的迭代形式:
\[\phi(n) = \left( \phi(n-1) + x[n] \cdot \text{Sensitivity} \right) \pmod{2\pi} \]
输出补偿波为:\(w(n) = e^{j\phi(n)}\)。
在当前系统(fft_len = 64)中,Sensitivity 设为 -31.25m (即 \(-2/64\)),其逻辑如下:
c. 要加 Delay 80?
d. Multiply(相乘/混频)
1)功能概述:这是系统的“分拣员”。
这个模块是一个状态机,它有三个主要阶段:
trigger 端口收到同步信号。out_hdr。header_data 端口告知包的具体长度),然后将剩余数据送往 out_payload。备注:在默认配置下,如果 Header 解析出错且没有处理机制,这个模块确实会“卡死”或导致后续所有数据包错位。防止这种的处理方式有:header 调制更保守;CRC 校验失败返回一个长度为 0 的消息;
2)参数详细解读
| 参数名称 | 这里设置的值 | 深度解析 |
|---|---|---|
| Header Length (Symbols) | 3 | 定义包头的长度。在收到触发信号后,接下来的 3 个符号(Symbol)会被认为是包头并送入 out_hdr,通常包含包长度信息(sync_word1+sync_word2+真正的 Header (报头数据)) |
| Header Padding | 0 | 这是一个容错参数。由于 Schmidl & Cox 算法探测到的同步位置(平台区)可能会有几个采样点的抖动(不一定次次都卡在最完美的那个点),所以我们需要在这个位置留一点余量。这里设置的 0 表示收到触发后立即开始切 Header,没有任何缓冲 |
| Items per symbol | 64 | 对应 fft_len,告诉模块一个完整的符号包含 64 个有效采样点。 |
| Guard Interval (items) | 16 | 对应 cp_len,模块知道每个符号前有 16 点的 CP,它会自动将其剥离。 |
| Length tag key | packet_len | 极其关键。模块在 out_payload 输出时,会给数据流打上一个标签,告诉后面的模块:“这个包总共有多长”。 |
| Output Format | Symbols | 选择输出的是原始采样点还是已经去除了 CP 的符号数据。这里设为 Symbols(item 选项是 CP+FFT;symbols 选项是 FFT)。 |
| Timing tag key | "rx_time" | 当 trigger 端口收到同步信号的一瞬间,Demux 模块会在输出的第一个采样点上贴一个名为 rx_time 的标签 |
| Sampling Rate | 50k | 当前系统的采样率为 50 kHz。 |
这部分流程图展示了 OFDM 接收机的核心解调与均衡网络。在经过第一部分的同步与分拣后,信号依然处于时域采样点状态,且携带了信道造成的幅度衰落和相位失真。
接下来的处理就像是 “翻译与纠偏”:将时域波形转回频域,并利用已知的“同步字”作为参照物,把被环境扭曲的信号“扶正”。

备注:此阶段分为两条并行的路径:Header(报头)解调路径和 Payload(载荷)解调路径。
所有 OFDM 的魔法都在频域发生。FFT 模块将时域的采样点转换回 64 个子载波上的复数电平值。
这是保证数据正确率最关键的一步。无线电波在空间传输时会产生多径效应,导致某些频率点信号强,某些点信号极弱。
1)OFDM Channel Estimation (仅存在于 Header 路径)
在 OFDM 接收系统中,OFDM Channel Estimation(信道估计)模块是实现相干解调的“定海神针”。它的任务是利用已知的 同步字(Sync Words) 来测量无线信道对信号造成的幅度和相位扭曲,并将这个“扭曲模板”传递给后续的均衡器。
a. 核心工作原理
无线信道就像一个会改变光线颜色和形状的“滤镜”。假设发送信号为 \(X(k)\),接收信号为 \(Y(k)\),信道的影响为 \(H(k)\)(信道传输函数),噪声为 \(N(k)\):
\[Y(k) = X(k) \cdot H(k) + N(k) \]
该模块的核心逻辑非常直接 (最小二乘法 (LS) 估计):既然我们知道发射端发送的 sync_word1 和 sync_word2 是什么,那么通过除法就能算出信道的增益 \(H(k)\):
\[\hat{H}(k) = \frac{Y(k)}{X_{known}(k)} \]
b. 执行流程
sync_word 进行对比,计算出每个子载波的复增益(幅度缩放和相位旋转)。c. 模块参数详解
| 参数名称 | 建议设置/典型值 | 深度解析 |
|---|---|---|
| FFT length | 64 |
必须与系统中所有 OFDM 模块一致。决定了信道向量的长度。 |
| Sync. symbol 1 | sync_word1 |
对应时域同步的那个序列。在频域通常是每隔一个子载波填充(如 \([0, 1.4, 0, -1.4...]\)),用于细频偏校准和初步信道估计。 |
| Sync. symbol 2 | sync_word2 |
通常是全填充的序列(除了保护频带)。它能覆盖所有子载波,提供更完整的信道频率响应。 |
| N Data Symbols | 1 (或更高) |
告诉模块一个 Packet 中包含多少个数据符号。模块需要知道何时停止寻找当前包的信道特征,并等待下一个包。 |
| Max Carrier Offset | 3 |
允许的子载波偏移量。如果你的频率偏差导致信号位移了几个子载波,该模块会在一定范围内尝试平移寻找匹配。 |
| Force One Sync Symbol | No |
若选 Yes,则只用 sync_word1 做估计。通常设为 No 以获取更精准的估计(利用两个同步字取平均或互补)。 |
d. 输出介绍
该模块的输出端不再包含输入的同步符号,仅输出经过筛选的数据符号。
2)OFDM Frame Equalizer (均衡器)
在完成了信道估计(Channel Estimation)并拿到了“药方”(ofdm_sync_eq_taps)后,OFDM Frame Equalizer 就是真正执行“治疗”的模块。
根据 GNU Radio Wiki,这个模块的作用是根据标签中的信道状态信息,对每一个子载波进行复数除法(或乘法),以消除信道引起的幅度和相位畸变。
a. 核心工作原理
均衡器的本质是一个复数乘法器。它并不自己计算信道响应,而是被动地等待输入流中的特定标签(Tags)。
ofdm_sync_eq_taps 标签时,它会提取其中的复数向量(\(Taps\))。\[\hat{X}(k) = Y(k) \cdot Taps(k) \]
由于Taps 向量在 Estimation 模块生成时已经包含了频率校正信息,因此这一步能同时完成信道均衡和粗频偏校正。b. 参数详解
| 参数名称 | 我们的设置值 | 深度解析 |
|---|---|---|
| FFT length | 64 |
定义了处理的频域宽度。 |
| CP length | 16 |
循环前缀长度。虽然该模块处理的是频域数据,但在计算符号间的时间关系时仍需此参数。 |
| Equalizer | <gnuradio...> |
核心算法对象。这通常指向一个 Python 脚本定义的均衡器类(如 ofdm_equalizer_1d_pilots)。它决定了如何处理导频(Pilots)以及如何更新信道估计值。 |
| Length Tag Key | packet_len |
告诉模块哪个标签定义了数据包的长度,以便模块知道一个 Frame 在哪里结束。 |
| Propagate Channel State | Yes |
极其关键。设为 Yes 时,模块在处理完当前包后,会将最后的信道状态传递给下一个包。这在连续传输且信道变化缓慢时非常有用。 |
| Fixed frame length | 1 (Header) / 0 (Payload) |
如果设为非 0,模块将忽略长度标签,强制处理固定数量的符号。在 Header 路径中通常设为固定值,因为 Header 长度是已知的。 |
c. 执行流程与逻辑
ofdm_sync_eq_taps,它就更新内部的“补偿矩阵”。packet_len)都会透传到输出端,保证后续模块正常工作。d. Header 与 Payload 路径的区别
Estimation 模块获取 eq_taps,只负责把那 3 个 Header 符号变清晰,以便解出 packet_len。Propagate Channel State: Yes 时,意味着它会利用 Header 路径摸清的“路况”来解调庞大的数据主体。总结: OFDM Frame Equalizer 是接收机中的“执行官”。它拿到了 Estimation 模块提供的“地图”,把被多径效应扭曲得面目全非的星座点,一个个精准地拉回到标准位置。
OFDM 符号中有 64 个子载波,但并不是所有子载波都带数据(有些是保护频带,有些是直流分量)。
1)核心工作原理
在经过 FFT 和均衡后,数据是以 64 个子载波并行的复数矩阵形式存在的。但实际上,这 64 个点中只有一部分是真正的数据(Occupied Carriers),其余的是保护频带或导频。
Serializer 的动作逻辑如下:
2)参数详解
| 参数名称 | 我们的设置值 | 深度解析 |
|---|---|---|
| FFT length | 64 |
告诉模块输入数据的“宽度”是多少,必须与系统 FFT 大小一致。 |
| Occupied Carriers | [[-26,-1],[1,26]] |
映射字典。定义了哪些位置是数据。例如该设置表示取索引 -26 到 -1 和 1 到 26 的子载波。 |
| Length Tag Key | frame_len |
符号包长标签。模块靠这个标签知道一个“帧”包含多少个 OFDM 符号。它以此决定何时重置处理逻辑。 |
| Packet Length Tag Key | packet_len |
字节包长标签(可选)。如果提供,它会将这个描述“字节长度”的标签透传到输出流,方便后续的 Packet Decoder 使用。 |
| Symbols skipped | 0 (H) / 1 (P) |
跳过符号数。在 Payload 路径通常设为 1,因为要跳过紧随同步字后的那个 Header 符号,直接从第一个 Payload 符号开始提取数据。 |
下面分别是 header 路和 payload 路的配置:

header 路在经过 FFT -> 信道估计 -> OFDM 帧均衡 -> OFDM 序列化之后,送入星座解调,然后送入 header 解析器,解析出头部信息:

header_mod = digital.constellation_bpsk() -> header_mod.base() => 星座解调对象header_formatter.base() => 格式化对象其中 header_formatter 参数有点多,这里做一个简单介绍:
# 定义报头格式对象
header_formatter = digital.packet_header_ofdm(
occupied_carriers=occupied_carriers,
n_syms=1, # 报头占 1 个符号
len_tag_key=packet_length_tag_key, # 这里的标签名要和 Demux 里的 Length Tag Key 一致
frame_len_tag_key=length_tag_key, # 物理层符号长度标签
bits_per_header_sym=header_mod.bits_per_symbol(), # Header 用 BPSK
bits_per_payload_sym=payload_mod.bits_per_symbol(), # 告诉接收机 Payload 是 QPSK
scramble_header=False
)
将 header_data 信息打印如下:
******* MESSAGE DEBUG PRINT ********
((frame_len . 9) (packet_num . 76) (packet_len . 400) (ofdm_sync_chan_taps . #[(0,0) (0,0) (0,0) (0,0) (0,0) (0,0) (4.43853,-0.882878) (2.87094,-3.49825) (-1.90735e-07,-4.52548) (-2.87094,-3.49825) (-4.43853,-0.882878) (-3.99112,2.1333) (-1.73183,4.181) (1.31368,4.33062) (3.7628,2.51422) (4.50369,-0.443575) (3.2,-3.2) (0.443575,-4.50369) (-2.51422,-3.7628) (-4.33062,-1.31368) (-4.181,1.73183) (-2.1333,3.99112) (0.882878,4.43853) (3.49825,2.87094) (4.52548,1.90735e-07) (3.49825,-2.87094) (0.882878,-4.43853) (-2.1333,-3.99112) (-4.181,-1.73183) (-4.33062,1.31368) (-2.51422,3.7628) (0.443575,4.50369) (0,0) (4.50369,0.443575) (3.7628,-2.51422) (1.31368,-4.33062) (-1.73183,-4.181) (-3.99112,-2.1333) (-4.43853,0.882878) (-2.87094,3.49825) (-2.38419e-07,4.52548) (2.87094,3.49825) (4.43853,0.882878) (3.99112,-2.1333) (1.73183,-4.181) (-1.31368,-4.33062) (-3.7628,-2.51422) (-4.50369,0.443575) (-3.2,3.2) (-0.443575,4.50369) (2.51422,3.7628) (4.33062,1.31368) (4.181,-1.73183) (2.1333,-3.99112) (-0.882878,-4.43853) (-3.49825,-2.87094) (-4.52548,1.90735e-07) (-3.49825,2.87094) (-0.882878,4.43853) (0,0) (0,0) (0,0) (0,0) (0,0)]) (rx_time . {1 0.4606}) (ofdm_sync_carr_offset . 0))
************************************
最终这个 header_data 送给了 Header/Payload Demux,然后实施 payload 切割。

payload 路在经过 FFT -> OFDM 帧均衡 -> OFDM 序列化之后,送入星座解调,然后借助 Repack Bits 进行格式转换成字节流,接着实施 CRC32 校验,最终出完整的 payload 数据:
----------------------------------------------------------------------
Tag Debug: Rx Bytes
Input Stream: 00
Offset: 7104 Source: n/a Key: packet_num Value: 75
Offset: 7104 Source: n/a Key: rx_time Value: {1 0.4414}
Offset: 7104 Source: n/a Key: ofdm_sync_carr_offset Value: 0
Offset: 7104 Source: n/a Key: ofdm_sync_chan_taps Value: #[(0,0) (0,0) (0,0) (0,0) (0,0) (0,0) (4.43853,-0.882878) (2.87094,-3.49825) (-2.88727e-08,-4.52548) (-2.87094,-3.49825) (-4.43853,-0.882878) (-3.99112,2.1333) (-1.73183,4.181) (1.31368,4.33062) (3.7628,2.51422) (4.50369,-0.443575) (3.2,-3.2) (0.443575,-4.50369) (-2.51422,-3.7628) (-4.33062,-1.31368) (-4.181,1.73183) (-2.1333,3.99112) (0.882878,4.43853) (0.349825,0.287094) (0.452548,-1.16849e-08) (3.49825,-2.87094) (0.0882877,-0.443853) (-0.21333,-0.399112) (-0.4181,-0.173183) (-0.433062,0.131368) (-0.251423,0.37628) (0.0443574,0.450369) (0,0) (0.450369,0.0443574) (0.37628,-0.251422) (0.131368,-0.433062) (-0.173183,-0.4181) (-0.399112,-0.21333) (-0.443853,0.0882879) (-2.87094,3.49825) (-1.17297e-08,0.452549) (0.287094,0.349825) (0.443853,0.0882877) (0.399112,-0.21333) (0.173183,-0.4181) (-0.131368,-0.433062) (-0.37628,-0.251423) (-0.450369,0.0443575) (-0.32,0.32) (-0.0443575,0.450369) (0.251423,0.37628) (0.433062,0.131368) (0.4181,-0.173183) (2.1333,-3.99112) (-0.0882877,-0.443853) (-0.349825,-0.287094) (-0.452549,-9.96802e-08) (-0.349825,0.287094) (-0.0882879,0.443853) (0,0) (0,0) (0,0) (0,0) (0,0)]
Offset: 7104 Source: n/a Key: packet_len Value: 96
----------------------------------------------------------------------
整个流程图如下:(将发送和接收全部展开,这样形成了一个巨大的流程图)

注意: 其中 OFDM Receiver 就是我们整个接收流程图的合并版(实现功能一样);当然整个发送流程图也有对应的合并版本,由于实现的功能一模一样,因此这里不做过多介绍。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。