简介:针对WiFi通信系统仿真场景的MATLAB脚本包,版本1.5,面向通信工程专业学生、研究人员及算法验证工程师,用于在真实部署前完成物理层算法验证与性能评估。该包以十九个MATLAB脚本文件组织起完整的无线收发链路,包括主控流程、发射端符号与短训练序列生成、接收端包检测、载波相位误差计算与校正、I/Q不平衡补偿、低通滤波等模块,压缩包整体仅18KB,文件量小且结构清晰,适合直接阅读和二次开发。对于希望快速搭建无线局域网物理层仿真原型、理解正交频分复用调制与载波同步机制、或逐模块对照调试算法的开发者,这些脚本提供了完整可运行的参考例程,可支撑误码率、吞吐量等关键指标的分析。目前已有八十人学习下载,在特定技术人群中具备一定参考价值,可结合实际课题需求提取对应功能模块进行扩展实验。
1. 从基带到天线:这套WiFi仿真链路为什么值得拆开看
做WiFi物理层仿真的人多半有过这种体验:算法在MATLAB里跑得干干净净,一进到射频前端或者实测环境,星座图就开始旋转、发散,BER曲线怎么都对不上。原因往往不在调制解调本身,而在两个容易被忽略的环节——发射端的I/Q不平衡和接收端的载波相位残差。这套V1.5的WiFi系统仿真脚本,正好把这两个点做成了独立模块,从短训练序列(STF)生成、时域组帧、正交下变频、包检测到载波相位恢复,完整覆盖了一条物理层收发链路。特别适合两类人:一类是刚接触OFDM系统级仿真、想找一套能跑通的参考实现来对照学习的工程师;另一类是在做WiFi基带算法验证、需要把I/Q失衡和相位噪声单独建模来评估系统裕量的研究者。接下来我按发射、接收、补偿和验证四条线逐个拆。
2. Tune与Main:仿真参数怎么注入,才能让每次跑出来的星座图有可比性
2.1 Main.m的调用顺序:谁先初始化,谁最后统计
一套物理层仿真最常见的翻车方式,不是算法写错,而是初始化顺序不对。载波频率偏移估计跑在了信道估计之前,自动增益控制还没收敛就去做包检测,诸如此类。这个工程里Main.m的职责很明确:先调Tune参数表,再生成发射端时域波形,然后过信道,最后进接收机解调并统计星座点。
我一般会把Main.m的流程拆成下面这个序列:
2.1.1 参数装载阶段
% Main.m 顶层流程骨架(节选) cfg = Tune(); % 载入全部仿真参数 [tx_i, tx_q] = TX_RF_Total_Time(cfg); % 生成I/Q两路时域射频信号 % ... 信道与加噪 ... [rx_i, rx_q] = RF_RX_I_Time_Domain(cfg, tx_i); % ... 同理 RF_RX_Q_Time_Domain ... [mag, phase] = IQ_Capture(rx_i, rx_q); % 捕获I/Q样本Tune()在开头调用是因为后续所有函数的参数结构体都由它来定。TX_RF_Total_Time把符号级处理全部封装在内部,对Main来说,只看得到最终要送进信道的I/Q时域序列。从工程角度看,这种封装比把OFDM符号生成、加循环前缀、上变频全部平铺在Main里要容易排查得多——中间任何一个模块出了问题,只需要看哪一级的波形先开始发散。
2.2 Tune.m的参数面:带宽、增益、符号数怎么影响后续模块
Tune.m不是一个简单的常量文件,它本质上是整个仿真链路的控制面板。我梳理了一份参数分组,对应到后续哪些模块会消费这些值:
| 参数分组 | 典型参数 | 被消费的模块 |
|---|---|---|
| 信道基础 | 带宽、子载波数、循环前缀长度 | TX_Symbol_Generate_Time、FFT |
| 训练序列 | STF符号数、重复周期 | TX_STF_Generate_Time、RX_Packet_Detect |
| 射频前端 | I/Q增益失配、相位失配角 | I_Imbalance_Matrix、Q_Imbalance_Matrix |
| 载波恢复 | 环路带宽、相位误差阈值 | Carrier_Phase_Error_Calculate、Demod_Correction_Of_Carrier_Phase |
| 滤波器 | 截止频率、阶数 | Low_Pass_Filter |
2.2.1 增益系数与I/Q失配建模的联动
% Tune.m 参数初始化片段(节选) cfg.fs = 20e6; % 采样率 20 MHz cfg.nFFT = 64; % FFT 点数 cfg.gainImbalance = 0.1; % I/Q 增益失配 10% cfg.phaseImbalance = 3; % I/Q 相位失配 3 度 cfg.stfRepetition = 10; % STF 短训练符号重复次数这里有个容易踩的坑:gainImbalance和phaseImbalance不是给发射机加损耗,而是让发射机生成一个“本身就不完美”的信号。这样接收端的I_Imbalance_Matrix和Q_Imbalance_Matrix才有事可做。如果把这两个参数设成0再去验证补偿算法,等于拿完美信号测消噪算法,结论没有任何参考价值。我建议在调星座图之前,先把这两个值设成非零,确认接收端的补偿矩阵能把它拉回来,再回到0验证链路底噪。
3. 发射链路:STF、符号组帧与射频时域合成
3.1 STF为什么用12个短训练符号而不是一个长序列
WiFi的短训练字段(STF)在设计上就考虑了自动增益控制和粗频率同步的双重需求。重复的短符号让接收机可以在不依赖精确符号定时的情况下,先通过自相关把频偏粗估计出来。这个工程里TX_STF_Generate_Time.m生成的就是这段周期性波形。
常见的实现是用指定的频域序列做IFFT,然后循环扩展成多个重复的时域符号。短训练序列的载波间隔是普通OFDM符号的4倍,这意味着它的重复周期更短,允许频偏估计的范围更大。在做包检测时,这个周期性结构也直接决定了自相关峰的形状。
3.1.1 STF时域生成的核心逻辑
% TX_STF_Generate_Time.m 逻辑示意(节选) function stf = TX_STF_Generate_Time(cfg) % 频域短训练序列,非零子载波间隔为 4 stfFreq = zeros(1, cfg.nFFT); stfFreq(2:4:end) = sqrt(2) * exp(1j * pi / 4 * (1:length(2:4:end))); % IFFT 到时域,取一个短符号周期 oneShort = ifft(ifftshift(stfFreq), cfg.nFFT); % 重复 cfg.stfRepetition 次,形成完整 STF stf = repmat(oneShort, 1, cfg.stfRepetition); end这里的关键在于2:4:end的间隔设计。每隔3个子载波插入一个非零值,时域上就会呈现出周期性的短符号结构。repmat的重复次数cfg.stfRepetition直接决定了接收机的AGC收敛时间和频偏估计精度。重复次数太少,自动增益控制还没稳定波形就结束了;太多则浪费信道资源,降低有效吞吐量。工程上一般取10到12个重复。
3.2 TX_Symbol_Generate_Time与TX_RF_Total_Time的分工
这两个脚本的分工是理解这套工程结构的关键。TX_Symbol_Generate_Time.m负责把比特流映射成OFDM符号,包括调制映射、导频插入、IFFT变换和加循环前缀;而TX_RF_Total_Time.m做的则是把STF、信号字段、数据符号这些片段拼成完整的时域帧,再叠加上射频端的I/Q不平衡效应。
3.2.1 数据符号生成的边界条件
% TX_Symbol_Generate_Time.m 逻辑示意(节选) function sym = TX_Symbol_Generate_Time(cfg, bits) % QPSK 调制映射 data = (1 - 2 * bits(1:2:end)) + 1j * (1 - 2 * bits(2:2:end)); % 子载波映射 txGrid = zeros(1, cfg.nFFT); txGrid(cfg.dataIdx) = data; % IFFT 与循环前缀 ifftOut = ifft(ifftshift(txGrid), cfg.nFFT); sym = [ifftOut(end - cfg.cpLen + 1:end), ifftOut]; end我特别想提一下cfg.dataIdx这个索引向量。它定义了数据子载波、导频子载波和空子载波的位置。IEEE 802.11a/g的64点FFT里,数据子载波只有48个,导频4个,其余是直流和边缘保护带。如果dataIdx配置错了,接收端解映射的时候星座点会整体错位,表现出来就是误码率居高不下,但眼图看起来又挺正常。调试这类问题,建议先打印发送端和接收端的子载波索引,确认两边一致再往下追。
4. 接收链路:包检测、IQ捕获与I/Q不平衡补偿
4.1 RX_Packet_Detect的能量检测与自相关策略
接收端的第一步不是解调,而是判断“有没有包来了、包从哪里开始”。RX_Packet_Detect.m通常用双窗口滑动自相关实现。延时相关利用的是STF的周期性,能量检测利用的是信号到达后的功率跳变。把两者结合起来,可以在低信噪比下仍然获得稳定的包起始位置估计。
4.1.1 自相关包检测的判决变量
% RX_Packet_Detect.m 逻辑示意(节选) function [detected, startIdx] = RX_Packet_Detect(rxSig, cfg) L = cfg.stfPeriod; % STF 周期长度 windowSum = zeros(size(rxSig)); for n = L + 1 : length(rxSig) autocorr = rxSig(n - L : n - 1) * rxSig(n - 2 * L : n - L - 1)'; power = rxSig(n - L : n - 1) * rxSig(n - L : n - 1)'; windowSum(n) = abs(autocorr)^2 / (power^2 + eps); end % 超过阈值视为检测到包 detected = any(windowSum > cfg.detectThresh); startIdx = find(windowSum > cfg.detectThresh, 1); end判决变量abs(autocorr)^2 / power^2在噪声环境下近似为0,在STF周期信号下趋近于1。阈值cfg.detectThresh设置在0.5到0.75之间比较常见。设太低会把噪声尖峰当包起点,设太高会漏检短包。另外注意这个算法对频率偏移并不敏感,因为自相关计算的是相邻周期样本的共轭相乘,频偏只影响结果的角度,不影响幅度,所以它能很好地配合后面的载波相位校正模块工作。
4.2 IQ_Capture与I_Imbalance_Matrix/Q_Imbalance_Matrix
正交解调的输出是I/Q两路基带信号。理想情况下I路和Q路幅度一致、相位相差90度。实际射频前端会因为本振泄漏、混频器不对称等原因引入增益失配和相位失配。IQ_Capture.m把时域I/Q样本组织成便于后续处理的复数序列,而I_Imbalance_Matrix.m和Q_Imbalance_Matrix.m负责建模或补偿这种失配。
4.2.1 失配矩阵的构造与补偿方向
% I_Imbalance_Matrix.m / Q_Imbalance_Matrix.m 逻辑示意 function [Icomp, Qcomp] = IQ_Compensate(I, Q, g, phi) % g 为 Q 路相对 I 路的增益误差, phi 为相位误差(弧度) % 补偿矩阵 A = [1, 0; -tan(phi), 1 / (g * cos(phi))]; % 对 I/Q 样本做线性变换 comp = A * [I(:)'; Q(:)']; Icomp = comp(1, :); Qcomp = comp(2, :); end这里有一个容易混淆的点:为什么要用-tan(phi)而不是+tan(phi)?关键在于失配建模的符号约定。如果建模时定义Q路超前I路phi度,那么补偿时就要反方向旋转。建议在仿真里先扫phi从-5度到5度,画出补偿前后星座图的误差向量幅度(EVM)曲线,确认符号方向一致。符号反了的典型表现是:补偿之后EVM不但没降,反而比补偿前更差。
4.2.2 补偿精度的边界
I/Q补偿是线性操作,它只能校正固定的幅度和相位失配。如果失配随频率变化(比如宽带信号下的频响不平坦),单个矩阵就不够用了,需要做频率依赖的均衡。这个工程里的矩阵版本假设整个带宽内失配恒定,这是一个合理的简化,但在80MHz或160MHz带宽的场景里,用多组矩阵分段补偿会更接近射频前端的真实行为。
5. 载波相位恢复与仿真验证技巧
5.1 载波相位误差计算与校正的闭环
发射端和接收端的本振不可能完全同频同相,残余频偏会让星座图持续旋转。Carrier_Phase_Error_Calculate.m负责估计相位误差,Carrier_Phase_Correction.m和Demod_Correction_Of_Carrier_Phase.m负责把误差补偿回去。估计的方法通常有两种:基于导频的判决辅助估计,以及基于星座点判决的盲估计。
5.1.1 面向判决的相位估计环路
% Carrier_Phase_Error_Calculate.m 逻辑示意(节选) function [phaseErr, corrected] = CarrierPhaseLoop(rxSym, pilotIdx, cfg) % 提取导频符号估计公共相位误差 (CPE) pilotRx = rxSym(pilotIdx); pilotRef = cfg.pilotRef(pilotIdx); phaseErr = angle(sum(pilotRx .* conj(pilotRef))); % 一阶环路滤波,限制相位跳变速度 phaseErr = sign(phaseErr) * min(abs(phaseErr), cfg.maxPhaseStep); % 对数据符号做相位旋转补偿 corrected = rxSym .* exp(-1j * phaseErr); end环路滤波这一步是很多实现里缺失的。直接用单符号相位误差旋转星座图,在低信噪比下会把噪声的随机抖动放大成相位跳变。限制maxPhaseStep相当于给环路的带宽做了约束:值设得越小,跟踪越平滑但对快速相位变化响应越慢;设得越大,跟踪越快但对噪声越敏感。实际调参时,先固定符号速率,从0.01弧度开始往上扫,观察星座图收敛速度和散点半径的权衡。
5.2 用星座图和EVM快速验证整条链路
验证这套仿真不要只看BER曲线。我习惯先在接收解调后加一段脚本,统计所有数据子载波上的星座点,计算每个点相对于理想星座的误差向量幅度。EVM和SNR之间有明确的换算关系,比误码率更早暴露出I/Q失配、相位噪声和频偏残留这类模拟域问题。
5.2.1 链路自检的推荐步骤
第一,把Tune里的I/Q失配参数设为0,相位误差设为0,此时EVM的底限应该完全由加性噪声决定。第二,只打开增益失配,观察星座图是否在Q方向被压缩或拉伸。第三,只打开相位失配,观察星座图是否从矩形变成平行四边形。第四,加上残余频偏,观察星座图是否整体旋转。每一步都对照补偿开关验证效果。
5.2.2 排查相位模糊的经验
做完载波相位校正后,星座图可能仍然整体旋转了90度或180度。这不是环路没收敛,而是判决模糊问题。此时需要利用帧结构里的已知序列做相位去模糊,比如用前导符号或信号字段里的已知比特来消除歧义。Demod_Correction_Of_Carrier_Phase.m里如果把这一步漏了,星座图看起来每个点都对着正确的位置,但解出来的比特全是反的。检查方法很简单:把解调输出和发射端的比特流对齐后对比,如果误码率接近0.5且星座图清晰,十有八九是相位模糊。
本文还有配套的精品资源,点击获取