[30]. GNU Radio 系列教程(三十)—— OFDM RX 详解 视频 GitHub 码云 | 2026-03-28 11:25 | 阅读: 70 ## 一、定时同步、细频率补偿与载荷提取 这部分流程图展示了一个**完整的 OFDM 接收机物理层同步与解复用网络**。它的核心任务是将天线接收到的连续复采样信号,变成一个个整齐的、频率校准过的、区分了“报头”和“载荷”的数据包。 ![][p1] 在无线传输中,信号会经历频率偏移(由于多径或晶振误差)和时间上的不确定性。这个电路的作用就像是一个**自动对焦+自动裁剪**的相机: - **自动对焦**:修正频率偏移,让星座图不再旋转 - **自动裁剪**:在一长串噪声和信号中,精准地把属于数据包的那一段“剪”出来,并分给后续的解码器 </br> ### 1.1 同步与频率估算层 (Schmidl & Cox OFDM synch) **1)功能概述**:这是整个系统的“眼睛” - **Schmidl & Cox 模块**:利用特定的训练序列(前导码)。它在信号流中寻找两个完全相同的半周期。 - **Output 0 (freq_offset)**:计算出信号偏了多少 Hz,输出一个比例值。 - **Output 1 (detect)**:一旦抓到同步序列的结尾,发出一声“哨响”(触发脉冲)。 - **Delay (延时器)**:数值为 **80**(即 $64$ FFT + $16$ CP)。 - 原理:S&C 模块需要处理完一整个符号才能给出结果。为了让后面校正频率时,“校正信号”能对准“原始信号”的开头,必须把原始信号拉后 80 个采样点。 </br> **2)原理详解** 首先我们参考《[[30]. GNU Radio 系列教程(三十)—— OFDM TX 详解:1.1.2 总结该块的功能][#1]》,了解到: > 实际发送时,先在 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**。</br>根据 IFFT 的特性,频域上的这种“空位填充”在时域上会产生**两个完全相同的半周期**(<u>如果在频域每隔一个点取一个值(中间补0),那么在时域就会产生两个完全一样的重复周期</u>)。 **定时同步** | 模块维护一个滑动窗口,计算当前半个符号与后半个符号的**互相关 $P(d)$** 和**能量 $R(d)$**:</br>- **度量函数** $M(d)$:$M(d) = \frac{\|P(d)\|^2}{(R(d))^2}$。</br>- **表现**:当窗口滑到 sync_word1 时,$M(d)$ 会出现一个明显的“平台区”(由于循环前缀 CP 的存在,平台宽度等于 CP 长度)。该模块通过检测这个平台的下降沿或中心点,在 detect 端口输出触发脉冲。 **频率偏移估算** | - **原理**:由于频率偏移的存在,时域上本应完全相同的两部分会产生相位差 $\phi$。</br> - **计算**:$\phi = \text{angle}(P(d))$。</br>- **范围**:这种方法(细频偏)只能检测到 $\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})$$ - **直观理解**:把接收到的信号复制一份,往后推 32 个点,然后把重叠的部分对应相乘并累加。 - **物理意义**:如果正好收到了 sync_word1,因为前 32 点和后 32 点是一样的,$P(d)$ 就会产生一个巨大的复数向量。这个向量的相位(Angle)就代表了频偏。 $R(d)$:能量归一化项 (Energy),它计算的是窗口内信号的总能量,用来给 $P(d)$ 做分母: $$R(d) = \sum_{m=0}^{L-1} |r_{d+m+L}|^2$$ - **注意**:在 GNU Radio 文档中,为了防范突发信号结束时的伪触发,它改进为计算 **整个符号(两个半段)** 的平均能量。 </br> **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` 确实只占用了偶数载波。 | </br> ### 1.2 频率校正层 (Freq Mod & Multiply) **1)功能概述**:这是系统的“修正器” - **Frequency Mod**:将 S&C 算出的频偏数值转换成一个不断旋转的复数波形(相控信号)。 - **Multiply**:将延时后的原始信号与这个反向旋转的波形相乘。 - 效果:抵消频偏,输出频率已经“放正”的基带信号。 </br> **2)原理详解** <u>**a**. 为什么要进行频率校正?</u> 在无线传输中,由于发射端和接收端的**晶振(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)**,导致星座图变成一圈圆环,无法解调。 </br> <u>**b**. Frequency Mod 模块:产生“纠偏波”</u> 这个模块的作用是将 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$),其逻辑如下: - **缩放因子**:S&C 模块输出的细频偏 $x[n]$ 是归一化值。为了将其还原为每个采样点的弧度增量,需要乘以校正系数。 - **负号的物理意义**:S&C 测量的是信号“偏离”的方向,校正层必须产生反向相位。例如,信号向逆时针偏 10°,校正层需产生顺时针 10° 的旋转。 - **计算推导**:对于 64 点 FFT,其归一化增益常数定义为 $-2/N = -2/64 = -0.03125$。 </br> <u>**c**. 要加 Delay 80?</u> - **同步计算需要时间**:同步模块必须看满一整个符号(64 FFT + 16 CP = 80点)才能算出频偏。 - **时间对齐**:为了让计算出的纠偏参数能准确作用在这个符号的“开头”,必须让原始信号在 Delay 模块里等 80 个采样点,确保“药方”和“病人”同时到达。 </br> <u>**d**. Multiply(相乘/混频)</u> - 将 Delay 模块输出的**带偏信号**与 Frequency Mod 输出的**纠偏波**逐点相乘。 - **数学结果**:$信号 \cdot e^{j\phi(n)} \times e^{-j\phi(n)} = 信号 \cdot 1$(频率立正) </br> ### 1.3 帧分割与解复用层 (Header/Payload Demux) **1)功能概述**:这是系统的“分拣员”。 这个模块是一个**状态机**,它有三个主要阶段: 1) **等待触发 (IDLE)**:平时它不输出任何数据,直到 `trigger` 端口收到同步信号。 2) **提取包头 (Header)**:收到触发后,它会截取指定长度的符号送往 `out_hdr`。 3) **提取载荷 (Payload)**:它会等待反馈(通过 `header_data` 端口告知包的具体长度),然后将剩余数据送往 `out_payload`。 **备注**:在默认配置下,如果 Header 解析出错且没有处理机制,这个模块确实会“卡死”或导致后续所有数据包错位。防止这种的处理方式有:header 调制更保守;CRC 校验失败返回一个长度为 0 的消息; </br> **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。 | </br> ### 总结:数据流的生命周期 1. **信号进入**,一路去算频偏,一路在 Delay 里排队。 2. **S&C 算出结果**,指挥 Freq Mod 产生校正波。 3. **校正波与排队信号相遇**,在 Multiply 里完成脱偏。 4. **校正后的干净信号**进入 Demux,被精准地切成“头”和“尾”两部分,送往虚拟接收槽(Virtual Sink)。 </br> ## 二、信道估计、均衡与星座图提取 这部分流程图展示了 OFDM 接收机的**核心解调与均衡网络**。在经过第一部分的同步与分拣后,信号依然处于时域采样点状态,且携带了信道造成的幅度衰落和相位失真。 接下来的处理就像是 **“翻译与纠偏”**:将时域波形转回频域,并利用已知的“同步字”作为参照物,把被环境扭曲的信号“扶正”。 ![][p2] 备注:此阶段分为两条并行的路径:Header(报头)解调路径和 Payload(载荷)解调路径。 </br> ### 2.1 频域转换层 (FFT) 所有 OFDM 的魔法都在频域发生。FFT 模块将时域的采样点转换回 64 个子载波上的复数电平值。 - **FFT Size**: 64(必须与发射端严格对应) - **Forward/Reverse**: Forward(正向 FFT,从时域到频域) - **Shift**: Yes。由于发射端通常会将零频(DC)移至频谱中心以符合物理特性,接收端必须进行反向 Shift,将子载波重新排列回正确的逻辑索引(0-63) </br> ### 2.2 信道估计与均衡层 (OFDM Channel Estimation & Equalizer) 这是保证数据正确率最关键的一步。无线电波在空间传输时会产生多径效应,导致某些频率点信号强,某些点信号极弱。 **1)OFDM Channel Estimation (仅存在于 Header 路径)** 在 OFDM 接收系统中,`OFDM Channel Estimation`(信道估计)模块是实现相干解调的“定海神针”。它的任务是利用已知的 **同步字(Sync Words)** 来测量无线信道对信号造成的幅度和相位扭曲,并将这个“扭曲模板”传递给后续的均衡器。 <u>**a.** 核心工作原理</u> 无线信道就像一个会改变光线颜色和形状的“滤镜”。假设发送信号为 $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)}$$ <u>**b.** 执行流程</u> 1) **时域转频域**:数据流经过 FFT 后进入此模块。 2) **提取前导码**:模块从流中提取出第一个(或前两个)OFDM 符号。 3) **计算信道响应**:将收到的复数值与本地预存的 `sync_word` 进行对比,计算出每个子载波的复增益(幅度缩放和相位旋转)。 4) **流裁剪与贴标**:这是该模块的关键特征。它会**从数据流中移除同步符号**,只保留后续的 n_data_symbols。同时,它将计算出的信道状态信息(CSI)和频率偏移量作为 **Stream Tag** 贴在输出的第一个数据符号上。 </br> <u>**c.** 模块参数详解</u> | 参数名称 | 建议设置/典型值 | 深度解析 | | :--- | :--- | :--- | | **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` 以获取更精准的估计(利用两个同步字取平均或互补)。 | </br> <u>**d.** 输出介绍</u> 该模块的输出端不再包含输入的同步符号,仅输出经过筛选的数据符号。 - **标签** ofdm_sync_carr_offset:一个整数标签,记录了探测到的粗频率偏移(子载波个数)。 - **标签** ofdm_sync_eq_taps:一个复数向量标签。**关键点**:该向量已经包含了频率校正补偿,即它不仅反映了信道 $1/H(k)$,还叠加了抵消频偏的相位。 - **元数据继承**:原本附着在同步符号上的所有标签(如 rx_time 或 packet_len)都会被自动搬移到第一个输出的数据符号上。 > ![][p3] </br> **2)OFDM Frame Equalizer (均衡器)** 在完成了信道估计(Channel Estimation)并拿到了“药方”(`ofdm_sync_eq_taps`)后,`OFDM Frame Equalizer` 就是真正执行“治疗”的模块。 根据 GNU Radio Wiki,这个模块的作用是根据标签中的信道状态信息,对每一个子载波进行复数除法(或乘法),以消除信道引起的幅度和相位畸变。 <u>**a.** 核心工作原理</u> 均衡器的本质是一个**复数乘法器**。它并不自己计算信道响应,而是被动地等待输入流中的特定标签(Tags)。 * **相干补偿**:当它从流中检测到 `ofdm_sync_eq_taps` 标签时,它会提取其中的复数向量($Taps$)。 * **均衡运算**:对于后续每一个数据符号的第 $k$ 个子载波 $Y(k)$,它执行以下计算: $$\hat{X}(k) = Y(k) \cdot Taps(k)$$ 由于 `Taps` 向量在 `Estimation` 模块生成时已经包含了频率校正信息,因此这一步能同时完成**信道均衡**和**粗频偏校正**。 * **状态保持**:均衡器会一直沿用这一组 $Taps$,直到下一个数据包到达并带有新的标签为止。 </br> <u>**b.** 参数详解</u> | 参数名称 | 我们的设置值 | 深度解析 | | :--- | :--- | :--- | | **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 长度是已知的。 | </br> <u>**c.** 执行流程与逻辑</u> 1) **标签触发**:模块监控输入流。一旦看到 `ofdm_sync_eq_taps`,它就更新内部的“补偿矩阵”。 2) **逐符号处理**:它将输入的 OFDM 符号(由 64 个复数组成)与补偿矩阵逐点相乘。 3) **导频处理(针对 Payload)**: * 如果均衡器配置了导频(Pilots)处理,它会在均衡数据符号的同时,利用每个符号中的导频点进行**相位跟踪**。 * 这可以修正残余的小频率偏移导致的“相位爬升”。 4) **标签传播**:所有控制标签(如 `packet_len`)都会透传到输出端,保证后续模块正常工作。 </br> <u>**d.** Header 与 Payload 路径的区别</u> * **Header Equalizer**:它是“自力更生”型。直接从 `Estimation` 模块获取 `eq_taps`,只负责把那 3 个 Header 符号变清晰,以便解出 `packet_len`。 * **Payload Equalizer**:它是“继承”型。由于 Payload 本身没有紧跟在同步字后面,它通常依赖于 **Header 路径传递过来的信道状态**。当你在图中看到 `Propagate Channel State: Yes` 时,意味着它会利用 Header 路径摸清的“路况”来解调庞大的数据主体。 > ![][p4] </br> **总结:** `OFDM Frame Equalizer` 是接收机中的“执行官”。它拿到了 `Estimation` 模块提供的“地图”,把被多径效应扭曲得面目全非的星座点,一个个精准地拉回到标准位置。 </br> ### 2.3 序列化与有效载荷提取 (OFDM Serializer) OFDM 符号中有 64 个子载波,但并不是所有子载波都带数据(有些是保护频带,有些是直流分量)。 - **Occupied Carriers**: 模块根据预设的掩码(如 [-26, -1], [1, 26]),只挑出那 48 个真正承载数据的子载波。 - **动作**:它将“并行”的子载波重新排队,变成“串行”的复数符号流,准备进行星座图映射。 - **丢弃冗余**:自动剔除用于同步的 sync_words 符号,只保留纯粹的数据部分。 </br> **1)核心工作原理** 在经过 FFT 和均衡后,数据是以 64 个子载波并行的复数矩阵形式存在的。但实际上,这 64 个点中只有一部分是真正的数据(Occupied Carriers),其余的是保护频带或导频。 Serializer 的动作逻辑如下: 1) 扫描与挑选:它根据 Occupied Carriers 的掩码列表,从 64 个子载波中只读取数据子载波上的复数值。 2) 去除冗余:自动丢弃空载波(Null Carriers)和直流分量(DC)。 3) 串行化:按照从低频到高频(或指定顺序)将这些复数点排成一队。 4) 分包边界同步:利用 Length Tag Key 识别包的边界。每当它处理完一个包定义的符号数量后,它会重新同步,确保下一个包的数据不会错位到上一个包里。 </br> **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 符号开始提取数据。 | </br> 下面分别是 header 路和 payload 路的配置: ![][p5] </br> ### 总结:从波形到符号的蜕变 1. **FFT** 把时域的“波”变成了频域的“点”。 2. **Estimation** 通过对比同步字,摸清了信道的“脾气”。 3. **Equalizer** 反向补偿,把歪斜的星座图“扶正”。 4. **Serializer** 剔除垃圾信息,把有用的数据挑出来排成一队。 </br> ## 三、星座解调、header 和 payload 数据提取 ### 3.1 header 部分 **header 路在经过 FFT -> 信道估计 -> OFDM 帧均衡 -> OFDM 序列化之后,送入星座解调,然后送入 header 解析器,解析出头部信息**: <img src="https://tuchuang.beautifulzzzz.com:3000/?path=202603/ofdm_rx_demod_header_decode.png" style="clip-path: inset(0 10px 10px 0);"></img> - `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 切割。 </br> ### 3.2 payload 部分 ![][p7] **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 ---------------------------------------------------------------------- ``` </br> ## 总结 整个流程图如下:(将发送和接收全部展开,这样形成了一个巨大的流程图) ![][p8] **注意:** 其中 OFDM Receiver 就是我们整个接收流程图的合并版(实现功能一样);当然整个发送流程图也有对应的合并版本,由于实现的功能一模一样,因此这里不做过多介绍。 </br> [#1]:https://beautifulzzzz.com/gnuradio/tutorial/lesson/48 [p1]:https://tuchuang.beautifulzzzz.com:3000/?path=202603/ofdm_rx_sync25105225.png [p2]:https://tuchuang.beautifulzzzz.com:3000/?path=202603/ofdm_rx_demod.png [p3]:https://tuchuang.beautifulzzzz.com:3000/?path=202603/ofdm_rx_demod_channel_estimation.png [p4]:https://tuchuang.beautifulzzzz.com:3000/?path=202603/ofdm_rx_demod_frame_equalizer.png [p5]:https://tuchuang.beautifulzzzz.com:3000/?path=202603/ofdm_rx_demod_serializer.png [p6]:https://tuchuang.beautifulzzzz.com:3000/?path=202603/ofdm_rx_demod_header_decode.png [p7]:https://tuchuang.beautifulzzzz.com:3000/?path=202603/ofdm_rx_demod_payload_decode.png [p8]:https://tuchuang.beautifulzzzz.com:3000/?path=202603/tx_ofdm.png