ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

OFDM MATLAB链路仿真:同步、信道估计与BER避坑

OFDM MATLAB链路仿真:同步、信道估计与BER避坑

简介:这份 MATLAB 仿真包面向通信工程专业学生、研究人员与工程师,围绕 OFDM 正交频分复用系统的完整链路搭建,帮助读者把多载波调制、循环前缀抗 ISI、频域均衡等抽象概念落到可分析的程序实现中。压缩包共 26 个文件,约 125KB,以 19 个 txt 训练序列与数据记录、4 个 dat 发送/接收数据文件、2 个 m 脚本和 1 个 doc 流程说明为主,便于按发送端、接收端与说明文档分层查阅。目前已有 790 人学习,具备一定参考热度。内容覆盖符号生成、IFFT 复用、循环前缀添加、频率选择性信道模拟、FFT 接收、信道估计与均衡、解调判决及 BER/SER 评估,并保留子载波数量、调制方式、CP 长度和信道参数等可调空间,适合用于课程实验、算法对比与 OFDM 收发链路理解。

1. 从一条平掉的 BER 曲线说起:这份 OFDM 仿真程序能干什么

有位做课设的同学贴过一张图,信噪比从 0 扫到 20dB,误码率曲线走到 10⁻² 附近就不再下降,后来查出来是循环前缀长度比信道最大时延还短,符号间干扰把均衡的结果全部污染了。OFDM 就是这么一种东西:公式看一眼就懂,真正手写一遍链路,同步、导频图样、功率归一化这些细节能卡人半个月。这份 MATLAB 完整仿真程序把发射机、多径信道、接收机、同步与信道估计、误码率统计一次性全部搭好了,基带仿真,不依赖通信工具箱也能改。它适合三类人:通信课设和毕设要做链路级仿真的学生、刚转物理层方向的工程师、以及需要一条干净的 baseline 来做算法对比的人。下面按「链路怎么走 → 参数怎么配 → 关键算法怎么写 → 哪里会翻车 → 怎么改成自己的」,把整个工程拆开。

2. 链路与文件职责:一个 OFDM 符号从比特到基带波形的完整通路

先把整体走一遍,知道每个函数在哪一环,改起来才不会摸黑。这份工程是标准的「一发一收 + 信道」结构,主脚本只做参数装配和循环扫点,所有信号处理都拆成独立函数,发射端和接收端严格对称。好处是你可以单独替换其中一环,比如把 LS 估计换成 MMSE,其他代码一行不动。

2.1 目录结构与各文件职责

工程规模不大,但分工清晰,典型布局如下:

文件职责改动频率
main_ofdm.m参数装配、SNR 循环、结果绘图高
ofdm_tx.m比特映射、导频插入、IFFT、加 CP中
ofdm_channel.mAWGN 与多径瑞利信道、时延抽头配置高
ofdm_rx.m去 CP、FFT、信道估计、均衡、解映射中
sync_timing.m基于 CP 或训练序列的定时与粗频偏估计低
est_channel.mLS / MMSE 信道估计,输出 H 估计中
utils/星座图、BER 理论曲线、QAM 归一化等小工具低

main_ofdm.m是唯一带for snrIdx = 1:length(cfg.SNRdB)的地方,所有可扫的参数都从cfg结构体读,这样你想加一组「不同 CP 长度对比」只要在外面再套一层循环,不必动内部函数。这种「单入口 + 结构体传参」的写法在通信仿真里很常见,比满地散落全局变量好维护得多。

2.2 发射机链路:映射、导频插入与 IFFT

发射端的核心是把一串比特铺到 N 个正交子载波上。下面这段是整条链路的起点,也是后面所有对齐的基准:

