简介:面向无线通信研究与工程实践的资料包,围绕802.11ax核心特性OFDMA展开,配套MATLAB仿真代码,适合需要理解多用户正交频分多址原理、子载波分配与调度机制的学生和工程师。压缩包共11个文件,其中9个m脚本覆盖多用户发射/接收、QAM符号映射、多相调制解调及循环前缀性能对比等关键模块,1个mat文件保存仿真所需信道或参数数据,1个md文档提供使用说明,整体仅29KB,轻量易用。已有319人学习浏览。代码以场景化方式演示了子载波资源划分、多用户调度、256-QAM调制、信道估计与均衡、多用户同步以及功率分配等核心环节,通过调整参数可直观比较不同配置下的误码率与系统吞吐量;脚本按功能拆分,便于按需修改与复用。整体上,这份资料为理解802.11ax中OFDMA双向多用户传输机制提供了可运行、可扩展的实验基础,也可作为相关课程设计或论文验证的起点。
1. 从 OFDM 到 OFDMA:802.11ax 真正改变的不是波形,而是用户调度方式
很多人初看 802.11ax 的 OFDMA,以为它只是把 OFDM 换了个名字。实际差别在于:传统 OFDM 一个时刻只服务一个终端,整个 20MHz 带宽上所有子载波全给这个终端用;而 802.11ax 的 OFDMA 把子载波划分成一个个资源单元(Resource Unit,RU),不同 RU 可以同时分配给不同终端。这个改动让 Wi-Fi 6 在高密度场景下能同时给几十个终端发小包,而不是让它们排队抢信道。用 MATLAB 仿真 OFDMA 链路,最关键的是吃透 RU 映射、FFT 尺寸和导频位置三件事。这篇博文直接从 64 点 FFT 的最小可运行链路讲起,逐步做到多用户资源分配和 BER 验证,适合正在做 Wi-Fi 6 物理层开发、协议测试或毕设的同学。
2. 802.11ax OFDMA 的 RU 结构与 MATLAB 最小发送链路的 64 点 FFT 实现
2.1 RU 尺寸、子载波数与导频位置对照表
802.11ax 定义了五种 RU 尺寸:26、52、106、242、484,分别对应 2MHz、4MHz、8MHz、20MHz、40MHz 带宽。RU 越大,单个用户可用的数据子载波越多,但可同时调度的用户数越少。以 20MHz 信道为例,整条信道可以分成 9 个 26-RU,或者 4 个 52-RU 加 1 个 26-RU,也可以 2 个 106-RU 加 1 个 26-RU。RU 内部不是所有子载波都能传数据,每个 RU 都预留了导频子载波用于相位跟踪。
| RU 类型 | 总子载波数 | 数据子载波数 | 导频子载波数 | 对应带宽 |
|---|---|---|---|---|
| 26-RU | 26 | 24 | 2 | 约 2MHz |
| 52-RU | 52 | 48 | 4 | 约 4MHz |
| 106-RU | 106 | 102 | 4(或 6) | 约 8MHz |
| 242-RU | 242 | 234 | 8 | 20MHz |
做仿真时最容易犯的错是把 26-RU 当成 26 个数据子载波直接用。协议里 24 个数据 + 2 个导频才是完整的 26-RU。教学链路为了突出 OFDMA 原理可以暂时忽略导频,但一旦要算吞吐量或者做信道估计,就得严格遵守这个分配比例。导频的插入位置也不是随意选的,802.11ax 的 OFDMA 由 OFDM 继承来的导频值本质上就是一组伪随机序列,接收端知道这组序列后,可以快速计算残余相位偏差。
2.2 用 MATLAB 构造带 4 个用户的 OFDMA 发送帧
我一般先把问题缩小到 64 点 FFT。虽然真实 802.11ax 在 20MHz 下是 256 点 FFT,但 64 点结构更容易看清子载波索引的来龙去脉。下面是完整的发送端代码,它把 4 个用户分别放到两个 26-RU 的左右分段上,每个用户实际只用 13 个数据子载波,方便演示多用户映射逻辑。
% ax_ofdma_tx_demo.m % 802.11ax OFDMA 发送端最小示例:64 点 FFT,4 用户共享 4 段 RU clear; clc; nFFT = 64; % FFT 点数,教学用;真实 20MHz 为 256 cpLen = 16; % 短 GI 对应 0.8us,长 GI 时改成 32 nUsers = 4; % 同时发送的用户数 % MATLAB 的 ifft 输入频率顺序是 0, +1, ..., +31, -32, -31, ..., -1 freqGrid = [0:nFFT/2-1, -nFFT/2:-1]; % 26-RU 教学版:左半边低频和右半边高频各取 26 个子载波 % 实际 802.11ax 的 26-RU 内还包含 2 个导频,这里先省略 ru26_left = (-31:-6); % 低频侧 26 个子载波 ru26_right = (6:31); % 高频侧 26 个子载波 % 两个 26-RU 各拆成 13+13,模拟 4 个用户 ruPools = {ru26_left(1:13), ru26_left(14:26), ... ru26_right(1:13), ru26_right(14:26)}; % 每个用户随机产生 BPSK 符号 for u = 1:nUsers nDataSc = length(ruPools{u}); txBits{u} = randi([0 1], nDataSc, 1); syms{u} = 2 * txBits{u} - 1; % BPSK: 0 -> -1, 1 -> +1 [~, binIdx{u}] = ismember(ruPools{u}, freqGrid); end % 频域组帧:把 4 个用户的符号映射到各自子载波 fdFrame = zeros(nFFT, 1); for u = 1:nUsers fdFrame(binIdx{u}) = syms{u}; end % IFFT 变换,乘 sqrt(nFFT) 保证频域时域功率一致 tdSym = ifft(fdFrame, nFFT) * sqrt(nFFT); txSig = [tdSym(end-cpLen+1:end); tdSym]; % 加循环前缀代码里最关键的是freqGrid和ismember的配合。MATLAB 的ifft认为输入数组第一个元素对应频率 0,第 33 个元素对应频率 -32。如果直接把负频率子载波塞到数组开头,IFFT 后会得到错误时域波形。通过ismember把真实子载波编号转成数组下标,可以回避这类索引混乱问题。tdSym * sqrt(nFFT)是功率归一化,很多初学 OFDM 的仿真都在这里出错,导致 AWGN 信噪比计算偏大或偏小。
2.3 用前导 LTS 作为整包信道估计的参照符号
OFDMA 接收机要做均衡,就得知道每个子载波经历了什么幅度和相位变化。802.11ax 沿用 OFDM 的长训练序列(LTS)思想:发送端在数据符号前插入一段收发双方都知道的序列,接收端把收到的 LTS 与本地 LTS 相除,就得到信道响应的最小二乘估计。下面这段代码生成 64 点 LTS,并把它插在数据符号前面。
% 生成 64 点 LTS:只在数据子载波上放序列,其他位置补 0 ltsFreq = zeros(nFFT, 1); for u = 1:nUsers ltsFreq(binIdx{u}) = 1; % 占位,实际应放已知 BPSK 序列 end ltsTime = ifft(ltsFreq, nFFT) * sqrt(nFFT); ltsWithCp = [ltsTime(end-cpLen+1:end); ltsTime]; % 组帧:LTS + 数据符号 txFrame = [ltsWithCp; txSig];这里 LTS 的值全部置 1 只是为了演示,真实系统要使用协议规定的序列值。LTS 的高价值在于它的频域幅度恒定,与数据符号使用相同的 FFT 网格,所以信道估计不需要额外的插值逻辑。要注意的是 LTS 必须和数据符号用同一个nFFT和cpLen,否则频域子载波对应关系会错位。在 802.11ax 中,前导部分还区分了传统 LTS 和 HE-LTF,后者支持多流和 OFDMA 多用户估计,但原理都是同一套 LS 估计思想。
3. 接收机的同步与 LS 信道估计:MATLAB 中完成 FFT 窗口对齐、频域均衡与用户解映射
3.1 用滑动相关确定 FFT 窗口起点
发送端加循环前缀之后,时域信号变成了连续流。接收端要做的第一件事不是 FFT,而是找到每个符号从哪里开始。常见做法是利用 LTS 的重复性,把收到的信号与本地 LTS 做滑动互相关,相关峰出现的位置就是符号起点。MATLAB 里处理这个问题的典型写法如下。
% rxSig 为收到的时域信号,假设已经完成基本的自动增益控制 ltsLocal = ltsWithCp; % 本地完整的 LTS 波形 corrLen = length(rxSig) - length(ltsLocal) + 1; corr = zeros(corrLen, 1); for n = 1:corrLen seg = rxSig(n : n + length(ltsLocal) - 1); corr(n) = abs(sum(seg .* conj(ltsLocal))); end [~, peakIdx] = max(corr); % 相关峰位置 startIdx = peakIdx + cpLen; % 从 LTS 数据起点开始取数据符号滑动相关的计算量是 O(N*M),在大型仿真里不建议直接循环,可以把循环改成xcorr或矩阵化卷积。startIdx的推导逻辑是:相关峰落在 LTS 第一个采样点上,循环前缀占cpLen个采样点,所以数据符号的起点在峰位置之后跳过 CP。实际系统中还会有采样频偏和定时漂移,802.11ax 接收机通常在相关峰附近做抛物线插值来获得小数倍时延估计,但数学链路里用最大值搜索已经够用。
3.2 LS 信道估计函数及其频域平滑处理
收到 LTS 后,把它做 FFT 得到频域响应,与本地 LTS 的频域响应逐点相除,即可得到每个数据子载波的信道估计值。这一步叫最小二乘估计,公式是 H_hat = Y / X。它的优点是简单直接,缺点是在低信噪比时噪声会被直接放大。解决方法是把相邻子载波的估计做滑动平均,因为真实信道的频域响应通常是缓慢变化的。
function H = ax_estimate_channel(rxLts, ltsTime, binIdx, nFFT) % 输入接收到的 LTS 时域波形和本地 LTS 时域波形 % 返回所有数据子载波位置的信道估计 fdRx = fft(rxLts(1:nFFT), nFFT) / sqrt(nFFT); fdTx = fft(ltsTime(1:nFFT), nFFT) / sqrt(nFFT); H = zeros(nFFT, 1); H(binIdx{:}) = fdRx(binIdx{:}) ./ fdTx(binIdx{:}); % 频域滑动平均,窗口长度 3 个子载波 Hf = H; for k = 2:nFFT-1 Hf(k) = mean(H(k-1:k+1)); end H = Hf; end这段代码里binIdx{:}把所有用户的下标一次性传入,方便在多用户场景下复用。滑动平均窗口建议取奇数,3 或 5 都行,窗口太大会把频率选择性信道的深衰落抹平,导致均衡后残留误差变大。如果你的仿真信道模型只有两条径,窗口取 3 比较安全;如果做 5 条径以上的多径信道,可以取 1,也就是不做平滑,用原始 LS 估计直接均衡。
3.3 单个用户子载波提取与 BPSK 判决的完整流程
信道估计完成后,把数据符号做 FFT,再按每个用户的binIdx把对应子载波取出来,逐点除以信道估计值,就完成了频域单抽头均衡。整个接收链路代码继续接在前面发送端代码后面运行。
% 构造两径信道,验证接收机有效性 chTaps = [1, 0.5 * exp(-1j * 0.8)]; rxSigNoNoise = filter(chTaps, 1, txFrame); rxSig = awgn(rxSigNoNoise, 20, 'measured'); % 用 LTS 做粗同步,后面以 startIdx 为准取数据符号 startIdx = 1; % 实际由 3.1 的相关峰给出 % 接收数据符号 FFT rxSym = rxSig(startIdx + length(ltsWithCp) : startIdx + length(ltsWithCp) + nFFT - 1); fdRx = fft(rxSym, nFFT) / sqrt(nFFT); % 提取 LTS 并估计信道,再均衡 rxLts = rxSig(startIdx : startIdx + nFFT - 1); H = ax_estimate_channel(rxLts, ltsTime, binIdx, nFFT); for u = 1:nUsers rxUserSym = fdRx(binIdx{u}) ./ H(binIdx{u}); rxBits{u} = real(rxUserSym) > 0; % BPSK 硬判决 ber(u) = mean(rxBits{u} ~= txBits{u}); end fprintf('各用户 BER: %s\n', mat2str(ber, 3));均衡后的rxUserSym理论上应该落在实数轴 ±1 附近,所以 BPSK 直接取实部判断正负即可。这里startIdx + length(ltsWithCp)的写法假设 LTS 与数据符号紧挨着,且整个帧在信道中经历的延迟已经包含在同步结果里。两径信道chTaps的第一个抽头增益为 1,第二个抽头幅度 0.5、相位偏移 0.8 弧度,这已经足够产生明显的频率选择性衰落,能检验均衡算法是否真的在工作。如果你的 BER 算出来接近 0.5,优先检查 FFT 窗口是否取错,而不是怀疑信道估计代码。
4. RU 资源分配策略与比例公平算法:用 MATLAB 在多用户 OFDMA 中验证调度性能
4.1 为什么固定 RU 分配会浪费 OFDMA 的多用户增益
如果 4 个用户的信道质量完全一样,随便分配 RU 都行。但真实场景里每个用户的位置不同,某个用户可能在低频段信号好、高频段信号差,另一个用户恰恰相反。频选信道意味着 RU 的优劣是随用户变化的。固定分配一个用户永远占某个 RU 的策略,会把这个用户在最差频段的劣势放大。OFDMA 的调度增益就来自这里的自由度:每个 RU 送给当前信道条件最好的用户。
这种自由度在 OFDM 时代是不存在的,因为整条信道只服务一个用户。所以做 802.11ax 仿真时,不能只仿真物理层误码率,还要仿真资源分配算法。资源分配的目标函数一般是最大化系统吞吐量,但只最大化吞吐量会导致信道差的用户永远得不到资源。实际系统需要平衡吞吐量和公平性,比例公平算法就是在这个背景下被广泛使用的方案。
4.2 比例公平 RU 分配的贪心实现与 MATLAB 代码
比例公平的核心思想是:每个用户维护一个历史平均速率 T(t),每次调度计算当前信道增益与平均速率的比值,选择比值最大的用户。这个比值大,说明这个用户现在信道好,或者历史速率低应该被补偿。下面的代码实现了一个基于信道增益矩阵的贪心 RU 分配器。
function ruAlloc = ax_pf_allocate(chGain, alpha) % 比例公平 RU 分配 % chGain: nUsers x nRU 矩阵,表示每个用户在每条 RU 上的信道增益 % alpha: 公平性参数,0 表示纯吞吐量优先,1 表示完全比例公平 [nUsers, nRU] = size(chGain); ruAlloc = zeros(1, nRU); avgRate = ones(nUsers, 1); % 历史平均速率的初始值 for r = 1:nRU score = (chGain(:, r).^alpha) ./ avgRate; [~, uBest] = max(score); ruAlloc(r) = uBest; % 用当前 RU 的增益累积更新平均速率 avgRate(uBest) = avgRate(uBest) + chGain(uBest, r); end endalpha是调节公平性倾向的旋钮。alpha = 0时chGain.^0恒为 1,评分只看谁的历史速率低,这时候每个用户轮流获得 RU,完全公平但不保证吞吐量;alpha = 1时信道好的用户更容易拿到 RU,但也要考虑历史速率。实际仿真中可以先从alpha = 0.8开始调。更新avgRate的公式没有引入时间窗,这是一种简化,真实系统会用指数加权移动平均,比如avgRate = (1 - 1/T) * avgRate + 1/T * currentRate,其中 T 是窗口长度。
调用示例:
rng(1); nUsers = 4; nRU = 4; chGain = abs(randn(nUsers, nRU)).^2; % 模拟频选信道增益 ruAlloc = ax_pf_allocate(chGain, 0.8); disp(ruAlloc);分配结果会随着chGain的变化而变化。跑通这个函数后,可以把它接回第 2 章的发送端:遍历ruAlloc,把第 r 个 RU 的数据子载波填上对应用户的符号。这样你就把物理层和媒体接入层的仿真串成了一条完整的 OFDMA 系统链路。
4.3 用蒙特卡洛循环统计 BER 与吞吐量,验证 OFDMA 调度效果
有了资源分配算法,评估链路好坏的手段就是蒙特卡洛仿真。在每个信噪比点上跑几百帧,统计所有用户的平均误码率。下面是这套验证流程的整体框架,它把前面几段代码组合成了一个可重复的实验。
% 主实验脚本:遍历 SNR,统计多用户 OFDMA 平均 BER snrList = 0:5:25; berAll = zeros(length(snrList), nUsers); for s = 1:length(snrList) berSum = zeros(1, nUsers); trials = 500; for t = 1:trials chGain = abs(randn(nUsers, nRU)).^2; ruAlloc = ax_pf_allocate(chGain, 0.8); % 根据 ruAlloc 把用户符号填入对应 RU(代码省略) rxBits = ax_orun_frame(ruAlloc, snrList(s)); for u = 1:nUsers berSum(u) = berSum(u) + mean(rxBits{u} ~= txBits{u}); end end berAll(s, :) = berSum / trials; end这段伪代码把帧构造、发送和接收封装成了一个名为ax_orun_frame的函数,方便重复调用。验证时有两个观察要点:一是所有用户的 BER 曲线应该接近且随 SNR 单调下降;二是如果某个用户 BER 特别高,检查该用户是否总被分配到深衰落频段,这往往是调度算法公平性不足的体现。OFDMA 的调度优势从曲线形状能看得很直观:固定分配策略下深衰落用户的 BER 曲线会卡在某个平台期,比例公平分配则可以把它拉下来。
5. 从 MATLAB 仿真到工程验证:三个最值得检查的 OFDMA 实现细节
最后落在工程验证方法上。OFDMA 仿真最容易出现“单用户能通、多用户就乱”的问题,根源往往是以下三个细节。
第一,循环前缀长度与 GI 类型必须配套。802.11ax 在 20MHz 下,短 GI 是 0.8us,对应 16 个采样点(256 点 FFT),长 GI 是 1.6us 或 3.2us,对应 32 或 64 个采样点。如果你的仿真把 FFT 从 256 点缩成 64 点,CP 长度也必须按比例缩成 16,否则符号间干扰会直接淹没 OFDMA 的子载波正交性。验证方法是去掉信道中的多径,只加高斯噪声,此时 BER 应该只由噪声决定;如果加的是无噪声单径信道,BER 必须是 0。
第二,多用户功率归一化要按总功率算,不能每个用户单独归一化。发送端把 4 个用户叠加在同一帧里,IFFT 输入的总功率是所有用户子载波功率之和。如果每个用户做完归一化后再叠加进fdFrame,整体功率会被放大 4 倍,接收端的 AWGN 信噪比计算就全错了。我在 2.2 节用sqrt(nFFT)做整体归一化,如果把每个用户单独归一化,最后必须再乘1/sqrt(nUsers)做修正。
第三,验证 OFDMA 链路正确性的最快方法是先测一用户的理想信道。把nUsers改成 1,信道设为单位响应,如果 BER 不是 0,说明 FFT 网格和索引映射有 bug;通过后再改成多用户,逐步加入信道、加噪声、加资源分配算法。这样每个环节都有明确的通过标准,不至于到最后分不清是信道估计的问题还是调度算法的问题。用 MATLAB 的bertool打开模拟误码率参考曲线对比,也能快速定位实现偏差。
本文还有配套的精品资源,点击获取