做 WiFi 物理层仿真的人,应该都有过这种体会:公式推得头头是道,代码一跑就原形毕露。尤其是 OFDM 的同步部分,纸上谈兵是一回事,真要在 MATLAB 里把一个带频偏、带多径的接收信号恢复出来,又是另一回事。IEEE 802.11a/g 的 ERP-OFDM 物理层链路级仿真,就是最适合拿来“完整盘一遍”的练手对象,因为它参数规整、带宽 20 MHz、帧结构清楚,而且足够把帧检测、频偏估计、定时同步、信道估计这些环节全部塞进去而不乱。这篇文章要聊的 n06_wifi_full_sync.m,就是一套我反复使用、并且拿给学生跑过的链路级仿真代码,整条链路从比特生成、扰码、卷积编码、交织、QAM 映射、导频插入、IFFT、加循环前缀,到接收端全同步链路,最后统计误码率,全部揉在一个脚本里。它适合正在学 OFDM 同步的学生,也适合刚转无线物理层的工程师拿来做算法基线。
1. 为什么需要这样一套链路级仿真代码
1.1 从“会算公式”到“能看见波形”
很多教材把同步算法写成三段式:给出相关表达式、画出流程图、推导估计方差。能看懂,但总觉得少点什么。少的是“信号在哪个时刻变成什么样”的实感。链路级仿真补的就是这一块,它把抽象的信号处理过程变成变量、数组和图像,你能清清楚楚看见接收信号的包络、自相关峰、频偏补偿前后的星座图。
我自己带学生时最常说的就是一句话:不要急着调参数,先把收发两端的时域波形打出来看一遍。IEEE 802.11a/g 的 OFDM 系统,短训练序列重复 10 次、长训练序列重复 2 次,这种结构在时域上特征非常明显。只要把 rx 信号画出来,帧边界在哪里、频偏造成的相位旋转有多严重,一眼就能看出来。n06_wifi_full_sync.m 的命名里,“full_sync”指的就是接收端没有假设理想同步,所有同步参数都靠算法从接收信号里估计出来,这才是链路级仿真该有的姿态。
1.2 链路级仿真解决的是哪种问题
链路级仿真和系统级仿真经常被混在一起,其实分工很明确。系统级仿真关心的是小区吞吐量、用户调度、干扰协调这类整网问题,一个用户可能被抽象成一条“链路性能曲线”;链路级仿真则相反,它只研究一条链路的物理层收发,输入是比特和信噪比,输出是误码率、误包率和星座图。回答的问题是:给定调制编码方式和信道条件,物理层方案能不能达到理论性能。
对教学和研究来说,链路级仿真的价值在于它可以“分段验证”。你怀疑频偏估计算法精度不够,就单独把 CFO 模块拎出来测;你想比较 ZF 和 MMSE 均衡的差异,只改均衡那一行代码即可。整套代码跑通之后,还能顺便画出 BER-SNR 曲线,与理论误码率对比,这是论文里最常用的一张图。
1.3 为什么用 MATLAB 而不是 C 或 Python
这个问题我经常被问到。Python 的 numpy/scipy 确实能做 OFDM 仿真,但 MATLAB 在通信领域依然有不可替代的优势:通信工具箱里已经实现好了卷积码的 trellis 结构、Viterbi 译码器、比特误码率统计等底层函数,省去很多抠细节的功夫。另外,MATLAB 的画图交互、断点调试、工作区变量可视化,对“学习同步算法”这个场景实在太好用了。
n06_wifi_full_sync.m 虽然是一个脚本文件,但它内部按模块分段,段落之间用清晰注释隔开。如果以后要移植成 C 或 Verilog,完全可以把每一段当成一个函数来重构。教学代码最重要的不是高效,而是逻辑透明。
2. ERP-OFDM 物理层结构:先把这个系统的底摸清楚
2.1 ERP-OFDM 在 802.11 家族中的位置
802.11g 工作在 2.4 GHz 频段,又要求向后兼容 802.11b,所以引入了 ERP(Extended Rate PHY)的概念。ERP 下面有两类调制方式,一类是 ERP-OFDM,基本照搬了 802.11a 的 OFDM 物理层结构,只是把频段换到 2.4 GHz;另一类是 ERP-DSSS/CCK,用于兼容 802.11b 的 1/2/5.5/11 Mbps 模式。严格讲,802.11g 的 OFDM 模式和 802.11a 的 OFDM 在物理层映射、编码、交织、训练序列上都高度一致,所以学习时可以把 11a/g 当成同一套系统。
n06_wifi_full_sync.m 实现的正是 ERP-OFDM 这条链路。它能跑 6、9、12、18、24、36、48、54 Mbps 共八种速率,对应 BPSK 到 64QAM 的不同调制编码组合。代码里一个 rate_idx 变量就能切换全部速率,这种设计对教学特别友好,学生可以同一套代码观察 QAM 阶数提升之后星座图的变化。
2.2 OFDM 符号的核心参数
802.11a/g 的系统参数在今天的眼光看很朴素,但正因为朴素,才容易讲清楚 OFDM 的基本原理。采样率 20 MHz,FFT 点数是 64,所以子载波间隔等于 20 MHz / 64 = 312.5 kHz。64 个子载波里,只有 52 个被使用,其中 48 个传数据,4 个是导频;其余子载波保留为空,尤其是直流分量那个子载波必须置零,避免对接收机零频附近的直流偏移过于敏感。
OFDM 符号总时长 4 us,由 3.2 us 的有效数据部分和 0.8 us 的循环前缀组成。循环前缀的作用是把多径信道的线性卷积变成循环卷积,这样接收端做 FFT 后,每个子载波上的信道就变成了一个复增益,均衡器只需要做一次复数乘法。0.8 us 的 CP 在 20 MHz 采样率下就是 16 个采样点,能抗住大约 240 米以内多径时延差的典型室内场景。这些数字非常具体,值得背下来。
2.3 帧结构与训练序列
802.11a/g 帧的物理层前导是接收机所有同步算法的基础。前导总长 16 us,前 8 us 是短训练序列(STS),由 10 个重复的短符号组成,每个短符号时长 0.8 us,对应 16 个采样点。短训练序列的重复结构非常适合做粗频偏估计和帧检测,因为它相位变化周期短、捕获范围大。后 8 us 是长训练序列(LTS),由 1.6 us 的保护间隔加上两个 3.2 us 长符号组成。长训练序列的重复周期是 3.2 us(64 个采样点),可以做精度更高的频偏估计,同时本身是已知序列,正好用来做信道估计。
前导之后是 SIGNAL 字段,用一个 OFDM 符号承载,BPSK 调制、码率 1/2 编码,指示了后续数据部分的速率和长度。然后是 DATA 部分,由若干个 OFDM 符号组成。接收机必须严格按照这个时间线工作:先检测帧到达,再做时间和频率同步,然后解析 SIGNAL 字段,最后才处理数据。n06_wifi_full_sync.m 的接收端就是按照这个顺序组织的,每个阶段都对应代码里一个注释块。
2.4 为什么同步是接收机的第一关
OFDM 同步之所以重要,是因为它对时间和频率误差极其敏感。符号定时偏移如果超过循环前缀的容限,会产生符号间干扰,所有子载波都会受害;频率偏移则直接破坏子载波之间的正交性,带来子载波间干扰(ICI)。系统里的晶振通常有 ±20 ppm 的频偏,在 2.4 GHz 频段下意味着几十到上百 kHz 的偏移,如果不做频偏补偿,64QAM 根本不可能解出来。
也正因为同步问题如此关键,通信工程面试、考研复试、项目答辩里,802.11a 的同步链路几乎成了标准考题。n06_wifi_full_sync.m 把同步链路完整放在一个脚本里,学生可以从头到尾追踪每一个中间变量,搞清楚“粗频偏到底粗在哪”“精定时为什么能到采样级”,这是纯看论文替代不了的。
3. n06_wifi_full_sync.m 的整体设计拆解
3.1 主脚本的执行流程
整个脚本的逻辑非常线性,大致分为五个阶段:参数初始化、发射机、信道、接收机、性能统计。参数初始化里定义采样率、FFT 点数、调制编码方式、信噪比、频偏、多径信道参数等。发射模块生成一帧完整的送出发射信号,信道模块叠加频偏、多径和 AWGN,接收模块做前导检测、粗频偏估计、精定时、精频偏校正、去 CP、FFT、信道估计、均衡、解映射、解交织、Viterbi 译码、解扰,最后把收发比特对比,统计 BER 和 PER。
这种结构最大的优点是方便调试。你可以把接收端的信号在某个位置截断,单独观测自相关曲线;也可以屏蔽多径只加高斯噪声,验证同步算法本身的性能。脚本里我习惯用这种写法:
% 发射端 tx_signal = wifi_tx(payload_bits, cfg); % 信道 rx_signal = add_awgn_multipath_cfo(tx_signal, cfg); % 接收端 [detected_flag, start_idx] = preamble_detect(rx_signal, cfg); [coarse_cfo, corrected_signal] = coarse_freq_sync(rx_signal, start_idx, cfg); [fine_start, fine_cfo, channel_est] = fine_sync_and_channel_est(corrected_signal, cfg); % 接收端解调 rx_bits = wifi_rx_demod(corrected_signal, fine_start, fine_cfo, channel_est, cfg);这里把收发逻辑封装成了几个子函数,实际教学版本会把它们拆开放在同文件底部或独立文件里,方便单步跟踪。
3.2 发射端模拟器
发射机流程对应 802.11a/g 的标准发送链路。发射比特先加扰码,初始状态在收发两端一致;然后做卷积编码,生成多项式是经典的 [171, 133] 八进制,约束长度 7,基础码率 1/2。如果需要 2/3 或 3/4 码率,需要做打孔(puncturing),代码里要有一张打孔模式表。
编码后的比特做块交织,交织深度是一个 OFDM 符号内的编码比特数。BPSK 的深度是 48,64QAM 的深度是 288。交织的目的有两个:把突发错误分散到不同子载波,以及把相邻比特映射到不同调制位置上,避免一次深衰落同时干掉一片相邻比特。交织之后是 QAM 映射、插入导频、IFFT、加循环前缀。这个过程每 4 us 产生一个符号,最终把一组符号串行拼接,前面再放上 16 us 的物理层前导。
发射端还有一个容易被忽略的点:SIGNAL 字段必须单独编码,因为它含有速率和长度信息,接收机在不知道这些信息的情况下也要能正确解出。仿真里 SIGNAL 字段一般用固定的 24 bit 表示,实际代码里要注意 SERVICE 字段的前 7 bit 是零,用来让 Viterbi 译码器在当前 OFDM 符号内完成归零点。
3.3 信道模型的选择
仿真里信道模型决定了代码的“真实感”,也决定了同步算法能不能得到充分检验。n06_wifi_full_sync.m 里至少会提供两种选择:纯 AWGN 信道和多径信道。AWGN 适合做理论对比,多径信道可以用一段脉冲响应来描述,比如时延分别对应 0、50、150 ns 的多径,每条路径有自己的复增益。这些值都是室内环境下比较典型的参数。
频偏是另一个必须模拟的损伤。实际接收机里频偏来自本地晶振不匹配,收发两端各带 ±20 ppm 误差,在 2.4 GHz 上最大就是 96 kHz 左右。教学代码里我习惯直接把频偏设成 40~100 kHz,观察粗频偏估计和精频偏估计如何分步校正。如果只把信道设为 AWGN,接收机会非常简单,仿真结果也会比实际乐观很多。
% 多径信道示例 path_delays = [0, 3, 8]; % 采样点延迟 path_gains = [1, 0.5, 0.3]; % 复增益幅度 mp_channel = zeros(1, max(path_delays)+1); mp_channel(path_delays+1) = path_gains;3.4 接收端全同步链路
接收端的步骤围绕一个核心目标:把接收信号逐级“对齐”到理想的 OFDM 网格上。第一步是帧检测,判定信号到达并找到前导的近似起点;第二步是粗频偏校正,利用短训练序列把几十 kHz 的频偏降到几个 kHz;第三步是精定时,用长训练序列与本地参考做互相关,找到符号起始点;第四步是精频偏校正,利用两个长训练符号之间的相位差把残余频偏尽量归零。
完成同步之后,从精定时点开始取符号、去循环前缀、做 FFT,得到频域接收信号。再用本地长训练序列做 LS 信道估计,得到每个子载波上的信道响应。均衡器对每个子载波完成一次复数除法或 MMSE 权重计算,之后就是解映射、解交织、解码、解扰。接收端代码如果写得好,每一步中间变量都能画图观察,这也是这套代码最值钱的地方。
4. 核心同步算法逐个解析
4.1 帧检测:基于短训练序列的延迟自相关
帧检测的原理是利用 STS 的周期重复性,在接收信号上做延迟自相关。窗口长度设为 16 个采样点,把相隔 16 个采样的两段信号相乘累加,再除以后半段的能量做归一化。当滑动窗口落在短训练序列区域时,相关值会明显抬高,形成一个峰值平台。
window_len = 16; m_metric = zeros(1, length(rx_signal)-2*window_len); for n = 1:length(rx_signal)-2*window_len a = rx_signal(n:n+window_len-1); b = rx_signal(n+window_len:n+2*window_len-1); r = sum(conj(a) .* b); d = sum(abs(b).^2) + eps; m_metric(n) = abs(r)^2 / d^2; end阈值一般取 0.5~0.7。由于噪声的影响,不要选峰值最高的点,而是选第一个超过阈值的点,再往前回退一个窗口长度作为帧起点。这个方法对频偏不敏感,因为自相关本身是共轭相乘,频偏只会让结果带一个固定相位,不影响幅值。
4.2 粗频偏估计:利用短训练序列的相位
一旦检测到帧起点,就可以用同一段延迟相关的相位来估计粗频偏。具体来说,相隔 16 个采样点的两个短训练符号,在频偏 f 的影响下会有一个相位差,大小是 2π f T_s D,其中 T_s = 50 ns,D = 16。把这个相位差解出来,再除以 2π D T_s,就是频偏估计值。
corr_val = sum(conj(rx_signal(n0:n0+15)) .* rx_signal(n0+16:n0+31)); phase_diff = angle(corr_val); Ts = 1 / 20e6; coarse_cfo = phase_diff / (2 * pi * 16 * Ts);这个估计方法的特点是捕获范围大。因为相位差被 mod 在 [-π, π],所以可估计的最大频偏是 ±20 MHz / (2 × 16) = ±625 kHz,覆盖实际晶振带来的频率偏差绰绰有余。精度方面,用 16 个采样点的相关只能做到大约几 kHz 量级,所以接下来还需要精频偏校正。
4.3 精定时与精频偏:长训练序列的分工
粗频偏补偿之后,接收信号与理想网格的偏差已经被控制在很小的范围,这时用 LTS 做精定时。做法是把本地已知的 LTS 与接收信号做互相关,相关峰的位置就是长训练序列第二个符号的起点,再根据前导长度推算出数据部分第一个 OFDM 符号的起点。
corr = xcorr(rx_cfo_comp, lts_local); [~, peak_idx] = max(abs(corr)); fine_start = length(rx_cfo_comp) - peak_idx + 1 + lts_len;精频偏则利用两个长训练符号的相位差。两个 LTS 之间的间距是 64 个采样点,相位差是 2π f × 64 × T_s。因为粗频偏已经把残余频偏限制在 ±10 kHz 以内,这里的精度可以做到几十 Hz,足够满足 64QAM 的要求。
4.4 信道估计与均衡:LS 估计加上单抽头消去
接收端做 FFT 之后,得到第 k 个子载波上的频域接收信号 Y[k] = H[k] X[k] + N[k]。对于长训练符号,X[k] 是已知的 ±1 数据,所以 H[k] 的 LS 估计就是 Y[k] / X[k]。在实际代码里,会对两个长训练符号分别估计然后平均,进一步压低噪声。
H_est = 0.5 * (Y_lts1 .* conj(X_lts) + Y_lts2 .* conj(X_lts));均衡时,如果用 ZF 均衡,就是 H_est 在上面,插值到数据子载波位置。计算数据符号的发送估计值用 Y_data / H_est;如果担心噪声放大,可以换成 MMSE 均衡,乘上 (conj(H)) / (|H|² + σ²)。教学版通常先实现 ZF,让学生理解最小二乘的本质,后续再自行扩展 MMSE。
4.5 数据解调解码:从软信息到信息比特
均衡后的复数符号要映射回比特。硬判决做法是找最近的星座点,把该点的比特标签取出来。仿真里为了方便做 BER 统计,我通常保留星座点索引再做解交织。如果需要逼近理论性能,可以生成软比特(LLR),把每个比特的似然信息送入 Viterbi 译码器,性能会比硬判决好 2 dB 左右。
Viterbi 译码在 MATLAB 里可以直接用通信工具箱的 poly2trellis 和 vitdec,约束长度 7、生成多项式 [171 133] 是标准配置。如果打孔过,接收端要先做解打孔(把删掉的比特位置补 0 或设定为高不确定度),再送入译码器。最后别忘了解扰,否则输出的比特序列是乱的。这一步看起来琐碎,但最考验对 802.11 帧结构的整体理解。
5. 关键参数设置与仿真结果对照
5.1 工作参数与仿真场景
实际跑仿真时,我不建议一上来就开多径、大频偏、高码率同时上。参数之间会互相干扰,出了问题根本不知道是谁的锅。我常用的教学方案是分三档:先做纯 AWGN 信道、零频偏,验证整条链路是否正确;再加频偏,验证同步算法;最后加多径,把同步和均衡一起考验一遍。
以下是一组推荐的起点参数:
| 参数 | 数值 | 说明 |
|---|---|---|
| 采样率 | 20 MHz | 802.11a/g 标准值 |
| FFT 点数 | 64 | 其中 52 个有效子载波 |
| 循环前缀 | 16 samples | 0.8 us |
| 调制方式 | 16QAM | 速率 24 Mbps |
| 编码速率 | 1/2 | 卷积编码 + 打孔 |
| 频偏 | 40 kHz | 约 2.4 GHz 下 16.7 ppm |
| 多径时延 | 0/50/150 ns | 室内典型信道 |
| SNR 范围 | 0~30 dB | 扫描 BER 曲线 |
5.2 典型曲线解读
把 BER-SNR 曲线画出来后,有几个特征值得注意。低阶调制下(BPSK、QPSK),同步误差的影响不明显,曲线和理论误码率相当接近;到了 64QAM,信噪比需求迅速提高,要得到 1e-4 的误码率,大约需要 25 dB 以上信噪比,这时频偏估计误差 100 Hz 和多径均衡不佳都会让曲线出现“平台期”——信噪比加了但误码率不再下降。
我自己在跑代码时,经常先看星座图而不是 BER。一个均衡后的 64QAM 星座图如果像揉皱的纸片,说明频偏或信道估计还有残留误差;如果星座点旋转但清晰,说明频偏没有完全补偿;如果星座点形成一圈圈的圆环,说明 IQ 幅度不平衡。星座图往往比一条误码率曲线更早暴露问题。
5.3 频偏估计的精度评估
为了评估同步算法的健壮性,可以专门做一组“频偏扫描”。固定 SNR 在 20 dB,把频偏从 0 扫到 200 kHz,观察 BER 变化。你会发现粗频偏估计在接近 625 kHz 边界时会突然失效,这是相位模糊导致的。实际工程上不会用到那么大的频偏,但这个现象能帮助理解估计范围与精度的折中关系。
同样可以扫描定时偏差,按采样点单位偏移 0 到 15 个采样点,测定时误差对误码率的影响。这个测试能让人直观理解循环前缀的容限。这类参数扫描代码只需要在外层包一层 for 循环,非常适合做课程作业或论文里的“性能分析”章节。
6. 常见问题与排查技巧
6.1 误码率不随 SNR 下降
这是最常见的问题,表现为信噪比从 10 dB 加到 30 dB,误码率始终在某个水平不动。最常见的原因是频偏或符号定时没有对准,剩余误差成了系统底噪。此时先把频偏设成 0、多径关掉,如果曲线恢复正常,说明问题在同步链路;如果依旧有平台,则检查信道估计或解交织/解打孔是不是对错了位置。
调试顺序我建议是:先看同步偏差变量,打印出估计频偏与实际频偏的差;再看均衡前的星座图是否发散;最后检查发送和接收的比特是否对整齐。大多数问题都出在“帧起点偏了一个采样点”或者“打孔位置错位一位”这种低级错误上。
6.2 大频偏导致帧检测失败
帧检测用的延迟自相关对频偏不敏感,但具体实现时,如果阈值设得太高或能量归一化没做好,在大频偏下也可能漏检。检查方法很简单:画一下帧检测度量 m_metric 的波形,看看在 STS 区域是否形成了明显的峰值平台。如果平台很矮,可以降低阈值到 0.4,或者增大延迟相关窗口数量。
另外一个技巧:不要直接用原始接收信号做能量归一化,加一个平滑滤波,避免突发噪声把归一化分母打乱。我遇见过很多次阈值设 0.6 就稳定、设 0.7 就间歇漏检的情况,这种灵敏度问题在工程上很典型。
6.3 多径信道下的定时偏移
多径信道会让互相关峰变宽,精定时选出的位置可能是某条多径的到达时刻,而不是最强径。如果信道的主径不是第一径,精定时会偏向主径位置,导致 FFT 窗口起点偏移。解决办法是选择第一径,而不是峰值最高的径;工程上可以用“峰值周围一定区域内最早超过峰值的一半”的点作为起点。
我在教学代码里故意保留了这种“算法缺陷”,叫学生观察什么时候会出问题。等到他们发现星座图发散了,再引导他们改成第一径检测。这个练习对理解“定时同步在真实信道下的难点”非常有效。
6.4 Viterbi 译码的坑
Viterbi 译码最常见的坑是模式选错。vitdec 在 MATLAB 里有 'cont' 和 'term' 两种模式。如果用 'term' 模式,输入必须在一个编码块内完整结束,且末尾有归零比特;如果输入流包含多个 OFDM 符号,用 'cont' 模式更合适。另一个常见坑是打孔后比特序与交织顺序不理解,导致译码结果完全错误。
调试编码链路时,我习惯在解调后、解交织前打印前 200 个比特,和发射端交织前的比特对比。如果前几个比特完全一致但后半段全错,通常是交织/解交织深度不匹配;如果比特全部是噪声,那问题出在更前面的均衡或信道估计。
6.5 随机种子与仿真重复性
蒙特卡洛仿真需要大量帧叠加以获得稳定的 BER 统计,但随机种子设置不当会导致结果不可复现。教学刻上我在脚本开头写死 rng(1),每个 SNR 点独立重新初始化,保证每次跑出来曲线一致。如果学生做对比实验,这个设置能让他们安心比较不同算法之间的细微差别。科研上跑大数据量时,也可以用并行 for 循环配合独立随机流,这属于工程细节了,但别小看,往往决定一篇论文数据是否可信。
7. 基于这套代码还能做的扩展
7.1 从 802.11a/g 走向 802.11n/ac
802.11n 的核心变化是 MIMO-OFDM,FFT 点数从 64 提高到 128,带宽扩展到 40 MHz,并且增加了短保护间隔模式。802.11ac 进一步支持 80/160 MHz、最多 8 流 MIMO。如果 n06_wifi_full_sync.m 的接收机链路你已经吃透了,升级到 MIMO-OFDM 只需要在信道估计和均衡部分扩展成矩阵运算,把单抽头均衡改成 ZF/MMSE 矩阵均衡。
建议的方向是先把现有代码加一个发射天线的空间映射,信道矩阵变成多径卷积矩阵,接收端做最小二乘信道估计。这个过程并不轻松,但有了单链路基础,至少你清楚每一个模块要做什么。
7.2 物理层安全与加密方向
传统加密都做在 MAC 层以上,WiFi 的 WEP/WPA/WPA2 正是如此。物理层本身并不对数据加密,只有一个扰码器,目的也不是安全,而是避免比特序列中出现长串直流和周期图案。不过“物理层安全”是学术界很活跃的方向,核心思路是把无线信道的随机性、收发双方的唯一性作为密钥来源,或者用扰码种子充当轻量级密钥,让非授权接收者即使拿到信号也无法正确解调。
在 n06_wifi_full_sync.m 基础上做安全扩展其实很方便。最简单的实验:把发射端扰码器的初始状态当作密钥,只有知道密钥的接收端才能解扰,从而测出错误用户在不同信噪比下的误码率。更进一步可以仿真基于信道特征提取密钥的协议,观察密钥一致性随信噪比的变化。这个概念也常被拿来讨论新一代短距无线通信系统的“物理层加密”能力,本质上都是在物理层增加安全维度。
7.3 向 C 或 DSP 移植的思路
仿真代码跑通以后,很多人会想把同步算法移植到 C 或定点 DSP 上做原型验证。移植的难点不是语法,而是精度和时序。MATLAB 默认 double 精度,DSP 通常用 16 bit 定点,自相关累加过程中很容易溢出或精度不足。我建议先做浮点 C 移植,仿真对照 BER;再做定点仿真,观察星座图是否依然清晰。帧检测的阈值、频偏估计的角度计算、FMMSE 均衡的分母,都需要重新标定。
移植时还有一个习惯值得养成:每个函数保持输入输出确定性,用测试向量做回归。我在 n06_wifi_full_sync.m 里保留了每个阶段的关键中间变量,就是为移植准备的。把这些变量 dump 成文件,再让 C 代码输出同样格式的结果,做逐级比对,能省掉大量排查时间。
在我自己用这套代码的经验里,最有价值的不是它跑出来的 BER 曲线,而是把“同步链路带来的各种非理想现象”在同一个环境里重现的能力。读一百篇论文不如亲手让信号在频偏下旋转一次,再看着补偿后的星座图落回网格。把这套代码吃透了,后面做 5G NR、WiFi 7 的物理层,很多思想都是相通的,底层功夫就是这时候积累下来的。