function [txWave, txGrid] = ofdm_tx(cfg, bits) % 输入:cfg 参数结构体;bits 一维比特流 % 输出:txWave 串行基带波形;txGrid 频域资源栅格 N = cfg.Nfft; % FFT 点数,常用 64 / 256 / 1024 Ncp = cfg.Ncp; % 循环前缀长度,必须 >= 信道最大时延抽头 M = cfg.M; % 调制阶数:4 / 16 / 64 bps = log2(M); % 每符号比特数 pilotPos = 1:cfg.NpilotStep:N; % 梳状导频,等间隔插入 dataPos = setdiff(1:N, pilotPos); % 剩余位置承载数据 nData = numel(dataPos); % 每符号有效数据子载波数 % 1) 比特 -> 星座索引 -> 复数符号 symIdx = bi2de(reshape(bits, bps, []).', 'left-msb'); dataSym = qammod(symIdx, M, 'UnitAveragePower', true); % 2) 资源映射:先算符号数,再按列填格 nSym = floor(numel(dataSym) / nData); txGrid = zeros(N, nSym); txGrid(pilotPos, :) = repmat(cfg.pilotSym, 1, nSym); txGrid(dataPos, :) = reshape(dataSym(1:nSym*nData), nData, nSym); % 3) IFFT + 加 CP,乘 sqrt(N) 做功率归一化 txTime = ifft(txGrid, N, 1) * sqrt(N); txCP = [txTime(end-Ncp+1:end, :); txTime]; % 4) 并串输出 txWave = txCP(:); end

几个参数值得说清楚。Ncp不是随便填的,它要大于信道最大时延扩展对应的样点数,否则相邻符号会串进来,均衡器再准也救不回来,前面那条平掉的曲线就死在这。cfg.pilotSym通常取全 1 或者一个固定的 QPSK 序列,接收端必须用同一份副本做最小二乘除法。'UnitAveragePower', true这个开关千万别省,默认的qammod输出平均功率不是 1,后面按 SNR 加噪时噪声功率就会算错,理论曲线能差出 3dB 来。ifft后乘sqrt(N)是为了让时域和频域功率一致,MATLAB 的ifft自带 1/N 缩放,不补回来,接收端 FFT 之后幅度会整体偏小。

reshape(dataSym, nData, nSym)这里的顺序很容易搞反。MATLAB 是列优先,reshape按列填,所以第 i 列正好对应第 i 个 OFDM 符号,跟txGrid的列语义一致。如果写成行优先,解调出来的比特顺序全乱,误码率会稳定在 0.5 附近,看起来像「完全没同步」,其实是排列错了。

2.3 接收机链路:去 CP、FFT、估计、均衡

接收端是发射端的逆运算,但由于要处理定时偏移和信道畸变,多了同步和估计两步:

function [bitsOut, eqSym, Hest] = ofdm_rx(cfg, rxWave) N = cfg.Nfft; Ncp = cfg.Ncp; pilotPos = 1:cfg.NpilotStep:N; dataPos = setdiff(1:N, pilotPos); % 1) 定时同步:找出帧起点(CP 自相关或训练序列互相关) startIdx = sync_timing(cfg, rxWave); rxWave = rxWave(startIdx : end); % 2) 串并 + 去 CP + FFT symLen = N + Ncp; nSym = floor(numel(rxWave) / symLen); rxMat = reshape(rxWave(1:nSym*symLen), symLen, nSym); rxMat = rxMat(Ncp+1:end, :); % 丢掉循环前缀 rxGrid = fft(rxMat, N, 1) / sqrt(N); % 与发端 sqrt(N) 抵消 % 3) 导频处做 LS 信道估计,再插值到全部子载波 Hp = rxGrid(pilotPos, :) ./ repmat(cfg.pilotSym, 1, nSym); Hest = interpolate_channel(Hp, pilotPos, N); % 线性/DFT 插值 % 4) 迫零均衡 eqGrid = rxGrid ./ Hest; eqSym = eqGrid(dataPos, :); % 5) 解映射回比特 idx = qamdemod(eqSym(:), cfg.M, 'UnitAveragePower', true); bitsOut = de2bi(idx, log2(cfg.M), 'left-msb').'; bitsOut = bitsOut(:); end

sync_timing返回的startIdx是整帧的样点起点,默认按整帧处理;如果要逐符号同步,把它挪到循环里即可。第 4 步的迫零均衡rxGrid ./ Hest写法最省事,但深衰落子载波上Hest接近 0 时噪声会被放大,实测在频率选择性信道下比 MMSE 差 2~3dB,这就是后面二次开发里最值得换掉的一处。

注意fft(rxMat, N, 1) / sqrt(N)这个除法和发端的乘sqrt(N)是配对的。有人一半抄一半改,只改了发端没改收端,星座图会整体缩小成一个小圈,看着像「信号太弱」,其实是归一化对不上。跑之前先把txGrid和rxGrid(无噪无信道时)逐元素比一下,误差在 1e-12 量级才算链路通。

