2026/10/23 17:58:31

MATLAB 16QAM星座图与误码率仿真:链路实现与避坑

MATLAB 16QAM星座图与误码率仿真:链路实现与避坑 简介面向通信原理与数字调制课程学习者的16QAM仿真代码包聚焦星座图绘制与误码率评估两个核心实验环节适合需要理解QAM映射、解调判决与AWGN信道性能的本科生或自学者动手复现。包内共8个文件均为MATLAB的.m脚本与函数压缩包约3KB体量轻便却覆盖了从随机二进制序列生成、串并转换、16QAM调制、星座点可视化、最佳接收解调到BER统计的完整链路函数职责拆分清晰便于按模块阅读和二次修改。已有1927人学习下载说明其在课程实验与入门仿真中具有一定参考价值。读者可据此搭建端到端仿真流程观察16个星座点在理想与含噪条件下的分布差异并通过改变信噪比得到误码率曲线进而理解幅度相位联合调制与最大似然检测的关系代码结构也可作为自建通信链路仿真框架的起点。1. 16QAM 星座图仿真与误码率仿真这套 MATLAB 脚本到底替你算了什么第一次在 MATLAB 里画 16QAM 星座图很多人都会撞上同一个画面16 个点倒是出来了要么全挤在 ±0.3 附近糊成一团要么整体转了 45 度再一看误码率曲线跟理论值整整差着 10 dB怎么调 SNR 都对不上。这套「16QAM 星座图仿真及误码率仿真」的 MATLAB 脚本处理的就是这一段最容易翻车的链路Gray 映射星座生成、平均功率归一化、按 Eb/N0 加复高斯噪声、最小欧氏距离判决最后把仿真 BER 和理论 erfc 曲线叠在同一张 semilogy 图上。它不是什么大工程就是一组能独立跑起来的 .m 文件但链路是完整的比特进来符号出去噪声加上去判决回来误码数出来。做通信原理课设的、刚接触链路级仿真想找一副能改的底子的、准备在它上面接瑞利衰落或者 OFDM 的都适合从这儿起步。代码量不大可每一个参数怎么来的、每一行为什么要这么写恰恰是新手最容易糊过去的地方。2. 链路骨架一条从比特到误码计数的 16QAM 仿真通路2.1 文件划分与主脚本的调用顺序这份资源一般拆成两个入口脚本加一组函数逻辑上跟真实链路的模块划分一一对应。目录大概长这样qam16_sim/ ├── main_qam16_ber.m % 主脚本: 扫 Eb/N0, 出 BER 曲线 ├── main_qam16_const.m % 主脚本: 单个 SNR 下的星座图 ├── qam16_mod.m % 比特 - 复符号 (Gray 映射) ├── qam16_demod.m % 复符号 - 比特 (硬判决) ├── add_awgn.m % 按 Eb/N0 加复高斯噪声 ├── theory_ber16qam.m % Gray 16QAM 理论误码率 └── plot_constellation.m % 星座图绘制调用顺序是一条直线没有任何分支randi产生比特流 →qam16_mod映射成复符号 →add_awgn按当前 Eb/N0 叠加噪声 →qam16_demod判决回比特 → 跟发送比特逐位比对mean(rx ~ tx)就是这一点的 BER。拆成多个文件而不是一个大脚本好处在调试阶段就显出来了。改 SNR 范围、改符号数这类操作重跑主脚本就行只想看星座图形状时直接调main_qam16_const.m不用陪着蒙特卡洛循环跑几十秒。我一般在写这类仿真时都会把「画图的」和「算数的」分开因为调参阶段你会反复画图但不想反复重算。真正要留意的是数据的方向约定。qam16_mod输出列向量qam16_demod也按列向量处理中间加噪保持同尺寸。一旦某一处不小心转置成行向量mean(a ~ b)就可能因为维度广播算出一个看着像模像样、其实是错的误码率——这种 bug 最阴因为程序不报错曲线也照画。2.2 参数表M、k、Eb/N0 与蒙特卡洛符号数仿真里所有「玄学」都藏在参数定义里先把这张表钉死后面看代码才不会迷路。参数变量名典型取值含义与取值依据调制阶数M164×4 方形星座M 取 2 的偶数次幂才方便方形映射每符号比特数k4log2(M)决定比特流分组的长度符号数Nsym2e5 ~ 1e6每个 SNR 点独立生成见 2.4信噪比扫描EbN0_dB0:2:16步长 2 dB 足够看清曲线拐点噪声方差N01/(k·EbN0)前提是符号功率已归一化为 1判决电平表lut[-3 -1 3 1]未归一化尺度下的 4PAM 电平归一化因子—sqrt(10)16QAM 平均符号功率的平方根k log2(M)这一行别写成硬编码 4否则你哪天想扩到 64QAM 时得回头把每个文件里的 4 全改一遍。用log2(M)写改成M 64时只有映射表要重写。EbN0_dB从 0 开始而不是从 -5 开始是因为 16QAM 在 0 dB 时的理论 BER 已经在 1.4e-1 量级再往下扫就是一片 0.3~0.5 的平线没有信息量。扫到 16 dB 时理论值约 1e-8仿真要到这个量级得跑上千万符号所以实际脚本常常把上限压到 12~14 dB曲线能做到 1e-5 就够交差了。2.3 归一化与 Es/N0 到 Eb/N0 的换算这是整套仿真里最容易翻车的一步也是绝大多数「曲线平移」问题的根源。16QAM 未归一化的标准星座I 路和 Q 路各取{-3, -1, 1, 3}四个电平。每路平均平方值是(9119)/4 5两路加起来平均符号能量Es 10。也就是说如果你直接拿I 1j*Q当发送符号那它的平均功率是 10 而不是 1。而add_awgn里算噪声方差时通常的写法是假定Es 1N0 1 / (k * EbN0_lin); % 符号功率 1 时成立一旦发送符号的实际功率是 10等效信噪比就被抬高了 10 dB仿真曲线会整体往左平移 10 dB看上去「比理论还好」——这不是算法厉害是账算错了。所以调制函数最后一定要有这一句sym (I 1j*Q) / sqrt(10); % 把平均符号功率拉回 1sqrt(10)不是随手写的魔法数它是平均符号能量的平方根。同理解调端拿到r之后要乘回去z r * sqrt(10)把接收符号还原到{-3,-1,1,3}的判决尺度上才能跟lut直接比距离。换算关系本身也值得在纸上推一遍。Es k · Eb已知Es 1那么Eb 1/k。要求Eb/N0就有Eb/N0 1 / (k · N0) N0 1 / (k · (Eb/N0))这里的Eb/N0是线性值不是 dB所以循环里必须写10^(EbN0_dB/10)。忘了这步、直接把 dB 值当线性值代进去曲线会变成一条形状诡异的东西从 0 dB 到 10 dB 几乎不动——踩过一次就记住了。复噪声的方差还要再分一半复高斯噪声n nI j·nQ的总方差是N0实部和虚部各占N0/2。所以生成时是n sqrt(N0/2) * (randn(size(sym)) 1j*randn(size(sym)));写成sqrt(N0)就相当于每个维度多给了 3 dB仿真结果又会好 3 dB。这个错和上一个错叠加起来正好能凑出一条「完美贴合理论」的假曲线很多人在课设答辩时才发现问题。2.4 判决方式硬判决、软判决与内置 qammod 的差别这份脚本用的是硬判决解调器直接吐 0/1然后逐位比对。好处是简单、快、误码率统计直白不需要额外的译码器。但如果你打算在这套东西上接卷积码或者 LDPC硬判决就会让编码增益打折。软判决输出的是每个比特的对数似然比LLR常见做法是 max-log 近似对每个比特找「该比特为 0 的所有星座点里距离最小的」和「该比特为 1 的所有星座点里距离最小的」两者欧氏距离平方之差再除以N0就是 LLR。16 个点做全搜索也就 16 次距离计算复杂度完全不是问题。另一条路是直接用 Communications Toolbox 的qammod/qamdemodsym qammod(bits_int, 16, InputType, bit, ... UnitAveragePower, true, PlotConstellation, false); bits_rx qamdemod(r, 16, OutputType, bit, ... UnitAveragePower, true);注意UnitAveragePower这个开关打开后qammod内部就替你做了/sqrt(10)此时add_awgn里按Es 1算噪声才成立。如果关掉它默认行为平均功率是 10你得把噪声方差乘 10。这两个选项混着用是仅次于归一化遗漏的第二大坑。自己手写映射表的价值在于可控想换成星形 16QAM、想人为制造 I/Q 不平衡、想给某些星座点加偏置改一张lut表就完事比跟工具箱的黑匣子较劲舒服得多。3. 核心代码逐段拆调制、加噪、判决三段主干3.1 查表法生成 Gray 映射星座Gray 映射的意义很实在相邻的星座点只差一个比特这样噪声把符号推到隔壁点时只错 1 个比特而不是 2 个。16QAM 的方形星座可以拆成 I、Q 两路独立的 4PAM每路用 2 个比特做 Gray 编码。function sym qam16_mod(bits) % QAM16_MOD Gray 映射的 16QAM 调制 % bits : 行向量, 元素 0/1, 长度必须是 4 的整数倍 % sym : 列向量, 复符号, 平均功率归一化为 1 k 4; if mod(numel(bits), k) ~ 0 error(比特数 %d 不是 4 的整数倍, numel(bits)); end b reshape(bits(:), k, []).; % N x 4, 每行是一个符号的 b1 b2 b3 b4 idxI b(:,1)*2 b(:,2); % 00-0 01-1 10-2 11-3 idxQ b(:,3)*2 b(:,4); % Gray 码 - 4PAM 电平, 索引 1..4 对应 idx 0..3 % 00 - -3, 01 - -1, 10 - 3, 11 - 1 lut [-3, -1, 3, 1]; I lut(idxI 1).; % 转成列向量 Q lut(idxQ 1).; sym (I 1j*Q) / sqrt(10); % 平均符号功率归一化为 1 end逐行看。reshape(bits(:), k, [])按列优先把比特流切成每 4 个一组转置后每行就是一个符号的[b1 b2 b3 b4]。这里用bits(:)强制成列向量是为了防止调用方传进来一个行向量导致reshape的分组顺序跟预期不一致——这种细节在单独测试时看不出问题接到长比特流上才会暴露。idxI b(:,1)*2 b(:,2)把两个比特拼成一个 0~3 的整数。lut的索引要1因为 MATLAB 从 1 开始。关键在于lut [-3, -1, 3, 1]这个顺序按电平排序是-3, -1, 1, 3对应的 Gray 码是00, 01, 11, 10。相邻电平两两比较00/01差 1 位、01/11差 1 位、11/10差 1 位Gray 性质成立。如果图省事写成lut [-3, -1, 1, 3]自然二进制顺序星座图看着一模一样但 BER 曲线在中等信噪比会明显抬高——因为相邻点可能差 2 个比特。这就是典型的「图对了、数不对」。最后/sqrt(10)做归一化。前面推过四个电平的平方和是 20除以 4 得 5I/Q 两路共 10。3.2 按 Eb/N0 反推复噪声方差function r add_awgn(sym, EbN0_dB, k) % ADD_AWGN 按每比特信噪比 Eb/N0 叠加复高斯白噪声 % sym : 平均功率已归一化为 1 的复符号 % EbN0_dB : 标量, 单位 dB % k : 每符号比特数, 16QAM 取 4 % r : 与 sym 同尺寸的接收符号 EbN0_lin 10^(EbN0_dB/10); % dB - 线性 N0 1 / (k * EbN0_lin); % 符号功率为 1 时的噪声功率谱密度 % 复噪声总方差 N0, 实部虚部各 N0/2 n sqrt(N0/2) * (randn(size(sym)) 1j*randn(size(sym))); r sym n; end10^(EbN0_dB/10)是 dB 到线性功率的换算注意是除以 10 而不是 20——功率比用 10log幅度比才用 20log。这里传进来的是功率意义上的Eb/N0所以用 10。N0 1/(k*EbN0_lin)的推导在 2.3 已经走过一遍符号功率归一化为 1Es 1Es/N0 k·Eb/N0于是N0 1/(k·Eb/N0)。如果哪天你把归一化因子改成别的值这一行必须同步改否则又是几 dB 的偏差。噪声生成里sqrt(N0/2)是标准差不是方差。randn产生的是单位方差的标准正态乘上标准差之后每个维度方差才是N0/2合成复噪声的方差是N0。这三个量方差、标准差、单边功率谱密度在通信仿真里经常被混着叫写代码时心里要有数。还有一点randn(size(sym))里的size(sym)是必要的。如果写成randn(1, N)而sym是列向量MATLAB 的隐式扩展会把它算成N×N矩阵内存瞬间爆掉。我在调试一个Nsym 1e6的脚本时被这个坑过一次任务管理器里 MATLAB 直接吃到 30 多个 G。3.3 最小欧氏距离判决与比特还原判决是调制的逆过程但因为 16QAM 是方形星座I 路和 Q 路可以完全独立判决不需要在 16 个点上做二维搜索。function bits qam16_demod(r) % QAM16_DEMOD 16QAM 硬判决解调 (最小欧氏距离) % r : 接收复符号, 列向量 % bits : 行向量, 长度 4 * 符号数 lut [-3, -1, 3, 1]; z r(:) * sqrt(10); % 还原到判决尺度 [~, idxI] min(abs(real(z) - lut), [], 2); % 每行取最小距离, 得 1..4 [~, idxQ] min(abs(imag(z) - lut), [], 2); iI idxI - 1; % 回到 0..3 iQ idxQ - 1; b1 floor(iI/2); b2 mod(iI,2); % 高位 / 低位 b3 floor(iQ/2); b4 mod(iQ,2); bits reshape([b1 b2 b3 b4]., 1, []); % 按符号顺序拼回比特流 endreal(z)是N×1lut是1×4相减时 MATLAB 的隐式扩展自动得到N×4的距离矩阵min(..., [], 2)沿第二维取最小返回的idxI就是每个符号在 I 路上最接近的电平索引。这一步等价于在{-3,-1,1,3}四个电平里找最近的只不过写成了向量化的矩阵运算比for循环快一个数量级。iI idxI - 1把索引换回 0~3 的整数然后floor(iI/2)和mod(iI,2)拆出两个比特。注意这里拆出来的顺序必须跟调制端b(:,1)*2 b(:,2)的拼接顺序严格对应否则比特顺序错位误码率永远卡在 0.5 附近下不去。最后的reshape([b1 b2 b3 b4]., 1, [])值得停一下。[b1 b2 b3 b4]是N×4转置成4×Nreshape按列优先展平得到的顺序是「符号 1 的 b1 b2 b3 b4符号 2 的 b1 b2 b3 b4……」。这跟调制端reshape(bits(:), 4, []).的分组方式完全一致一进一出才对得上。这里其实还藏着一个性能点min(abs(real(z) - lut), [], 2)会临时创建一个N×4的中间矩阵。Nsym 1e6时是 4M 个 double32 MB还好但如果哪天你扩到 256QAM 并且一次跑 1e7 个符号这个中间矩阵就上 G 了。真到那一步改成三次if判断边界会更省内存。3.4 误码统计循环与理论 erfc 曲线主脚本把上面三段串起来外面套一个 SNR 扫描循环。M 16; k log2(M); EbN0_dB 0:2:16; Nsym 2e5; ber_sim zeros(size(EbN0_dB)); ber_thr zeros(size(EbN0_dB)); rng(2024); % 固定种子, 结果可复现 bits_tx randi([0 1], 1, k*Nsym); sym_tx qam16_mod(bits_tx); for ii 1:numel(EbN0_dB) r add_awgn(sym_tx, EbN0_dB(ii), k); bits_rx qam16_demod(r); ber_sim(ii) mean(bits_rx ~ bits_tx); EbN0_lin 10^(EbN0_dB(ii)/10); ber_thr(ii) 3/8 * erfc(sqrt(2/5 * EbN0_lin)); % Gray 16QAM 理论值 end semilogy(EbN0_dB, ber_thr, k-, LineWidth, 1.2); hold on; semilogy(EbN0_dB, ber_sim, bo, MarkerFaceColor, b); grid on; xlabel(E_b/N_0 (dB)); ylabel(BER); legend(理论值, 仿真值, Location, southwest);rng(2024)是后悔药。通信仿真跑一次几十秒曲线抖一下你根本分不清是噪声采样波动还是代码改坏了。固定种子之后每次跑出来的数完全一样改一行代码对比前后差异才有意义。种子取什么值无所谓我习惯用当天的日期。randi([0 1], 1, k*Nsym)一次生成全部比特在循环外完成。这样每个 SNR 点用的都是同一组发送符号曲线上的点之间才有可比性如果在循环内重新生成低信噪比和高信噪比的差异里就混进了随机性。理论值那一行3/8 * erfc(sqrt(2/5 * EbN0_lin))是 Gray 编码方形 16QAM 的近似式。推导路径是符号错误率Ps 3·Q(sqrt(4Eb/(5N0)))Gray 映射下Pb ≈ Ps/k再把Q(x) 0.5·erfc(x/√2)代进去化简就得到3/8·erfc(sqrt(0.4·Eb/N0))。这个式子在Eb/N0 5 dB时跟精确值贴得很紧低信噪比会略微高估看曲线时心里有数就行。semilogy画图时仿真点用bo而不是连线是为了让读者一眼看出「这是蒙特卡洛采样点不是连续函数」。3.5 星座图绘制让 16 个点分得开星座图本身信息量不大但它是排查问题最快的工具——曲线不对时先看星座图形状八成能定位到是归一化、相位还是判决尺度的问题。function plot_constellation(sym, ttl) % PLOT_CONSTELLATION 画接收符号的星座分布并叠加理想点 figure(Color, w); plot(real(sym), imag(sym), b., MarkerSize, 4); hold on; ref_bits dec2bin(0:15, 4). - 0; % 4x16 的 0/1 矩阵 ref_sym qam16_mod(ref_bits(:).); % 16 个理想星座点 plot(real(ref_sym), imag(ref_sym), ro, ... MarkerSize, 9, LineWidth, 1.2); axis equal; grid on; xlabel(同相分量 I); ylabel(正交分量 Q); title(ttl); enddec2bin(0:15, 4)生成 16 个 4 位二进制串转置减0变成数值矩阵(:).拉成行向量喂给qam16_mod正好得到 16 个理想点。axis equal这一句不能省。不加的话 MATLAB 会按数据范围自动拉伸坐标轴16 个点看起来是长方形的你会误以为 I/Q 两路增益不平衡。scatterplot(r)是工具箱里的替代方案一行就能画出带密度着色的星座图做论文插图挺好看。但它默认按接收符号的实际功率缩放跟手动画的理想点对不上调试阶段反而添乱。4. 结果怎么读星座图形态与 BER 曲线的四组对照4.1 星座图的四种典型形态跑完之后看到的图基本逃不出这四类每一种都对应一个具体的代码位置。第一类16 个点聚成 16 团中心落在±0.316、±0.949这些位置也就是3/sqrt(10)和1/sqrt(10)团的大小随 SNR 变化。这是完全正常的形态说明归一化和加噪都对。第二类16 个点聚得挺好但整体落在±1、±3上。这说明调制函数里的/sqrt(10)被删了或者被注释掉了。此时add_awgn仍然按Es 1算噪声等效信噪比被抬高 10 dBBER 曲线会平移。第三类点云整体旋转了一个角度比如 45 度或者一个小角度。45 度通常是星座点定义成了「偏移」形式点在±1±j上而不是±1, ±3上等价于换了一套映射小角度旋转一般是加了相位噪声或者载波频偏但没做补偿。第四类SNR 很低时点云糊成一整片圆形看不出 16 个团。这是正常的Eb/N0 0 dB时噪声标准差跟点间距差不多本来就分不开。这时候该看的是 BER 值在 0.14 附近而不是指望星座图还有结构。调参时的实用技巧把同一个sym_tx分别用 5 dB、10 dB、15 dB 加噪画三张图放一起一眼就能看出点云收缩的过程。这比盯着一堆数字有说服力。4.2 仿真点与理论线的贴合判据BER 曲线贴不贴别用眼睛「差不多」来判断用数字。中高信噪比区间Eb/N0 ≥ 8 dB仿真点和理论值的相对误差控制在 10% 以内算是合格。比如理论值 1e-3 时仿真落在 0.9e-3 ~ 1.1e-3 都正常。低信噪比区间误差会大一些因为理论式本身是近似加上误码率接近 0.5 时统计涨落小、反而稳定。如果整条曲线平行偏移是常数增益问题回去查 2.3 的归一化和N0/2如果曲线形状不一样比如低 SNR 贴得上、高 SNR 翘起来那是判决或映射的问题回去查lut的 Gray 顺序和比特拼接顺序。还有一个容易忽略的点Nsym 2e5时理论 BER 到 1e-5 就意味着平均只有 2 个错误比特统计涨落极大曲线会开始「抖」。我一般会要求每个 SNR 点至少累积 100 个错误不够就自动加大Nsym重跑这比拍脑袋定一个固定值靠谱。4.3 换瑞利信道后曲线为什么「躺平」把 AWGN 换成瑞利衰落是这套脚本最常见的第一次改造。改动很小% 瑞利衰落信道 迫零均衡 h (randn(size(sym_tx)) 1j*randn(size(sym_tx))) / sqrt(2); % CN(0,1) r h .* sym_tx n; % 先过信道 r_eq r ./ h; % 迫零均衡, 把信道搬回去h除以sqrt(2)是为了让复信道增益的平均功率为 1这样Eb/N0的定义才不变。跑完会发现曲线在高 SNR 段变成一条斜率很缓的直线误码率下降得极慢。这不是代码写错了是瑞利衰落的本质深衰落发生时|h|接近 0r./h把噪声放大了几十倍这一批符号无论 SNR 多高都会错。误码率曲线的斜率从「指数下降」变成「线性下降」分集阶数只有 1。迫零均衡还有个数值陷阱h是复高斯随机变量偶尔会采到极小值r./h直接产生一个巨大的数。实际脚本里通常会加一句保护h(abs(h) 1e-3) 1e-3 * exp(1j*angle(h(abs(h) 1e-3)));或者干脆改用 MMSE 均衡r_eq conj(h).*r ./ (abs(h).^2 N0)用噪声方差兜底避免除零。4.4 用内置 qammod 做交叉验证自己写的映射表对不对最省事的验证方式是跟工具箱对一遍。bits_int bit2int(bits_tx(:), 4); % 每 4 比特转成一个整数 sym_ref qammod(bits_int, 16, InputType, integer, ... UnitAveragePower, true); err max(abs(sym_ref(:). - sym_tx));err应该是 0 或者机器精度量级1e-16。如果不是说明lut的 Gray 顺序跟工具箱不一致。注意工具箱的整数到星座点映射有自己的一套约定跟你的lut不一定要逐点相同但误码率性能应该一致。所以更稳妥的验证是两边各自跑一遍完整链路看 BER 曲线是否重合。曲线重合但星座点位置不同说明只是映射约定不同不影响结论曲线不重合那就是有一边的 Gray 性质坏了。我一般在写完自定义映射后都会做这一步三十秒的事能省掉后面几个小时的怀疑人生。5. 避坑与排查曲线跑出来了但结论不对的 5 个现场5.1 仿真 BER 比理论好 3 dB 到 10 dB现象Eb/N0 10 dB时理论值 1.75e-3仿真跑出来 1e-4曲线整体左移。原因归一化和噪声方差两处账对不上。最常见的是发送符号没除sqrt(10)少 10 dB或者复噪声按sqrt(N0)生成而不是sqrt(N0/2)多 3 dB。解决在add_awgn里临时插一句fprintf(Es%.3f, N0%.3e\n, mean(abs(sym).^2), N0);Es必须等于 1。不等就先修归一化再谈曲线。5.2 星座图整体转了 45 度现象16 个点不在±0.316 / ±0.949上而是落在±0.316±0.316j这种对角位置。原因映射表被改成了偏移形式或者用了qammod的默认配置默认不是UnitAveragePower也不是你手写的那套 Gray 表两套映射混用。解决确认lut是[-3 -1 3 1]且sym (I 1j*Q)/sqrt(10)这一行完整。若改用工具箱qammod和qamdemod必须成对使用同一套参数。5.3 低信噪比处 BER 卡在 0.3 左右下不去现象Eb/N0 0 dB附近 BER 是 0.3扫到 16 dB 掉到 0.25 就不动了。原因比特顺序错位。调制端按[b1 b2 b3 b4]分组解调端reshape时顺序反了或者floor/mod的高低比特位搞反导致即使符号判决全对比特还是对不上。解决单独测一次无噪声回环——qam16_demod(qam16_mod(bits))应该原样返回bitsisequal为 true。这一条过不了后面全是白跑。5.4 高信噪比点直接从曲线上消失现象semilogy画出来的仿真曲线在 14 dB 之后断掉了没有点。原因那一点的误码数是 0log(0)在 MATLAB 里是-Inf画不出来。解决不是 bug是符号数不够。要么加大Nsym要么在画图前把 0 替换成NaN让曲线断开而不是掉到 -Inf要么老老实实把 SNR 上限压到能跑出 100 个错误的水平。我倾向于第三种因为硬凑出来的低误码率点没有统计意义。5.5 换个 SNR 重跑结果每次都不一样现象同样参数跑两遍Eb/N0 6 dB那点一次是 2.1e-2一次是 2.4e-2。原因没有固定随机种子randi本文还有配套的精品资源点击获取