☰
OFDM可见光通信MATLAB仿真:DCO-OFDM链路与BER/PAPR分析
2026/10/26 3:38:17 网站建设 项目流程

简介:这是一份基于MATLAB实现的OFDM可见光通信系统仿真代码包,面向刚接触光通信与OFDM调制的初学者,也适合需要快速复现实验的研究生和工程人员。资源围绕朗伯模型可见光无线信道展开,包含发送端、接收端、QAM/QPSK调制解调、加窗与根升余弦滤波等完整链路,代码注释较为详尽,可直接运行并观察系统性能。压缩包共10个文件,由9个.m脚本和1个.mat数据文件组成,整体约10KB,脚本承担信道建模、调制解调与主流程仿真,数据文件便于结果对比与参数复现,结构轻巧、便于按模块阅读。目前已有1978人学习下载。通过这份材料,读者可以理解可见光信道建模思路,掌握OFDM收发链路搭建、调制解调与窗函数处理的具体实现,并借助现成脚本完成运行验证和二次修改,为课程设计、毕业课题或相关仿真实验提供参考。

1. 一套能跑出完整 BER 曲线的 OFDM 可见光通信仿真,先看它到底解决了什么

射频里跑通的 OFDM,搬到 LED 灯珠上会立刻撞上一个反直觉的约束:光强不能为负。这意味着复基带信号必须先变成实数、再整体抬到一个直流工作点上,才能驱动 LED 发光。这份 OFDM 可见光通信系统仿真压缩包,就是围绕这条约束搭起来的一整条 MATLAB 链路:随机比特生成、QAM 映射、Hermitian 共轭对称、IFFT、加循环前缀、直流偏置与削波,过朗伯信道、光电探测器,再做去直流、FFT、单抽头均衡和误码统计,跑完就能出 BER 与 PAPR 曲线。它适合两类人:一类是要交课程设计或论文仿真图、需要一条结果可复现链路的同学;另一类是手上有预失真、PAPR 抑制、自适应偏置这些想法,想找个现成试验台先验证再谈硬件的开发者。纯 MATLAB 脚本加函数,不依赖 Simulink,也不需要额外的通信工具箱之外的东西。

2. 链路骨架拆解:从比特到 LED 光功率的七个环节与文件分工

把这条链路拆开看,发射侧只有七个动作:生成比特、QAM 映射、共轭对称填子载波、IFFT、加 CP、加直流偏置、削波到 LED 线性区;接收侧是它的镜像:去直流、去 CP、FFT、频域均衡、取正频率子载波、QAM 解映射、比对误码。听起来简单,但每一步都存在“物理上说得通、代码里写错一行就全盘歪掉”的地方,尤其是共轭对称的索引位置和偏置的定标方式,后面会重点讲。

2.1 目录结构与每个文件到底管什么

工程不大,胜在分层清楚,函数之间靠一个参数结构体p传递,没有满屏的全局变量。目录大致是这样组织的:

VLC_OFDM_Sim/ ├── main.m % 唯一入口:配置 -> 循环 SNR -> 画 BER/PAPR 图 ├── config/ │ └── sim_params.m % 所有可调参数集中在这里,返回结构体 p ├── transmitter/ │ ├── gen_bits.m % 随机比特流生成 │ ├── dco_ofdm_tx.m % DCO-OFDM 发射机主函数 │ └── hermitian_sym.m % 共轭对称子载波填充(单独抽出来便于复用) ├── channel/ │ ├── los_channel_gain.m % 朗伯辐射模型,算 H(0) 和接收光功率 │ ├── led_nonlinear.m % LED 非线性(Rapp / 多项式两种可选) │ └── add_noise.m % 按电域 SNR 精确加噪 ├── receiver/ │ ├── dco_ofdm_rx.m % 接收机主函数 │ └── qam_demap.m % 解映射 + 比特还原 ├── utils/ │ ├── ber_curve.m % SNR 扫描主循环 │ ├── papr_ccdf.m % PAPR 互补累积分布 │ └── plot_spectrum.m % 电谱 / 光功率谱对比图 └── results/ % 图片和 .mat 结果落盘
文件职责常改的参数
config/sim_params.m参数总入口N、Ncp、M、mu_dB、snrVec
transmitter/dco_ofdm_tx.m比特到光功率波形偏置系数、削波上下界
channel/los_channel_gain.m光信道 DC 增益半功率角、距离、PD 面积
channel/add_noise.m电域噪声注入噪声方差换算方式
receiver/dco_ofdm_rx.m波形回到比特均衡方式、子载波切片