3. 参数面板与帧结构:决定仿真跑不跑得对的那几个数

链路搭好只是骨架,参数配错一样出不来正确结果。这一章把cfg里真正影响结果的字段拎出来,逐个说明该填什么、为什么。

3.1 核心参数表与取值建议

参数含义典型取值踩坑点
NfftFFT 点数64 / 256 / 1024太小则 CP 开销大,太大则仿真慢
NcpCP 长度Nfft/8 ~ Nfft/4必须大于信道最大时延样点数
M调制阶数4 / 16 / 64阶数越高对相位噪声越敏感
NpilotStep导频间隔4 ~ 8大于相干带宽就估不准
SNRdB信噪比扫描范围0:2:20是 Es/N0 还是 Eb/N0 要说清
nFrame每点仿真帧数100 ~ 1000太少则高 SNR 处统计不到误码

Ncp和信道的关系是最容易搞混的。假设采样率 20MHz、信道最大时延 1μs,对应 20 个样点,那Ncp至少要 20,工程上会留一倍余量取 32 或 40。填小了,符号间干扰直接吃掉 CP 的保护作用,表现就是高 SNR 下 BER 曲线出现「地板效应」,怎么加功率都压不下去。

nFrame关系到高 SNR 点的统计可信度。误码率到 10⁻⁴ 时,一帧如果只有几千比特,你可能连一个错误都碰不到,画出来的曲线是断的或者跳到 0。稳妥做法是按「至少累计 100 个错误比特」动态停,而不是固定帧数:

errCnt = 0; bitCnt = 0; frameIdx = 0; while errCnt < 100 && frameIdx < cfg.maxFrame frameIdx = frameIdx + 1; bits = randi([0 1], cfg.nBitsPerFrame, 1); % ... 过发端、信道、收端 ... errCnt = errCnt + sum(bitsOut ~= bits(1:numel(bitsOut))); bitCnt = bitCnt + numel(bitsOut); end ber = errCnt / bitCnt;

while的双条件是必要的:errCnt < 100保证统计置信度,frameIdx < maxFrame防止在低 SNR 处死循环——那种情况下错误多到爆,其实几百帧就够,但如果没有上限,某次参数改错导致误码率恒为 0.5,循环会一直跑下去。

3.2 导频图样:梳状、块状与格状的选择

导频怎么摆,直接决定信道估计能做到多准。这份工程默认用梳状(comb)导频,也就是每隔NpilotStep个子载波插一个,优点是每个 OFDM 符号上都有导频,能跟踪时间变化的信道;缺点是频域采样稀疏,遇到深衰落会插值失真。

块状(block)导频是整符号铺满,适合慢衰落,估计精度高但开销大。格状(grid)是两者的折中,在时频二维上打点,适合高速移动场景。改导频图样只需要动pilotPos的定义:

% 梳状:每个符号内等间隔插入 pilotPos = 1:cfg.NpilotStep:N; % 块状:第 1 个符号全导频,其余全数据(需在 txGrid 里按列区分) pilotPos = 1:N; txGrid(:, 2:end) = dataGrid; % 格状:时频二维打点 [tt, ff] = meshgrid(1:cfg.NpilotStepT:nSym, 1:cfg.NpilotStepF:N);

导频间隔的选择有个经验判据:频域间隔要小于相干带宽对应的子载波数,时域间隔要小于相干时间对应的符号数。基站低速场景下相干带宽往往有几十个子载波宽,间隔取 4~8 都够;高速移动时相干时间可能只有几个符号,格状导频的时域间隔就得压到 2 以内。

3.3 信道模型:从 AWGN 到多径瑞利

仿真的说服力全在信道模型上。只跑 AWGN 只能验证链路通不通,要证明算法有效必须上多径。这份工程把两种模型都放在ofdm_channel.m里,通过cfg.chanType切换:

