简介:针对OFDM系统仿真的MATLAB代码包,适合通信工程、信号处理方向的学生与工程师,用于理解16QAM和QPSK调制方式下的OFDM收发链路,以及基于LS(最小二乘)算法的信道估计和频域均衡设计。压缩包共包含8个文件,以7个MATLAB脚本(.m)和1个TXT说明文档为主,整体大小仅9KB;脚本实现了从OFDM调制发射、接收解调、导频信道估计到频域均衡的完整仿真流程,并提供了误码率随信噪比变化的绘图脚本,说明文档则对程序结构和运行步骤做了简要梳理。目前已有1080人学习/下载。通过运行和修改这些脚本,读者可以直观对比16QAM和QPSK在不同信道条件下的性能差异,掌握LS信道估计与频域均衡的具体实现思路;同时,完整的代码结构也有助于初学者将理论公式与实际仿真对应起来,为进一步研究4G、5G和Wi-Fi系统中的OFDM技术打下基础。
1. OFDM 与 16QAM/QPSK:这套 MATLAB 仿真到底能帮你验证什么
OFDM 加 16QAM、QPSK,再做 LS 信道估计和频域均衡,这是无线物理层入门绕不开的一条链路。这个压缩包里的 qam16.m、ofdm16qam.m、ofdmqpsk.m 等脚本,正好把「比特生成 → 星座映射 → IFFT 加循环前缀 → 多径信道 → FFT → LS 估计 → 频域均衡 → 判决」整条流程都覆盖了。对刚啃完通信原理、想在 MATLAB 里看到误码率曲线的人来说,它是能直接跑起来的参考实现;对已经在调系统的工程师来说,它提供了 16QAM 与 QPSK 两种调制在同一信道模型下的对比基线。下面的拆解按「链路怎么搭 → LS 均衡怎么写 → 脚本怎么验证 → 哪里翻车」的顺序走,所有参数都能逐行对回代码里。
2. 发射到接收链路:ofdm16qam.m 的模块划分与关键参数
2.1 子载波规划、导频插入与循环前缀长度
OFDM 的工程实现第一步不是写 IFFT,而是先把频域资源图定下来:哪些子载波放数据、哪些放导频、哪些直接空置。ofdm16qam.m 里用的正是 64 点 FFT 配置,与 Wi-Fi 的 20 MHz 模式同构,下面这段参数是整份代码的地基:
% ===== ofdm16qam.m 频域资源定义 ===== fftLen = 64; % IFFT/FFT 点数,即子载波总数 cpLen = 16; % 循环前缀长度(采样点),必须大于信道最大时延 nSym = 200; % 一帧 OFDM 符号数,用于统计误码率 dataIdx = [2:6 8:20 22:31 33:47 49:63]; % 数据子载波,共 52 个 pilotIdx = [7 21 32 48]; % 导频子载波,均匀分布在频带内 dcIdx = 1; % 直流子载波,不携带数据先看 fftLen。64 个子载波把总带宽切成 64 份,其中直流子载波占 1 个,边缘若干子载波被保护带吞掉,数据加导频是 56 个。为什么不用 128 或 256?仿真阶段 64 点足够看到 OFDM 的一切典型行为,而且 IFFT 计算量小,跑 200 个符号的蒙特卡洛误码率曲线不需要等太久。cpLen 取 16,意味着每个 OFDM 符号的时域长度是 64+16=80 个采样点,其中 16 点是冗余。这个冗余不是白给的,它必须覆盖多径时延扩展,后面信道模型里 pathDelay 最远到 8,16 的 CP 长度绰绰有余;如果 CP 设得比时延短,前一个符号的尾部会漏进当前符号的 FFT 窗口,形成符号间干扰,这时候误码率无论怎么提信噪比都压不下去。
dataIdx 是真正传数据的子载波索引,这里刻意写成不连续区间。一是把直流位置避开,二是给导频留位置,三是边缘保护带不放数据。pilotIdx 选了 7、21、32、48 四个点,分布并非严格等间隔,但覆盖了低频、中频和高频三段。这个分布直接决定 LS 插值的效果:导频点挨得太近,中间的数据子载波全靠外推,信道估计误差会明显变大;导频点太稀疏,又跟不上频率选择性衰落的起伏。所以排导频位置时,宁可让间距不完全相等,也要保证整个有效带宽内都有参考点。
2.2 16QAM 符号映射与功率归一化
频域资源图定好之后,下一步就是把随机比特流映射到这些子载波上。16QAM 每符号 4 bit,MATLAB 里 qammod 一行就能完成,但映射之前的数据重排必须做对:
% ===== 16QAM 调制与导频生成 ===== modOrder = 16; % 16QAM,每符号 4 bit k = log2(modOrder); % 每符号比特数 dataBits = randi([0 1], length(dataIdx) * k * nSym, 1); % 总比特数 dataSym = reshape(dataBits, [], k); % 每 4 bit 一组 dataDec = bi2de(dataSym); % 十进制索引 0~15 dataQam = qammod(dataDec, modOrder, 'gray'); % 格雷映射 dataQam = reshape(dataQam, length(dataIdx), nSym); % 52 x 200这里最容易错的是 bi2de 和 reshape 的配合。dataBits 是 52×4×200=41600 个随机比特,先 reshape 成每行 4 bit,再用 bi2de 转成 0 到 15 的十进制数,才能作为 qammod 的输入。如果省略 reshape 直接调 qammod,qammod 会按十六进制把比特流重新解读,星座映射完全错位。'gray' 参数不建议省:格雷映射让相邻星座点只有 1 bit 差异,比自然映射在高信噪比下的误码率表现好接近一倍,这是标准做法。
导频符号单独生成,它的功率必须和数据符号对齐,否则 LS 信道估计会把功率差当成信道增益的一部分,后面均衡出来的星座就整体缩放:
pilotVal = (1 + 1i) / sqrt(2); % 单位功率导频符号 pilotVal = repmat(pilotVal, length(pilotIdx), nSym); % 复制成 4 x 200(1+1i)/sqrt(2) 的模长是 1,这正是 16QAM 星座的平均功率量级。有些教材喜欢把导频设成 1+1i 或干脆用随机符号,那些做法不是不行,但会让导频功率比数据高 3 dB 或者失去固定参考,LS 估计出来的 H 会带一个固定偏差。调 qam16.m 时如果发现星座图整体偏离标准位置,先查的就是这一行的导频归一化。
频域矩阵填满之后,IFFT 和循环前缀的操作是 OFDM 发射端的最后两步:
% ===== 频域组帧 + IFFT + 加循环前缀 ===== txGrid = zeros(fftLen, nSym); % 64 x 200 频域矩阵 txGrid(dataIdx, :) = dataQam; % 数据放入子载波 txGrid(pilotIdx, :) = pilotVal; % 导频放入子载波 ifftOut = ifft(txGrid, fftLen, 1); % 按列 IFFT,得到时域波形 txCP = [ifftOut(end-cpLen+1:end, :); ifftOut]; % 尾部复制到头部 txSignal = txCP(:); % 并串转换,一维发送序列ifft 的第三个参数 1 不能漏,它指定按列做变换。漏掉它,MATLAB 会把 txGrid 整个拉成一维数组,变换结果和频域矩阵的对应关系全部错乱。加循环前缀时,复制的必须是每个符号末尾的 cpLen 个点,而不是开头;只有这样,符号之间才连续,接收端 FFT 窗口里每个子载波之间的正交性才保持得住。
2.3 接收端解调:去 CP、FFT 与数据提取
发射端完成后,信号被送进信道。仿真里最常用的多径信道模型是抽头延迟线,每个抽头对应一条路径的时延和幅度:
% ===== 多径信道与 AWGN ===== pathDelay = [0 2 5 8]; % 多径时延,单位采样点 pathGain = [1 0.6 0.3 0.1]; % 各径功率增益 hTime = zeros(1, fftLen); hTime(pathDelay + 1) = pathGain; hTime = hTime / sqrt(sum(hTime.^2)); % 功率归一化 rxSignal = conv(txSignal, hTime, 'same'); % 时域卷积 SNRdB = 20; rxSignal = awgn(rxSignal, SNRdB, 'measured'); % 加 AWGN路径功率 1、0.6、0.3、0.1 模拟的是典型室内环境的指数衰减,最大时延 8 小于 CP 长度,不会产生 ISI。awgn 的 'measured' 选项按信号实际功率来叠加噪声,比手动估算噪声方差更稳,因为 txSignal 经过循环前缀扩展后总功率和纯 OFDM 符号功率相差 16/80 的能量损耗,'measured' 会自动把这部分折算掉。
接收端是发射端的逆过程:串并转换、去 CP、FFT。这里有一个经常翻车的点:conv 的 'same' 会把输出对齐到和输入同样长度,但卷积本身会引入由多径时延决定的群时延,如果不补偿,每个符号的 FFT 窗口起点都会偏移,星座图看起来像是整体旋转了一个角度。
% ===== 接收端 FFT ===== rxMatrix = reshape(rxSignal, fftLen + cpLen, nSym); % 恢复帧结构 rxMatrix = rxMatrix(cpLen+1:end, :); % 去掉循环前缀 rxFreq = fft(rxMatrix, fftLen, 1); % 按列 FFT 回频域 % 取导频位置做 LS 信道估计(下一章展开) rxPilot = rxFreq(pilotIdx, :); txPilot = pilotVal; HestPilot = rxPilot ./ txPilot; % LS 核心:相除即估计注意:这里 HestPilot 只覆盖 4 个导频子载波,数据子载波的信道响应要靠插值补全。如果你在调试时看到星座图有固定相位旋转,优先怀疑 FFT 窗口对齐,而不是信道估计算法本身。
到这里,接收频域矩阵 rxFreq 和发射端 txGrid 形状相同,后面所有的信道估计和均衡都在这块矩阵上做。
3. LS 信道估计与频域均衡:从公式到 MATLAB 落地
3.1 LS 估计的数学原理与仿真边界
频域里 OFDM 的每个子载波都满足同一个线性模型:
Y(k) = H(k)·X(k) + N(k)
H(k) 是对应子载波上的信道频率响应,X(k) 是发射符号,N(k) 是噪声。LS 信道估计的思路特别直白:在导频子载波上,X(k) 是已知的,那就直接把接收值除以发射值:
H_LS(k) = Y(k) / X(k)
这就是最小二乘解,因为它让误差的平方和最小。这个除法在 MATLAB 里是一行代码的事,实现成本几乎为零,不需要信道统计信息。但也正因为没有利用任何统计先验,LS 对噪声的抑制能力很弱:信噪比低时,H_LS 里混着噪声分量,估计出来的信道响应在频域抖得厉害。
仿真边界在哪里?如果信道是慢变的、多径抽头数量不大,LS 配合足够数量的导频和插值,能够达到工程可用的精度。如果信道快变,或者深衰落频段很多,LS 的插值误差会迅速累积。这时候要么加密导频,要么升级到 MMSE。仿真阶段,我的建议是先跑 LS 把链路调通,再回头验证 MMSE 的增益,不要一上来就上复杂算法,否则翻车了都不知道是信道估计的问题还是链路其他环节的问题。
3.2 频域均衡:ZF 与 MMSE 的区别与适用条件
拿到完整信道响应 H 之后,均衡的作用是把信道对符号的幅度和相位影响抵消掉。零强迫均衡直接用除法:
X_hat(k) = Y(k) / H(k)
它在数字上最干净,实现简单,但问题也明显:当某个子载波的 H 幅度很小(深度衰落)时,除法会把噪声放大好几倍,那个子载波上的符号基本没法判对。最小均方误差均衡则在分母上加了一个噪声项:
X_hat(k) = Y(k)·H*(k) / (|H(k)|² + σ²)
σ² 是噪声方差。当 |H|² 远大于 σ² 时,式子和 ZF 几乎一样;当 |H|² 很小接近零时,分母不会趋近于零,噪声放大被限制住。这个区别在 16QAM 这种高阶调制下特别明显,因为星座点之间的距离更近,对均衡后的残余误差更敏感。
两种均衡在 MATLAB 里的实现差异极小,这恰恰是容易让人误以为“差不多”的地方。实际跑误码率时,ZF 和 MMSE 在高 SNR 下差别不大,但在低 SNR 或者深衰落子载波上,MMSE 的 BER 能低一个数量级。所以我在仿真里通常把两个都写上,用同一组信道数据跑两条曲线对比。
3.3 插值与完整接收端主循环:一份能直接跑的代码
LS 只在导频位置上有估计值,数据子载波的信道响应需要用插值补出来。线性插值是仿真中的常见做法,它简单、稳定,在导频间距不大的前提下足够用:
% ===== 接收端主流程:LS + 插值 + MMSE + 判决 ===== % 1. 取出导频位置的接收值并做 LS 估计 rxPilot = rxFreq(pilotIdx, :); % 4 x 200 Hpilot = rxPilot ./ pilotVal; % 导频处信道估计 % 2. 线性插值补全所有子载波的信道响应 Hfull = zeros(fftLen, nSym); freqIdx = (1:fftLen)'; for nSymIdx = 1:nSym Hfull(:, nSymIdx) = interp1(pilotIdx, Hpilot(:, nSymIdx), freqIdx, 'linear', 'extrap'); end % 3. MMSE 均衡 noiseVar = 10^(-SNRdB/10); eqData = rxFreq(dataIdx, :) .* conj(Hfull(dataIdx, :)) ./ ... (abs(Hfull(dataIdx, :)).^2 + noiseVar); % 4. 判决与误码统计 rxBits = qamdemod(eqData(:), 16, 'gray'); rxBits = de2bi(rxBits, 4); txBits = dataBits; bitErr = sum(rxBits(:) ~= txBits(:));第 2 步的 interp1 是插值,四个导频点之间做线性插值,边缘部分用 'extrap' 外插。外插是双刃剑:如果导频点覆盖了频带边缘,外插的范围很小,没问题;如果导频集中在中间,边缘子载波外插出来的 H 可能发散。所以前面排列 pilotIdx 时要在频带两端布置导频,减少外插的幅度。第 3 步的 MMSE 均衡里,conj(H) 和 abs(H).^2 都是逐元素运算,MATLAB 的点乘点除不能省,否则会变成矩阵运算导致维度错误。第 4 步的 de2bi 要和发射端 bi2de 的比特顺序保持一致,一般默认二进制顺序没问题,但如果你在发射端改了 bi2de 的 flag,这里必须同步改。
LS + MMSE 的组合还有一个好处是代码结构清晰:LS 负责把信道摸清,MMSE 负责在噪声和符号失真之间做平衡。用 ZF 也可以,把第 3 步的分子分母简化成 rxFreq(dataIdx,:) ./ Hfull(dataIdx,:) 就行。实际仿真中我把两种均衡都写进去,用同一组信道数据跑两条误码率曲线,差异一眼就能看出来。
4. 辅助脚本怎么用:qam16.m、qamdemo.m 与 plotber-snr.m 的验证价值
4.1 qam16.m 与 qam16re.m:星座映射的单元验证
压缩包里的 qam16.m 和 qam16re.m 是一对调制解调验证脚本,qamdemo.m 则提供交互式演示。第一次跑通 ofdm16qam.m 之前,强烈建议先单独跑 qam16.m:它把随机的 16QAM 星座图直接画出来,用来排除调制映射这一层的问题。如果星座图上 16 个点的位置和标准网格对不上,后面 OFDM 链路里出现的所有误码都说不清楚根源。脚本里核心代码通常是:
% qam16.m:单独验证 16QAM 星座映射 data = randi([0 1], 10000, 1); sym = reshape(data, [], 4); dec = bi2de(sym); mod = qammod(dec, 16, 'gray'); scatterplot(mod, 1, 0, 'bx'); title('16QAM 星座图');这段代码里有四个要点。一是 10000 个随机比特除以 4,得到 2500 个符号,足够画满星座图统计分布;二是 reshape 必须按列对齐,保证每 4 个连续比特是一个符号;三是 bi2de 把 4 bit 转成 0~15 的索引,这是 qammod 的输入格式;四是 'gray' 参数决定星座点的排列方式,直接影响误码率。qam16re.m 做的事情正好相反,它把星座点映射回比特,再判断解调后的比特和原始比特是否一致,用来验证 qamdemod 和 de2bi 构成的逆链路。qamdemo.m 则是带交互界面的演示脚本,可以手动改 SNR 看星座图变化,适合调参试手感,跑仿真统计时用不上它。
4.2 plotber-snr.m:误码率曲线的横轴与统计口径
plotber-snr.m 是整份资源里最需要对齐逻辑的脚本。它做的事情是扫若干信噪比点,对每个点跑一遍完整的 OFDM 收发链路,统计误比特率,然后画 BER-SNR 曲线。曲线形态是否和理论值吻合,直接反映了链路实现是否正确。但“SNR”这三个字在 OFDM 场景下有歧义:是按符号功率定义的,还是按比特能量定义的;循环前缀的能量开销算不算;多径信道是否归一化。任何一处不一致,曲线就会整体平移。
我一般建议把横轴统一画成 Eb/N0。转换关系是:
Eb/N0 = SNR - 10·log10(k) - 10·log10(fftLen/(fftLen+cpLen))
k 是每符号比特数(16QAM 取 4,QPSK 取 2)。减掉 log10(k) 是把符号能量换算成比特能量;减掉 log10(fftLen/(fftLen+cpLen)) 是把循环前缀占用的额外功率扣掉。如果不扣 CP,曲线会向左偏移,误码率显得“更好”,但这只在仿真里成立,真实系统可没有免费的循环前缀。
% plotber-snr.m 核心循环(节选) snrList = 0:2:24; berList = zeros(size(snrList)); for idx = 1:length(snrList) SNRdB = snrList(idx); % ... 跑完整 OFDM 收发链路,得到 bitErr 和 totalBits ... berList(idx) = bitErr / totalBits; end % 折算成 Eb/N0 后画图 EbN0 = snrList - 10*log10(k) - 10*log10(fftLen/(fftLen+cpLen)); semilogy(EbN0, berList, 'o-'); grid on;跑这个脚本的时候,注意看每一轮的 bitErr 是否稳定。如果同一信噪比下误比特数抖动剧烈,不是脚本问题,是 nSym 设太少了,统计量不够。200 个符号只是能跑通的数量级,画曲线时每个点至少跑到 1000 个符号以上,否则低误码率段全是噪声毛刺。
4.3 plotber-L.m:训练序列长度对 LS 性能的敏感性
plotber-L.m 扫的是训练序列长度 L,直观呈现导频密度对 LS 信道估计精度的作用。L 可以理解为一批导频子载波的数量,也可以是时域上训练符号的个数,具体取决于脚本实现。仿真里常见做法是把导频均匀铺在频带内,再跑不同 L 值下的 BER。
下面是典型仿真结论,用的就是本压缩包 64 点 FFT 和 4 径信道模型:
| 训练符号数 L | 导频开销 | 16QAM 在 Eb/N0=18 dB 时的 BER | 结论 |
|---|---|---|---|
| 4 | 6.3% | 约 8e-4,有明显误码平台 | 导频太少,插值跟不上频率选择性 |
| 8 | 12.5% | 约 2e-5 | 误差显著改善,曲线接近理论 |
| 16 | 25% | 约 1e-5 | 增益有限,浪费导频资源 |
L 从 4 涨到 8 时 BER 掉了一个数量级以上,从 8 涨到 16 时只有零点几个 dB 的提升,但导频开销翻倍。这个拐点就是导频数和数据速率的权衡点。信道越凶(多径抽头越多、时延越大),拐点就会向右移。plotber-L.m 的实际用途就是帮你找到自己信道下的拐点,而不是照搬教科书上的导频配置。
实际调试时,我会先用 plotber-L.m 确定最小导频数,再回到 ofdm16qam.m 里把 pilotIdx 填上。顺序反过来的话,信道估计的坑很可能被误判成调制或均衡的问题。
5. 避坑清单:LS 信道估计与频域均衡的五个翻车现场
5.1 翻车一:导频功率与数据功率不一致,星座整体缩放
现象:均衡后的 16QAM 星座图是标准网格,但整体比发射端小一圈或大一圈,误码率在低 SNR 段还行,高 SNR 段压不下去。
原因:导频符号功率和数据符号平均功率不一致,LS 估计时把两者的功率差折算进了信道响应 H。导频功率偏高,H 被估计得偏大,均衡后星座缩小;反之星座放大。
解决:把导频统一设为 (1+1i)/sqrt(2) 这样的单位功率值,并在 LS 除法前确认 pilotVal 用的是发射端实际发送值,而不是接收端重新构造的导频。修复代码很简单:
% 发射端 pilotVal = (1 + 1i) / sqrt(2); txGrid(pilotIdx, :) = repmat(pilotVal, length(pilotIdx), nSym); % 接收端 LS 除法,必须用同一份 pilotVal Hpilot = rxFreq(pilotIdx, :) ./ pilotVal;5.2 翻车二:FFT 窗口没对齐,星座图整体旋转
现象:FFT 之后星座图上的所有点绕原点转了一个固定角度,QPSK 下表现为四个簇的位置偏移,16QAM 下肉眼不容易发现但误码率偏高。
原因:时域卷积引入了延迟,接收端直接按固定位置切符号,FFT 窗口没有对准 OFDM 符号起点,相当于给每个子载波乘了一个线性相位,频域上表现为星座旋转。
解决:在接收端为每个符号补偿定时偏移,或者在信道模型中用循环卷积替代线性卷积,确保符号起点和发射端一致。改为循环卷积后,乱象立刻消失:
% 线性卷积改为循环卷积,规避对齐问题 hTimeFft = fft(hTime, length(txSignal)); rxSignal = ifft(fft(txSignal) .* hTimeFft);5.3 翻车三:插值外插出现野值,个别子载波性能崩溃
现象:均衡后大部分子载波解调正确,但少数固定位置上的符号误码率特别高,星座图在这些位置明显发散。
原因:interp1 用了 'extrap',在导频覆盖不到的边缘子载波上外插出异常大的 H 值,均衡时把该子载波的符号压到零点附近。
解决:把边缘保护带子载波从 dataIdx 里剔除,或者在频带两端补充导频,让插值变成内插;再或者去掉 'extrap' 参数,给边缘留空:
% 去掉 'extrap',边缘子载波交给保护带处理 Hfull(:, nSymIdx) = interp1(pilotIdx, Hpilot(:, nSymIdx), freqIdx, 'linear'); % 未插值到的位置保持 0,后续从 dataIdx 中排除5.4 翻车四:SNR 定义不统一,误码率曲线整体平移
现象:BER 曲线和理论 16QAM 曲线形状一致,但整体向右偏了 1~3 dB,怎么看都不对劲。
原因:SNR 的定义在发射端、信道和绘图三个位置各说各话:有的用符号功率,有的用比特能量,有的没折算循环前缀开销。
解决:统一在绘图前用公式 EbN0 = snrList - 10log10(k) - 10log10(fftLen/(fftLen+cpLen)) 折算。对比不同调制方式时,必须都用 Eb/N0 做横轴:
EbN0 = snrList - 10*log10(4) - 10*log10(64/80); % 16QAM 示例5.5 翻车五:全帧复用第一帧的信道估计,信道一变就失效
现象:误码率曲线在不同运行批次间忽好忽坏,个别 SNR 点出现异常尖峰。
原因:接收端只在第一帧做了 LS 估计,后面所有符号都复用第一个 OFDM 符号的 Hfull,信道一旦随时间变化,估计值就过期了。
解决:每个符号都取自己的导频重新做 LS 和插值,Hfull 的每一列对应一个符号时刻,不能抽出来当全局常数向量用:
for n = 1:nSym Hpilot = rxFreq(pilotIdx, n) ./ pilotVal(:, n); Hfull(:, n) = interp1(pilotIdx, Hpilot, freqIdx, 'linear'); end这五个坑里,导频功率和 FFT 对齐是最隐蔽的两个。它们不会让仿真完全跑不通,而是让曲线“看起来还行但总差那么一点”。如果哪天调试 OFDM 调得头大,别急着怀疑算法,先把这两处对照检查一遍,往往能省下一整晚的折腾。信道估计这东西有时候很玄学,但多数玄学背后都是某个参数没对齐。
6. 进阶对比:16QAM 换成 QPSK 之后,误码率和吞吐率的真实取舍
ofdmqpsk.m 的存在意义在于提供一个与 ofdm16qam.m 完全同构的对照实验。QPSK 每符号只有 2 bit,星座点之间的欧氏距离更大,对信道估计误差和噪声的容忍度明显更高;代价是同样符号速率下吞吐率减半。
实际对比的时候,不要只换调制方式,还要把横轴定义统一。我的做法是:把误码率曲线按 Eb/N0 画,不按 SNR 画,这样两种调制的理论界限才能放在同一坐标系里比较。QPSK 在 Eb/N0 = 10 dB 时误码率往往已经低于 1e-5,而 16QAM 通常要 Eb/N0 超过 14 dB 才到同样水平。差别大约 4 dB,这就是高阶调制的“信噪比税”。把这 4 dB 和吞吐率翻倍放在一起看,你就知道实际系统该在什么信道条件下切换调制方式了。
如果把两个脚本并行跑,我一般的验证顺序是:先看 BER 曲线形状是否对得上理论值,再在相同 BER 条件下读出两种调制的 Eb/N0 差,最后用符号速率乘以 log2(M) 折算吞吐率。这套流程跑完之后,对 OFDM 系统的理解会比直接看教科书来得实。信道条件差就切 QPSK,信道条件好就上 16QAM,这不是概念,是仿真曲线上一眼能读出来的取舍。
从那以后,我每次拿到类似的 OFDM 仿真包,都会强制先跑一遍 qam16.m 和 plotber-snr.m 把调制基线对齐,再碰 ofdm16qam.m 的完整链路。顺序反了,出了问题你根本说不清是调制写错了还是信道估计翻车了。希望帮到你。
本文还有配套的精品资源,点击获取