news 2026/10/23 17:56:17

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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}四个电平。每路平均平方值是(9+1+1+9)/4 = 5,两路加起来,平均符号能量Es = 10。也就是说,如果你直接拿I + 1j*Q当发送符号,那它的平均功率是 10 而不是 1。

而add_awgn里算噪声方差时,通常的写法是假定Es = 1:

N0 = 1 / (k * EbN0_lin); % 符号功率 = 1 时成立

一旦发送符号的实际功率是 10,等效信噪比就被抬高了 10 dB,仿真曲线会整体往左平移 10 dB,看上去「比理论还好」——这不是算法厉害,是账算错了。所以调制函数最后一定要有这一句:

sym = (I + 1j*Q) / sqrt(10); % 把平均符号功率拉回 1

sqrt(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/qamdemod:

sym = 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 得 5,I/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; end

10^(EbN0_dB/10)是 dB 到线性功率的换算,注意是除以 10 而不是 20——功率比用 10log,幅度比才用 20log。这里传进来的是功率意义上的Eb/N0,所以用 10。

N0 = 1/(k*EbN0_lin)的推导在 2.3 已经走过一遍:符号功率归一化为 1,Es = 1,Es/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, []); % 按符号顺序拼回比特流 end

real(z)是N×1,lut是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×N,reshape按列优先展平,得到的顺序是「符号 1 的 b1 b2 b3 b4,符号 2 的 b1 b2 b3 b4,……」。这跟调制端reshape(bits(:), 4, []).'的分组方式完全一致,一进一出才对得上。

这里其实还藏着一个性能点:min(abs(real(z) - lut), [], 2)会临时创建一个N×4的中间矩阵。Nsym = 1e6时是 4M 个 double,32 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); end

dec2bin(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 dB,BER 曲线会平移。

第三类,点云整体旋转了一个角度,比如 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|接近 0,r./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))应该原样返回bits,isequal为 true。这一条过不了,后面全是白跑。

5.4 高信噪比点直接从曲线上消失

现象:semilogy画出来的仿真曲线在 14 dB 之后断掉了,没有点。原因:那一点的误码数是 0,log(0)在 MATLAB 里是-Inf,画不出来。解决:不是 bug,是符号数不够。要么加大Nsym,要么在画图前把 0 替换成NaN让曲线断开(而不是掉到 -Inf),要么老老实实把 SNR 上限压到能跑出 100 个错误的水平。我倾向于第三种,因为硬凑出来的低误码率点没有统计意义。

5.5 换个 SNR 重跑,结果每次都不一样

现象:同样参数跑两遍,Eb/N0 = 6 dB那点一次是 2.1e-2,一次是 2.4e-2。原因:没有固定随机种子,randi

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/23 17:41:23

CUPS 2.3.3 源码编译、IPP 链路架构与打印排错

简介&#xff1a;CUPS 2.3.3 源代码压缩包面向 Linux、BSD、macOS 等类 Unix 平台的系统管理员与打印系统开发者&#xff0c;用于在本地编译、定制和二次开发通用 Unix 打印系统&#xff0c;解决跨平台打印作业管理、驱动适配与打印服务部署问题。压缩包共 1294 个文件、约 7.7…

作者头像 李华
网站建设 2026/10/23 17:40:30

试用 no-ai-slop 一周,我对“去 AI 味“这个风口的三个不服

试用 no-ai-slop 一周&#xff0c;我对"去 AI 味"这个风口的三个不服 【免费下载链接】no-ai-slop Removes 20 patterns of AI slop from any piece of writing. 项目地址: https://gitcode.com/gh_mirrors/no/no-ai-slop 上周刷 GitHub 热榜&#xff0c;一个…

作者头像 李华
网站建设 2026/10/23 17:37:20

信号分析与处理实验全解析:从时域到频域再到系统

简介&#xff1a;这份资源是南京邮电大学「信号分析与处理实验」课程的完整实验报告&#xff0c;面向正在修读数字信号处理、信号与系统相关课程的高校学生&#xff0c;以及需要借助 MATLAB 完成实验与课程设计的自学者。报告覆盖信号的产生和运算、连续时间信号的频域分析、时…

作者头像 李华
网站建设 2026/10/23 17:25:57

长江经济带GIS矢量图构建:从数据整理到标准化底图实战

简介&#xff1a;这是一份面向GIS研究者、规划与政策分析人员的2015年长江经济带地级市矢量底图数据包&#xff0c;可直接服务于区域经济对比、生态环境评估和城市边界分析等场景。数据内包含长江经济带沿线所有地级行政区的边界、行政划分及地理位置信息&#xff0c;并附有道路…

作者头像 李华
网站建设 2026/10/23 16:38:13

Python 3.11错误定位与异常处理新机制深度解析

简介&#xff1a;本资源是Python 3.11.0官方中文版完整教程PDF&#xff0c;面向Python中高级学习者、开发者及技术文档查阅者&#xff0c;系统解决新版本特性理解难、迁移适配无依据、类型提示实践缺参考等实际问题。全书覆盖PEP 657细粒度错误定位、PEP 654异常组、PEP 678异常…

作者头像 李华
网站建设 2026/10/23 16:28:08

扫码枪网口TCP通讯demo:从连接建立到稳定取码的工程实践

简介&#xff1a;这份资源面向需要将基恩士扫码枪接入上位机系统的开发者&#xff0c;提供网口TCP通讯的可运行demo与完整源码&#xff0c;解决工业现场扫码数据实时采集与连接稳定性问题。压缩包共51个文件&#xff0c;约291KB&#xff0c;以C#源码文件为主&#xff0c;辅以项…

作者头像 李华