main.m里做的事很朴素:调sim_params拿参数,调ber_curve扫一遍 SNR,最后两张图一画。真正值得读的是被它调用的那几个函数,尤其是发射机——所有“这是可见光不是射频”的适配都压在它身上。

2.2 发射机:QAM 映射与 Hermitian 共轭对称

射频 OFDM 直接对复数符号做 IFFT,出来是复基带,上变频后正负摆幅都无所谓。可见光不行,LED 的瞬时功率必须是实数且非负,所以 IFFT 的输入必须满足共轭对称,这样时域输出才是实信号。这一步是整个仿真最容易写错的地方,索引偏一位,时域就出虚部,后面怎么调都救不回来。

function [s, meta] = dco_ofdm_tx(bits, p) % DCO-OFDM 发射机:比特 -> QAM -> 共轭对称 -> IFFT -> CP -> 偏置/削波 % bits : 1 x (Nsym * Nd * log2(M)) 的行向量,Nd = N/2 - 1 % s : 列向量,已经抬过直流偏置、削过波的实信号 k = log2(p.M); Nd = p.N/2 - 1; % 去掉 DC 与 Nyquist 后的可用子载波 assert(numel(bits) == p.Nsym * Nd * k, '比特数与子载波配置不匹配'); symIdx = reshape(bits, k, []).'; % 每行一个符号的 k 个比特 qamSym = qammod(bin2dec(num2str(symIdx)), p.M, ... 'UnitAveragePower', true); % 平均功率归一化到 1 Xk = zeros(p.N, p.Nsym); for n = 1:p.Nsym Xk(2:Nd+1, n) = qamSym((n-1)*Nd + (1:Nd)); % 正频率 Xk(p.N:-1:p.N-Nd+1, n) = conj(Xk(2:Nd+1, n)); % 负频率共轭镜像 end % Xk(1) 与 Xk(N/2+1) 保持为 0:DC 交给偏置,Nyquist 不用 x = real(ifft(Xk, p.N, 1)); % 理论实数,real 只抹掉数值残差 xcp = [x(end-p.Ncp+1:end, :); x]; % 循环前缀 s = xcp(:); s = s / sqrt(mean(s.^2)); % 先归一化到单位电功率 s = sqrt(p.P_elec) * s; % 再定标到目标电功率 mu = sqrt(10^(p.mu_dB/10) - 1); % 偏置比 mu = B / sqrt(P_elec) B = mu * sqrt(p.P_elec); s = min(max(s + B, 0), 2*B); % 抬偏置 + 上下对称削波 meta.B = B; meta.Nd = Nd; meta.clipRatio = mean(s <= 0 | s >= 2*B); % 削波比例,调试时必看 end

这段里最关键的三行是Xk的填充、real(ifft(...))和最后的削波。填充部分:长度为N的 IFFT,索引 2 到N/2放数据,索引N/2+2到N放共轭镜像,索引 1(直流)和N/2+1(奈奎斯特)保持实数——这里直接置 0,因为直流位置后面要用来放偏置,不能让数据占着。如果只填了正频率忘了负频率,ifft出来就是复数,real一取等于凭空丢了一半能量,BER 曲线会好看得不真实,这是典型的“仿真比理论还好”的翻车现场。

