ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

OFDM系统中QPSK与16QAM误码率对比:噪声功率换算与MATLAB仿真

OFDM系统中QPSK与16QAM误码率对比:噪声功率换算与MATLAB仿真 最近在折腾通信系统仿真把OFDM链路搭起来之后第一个让我觉得有点意思的点就是调制方式的选择对误码率的影响。同样是发数据用QPSK和16QAM抗噪能力完全是两种画风——QPSK像穿了一件厚防弹衣16QAM则为了多捎带两倍的比特把防弹衣换成了衬衫。这篇文章就把我做的一个最简单的OFDM链路下的QPSK vs 16QAM对比实验完整记录下来代码直接给能跑顺便把我调参时踩过的几个关键坑也一并说清楚。本文适合正在学通信原理、做课程设计、或者刚接触通信系统仿真、想验证课本上误码率公式的朋友。看完你不仅能复现这条对比曲线更重要的是能搞明白仿真里最容易搞错的“噪声功率到底该怎么加”。下面开工。1. 调制阶数与误码率先搞清楚差距是怎么来的1.1 星座图视角同样的功率点越多越挤OFDM系统里IFFT之前的每个子载波都承载一个星座符号。QPSK用的是4个点每个符号携带2比特16QAM用的是16个点每个符号携带4比特。在平均功率归一化到1的前提下16个点要均匀铺在同样大小的能量范围内彼此之间的间距必然比4个点小得多。这个间距叫星座点最小欧氏距离它直接决定了抗噪声能力。计算一下QPSK的星座点坐标是(±1±1j)/√2最小距离d_min √2 ≈ 1.41416QAM归一化星座点的最小距离d_min 2/√10 ≈ 0.632。也就是说QPSK相邻星座点的距离大约是16QAM的2.24倍。接收机在判决时噪声会让接收符号在星座图周围形成一个“模糊圈”噪声功率相同的情况下模糊圈的半径差不多但星座点间距不一样越挤自然越容易判错。打个不严谨但好记的比方一个房间里摆4张床和16张床人半夜迷迷糊糊起来找床4张床的房间随便摸一下不太容易躺错16张床的房间基本必错无疑。调制阶数越高就是往同样的功率“房间”里塞了更多可区分的状态。这个差距最终体现在误码率曲线上就是同等信噪比下16QAM的误码率比QPSK高一截而且信噪比越低越明显。1.2 理论误码率公式曲线长什么样先有个预期在AWGN信道下QPSK的理论误码率公式很干净Pb Q(√(2Eb/N0))16QAM在采用格雷映射时误码率可以近似写成Pb ≈ (3/4)·Q(√(4Eb/(5N0)))这里Eb/N0是每比特信噪比。Q(x)是高斯Q函数。先算几个典型点给后续仿真曲线建立一个心理预期Eb/N0QPSK理论BER16QAM理论BER6 dB约 2.3×10⁻³约 2.8×10⁻²10 dB约 3.3×10⁻⁵约 4.4×10⁻³14 dB约 1×10⁻¹²量级约 2.7×10⁻⁶可以看到在BER10⁻³这个常用参考点附近16QAM需要的Eb/N0比QPSK高约3.8~4 dB。这就是高频谱效率换来的功率代价——你多传了1倍的比特却要多付大约4 dB的发射功率或者接受更高的误码率。需要特别说明的是我这里统一用Eb/N0作为横轴。这个选择其实是有讲究的Eb/N0是每信息比特的能量它剔除了调制阶数本身带来的“速率红利”让两种调制方式站在公平的赛道上比较“单位比特的抗噪能力”。如果拿Es/N0每符号信噪比做横轴16QAM曲线会显得更差因为一个16QAM符号要额外承担4比特的传输任务同样Es/N0下每比特分到的能量天然就少。这个区别在第4章还会再提。2. 仿真链路怎么搭OFDM基础模块与噪声功率换算2.1 OFDM收发链路里每个模块在干什么代码里面其实就三步调制后的频域符号做IFFT变到时域加循环前缀然后过信道接收端先去循环前缀再FFT回频域最后解调。具体过程是这样随机产生N_sub×k的比特矩阵每一行k个比特映射成一个子载波符号用qammod做调制得到64个频域复符号对这64个符号做IFFT得到64点时域信号把时域信号末尾N_cp个采样复制到头部形成带循环前缀的OFDM符号送入AWGN信道叠加复高斯噪声接收端丢掉前N_cp个采样再对剩下的64点做FFT把得到的频域符号送进qamdemod解调对比发送比特统计误码。这里我要强调一下在纯AWGN信道下OFDM的IFFT/FFT和循环前缀并不会改变每个子载波的信噪比它们更像是“无损通道”。OFDM的真正价值在于对抗频率选择性衰落——那需要多径信道才能体现。本文的实验目的比较单纯就是看调制方式对BER的影响所以用AWGN隔离其他变量逻辑上更干净。2.2 最容易搞错的参数时域噪声功率怎么算这一节是全篇最重要的部分。很多刚做OFDM仿真的朋友代码跑出来的BER曲线跟理论值差一大截十有八九是噪声功率换算错了。问题的根源在于MATLAB的ifft默认做了1/N缩放。频域符号的平均功率如果是1经过ifft之后时域信号的每个采样点平均功率就变成了1/N。也就是说时域信号比频域“看起来”弱了N倍。如果这时候你不管不顾直接在时域上按照“频域SNR1/N0”的方式去叠加噪声方差那接收端做FFT之后噪声功率会被FFT的累加效应放大N倍。最终算出来的SNR其实比预期高或者低取决于你怎么定义BER曲线就会整体偏移。这里给出标准换算流程将目标Eb/N0dB转成线性值EbN0_lin计算符号信噪比EsN0 k × EbN0_lin其中klog2(M)由于调制符号平均功率归一化为1即Es1所以N0 1/EsN0时域噪声每个采样点的总功率 N0 / N_sub复高斯噪声实部和虚部各自独立每维方差再除以2。写成代码就是noise_pow_time 1 / (k * EbN0_lin * N_sub); noise_std sqrt(noise_pow_time / 2); noise noise_std * (randn(size(ofdmTxCP)) 1j*randn(size(ofdmTxCP)));这个换算的物理含义是FFT本身不归一化时域上N个独立噪声样本的功率会在线性变换后叠加起来。所以想让FFT之后的频域噪声功率恰好等于N0时域上每个采样点的噪声功率就必须是N0/N。这一点理解了比背公式有用得多。3. 直接上代码OFDM链路下QPSK与16QAM对比实验3.1 完整MATLAB仿真脚本下面是完整的仿真代码。我用64个子载波16个采样点的循环前缀每个信噪比点跑1000个OFDM符号。Eb/N0从0 dB扫到16 dB。clear; close all; clc; %% 参数设置 N_sub 64; % OFDM子载波数 N_cp 16; % 循环前缀长度 N_sym 1000; % 每个信噪比点下的OFDM符号数 EbN0_dB 0:2:16; % 每比特信噪比扫描范围 M_qpsk 4; % QPSK4-QAM M_16qam 16; % 16QAM k_qpsk log2(M_qpsk); k_16qam log2(M_16qam); ber_qpsk zeros(size(EbN0_dB)); ber_16qam zeros(size(EbN0_dB)); %% 主仿真循环 for snrIdx 1:length(EbN0_dB) EbN0_lin 10^(EbN0_dB(snrIdx)/10); %% ---------- QPSK ---------- EsN0 k_qpsk * EbN0_lin; N0 1 / EsN0; noise_pow_time N0 / N_sub; noise_std sqrt(noise_pow_time / 2); errBits 0; totalBits 0; for sym 1:N_sym bits randi([0 1], N_sub, k_qpsk); txSym qammod(bits, M_qpsk, InputType, bit, UnitAveragePower, true); % OFDM调制 ofdmTx ifft(txSym, N_sub); ofdmTxCP [ofdmTx(end-N_cp1:end); ofdmTx]; % AWGN信道 noise noise_std * (randn(size(ofdmTxCP)) 1j*randn(size(ofdmTxCP))); ofdmRxCP ofdmTxCP noise; % OFDM解调 ofdmRx ofdmRxCP(N_cp1:end); rxSym fft(ofdmRx, N_sub); % 解调与误码统计 rxBits qamdemod(rxSym, M_qpsk, OutputType, bit, UnitAveragePower, true); errBits errBits sum(sum(bits ~ rxBits)); totalBits totalBits numel(bits); end ber_qpsk(snrIdx) errBits / totalBits; %% ---------- 16QAM ---------- EsN0 k_16qam * EbN0_lin; N0 1 / EsN0; noise_pow_time N0 / N_sub; noise_std sqrt(noise_pow_time / 2); errBits 0; totalBits 0; for sym 1:N_sym bits randi([0 1], N_sub, k_16qam); txSym qammod(bits, M_16qam, InputType, bit, UnitAveragePower, true); ofdmTx ifft(txSym, N_sub); ofdmTxCP [ofdmTx(end-N_cp1:end); ofdmTx]; noise noise_std * (randn(size(ofdmTxCP)) 1j*randn(size(ofdmTxCP))); ofdmRxCP ofdmTxCP noise; ofdmRx ofdmRxCP(N_cp1:end); rxSym fft(ofdmRx, N_sub); rxBits qamdemod(rxSym, M_16qam, OutputType, bit, UnitAveragePower, true); errBits errBits sum(sum(bits ~ rxBits)); totalBits totalBits numel(bits); end ber_16qam(snrIdx) errBits / totalBits; end % 仿真结果为0的点给个下限方便画对数坐标 ber_qpsk(ber_qpsk 0) 1e-7; ber_16qam(ber_16qam 0) 1e-7; %% 理论曲线 EbN0_lin_vec 10.^(EbN0_dB/10); ber_qpsk_theory qfunc(sqrt(2*EbN0_lin_vec)); ber_16qam_theory (3/4)*qfunc(sqrt(4*EbN0_lin_vec/5)); %% 画图 figure; semilogy(EbN0_dB, ber_qpsk, o-, LineWidth, 1.5); hold on; semilogy(EbN0_dB, ber_16qam, s-, LineWidth, 1.5); semilogy(EbN0_dB, ber_qpsk_theory, --, LineWidth, 1); semilogy(EbN0_dB, ber_16qam_theory, --, LineWidth, 1); grid on; xlabel(Eb/N0 (dB)); ylabel(误码率 BER); legend(QPSK 仿真, 16QAM 仿真, QPSK 理论, 16QAM 理论, Location, southwest); title(AWGN信道下OFDM系统QPSK与16QAM误码率对比);3.2 代码里的几个关键选择说一下为什么首先qammod我特意加了UnitAveragePower, true。这个参数会把星座图的平均功率归一化到1。没有它星座点坐标是按最小距离2来定义的符号能量不等于1那信噪比设置和噪声功率换算全部要跟着改非常容易出错。归一化之后Es就可以心安理得地当成1来参与信噪比换算。其次调制对象是N_sub×k的比特矩阵。qammod的InputType,bit模式要求输入矩阵每行正好是k个比特输出对应一个复符号。这样就不用手写比特到符号的映射了16QAM的格雷码映射交给库函数处理省心。最后我这里保留了完整的IFFT、加CP、去CP、FFT流程而不是偷懒直接在频域上加噪声。虽然在AWGN下效果一样但保留完整链路的好处是如果你后面把信道换成多径衰落信道这套代码框架可以直接复用只要把信道那一段替换掉就行。4. 实验结果误码率曲线到底差了多少4.1 仿真曲线与理论曲线对照跑完代码之后你会看到仿真点基本都落在理论曲线上QPSK和16QAM两条曲线在1e-2到1e-4区间跟理论值贴合得相当好。这是我最喜欢这组实验的一点代码简简单单但结果和公式“严丝合缝”说明噪声功率换算没有出错。几个典型观察在Eb/N08 dB附近QPSK的BER大约是2×10⁻⁴量级16QAM则到不了这么低大约在1×10⁻²量级差了两个数量级左右16QAM要拿到和QPSK相同的BEREb/N0大约需要高出4 dB。比如要BER10⁻³QPSK在7 dB附近就能做到16QAM得推到10.5 dB以上高信噪比端QPSK在12 dB以后误码率就低到单次仿真可能一个错误比特都抓不到曲线直接掉底。这是有限样本统计的必然结果不是代码bug。如果你画完图发现两条曲线的间距比理论预期明显偏大或偏小优先回头检查噪声波形。最常见的低级错误是把时域噪声标准差直接写成了sqrt(N0/2)忘了除以子载波数N_sub。4.2 如果横轴换成Es/N0结论会变化吗有些朋友习惯把横轴设为SNR也就是Es/N0两种画法结论并不矛盾但表达的重点不同。用Eb/N0做横轴QPSK和16QAM的曲线差距是“调制自带的抗噪差距”加上“采样判决时星座点疏密的影响”这是一个公平的比特级比较。用Es/N0做横轴等于假设两种调制都能拿到相同的符号能量那16QAM每个比特分到的能量天然只有QPSK的一半曲线差距会拉得更开。工程上做链路预算时两种口径都会出现。但在做“同样发射功率下哪种调制更可靠”这个问题时Eb/N0是更干净的参考系。这也是为什么本文所有公式和仿真点都以Eb/N0为基准。5. 实验中的几个坑和我的调参心得5.1 噪声功率换算的快速自检方法噪声加得对不对有个特别快的自检方法看BER在极低信噪比和极高信噪比两端的表现。在Eb/N00 dB这种极低信噪比下QPSK的理论BER大约是0.08量级16QAM大约是0.14量级。如果你的仿真结果在这附近那说明噪声功率量级基本对了。如果出来是0.4或者0.5这样的数值基本等于在扔硬币猜比特那说明噪声加太大了或者调制解的映射方式有问题。高信噪比端的自检反过来QPSK在16 dB时BER应该低到你根本数不出几个错误比特如果这时曲线还在1e-2量级趴着赶紧回查是否忘了做归一化。5.2 为什么我坚持用“公平横轴”这个说法再强调一次Eb/N0和Es/N0的换算关系Es Eb × log2(M)QPSK里k2所以Es/N0比Eb/N0高3 dB16QAM里k4所以Es/N0比Eb/N0高6 dB。很多人仿真出来的QPSK和16QAM曲线在图上错位得很奇怪有可能是把“SNR”直接当成横轴然后用同一个N0去套两种调制却忽略了每符号携带比特数不同导致Es对应的Eb不同。只要严格按本文第2章的步骤走这个坑基本不会踩到。5.3 仿真点数、抖动与统计置信度N_sym1000的话每个信噪比点QPSK要统计64×2×1000128000个比特这个量级在低信噪比下足够稳定但在高信噪比下就会显得寒酸。我的经验是如果你的目标误码率要到1e-5以下至少要让误码数累积到几十个以上否则两次跑脚本的结果可能差好几倍。建议做法是分段设置仿真点数——低信噪比区间用1000个OFDM符号高信噪比区间用5000甚至更多。仿真时间多花不了几秒曲线平滑度会好很多。5.4 不想依赖通信工具箱的话怎么自己写映射qammod是Communications Toolbox的函数如果机器上没装这个工具箱可以自己写映射。QPSK很简单00/01/11/10映射到(±1±1j)/√2注意采用格雷码排列。16QAM稍微麻烦一点需要把每4个比特拆成I路2比特和Q路2比特分别映射到{-3,-1,1,3}/√10然后IjQ组合。我自己做实验的时候喜欢先用工具箱把流程跑通确认理论曲线对上了再手写映射去验证对星座图的理解。两者结果完全一致的话说明你对调制原理的理解已经过关了。最后说点实在的我个人的体会是做通信仿真最难的不是写代码本身而是确保你测的物理量和你以为的物理量是同一个东西。Eb/N0、Es/N0、时域噪声功率、频域噪声功率这几个概念折腾清楚了后面加多径信道、加载波频偏、加相位噪声都是有章可循的因为核心里面都是同一套信噪比思维。这次实验做完之后我又顺手把信道换成了频率选择性衰落信道加上循环前缀和多径时延再跑QPSK和16QAM曲线形态和AWGN下完全不一样——OFDM在对抗多径上的价值立刻体现出来了。如果你想继续深入这个方向是个很自然的下一站。先写到这下一篇聊衰落信道下的对比时再接着展开。
返回列表