简介:这是一份面向通信、电子信息工程等专业学生与MATLAB初学者的WiFi 802.11ax物理层仿真代码包,专注IEEE 802.11ax标准中BCC(二进制卷积编码)与打孔(puncturing)两个关键环节,可用于课程设计、期末大作业或毕业设计中的物理层链路仿真。资源包仅2KB,共2个m文件,均为参数化编程,代码结构清晰、注释明细,用户可根据需要灵活调整编码率、打孔模式等参数,并可在MATLAB 2014/2019a/2021a中直接运行。附带的案例数据能帮助快速验证算法输出,降低上手门槛。对于想理解WiFi 6物理层前向纠错编码原理、或需要搭建仿真链路对照教材公式的读者,这套小体积代码提供了简洁的参考实现。目前已有192人学习下载,适合配合博文中的原理讲解与运行结果进行实践。
1. WiFi 802.11ax 物理层matlab代码.zip能给你带来什么
拿到一份 WiFi 802.11ax 物理层matlab代码.zip,我通常不会先去看 readme,而是先找入口脚本。802.11ax 物理层已经不是一个单载波、单链路的玩具模型,OFDMA 把信道按资源单元(RU)切分,1024-QAM 让整个系统对频偏、相位噪声和信道估计误差极其敏感。如果这份代码只把函数堆在一起,那它最多是“代码艺术品”,而不是能帮你做算法验证的测试台。真正值得投入的是把它跑成一个收发闭环:生成 HE PPDU,经过信道模型,再收回来,看星座图和 EVM。下面顺着这类 MATLAB 代码包的常见组织形式,告诉你入口在哪、参数怎么设、最常见的五个坑是什么,以及怎么把这个 zip 变成你自己的物理层验证环境。适合做信号处理、算法评估和 Wi-Fi 芯片预研的工程师,也适合刚接触 802.11ax 但会 MATLAB 的在校学生。
2. 802.11ax物理层到底在模拟什么:从HE PPDU到RU分配的代码视角
在打开别人的 MATLAB 代码之前,先想清楚这段代码试图复现什么。802.11ax 物理层的核心是 OFDMA 和 1024-QAM,而 OFDMA 又把 20MHz 信道切成多个 RU,每个 RU 分给不同的用户。在代码层面,这个分配最终落到一个数组上。这个数组的维度、索引和子载波偏移量,决定了发送端把多少数据放在哪个频率位置,也决定了接收端能不能正确解出每个用户的数据位。如果你手里的代码包只做单用户场景,RU 分配可能退化成一条直线;但只要涉及多用户,你的调试时间起码有一半会耗在 RU 映射上。
HE PPDU 的结构从时间上分,有 L-STF、L-LTF、L-SIG、RL-SIG、HE-SIG-A、HE-SIG-B、HE-STF、HE-LTF 和 HE-Data。接收端通过这些字段完成帧检测、粗同步、精同步、信道估计和数据解调。在 MATLAB 代码里,HE-SIG-B 是一个很高频的出错点,因为它的比特位掩码(BCC 编码后的分配信息)一旦解错,后面所有 RU 位置都是错的。所以你拆代码时,第一优先级是找到 HE-SIG-B 的解析函数和 RU 映射函数。
2.1 看懂HE PPDU:HE-SIG-B与RU分配的代码结构
一份能跑的 802.11ax 物理层代码,一般把波形看成“一个复数数组 + 一个配置结构体”。配置结构体里面放信道带宽、MCS、保护间隔、RU 索引等等,波形数组则是一维的时域采样点。RU 分配在自定义代码里,最常见的是用固定向量来表示,比如 20MHz 信道可以分成 9 个 26-tone RU,4 个 52-tone RU,2 个 106-tone RU,或者 1 个 242-tone RU。下面是一段 RU 大小和激活状态的典型写法:
% 20MHz信道 RU大小分配示例 ruSizes = [26 26 26 26 26 26 26 26 26]; % 1表示该RU当前有用户,0表示空 ruActive = [1 0 1 0 1 0 1 0 1]; % 每个26-tone RU的起始子载波偏移 ruCarrierOffset = [-103 -77 -51 -25 -1 25 51 77 103];这段代码的逻辑很直接:先列出每个 RU 的数据子载波数,再标记哪些 RU 被使用,最后给出每个 RU 在 OFDM 频域网格里的起始位置。需要特别注意的是,802.11ax 的 20MHz 信道 FFT 大小是 256,直流子载波位于正中间,所以偏移量不是从 -128 到 127 线性排列,而是把直流空出来。修改分配时,ruSizes、ruActive和ruCarrierOffset三者必须同时改;只改其中任意一个,接收端在做逆映射时就会出现子载波错位。很多翻车现场都出在这里。
如果你看到的代码包里有个叫ru_map.m或者generate_routing.m的文件,那么它很可能就干了上面这三件事。我的习惯是先把ruActive全设成 1,跑通一个 9 用户的极端场景,再回头改回你真正要模拟的用户数。这样能快速暴露接收端是不是真的按照 HE-SIG-B 的解码结果来取数。
2.2 用MATLAB代码跑通一次包生成的最小流程
不要一上来就去改信道模型和均衡器,先让代码从发送到接收完整地走一遍。如果代码是基于 MATLAB 的 WLAN Toolbox,那么最小入口通常是这样:
% HE SU PPDU 基本参数配置 cfgHE = wlanHESUConfig; cfgHE.ChannelBandwidth = 'CBW20'; % 20MHz cfgHE.MCS = 11; % 1024-QAM cfgHE.GuardInterval = 0.8; % 短GI,单位微秒 cfgHE.APEPLength = 1024; % 载荷字节数 % 生成随机PSDU比特 psdu = randi([0 1], cfgHE.APEPLength * 8, 1); % 生成基带时域波形 txWaveform = wlanWaveformGenerator(psdu, cfgHE);这段代码把“配置字段填满”和“波形生成”两件事分开了。cfgHE里面的MCS、ChannelBandwidth、GuardInterval都是接收端再现设置所必需的参数,收发两端必须完全一致。如果你的代码包是自定义实现,没有依赖于 WLAN Toolbox,那么入口函数可能叫ax_ppdu_generator或phy_tx_main,但参数结构体里仍然需要有带宽、MCS、GI、PSDU 长度这些字段。跑通这个最小流程之后,你先画一下plot(real(txWaveform), imag(txWaveform), '.'),看到一只“圆饼”而不是一条直线,说明至少波形产生成功。很多人忽略这一步,直接跑接收端,结果出错了都不知道是发送端的问题。
3. 把物理层代码拆开:核心函数与关键配置在哪
zip 里的代码不会自己说话,你需要先把它解压出来,再按文件层级判断这是什么风格的项目。有的包把发送、信道、接收全部写在一个脚本里,方便教学但是难扩展;有的包是一个完整的函数库,带一个main.m和十几二十个辅助函数。这两种形式下,你要找的东西不一样。
3.1 入口脚本、初始化函数与波形生成在文件里怎么找
先解压,再列目录,这是我拿到任何 MATLAB代码包的第一反应。命令行做这件事最省事:
unzip WiFi_80211ax_phy_matlab.zip -d ax_phy && cd ax_phy find . -name "*.m" | head -20列出来之后,优先看几个名字:main.m、init_config.m、channel_model.m、rx_packet.m、evm_calc.m。如果这些文件存在,说明代码作者是有意识地把收发链路分段的。打开main.m,先找最顶上的参数区。一般会看到下面的结构体初始化方式:
% init_config.m 片段 cfg = struct(); cfg.BW = '20'; % 信道带宽 MHz,可选 20/40/80/160 cfg.Ntx = 1; % 发送天线数 cfg.Nrx = 1; % 接收天线数 cfg.MCS = 9; % 0-11,9对应1024-QAM cfg.GI = 0.8; % 保护间隔,单位us cfg.RU = 'RU242'; % 低频段20MHz整带宽用单用户RU242这个代码块看起来简单,但它是整个仿真里最容易出现隐性 bug 的地方。BW是字符向量,MCS是 double,GI也是 double,类型不匹配会直接在矩阵索引时崩掉。常见的错误是把MCS写成'9',字符串被当字符数组参与加减法,结果全是不明数字。我一般会把它单独抽成一个init_config.m,并在每行后面写清楚可选的取值。这样当你需要扫描 MCS 时,只需要循环去改cfg.MCS,而不是去翻生成函数内部。
3.2 参数怎么设:MCS、带宽、GI、RU index一个都不能错
物理层参数的数量不多,但相互之间有强依赖。以 20MHz 信道为例,FFT 大小是 256,OFDM 符号的数据子载波总数是 234,其余的包含导频、直流和保护边带。不同 RU 大小决定了数据子载波和导频子载波的数量:26-tone RU 有 24 个数据子载波和 2 个导频,52-tone RU 有 48 个数据和 4 个导频,106-tone RU 有 96 个数据和 6 个导频。这个映射关系不理解,你改 RU 大小就是在盲调。
MCS 的设定也要跟着调制阶数走。MCS 0-2 对应 BPSK/QPSK,MCS 3-4 对应 16-QAM,MCS 5-7 对应 64-QAM,MCS 8-9 对应 256-QAM,MCS 10-11 对应 1024-QAM。如果你的代码包的星座映射表只实现到 MCS 9,你硬填 MCS 10 就会遇到查找表越界,或者接收端误码率瞬间到 0.5。在 802.11ax 里,1024-QAM 的 EVM 要求非常严格,正常需要 -32dB 以下,这直接限制了信道模型和均衡器的最低精度。所以参数设置的顺序应该是:先定带宽和 RU 大小,再定 MCS,最后定 GI。保护间隔有 0.8us、1.6us、3.2us 三档,短 GI 在多径时延较大的信道下会导致符号间干扰明显抬头,做仿真演示用 0.8us 最直观,做系统级评估建议跑 1.6us 做一个对比。
4. 从波形到指标:EVM、星座图和吞吐预估怎么做
只跑通发送端不算完成,你还得让接收端把数据吃回去,这样才能验证代码包里的算法是否自洽。很多代码包其实作者自己只调通了发端,收端是给后续学习者的练习,你一跑就会发现一堆待办。所以先把收发闭环跑通,再谈优化。
4.1 让接收端吃点噪声,先把收发闭环跑通
最简单的信道模型就是加性白高斯噪声(AWGN)。在你已经生成txWaveform的前提下,加噪并调用接收端解调函数,是所有人应该做的第一项测试:
SNRdB = 30; rxWaveform = awgn(txWaveform, SNRdB, 'measured'); % 调用接收端,返回解调比特和均衡后的星座点 [dataBits, rxSymbols] = he_rx(rxWaveform, cfg);这里的awgn函数第三个参数'measured'的意思是:先测量输入信号的功率,再按信噪比加噪。如果不加'measured',MATLAB 默认按 0dB 功率加载,你的 SNRdB 定义就和实际不符。接收端函数he_rx的具体名字可能不一样,但返回值一般是一个比特向量和一个复数星座点向量。看到rxSymbols之后,用scatterplot(rxSymbols)画一张图,判断有没有收敛到 QAM 网格上。这一步能快速过滤掉射频前端的建模错误,比如滤波器的群延迟没补偿。
如果你的代码包的接收端没有单独提供星座点输出,你可以绕路:在均衡器之后、判决器之前,把中间变量截获出来。这也是为什么我建议大家用函数文件而不是超长脚本来组织代码,因为夹断点根本找不到。
4.2 用EVM和BER判断物理层代码对了没有
EVM(误差向量幅度)是判断物理层实现质量的最直接指标。它表示实际接收符号和理想参考符号之间的差异,放到 MATLAB 代码里就是下面这个公式:
% rxEq是接收端均衡后的星座点,ref是对应理想星座点 M = 1024; % 对应MCS 11 ref = qammod(orderMap, M, 'UnitAveragePower', true); EVMdB = 20 * log10(rms(rxEq - ref) / rms(ref));这里orderMap要与发送端使用的调制映射一致,UnitAveragePower保证星座点的平均功率归一到 1。注意 EVM 的单位通常是 dB,有些代码包里会输出百分比,两者不要混用。按 802.11ax 的指标参考,MCS 11(1024-QAM)的 EVM 要求约 -32dB,MCS 9(256-QAM)约 -21dB,MCS 5(64-QAM)约 -11dB。如果你的代码在 AWGN 信道下连这个门限都达不到,那说明接收端同步或均衡还有问题,而不是信道噪声太大。
BER 的计算要放在解调比特之后。把dataBits和发送比特psdu对齐,再算误码率:
errRate = sum(dataBits ~= psdu) / numel(psdu);不匹配的根因经常不是算法错了,而是发送比特和接收比特存在延迟。你需要在接收端做一个比特滑动对齐,或者先用已知序列做同步。很多代码包发送端会在 PSDU 前面加一个固定序列,接收端可以用它来找帧头。这样做完之后,BER 曲线才不是一条乱跳的线。
5. 跑通802.11ax物理层代码时绕不开的5个坑:现象、原因与排查顺序
这一章全是血泪经验。物理层仿真最磨人的不是看不懂算法,而是看懂了却跑不出理想结果。下面五个坑是我在评估这类 MATLAB 代码包时最常遇到的,每条都按“现象、原因、解决”来说明。
5.1 现象:接收端一直找不到数据包起始位置
你发送端生成了波形,给接收端喂进去,但接收端老是报“packet not detected”。原因通常是两个:一是发射信号里没有做帧前导的能量抬升,或者接收端的能量检测门限设置得太高;二是发送和接收两个模块的采样率不一致,导致时间偏移被放大。解决方法是先用 MATLAB 自带的相关同步函数或代码包里的 preamble 检测函数,检查 L-STF 的相关峰。如果相关峰不明显,把发送波形的幅度整体放大三倍再看。还有一个常见细节:cfgHE里如果开了多个空间流,而接收端的流数是 1,那 HE-LTF 相关峰会变得很钝。
% 用L-STF做粗同步,常见做法:互相关 [corr, lag] = xcorr(rxWaveform, lStfReference); [~, idx] = max(abs(corr)); frameStart = lag(idx);5.2 现象:星座图旋转或整体相位偏移
接收端能够检测到包,但星座图是一团转圈的点。根因是残留频偏没有彻底消除。802.11ax 的 OFDM 符号数量多,累积相位偏移线性增长,均衡器如果只在符号级别估相一次,后面的符号全都会旋转。解决方法是每个 OFDM 符号的导频子载波都要重新做一次相位补偿。在 MATLAB 代码里,导频相位跟踪是最容易漏掉的一环。你可以查看代码里是否用到了一个pilot_phase_track.m函数,如果没有,就要自己加。
% 对每个OFDM符号,用导频符号估计剩余相位 pilotPhases = angle(pilotRx ./ pilotRef); phaseCorrection = exp(-1i * mean(pilotPhases)); dataEq = dataRx .* phaseCorrection;5.3 现象:OFDMA多用户场景下用户数据互相穿插
如果在单用户情况下一切正常,切到多用户 RU 分配就出错,十有八九是 RU 映射表的下标错了。前面提到的ruCarrierOffset是按 26-tone RU 排的,如果你的代码里分配的是 52-tone RU,偏移量和 RU 大小都要跟着变,不能只改大小不改偏移量。更隐蔽的是,接收端在解 RU 时用到了 FFT 移位顺序,有的代码用fftshift之后没有把直流位置再对齐,导致高频段拼到低频段。解决方法是打印出每个 RU 对应的子载波索引,和 802.11ax 的标准表格比对。
% 打印RU边界,检查是否与标准一致 for k = 1:numel(ruSizes) fprintf('RU%d: 子载波 %d ~ %d\n', ... k, ruCarrierOffset(k), ruCarrierOffset(k)+ruSizes(k)-1); end5.4 现象:仿真慢到不能忍,一个包跑十分钟
自定义 MATLAB 代码性能差是常态,但有些慢不是硬件问题,而是算法写崩了。内层循环用了for逐个符号做 FFT,没有预分配矩阵,或者每步都调用plot来回显。解决方法是把符号级循环改成矩阵运算,至少把 FFT 那步向量化。更常用是用tic/toc把发送端、信道、接收端三部分分别测一次,找出时间占比在哪。如果是接收端的解调判决里嵌套了逐子载波循环,改成qamdemod(rxEq, M, 'UnitAveragePower', true)一次性解所有子载波。
% 不好的写法:逐符号循环FFT % for sym = 1:numSymbols % fdata(:, sym) = fft(rx(:, sym)); % end % 好的写法:一次矩阵FFT fdata = fft(rx, [], 1);5.5 现象:EVM在某个参数下反复横跳,无法稳定
现象是昨天还能跑到 -30dB,今天换个参数就变成 -20dB,甚至两次运行结果都不一样。原因多半是随机数种子没有固定。AWGN 信道和随机 PSDU 会改变噪声实现,如果你要复现某个结果,必须锁定rng。另一个隐蔽原因是变量名冲突,某些函数内部用了全局变量i,被for循环改写成了整数,导致复数单位虚数被覆盖。解决方法是每次仿真前固定随机数种子,并且把cfg结构体里的全部字段作为函数参数传进去,不要依赖全局变量。
rng(42); % 固定随机种子,保证结果可复现6. 进阶:把PHY代码做成算法验证测试台,而不是黑匣子
当你把代码包跑通,下一步就要让它为你服务。我的建议是做三个改造:一是加一个 SNR 扫描外壳,二是加一个参考结果比对,三是把波形导出去供外部工具使用。SNR 扫描不需要改电层代码,只用循环包裹现有收发链路,把 EVM 和 BER 曲线画出来。参考比对可以借助 WLAN Toolbox 自带函数生成同配置的波形,和你自定义代码的星座图做差分,这样能立刻看出哪个环节有实现偏差。导波形这件事经常被忽略,实际上你把txWaveform写成二进制文件,后面用信号分析仪回放,或者用软件无线电设备做空口测试,都离不开这一步。
% 扫描SNR并记录EVM snrList = 15:5:35; evmList = zeros(size(snrList)); for snrIdx = 1:numel(snrList) rxW = awgn(txWaveform, snrList(snrIdx), 'measured'); [~, rxEq] = he_rx(rxW, cfg); evmList(snrIdx) = 20*log10(rms(rxEq-ref)/rms(ref)); end plot(snrList, evmList, 'o-');我现在的习惯是每改一个 RU 分配或 MCS,都会先固定随机种子跑一遍 EVM,再跑一遍误包率,最后把波形导出存一个版本。这样查问题的时候,手里永远有后悔药。这个流程听起来很基础,但绝大多数翻车的 MATLAB 代码包,都是栽在这些看不到的“基础”上。希望帮到你。
本文还有配套的精品资源,点击获取