function [rxWave, Htrue] = ofdm_channel(cfg, txWave) switch cfg.chanType case 'awgn' % 纯高斯信道:只有噪声,没有多径 Htrue = ones(cfg.Nfft, 1); rxWave = txWave; case 'multipath' % 多径瑞利:每径独立复高斯,功率按指数衰减 delays = cfg.pathDelay; % 各径时延(样点) gains = (randn(cfg.nPath,1)+1j*randn(cfg.nPath,1))/sqrt(2); gains = gains .* sqrt(cfg.pathPow(:)); % 按功率分配 h = zeros(max(delays)+1, 1); h(delays+1) = gains; % 抽头位置对齐 rxWave = conv(txWave, h, 'same'); % 卷积信道 Htrue = fft(h, cfg.Nfft); % 频域真实响应,用于对比 end % 按 SNR 加噪,注意噪声功率要按信号实际功率缩放 sigPow = mean(abs(rxWave).^2); noiseVar = sigPow / 10^(cfg.SNRdB/10); rxWave = rxWave + sqrt(noiseVar/2)*(randn(size(rxWave))+1j*randn(size(rxWave))); end

conv(..., 'same')这个选项有讲究。用'full'会让输出比输入长,接收端按nSym*symLen切的时候会错位,表现是整帧解出来全是噪声。用'same'保持长度一致,但代价是尾部有截断,所以仿真里通常会在发端多送一个符号做保护,收端丢掉最后一段。Htrue是留给对照用的,把Hest和它画在一张图上,能立刻看出估计器在哪几个子载波上跑偏。

加噪那两行也常见写错的:噪声方差必须按接收信号的实际功率算,而不是按发射功率。如果信道有衰落,接收功率已经变了,还按发端功率算 SNR,实际信噪比比设定值高出一截,画出来的 BER 曲线会莫名其妙比理论值还好,看着像「算法超越理论」,其实是噪声加少了。

4. 关键算法拆解:同步、信道估计与均衡的落地写法

链路能跑通之后,真正决定性能的就是这三个模块。它们也是二次开发时最常被替换的部分,值得逐段看清楚。

4.1 定时同步:用 CP 自相关找帧头

OFDM 有个天然的同步红利:CP 是符号尾部的复制,把信号和它自己延迟 N 个样点做相关,在正确位置会出现一个峰值。这就是最常见的 CP 自相关法:

function startIdx = sync_timing(cfg, rxWave) N = cfg.Nfft; Ncp = cfg.Ncp; L = N + Ncp; % 滑动窗:窗口前半段与后半段(相隔 N 点)做共轭相关 metric = zeros(numel(rxWave)-2*N, 1); for n = 1:numel(metric) seg1 = rxWave(n : n+N-1); % 对应 CP + 数据前段 seg2 = rxWave(n+N : n+2*N-1); % 对应数据后段 metric(n) = abs(sum(seg1 .* conj(seg2))); end % 取最大相关峰,回退一个 CP 长度得到符号起点 [~, peak] = max(metric); startIdx = max(1, peak - Ncp);

metric里存的是相关值的模,取max得到峰值位置。之所以要- Ncp,是因为相关窗的起点其实落在 CP 的开头,而我们要的是去掉 CP 之后的 FFT 窗起点。这一步偏差几个样点问题不大,OFDM 对定时误差有 CP 长度的容限,只要误差落在 CP 内,子载波之间的正交性不受破坏,最多引入一个线性相位旋转,均衡时会被一起吸收掉。

真正的坑在于多径。如果信道有强径,相关峰会出现平台甚至双峰,max可能挑到后一个径上,导致定时偏后。稳妥做法是取「第一个超过峰值 80% 的位置」而不是全局最大值:

thresh = 0.8 * max(metric); startIdx = find(metric > thresh, 1, 'first') - Ncp;

这个改法在多数多径场景下更稳,代价是实现复杂度略高一点。

4.2 频偏估计与补偿:相位旋转的连锁反应

收发晶振不可能完全一致,残余频偏会让星座图逐符号旋转。CP 自相关同样能估频偏——相关值的相位角正比于频偏:

% 在同步确定的位置上重新算一次相关,取相位 seg1 = rxWave(startIdx+Ncp : startIdx+Ncp+N-1); seg2 = rxWave(startIdx+Ncp+N : startIdx+Ncp+2*N-1); angDiff = angle(sum(seg1 .* conj(seg2))); freqOff = angDiff / (2*pi*N); % 归一化频偏(单位:子载波间隔) % 补偿:对时域信号乘反向旋转因子 n = (0:numel(rxWave)-1).'; rxWave = rxWave .* exp(-1j*2*pi*freqOff*n);