mu_dB的定义是10*log10(1 + mu^2),所以反解是mu = sqrt(10^(mu_dB/10) - 1)。很多人图省事写成10^(mu_dB/20),在 7 dB 附近两者差不到 0.2 dB,但如果你扫到 13 dB 以上,偏差就会让削波比例和曲线斜率对不上。p.P_elec只是电域归一化基准,取 1 就行,真正影响结果的是mu_dB和p.Ncp这两个。

qammod的'UnitAveragePower'在 R2017b 之前不支持,老版本要自己除以sqrt(mean(abs(qamSym).^2));bit2int是 R2021b 之后才有的,压缩包里用bin2dec(num2str(...))就是为了兼容更老的安装环境,慢一点但不会报错。

2.3 接收机:去直流、FFT 与单抽头均衡

接收端的第一件事是去直流。很多刚上手的人会把这一步漏掉,觉得反正后面 FFT 会处理——但 FFT 的第一个 bin 恰好就是直流分量,偏置一旦漏进去,会直接砸在 DC 子载波上,而 DC 子载波本来就没放数据,看起来“没事”,实际上时域削波引入的畸变会通过它泄漏到相邻子载波,高 SNR 段 BER 会莫名其妙抬起来。

function [bitsHat, ber, eqSym] = dco_ofdm_rx(r, bits, p, H) % r : 接收端离散实信号(列向量),已含噪声 % H : p.N x 1 的频域信道响应;平坦信道时取 ones(N,1)*gain 即可 r = r(:) - mean(r); % 1) 去直流:偏置不携带信息 r = reshape(r, p.N + p.Ncp, p.Nsym); r = r(p.Ncp+1:end, :); % 2) 去循环前缀 R = fft(r, p.N, 1); Heq = R ./ H; % 3) 频域迫零均衡 Y = Heq(2:p.N/2, :); % 4) 只取承载数据的正频率子载波 symHat = qamdemod(Y(:), p.M, 'UnitAveragePower', true); bitsHat = reshape(dec2bin(symHat, log2(p.M)).' - '0', 1, []); ber = mean(bitsHat ~= bits); eqSym = Y; end

参数说明上,H是一整列频域响应,平坦信道直接ones(p.N,1)*h0;如果后面加了多径,就把los_channel_gain的输出乘上各径的相位旋转再拼成频域向量,接口不用改。Heq(2:p.N/2, :)这个切片长度是Nd = N/2 - 1,和发射端Xk(2:Nd+1)必须严格对齐,差一位就会整体错位、BER 恒定在 0.5 附近——这也是排查时第一个要打的断点。

迫零均衡在深衰落子载波上会放大噪声,如果后面你自己加了多径模型,建议换成 MMSE:Heq = R .* conj(H) ./ (abs(H).^2 + sigma2),其中sigma2是每个子载波上的噪声方差。压缩包里默认是平坦信道,迫零够用,但接口留好了。

2.4 参数配置:sim_params.m里真正影响结果的几个量

参数文件看起来是杂项集合,实际只有六个量决定曲线形状:

参数典型取值影响
p.N64 / 128 / 256子载波数,越大 PAPR 越高
p.NcpN/8或N/4CP 开销,直接吃掉有效 SNR
p.M4 / 16 / 64每符号比特数,决定理论 BER 斜率
p.mu_dB7 ~ 13偏置大小,权衡削波噪声与光功率效率
p.P_elec1只是归一化基准,改它不影响 BER
p.snrVec0:2:30扫描范围,注意别用光 SNR 混着电 SNR

p.Ncp这一项常被忽略:它带来的开销是10*log10(N/(N+Ncp)),N=64、Ncp=16时正好 0.97 dB。如果你把仿真曲线和理论 AWGN 曲线画在一张图上,发现整体平移了将近 1 dB,先别怀疑算法,去把这个开销减回来再看。

3. DCO-OFDM 的定标细节:直流偏置、削波和电域 SNR 怎么算才不虚高

链路跑通只是第一步,能不能画出和别人对得上的曲线,全在定标。可见光 OFDM 里同时存在两个功率域:光功率和电功率,混用是 BER 曲线整体偏移的头号原因。这一章把偏置系数、削波噪声和 SNR 定义这三件事捋顺。

3.1 电域 SNR、光域 SNR 和那个容易搞混的平方关系

在 IM/DD 系统里,LED 发的是光功率P_opt,PD 输出的是电流I = R * P_opt,电功率正比于电流平方,也就正比于光功率的平方。所以光 SNR 和电 SNR 差一个平方关系:SNR_elec = SNR_opt^2(同阻抗下)。仿真里加噪声是在电域加的,add_noise应该按电功率定标:

function r = add_noise(s, p) % 按电域 SNR 精确加噪:先把直流分量扣掉再算功率 x = s - mean(s); % 只对交流分量定噪,偏置不算信号功率 Px = mean(x.^2); sigma2 = Px / 10^(p.snrDb/10); % 电域 SNR 定义 r = s + sqrt(sigma2) * randn(size(s)); end

这里的x = s - mean(s)是血泪经验。如果直接awgn(s, snrDb, 'measured'),awgn会把直流偏置也算进信号功率里,偏置越大,实际注入的噪声越强,等效 SNR 被压低好几个 dB,而且压低量随mu_dB变化——你会看到扫出来的曲线斜率没问题,但整体位置随偏置参数漂移,查半天查不出原因。扣掉均值再定噪,SNR 的定义才和论文里的对得上。

如果你在论文里写的是光 SNR,换算时记得:给定光调制指数m = P_ac,opt / P_opt,dc,电域 SNR 大约是m^2 * SNR_opt。压缩包里默认用电域定义,图表横轴标注的是E_b/N_0,别混。

3.2 削波噪声:为什么 SNR 扫到 30 dB 曲线就躺平了

DCO-OFDM 的偏置不能无限大,偏置越大,LED 的平均光功率越高,但交流摆幅占比越小,光调制效率越低;偏置越小,削波越严重,削波噪声会形成一个 SNR 天花板。这个天花板可以用 Bussgang 定理近似估:高斯输入经过软削波后,输出 = 衰减后的输入 + 与输入不相关的削波噪声,有效 SNR 满足

SNR_eff = SNR / (1 + (1/SNR_clip) * SNR) => SNR_eff -> SNR_clip (高 SNR 时)

实际调的时候不用推公式,看meta.clipRatio就够了。经验值是:削波比例控制在 1% 以内,SNR 天花板大约在 25~30 dB;削到 5%,天花板掉到 18 dB 左右。所以扫描范围超过 25 dB 时,如果曲线开始变平,先看削波比例,别急着怀疑均衡器。

这里有个取舍:mu_dB从 7 提到 13,削波比例能降一个量级,但光功率效率会掉 3 dB 以上。多数论文里取 7 dB 是有道理的——它对应的削波概率约 0.3%,天花板够高,光效又不算太亏。你要做自适应偏置的研究,那就把这个系数做成随信号峰均比动态调整的,压缩包里dco_ofdm_tx的mu是单独一行算的,改成从外部传入数组就行。

3.3 一条可复现的 BER 扫描循环

ber_curve.m是整份代码里跑得最久、也最需要固定随机种子的地方。同一个参数跑两遍结果差一点点,多半是没锁rng。

function [snrDb, ber] = ber_curve(p, snrVec) rng(2024); % 固定种子,保证曲线可复现 ber = zeros(size(snrVec)); for i = 1:numel(snrVec) p.snrDb = snrVec(i); bits = gen_bits(p); % 生成长度匹配的比特流 [s, meta] = dco_ofdm_tx(bits, p); % 发射:含偏置与削波 r = add_noise(s, p); % 电域加噪 [~, ber(i)] = dco_ofdm_rx(r, bits, p, ones(p.N,1)); if p.verbose fprintf('SNR=%4.1f dB BER=%.3e 削波比例=%.2f%%\n', ... snrVec(i), ber(i), 100*meta.clipRatio); end end end

循环里最值得注意的不是循环本身,而是gen_bits(p)每次都要重新生成——如果为了省时间把比特流提到循环外面复用,低 SNR 段的误码会呈现非统计性的抖动,因为噪声每次不同但比特固定,误码位置会“聚堆”。另外ber在 0 附近要显示成<1e-5而不是直接写 0,否则画对数坐标时 log(0) 会告警。

p.verbose这个开关建议保留,扫参时盯着削波比例那一列,能省掉大量事后排查。

4. 信道与器件建模:朗伯 LOS 增益、LED 非线性和噪声预算

链路骨架定了之后,仿真到底“像不像真的”,取决于三个物理模型:LED 的朗伯辐射、LED 的电光非线性、以及接收端的噪声预算。这三块缺一块,曲线都会偏乐观。

4.1 朗伯辐射模型:H(0)到底怎么算

室内可见光信道在带宽远小于信道相干带宽时,可以当成平坦的 DC 增益处理。视距链路下,单个 LED 到单个 PD 的直流增益是:

H(0) = (m+1)A / (2πd²) * cos^m(φ) * Ts(ψ) * g(ψ) * cos(ψ), 0 ≤ ψ ≤ FOV

m是朗伯阶数,由半功率角Φ_1/2决定:m = -ln2 / ln(cos Φ_1/2)。半功率角 60° 对应m = 1,是很常见的商用 LED 参数;半功率角 30° 对应m ≈ 4.8,光束更集中,覆盖更窄。

function [H0, Prx] = los_channel_gain(p) % 朗伯辐射模型:返回 DC 光信道增益 H(0) 和接收光功率 phi = p.tx_angle; % 发射角,rad psi = p.rx_angle; % 接收入射角,rad m = -log(2) / log(cos(p.phi_half)); % 朗伯阶数 A = p.pd_area; % PD 有效面积,m^2 d = p.dist; % 收发距离,m H0 = (m+1) * A / (2*pi*d^2) * cos(phi)^m .* ... p.filter_gain * p.concentrator_gain * cos(psi); H0(psi > p.fov) = 0; % 超出视场角直接截止 Prx = p.Pt * H0; % 接收光功率,W end

参数上,p.Pt是单灯发射光功率,量级通常几十毫瓦到几瓦;p.pd_area常见 1e-4 m²(1 cm²);p.concentrator_gain对非成像聚光器是n²/sin²(FOV),不加聚光器就填 1。这里最容易出错的不是公式,是单位:距离用米、面积用平方米、角度用弧度,三个单位混一个,H0就能差出三四个数量级,然后你会看到接收光功率是几万瓦,明显不对却不知道从哪改。

算完H0,接收端的电信号幅度是R * Prx,R是 PD 响应度,典型 0.4~0.6 A/W。这个乘积直接决定 SNR 的绝对水平,所以如果你只关心 BER 曲线形状,可以跳过物理量纲,直接用归一化的H0;但要画“不同距离下的 BER”这种图,量纲必须对。

4.2 LED 非线性:线性区之外会发生什么

真实的 LED 不是理想电光转换器,输入电流和输出光功率在两端都会压缩。仿真里通常用两种模型:Rapp 模型(从功放模型借来的)和记忆多项式模型。

function y = led_nonlinear(x, p, mode) % x : 已归一化到 LED 线性区 [-1, 1] 的驱动信号 switch lower(mode) case 'rapp' % Rapp 模型:只描述幅度压缩,无记忆 y = x ./ (1 + (abs(x)/p.rapp_sat).^(2*p.rapp_p)).^(1/(2*p.rapp_p)); case 'poly' % 三阶记忆多项式:模拟 AM-AM 与 AM-PM y = p.c1*x + p.c3*x.^3; otherwise y = x; % 理想线性,用于对照组 end end

rapp_sat是饱和点,rapp_p是平滑因子,典型值 2~3。多项式模型里的c3取负值表示压缩,量级在 -0.1 到 -0.3 之间,取太大信号会直接翻转,这属于模型参数没调好,不是链路问题。

用不用非线性模型,差别很直观:削波比例不变的前提下,加了 LED 压缩后高 SNR 段会再压下去 2~3 dB,而且星座图外圈点会向内塌陷。如果你在研究预失真,这一段就是你要动的地方——把led_nonlinear换成你的预失真器加逆模型,其余链路不动,直接对比 BER 就行。

4.3 噪声预算:散粒噪声和热噪声各占多少

接收端的噪声主要两项:PD 的散粒噪声和前置放大器的热噪声。

function sigma2 = noise_variance(p, Prx) q = 1.602176634e-19; % 电子电荷 k = 1.380649e-23; % 玻尔兹曼常数 sigma_shot = 2*q*p.R*Prx*p.BW; % 散粒噪声,与接收光功率成正比 sigma_therm = 4*k*p.T*p.BW / p.RL; % 热噪声,与带宽和负载电阻有关 sigma2 = sigma_shot + sigma_therm; end

量级上,BW取 20 MHz、R = 0.5、Prx在毫瓦级时,散粒噪声大约 1e-14 A² 量级,热噪声在RL = 50 Ω、T = 300 K时大约 6.6e-15 A² 量级,两者同量级——这意味着你不能只留一项,只留热噪声会低估总噪声,只留散粒噪声又会漏掉与光功率无关的本底。

把sigma2折算成电域 SNR:SNR = (R*Prx)^2 / sigma2。这个值和你在add_noise里用的p.snrDb应该是同一套定义,两边对不上的话,物理模型算出来的 SNR 和仿真扫描的 SNR 就会打架,图注写不清来源。

5. 避坑与排查:BER 曲线跑歪时先查这五个地方

仿真类项目最耗时间的从来不是写代码,是曲线不对之后的定位。下面五条是这条链路上出现频率最高的问题,按“现象 → 原因 → 解决”整理,基本能覆盖八成情况。

5.1 曲线层面的三个异常

第一条:高 SNR 段出现地板(error floor),BER 卡在 1e-3 附近降不下去。

现象是低 SNR 段斜率正常,到 20 dB 之后变平。原因通常有两个:一是削波太狠,mu_dB设成了 3~4 dB,削波比例超过 5%,削波噪声形成 SNR 天花板;二是 IFFT 之后没取实部或者共轭对称填错,残留的虚部在接收端被当成噪声。看meta.clipRatio,超过 2% 就先调大mu_dB;如果削波比例已经很低还是地板,打max(abs(imag(ifft(Xk)))),非零就说明对称填错了。

第二条:整条曲线比理论 AWGN 曲线右移 2~3 dB,斜率正常。

现象是形状一模一样,就是整体平移。原因几乎一定是 SNR 定义不一致:要么add_noise里没扣均值,把偏置功率算进了信号功率;要么 CP 开销没折算进去;要么qammod没开'UnitAveragePower',符号功率不是 1。三个都查一遍,顺序是先查mean(s)是否为零、再算10*log10(N/(N+Ncp))、最后打印mean(abs(qamSym).^2)看是不是 1。这三项修完基本就对齐了。

第三条:高 SNR 时 BER 不降反升。

这个最反直觉,但原因很朴素:信号摆幅超过了 LED 线性区,led_nonlinear的压缩把外圈星座点向内推,判决边界发生偏移。检查方法是画接收端均衡后的星座图,看最外圈点是不是被抓到了内圈。解决要么调小p.P_elec,让信号整体回到线性区;要么把rapp_sat调大。

5.2 波形与运行层面的两个坑

第四条:接收端ifft出来的时域信号虚部不为零。

现象是imag部分量级在 1e-17 到 1e-3 之间浮动,前者是浮点误差可忽略,后者是真错。原因在负频率填充的索引:Xk(p.N:-1:p.N-Nd+1)这一句,p.N-Nd+1算错一位就会漏掉或多填一个子载波。用N = 64手算一遍:Nd = 31,正频率占 2..32,负频率应该占 34..64,对应Xk(64:-1:34)。对不上就修索引。

第五条:换台电脑或重启 MATLAB 后,同一组参数跑出的 BER 不一样。

现象是差得不多,但在 1e-4 量级上明显不同。原因是没固定随机种子,randn每次都在动。在ber_curve开头加一句rng(2024)就解决。顺带提一句,如果开了parfor并行扫参,每个 worker 的随机流是独立的,要在循环内用rng(2024 + i)给每个 SNR 点单独设种子,否则结果不可复现。

另外还有一条不算坑但很费时间的:MATLAB 的qammod默认星座是 Gray 映射的,你如果自己手写了一张星座表做对比,会发现低 SNR 段 BER 差一截——不是链路错,是映射方式不同。要么统一用内置函数,要么自己实现 Gray 编码。

6. 二次开发:把固定链路改成能验证新算法的试验台

这套代码写到这里,链路是死的:固定 DCO-OFDM、固定平坦信道、固定偏置。要拿它验证自己的想法,得先把几个接缝撬开。下面是我实际改过的几个方向,按改动量从小到大排。

改造点动手位置改动量验证方式
换 ACO-OFDMhermitian_sym.m+ 削波段小只填奇数子载波,对比 PAPR
PAPR 抑制(SLM / 压扩)dco_ofdm_tx的 IFFT 之后小画 CCDF,同时看 BER 有无恶化
自适应直流偏置dco_ofdm_tx的mu计算行中固定 BER 目标,比平均光功率
多径信道dco_ofdm_rx的H入参中对比平坦信道,看均衡是否生效
LED 预失真led_nonlinear前插一级大看星座图外圈是否回正

以 PAPR 抑制为例,最省事的接入点是在 IFFT 之后、加 CP 之前,对时域信号做压扩,然后把压扩函数写成一个独立文件,接收端做逆变换:

function y = mu_compand(x, mu) % μ 律压扩:压缩大摆幅、放大弱小信号,降低 PAPR v = log(1 + mu*abs(x)) / log(1 + mu); y = v .* sign(x); end

mu取 0 就是不压扩,取 10~100 之间能看到 PAPR 的 CCDF 曲线整体左移 2~4 dB。但注意,压扩是有代价的:它在降低峰值的同时抬高了小信号的功率,等效噪声被放大,BER 会恶化。所以验证时不能只看 PAPR 一张图,必须两张图并排——PAPR 掉了 3 dB、BER 多花 0.5 dB,这个交易划不划算才是你要论证的东西。

多径那块改造稍微麻烦一点。dco_ofdm_rx的H入参已经是频域向量,你只要在信道模块里生成多径响应:每条径按los_channel_gain算出增益,乘上exp(-1j*2*pi*k*delay/Ts)的相位,最后对各径做fft拼成N×1。这一步最容易错的是时延的单位换算,delay要用采样点数而不是秒,否则相位斜率完全不对,均衡后看起来“有效果”但其实是在乱修。

验证方法我一般强制走三步:第一步,把所有非理想因素关掉(不加削波、不加 LED 非线性、信道设成纯增益),曲线必须和 16-QAM AWGN 理论值重合,差超过 0.3 dB 就说明链路本身有问题;第二步,一个个打开削波、非线性、多径,每打开一个记录 SNR 损失;第三步,才接上你要验证的算法,对比打开和不打开的差值。跳过前两步直接上算法,很容易把链路 bug 算成算法收益,那才是真的后悔药都没处买。

从那以后我每次改这条链路,不管改多小,都先把理想链路那 0.3 dB 的基准跑一遍再往下动。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询