freqOff的单位是子载波间隔的倍数,实际工程里会再乘上子载波间隔换成 Hz。这里有个精度上限:CP 自相关只能估到 ±0.5 个子载波间隔的范围,超了就发生相位模糊,估出来是错的。所以真实系统通常分两步,先用 CP 做粗估,再用专门的训练序列做精估。

补偿那行用的是exp(-1j*2*pi*freqOff*n),符号不能反。反了会让频偏翻倍,星座图转得更快,看起来像「补偿没生效」。验证方法很简单:不加噪声不加信道,只人为注入一个已知频偏,跑完看残余相位是不是接近 0。

4.3 LS 与 MMSE 信道估计:一个除法和一个矩阵

最小二乘(LS)估计就是导频处的接收值除以导频符号,一句话能说完:

function Hest = est_channel(cfg, rxGrid, pilotPos) Hp = rxGrid(pilotPos, :) ./ repmat(cfg.pilotSym, 1, size(rxGrid,2)); % 频域线性插值到全部子载波 Hest = interp1(pilotPos, Hp, 1:cfg.Nfft, 'linear', 'extrap'); end

优点是简单、无偏,缺点是把噪声也一起除了进去。在深衰落子载波上Hp很小,噪声被放大,均衡之后误码集中爆发。MMSE 估计多用一个先验:信道的统计协方差。工程上常用简化版,按子载波做标量维纳滤波:

function Hest = est_channel_mmse(cfg, rxGrid, pilotPos, noiseVar) Hls = rxGrid(pilotPos,:) ./ repmat(cfg.pilotSym, 1, size(rxGrid,2)); % 标量 MMSE:H_mmse = R_hh / (R_hh + sigma2) * H_ls Rh = cfg.chanVar; % 信道先验方差 w = Rh / (Rh + noiseVar); % 收缩因子,0~1 之间 Hp = w * Hls; Hest = interp1(pilotPos, Hp, 1:cfg.Nfft, 'linear', 'extrap'); end

w这个收缩因子是关键:噪声大时它趋近 0,估计结果被压向均值,抑制了噪声放大;噪声小时它趋近 1,退化成 LS。cfg.chanVar要按实际信道功率填,填错会导致过度收缩或收缩不足,效果还不如纯 LS。

对应的均衡器也分两档。迫零(ZF)就是rxGrid ./ Hest,实现一行,但会放大噪声;MMSE 均衡多考虑噪声项:

% MMSE 均衡:H* / (|H|^2 + sigma2) eqGrid = conj(Hest) ./ (abs(Hest).^2 + noiseVar) .* rxGrid;

这里noiseVar用的是每子载波的噪声方差,跟前面加噪时算的一致。如果 SNR 设定变了但这里没跟着更新,MMSE 就退化成一个奇怪的加权,实测可能比 ZF 还差——这也是很多人在自己代码里「MMSE 反而不如 LS」的原因:参数没跟着 SNR 走。

4.4 误码率统计与星座图:让结果可解释

仿真最后要出两张图:BER 曲线和星座图。前者证明性能,后者证明链路健康。BER 曲线要注意两点,一是横轴到底是 Es/N0 还是 Eb/N0,两者差log2(M)倍;二是理论曲线要用对应口徑的公式,否则两条线放一起比毫无意义。

semilogy(cfg.SNRdB, ber_sim, 'o-', cfg.SNRdB, ber_theory, 'k--'); xlabel('E_b/N_0 (dB)'); ylabel('BER'); grid on; legend('仿真', '理论'); % 星座图:均衡后、判决前 scatterplot(eqSym(:)); title('均衡后星座图');

看星座图的诀窍:如果四个簇清晰但整体旋转了一个角度,是残余频偏没补干净;如果簇是云状扩散,是噪声或信道估计误差;如果簇被压扁成椭圆,多半是 IQ 不平衡或者某个子载波上的幅度没归一化。这几种形态对不上号的时候,基本可以确定问题出在链路而不是算法。

5. 避坑与排查:跑不出理论 BER 曲线的五个真实原因

5.1 高 SNR 处曲线出现地板效应

现象:BER 曲线在 10⁻² 到 10⁻³ 之间就不再下降,加多少功率都平着走。原因:CP 长度小于信道最大时延对应的样点数,符号间干扰和子载波间干扰形成了无法靠功率消除的误差底噪。解决:把Ncp提到max(pathDelay) + 余量,或者减少信道抽头数。改完先在无噪条件下对比txGrid和rxGrid,误差应该在 1e-10 以下。

5.2 星座图整体旋转、误码率异常高

现象:四个簇形状很干净,但整体转了个角度,而且每个符号转的角度还不一样。原因:频偏补偿的符号写反了,或者只做了粗估没做精估,残余频偏在多个符号上累积。解决:单独做一次「注入已知频偏—估计—补偿」的闭环测试,看残余相位是否接近 0。多符号累积的场景可以考虑加一个基于导频的相位跟踪(每符号估一次公共相位)。

5.3 换调制阶数后直接报维度错误

现象:M从 4 改成 16 之后,reshape报错或者解出来的比特数对不上。原因:比特流长度没有按log2(M)重新算,或者nSym取整后丢了一部分数据,收端却按全长解。解决:统一在发端截断到整数个符号,把实际发送的比特数作为输出返回,收端按这个长度比较。别用length(bits)硬算。

5.4 低 SNR 段仿真 BER 比理论值好 3dB

现象:曲线形状对,但整体左移,看起来算法「超越了理论」。原因:加噪时按发射功率算噪声方差,而信道有衰落导致接收功率变低,实际加进去的噪声偏少;或者qammod没开UnitAveragePower,符号平均功率不是 1。解决:噪声方差一律用mean(abs(rxWave).^2)动态计算,并在调制处显式打开单位功率开关。改完再跑一遍 AWGN,应该和理论曲线几乎重合。

5.5 高 SNR 点曲线断裂或直接为 0

现象:SNR 超过 15dB 后曲线出现断点,或者某些点直接掉到坐标轴外。原因:固定帧数仿真导致高 SNR 处一个错误都统计不到,ber = 0在对数坐标下画不出来。解决:改成「累计错误数达到阈值或帧数上限」的双条件循环,并在绘图前把 0 值替换成一个小于最小非零值的占位,比如ber(ber==0) = 1e-6,同时在图注里说明这是统计下限。

6. 二次开发:把这份仿真改成你自己的链路验证台

代码跑通之后,多数人真正的需求是往里塞自己的东西。下面三处是我改动最频繁、收益也最直接的地方。

6.1 把 LS 换成 MMSE,用同一份数据做 A/B

最省事的改造是保留est_channel.m的签名,内部换实现,然后在外层跑两遍:

cfg.estType = 'ls'; ber_ls = run_link(cfg); cfg.estType = 'mmse'; ber_mmse = run_link(cfg);

关键前提是两次跑用同一份随机种子,否则信道实现不同,对比没有意义。在main_ofdm.m开头加rng(cfg.seed),seed写死一个常数(比如 2024),每次改参数都从同一颗种子出发,曲线差异才能归因到算法本身。

6.2 加一条 MIMO 支路的最小改动量

想扩到 2×2 空分复用,不必重写整条链路。把txGrid从N×nSym变成N×nSym×nTx,ofdm_channel.m里把conv换成矩阵乘,接收端每个天线各做一次 FFT 和信道估计,最后在均衡处换成 2×2 的矩阵求逆(ZF 或 MMSE 检测)。改动集中在三个函数,其余不动。

6.3 扫参数验证:先对齐理论,再上多径

改完任何一处,验证顺序别乱:第一步只跑 AWGN,BER 必须和理论曲线贴合在 0.5dB 以内;第二步加单径(时延为 0),结果应该和 AWGN 几乎一致,因为单径只是一个复系数;第三步再上多径,此时曲线相对 AWGN 会有几个 dB 的损失,损失大小取决于信道时延扩展和 CP 的比值。顺序反了,出了问题你根本不知道是算法错还是信道配错。

有个习惯我一直保留:每次动核心函数之前,先把当前版本的 BER 数据存成baseline.mat,改完直接max(abs(ber_new - ber_base))看一眼。有一次我把均衡器里的conj(Hest)漏了,曲线只差了一点点,肉眼几乎看不出来,是差值报出来的 0.03 提醒了我。从那以后我每次改完同步和均衡模块,都强制走一遍这个差值对比,宁可多花十分钟,也不想在错误的结果上继续往下叠功能。

这份 OFDM 仿真程序的完整脚本、参数配置和绘图工具都放在下载包里,拿到之后建议先按第 5 章的五个坑逐个自查一遍,再动手改自己的算法。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进