ARTICLE DETAIL

资讯详情

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

QAM误码率仿真:从16-QAM到256-QAM的最大似然检测与排查

QAM误码率仿真:从16-QAM到256-QAM的最大似然检测与排查 简介面向通信工程与数字信号处理学习者的64QAM调制仿真脚本用于理解正交幅度调制原理、误码率统计方法及最大似然检测的完整实现流程。脚本围绕64QAM系统展开首先生成随机二进制序列并映射为64种符号随后在信道中叠加高斯噪声以模拟真实传输环境接收端通过最大似然算法计算所有符号与接收信号的距离并选取最优解调结果最终统计输出误码率。这一闭环设计不仅展示从发送到接收的完整链路还便于读者修改噪声强度、观察不同信噪比下的性能变化是学习调制解调与信道编码的实用工具。资源包仅含1个m文件大小约1KB为MATLAB可执行脚本无额外依赖适合直接运行与二次开发。已有568人学习使用对正在准备通信原理课程实验或研究高效调制技术的读者尤其具有参考价值。1. 从 16-QAM 到 256-QAM最大似然检测为什么是误码率曲线的基准线在 16-QAM 星座图上每 4 个比特被映射成一个复符号接收端面对的是叠加了高斯噪声的观测点。最大似然检测是白噪声信道下误码率最低的判决方式遍历所有可能的发射符号选后验概率最大的那个。把“调制—加噪—判决—统计误码率”整条链路用代码跑一遍能得到一条可以和理论公式逐点对照的 BER 曲线这也是网络接口误码率测试、数字电视前端和回传链路常拿来做裕量判断的基准线。QAM 调制的阶数从 16 升到 64星座点间距从 0.63 缩到 0.31归一化能量下同样的低误码率要多付出约 4~5 dB 信噪比再升到 256-QAM 还要再付类似代价。这种跳变不是噪声变大了而是星座点密度翻倍带来的必然结果从误码率曲线上能直接看出来。下面按“理论 → 仿真代码 → 排查 → 进阶”的顺序把这条链路拆开读完你应该能自己跑出 16-QAM 和 64-QAM 的 BER 曲线并且知道仿真曲线和理论公式对不上时从哪里下手。2. 最大似然检测的理论坐标QAM 映射、判决模型和误码率公式2.1 QAM 调制的基本结构与选型原因I/Q 双路并行与调制优缺点QAM 的本职工作是在同一段载波时间内用两个正交分量 I 和 Q 同时传递信息。矩形正方形 M-QAM 星座把 I、Q 各分成 √M 个电平组合出 M 个点。每个点对应 log2(M) 比特。16-QAM 是 4 个 I 电平和 4 个 Q 电平每符号 4 比特64-QAM 和 256-QAM 分别是 6 比特和 8 比特每符号。电平数越多频谱效率越高但相邻星座点挨得越近抗噪声能力越差。QAM 调制优缺点需要放回候选调制族里比。PSK 只看相位ASK 只看幅度QAM 同时看两个频谱效率最高MSK、DPSK 这类恒包络方案用恒定信号包络换取低成本功放的应用空间但在带宽效率上吃亏。DPSK 以符号间相位差传信息接收机可以做差分检测不用做载波相位同步在突发通信和低成本场合常见MSK 作为连续相位调制在卫星和短距离无线里一直有人用。QAM 对幅度失真最敏感星座点密集时功放非线性和链路增益误差都会让点位置漂移高谱效换来的是更高的硬件要求。选型时看四条就够带宽效率、抗噪性能、功放回退量、接收机复杂度。仿真是这四条判断的最小验证单元。先看三档的间距和效率调制阶数 M每符号比特数归一化后最小点间距相对 BPSK 频谱效率1640.6324 倍6460.3096 倍25680.1538 倍间距随阶数大致按 1/√M 关系缩小这就是为什么高阶 QAM 在同样 Eb/N0 下误码率更高。构造星座图时我习惯用正方形星座、Gray 映射、平均能量归一化为 1。三条选件事先定死后边的理论公式才能和仿真曲线对齐。import numpy as np def gray_code(n): # 标准 Gray 码n ^ (n 1)保证相邻序号只有 1 比特不同 return n ^ (n 1) def build_qam_constellation(M): 构造归一化正方形 M-QAM 星座点与 Gray 标签。 M 必须是平方数例如 16、64、256。 返回 points复数星座点数组平均符号能量归一化为 1 labels与每个星座点对应的 Gray 标签整数。 L int(round(np.sqrt(M))) assert L * L M, M 必须是平方数 levels np.arange(-(L - 1), L, 2) # L4 时得到 [-3, -1, 1, 3] points, labels [], [] for i, re_val in enumerate(levels): for j, im_val in enumerate(levels): points.append(re_val 1j * im_val) labels.append(gray_code(i) * L gray_code(j)) points np.array(points) avg_power np.mean(np.abs(points) ** 2) points / np.sqrt(avg_power) # 平均能量归一化为 1 return points, np.array(labels)这里有个细节Gray 码作用的对象是 i、j 这两个离散索引不是电平值本身。电平序列从低到高排好之后相邻星座点对应的正是相邻索引所以对索引做 Gray 化就能保证相邻点之间只有 1 个比特不同。归一化那一步最容易被忽略后面 Es/N0 与噪声方差的换算全部建立在“平均符号能量 1”这个前提上少了它整条曲线都会平移。2.2 最大似然检测从后验概率到最小欧氏距离的条件在加性高斯白噪声信道里接收符号 y s n其中 n 是零均值复高斯噪声条件概率密度是p(y|s) (1 / (πN0)) · exp(-|y - s|² / N0)发射符号先验等概率时最大后验概率等价于最大化 p(y|s)。取对数后最大化 p(y|s) 就等价于最小化 |y - s|²。所以单符号 AWGN 场景下的最大似然检测规则极其简单遍历星座图找距离最近的星座点。这就是为什么很多教科书把 AWGN 下的 ML 检测直接写成最近邻判决。但有几个前提经常被忽略。存在频率偏移或相位偏移时星座图整体旋转欧氏距离直接失效要先做载波同步或相位校正存在码间干扰时当前符号受前后符号影响单符号判决不够要做序列检测噪声不是白噪声时得先白化再判决。仿真里最容易出问题的就是相偏后面第 4 章会单独讲它导致的地板效应。2.3 误码率公式的闭式表达理论值怎么算才和仿真能对上矩形 M-QAM 的符号错误概率在 AWGN 下有闭式解。记 k log2(M)Es/N0 为符号信噪比Q(x) 为标准高斯 Q 函数P_s 4(1 - 1/√M) · Q(√(3/(M-1) · Es/N0)) - 4(1 - 1/√M)² · Q²(√(3/(M-1) · Es/N0))第一项是四个最近邻方向上的错误概率第二项修正同时错两个方向的事件。对 Gray 映射高信噪比下误比特率用 P_b ≈ P_s / k 逼近因为一次符号错误平均只引入 1 个比特错误。两个换算关系必须记牢Es/N0(dB) Eb/N0(dB) 10·log10(k)以及 Q(x) 0.5·erfc(x/√2)。import math def qfunc(x): # Q(x) 0.5 * erfc(x / sqrt(2)) return 0.5 * math.erfc(x / math.sqrt(2)) def theoretical_ber_qam(M, eb_n0_db): Gray 映射下矩形 M-QAM 的理论误比特率近似。 eb_n0_db 是每比特信噪比单位为 dB。 k int(math.log2(M)) es_n0_lin 10 ** ((eb_n0_db 10 * math.log10(k)) / 10) sqrt_m int(math.sqrt(M)) x math.sqrt(3 / (M - 1) * es_n0_lin) ps 4 * (1 - 1 / sqrt_m) * qfunc(x) - 4 * (1 - 1 / sqrt_m) ** 2 * qfunc(x) ** 2 return ps / k这个公式只适用于 Gray 映射和较高信噪比。低信噪比下“一次符号错误只错 1 个比特”的假设偏乐观理论值会比仿真略低但工程关心的通常是地板以下的工作区这个偏差可接受。仿真时如果发现曲线形状和理论一致、只是整体平移多半是换算或归一化的问题先查这两处。3. 可复现的 QAM 误码率仿真调制、ML 检测和主循环全代码3.1 调制器把比特组映射成归一化复数符号调制器的输入是一串 0/1 比特输出是对应符号位置的复数。实现上先按 k 比特一组转成整数标签再用标签反查星座点下标。def qam_modulate(bits, M, constellation, labels): 将 0/1 比特数组映射为 QAM 复数符号。 bits 长度必须是 k log2(M) 的整数倍。 constellation、labels 来自 build_qam_constellation。 k int(np.log2(M)) bit_groups bits.reshape(-1, k) powers 1 np.arange(k - 1, -1, -1) label_vals bit_groups powers # 每组 k 比特得到一个 0~M-1 的整数 label_to_index {lb: idx for idx, lb in enumerate(labels)} symbol_indices np.array([label_to_index[lv] for lv in label_vals]) return constellation[symbol_indices]bit_groups powers 是二进制向量转整数把每 k 比特当成一个 0 到 2^k-1 的标签。labels 数组把每个整数标签映射到唯一星座点下标这个映射和理论公式中的 Gray 假设必须保持一致。如果你换了星座定义一定要同步改这里。3.2 ML 解调器最近邻判决与比特还原解调端做的事情正好反过来对每个接收符号遍历所有星座点取欧氏距离最小的那个再把它的标签还原成比特。def qam_demodulate_ml(rx, constellation, labels, k): 最大似然解调AWGN 下等价于最小欧氏距离。 rx接收复数符号数组形状为 (n_symbols,)。 返回还原的 0/1 比特数组。 dist np.abs(rx[:, None] - constellation[None, :]) # (n_symbols, M) symbol_indices np.argmin(dist, axis1) # 每行取距离最小者 label_vals labels[symbol_indices] bits np.zeros((len(rx), k), dtypeint) for bit in range(k): bits[:, k - 1 - bit] (label_vals bit) 1 # 标签展开成 k 比特 return bits.reshape(-1)rx[:, None] 把形状从 (n_symbols,) 扩成 (n_symbols, 1)constellation[None, :] 扩成 (1, M)NumPy 广播后得到 n_symbols × M 的欧氏距离矩阵。argmin 沿星座维度取最小值这一步在 AWGN 下就是严格的最大似然判决。复杂度是 O(M)16-QAM 无所谓256-QAM 会明显变慢第 5 章再讲裁剪办法。3.3 仿真主循环加噪、统计误码率和调用示例加噪声时最容易搞错的是 N0 的定义。复噪声的总方差是 N0实部和虚部分别是 N0/2。因为星座能量已归一化为 1所以 N0 1 / (Es/N0 的线性值)。def simulate_ber_qam(M, eb_n0_db, n_bits400000, seed0): 单点 Eb/N0 下的 QAM 误码率蒙特卡洛仿真。 返回误比特率估计值。 rng np.random.default_rng(seed) k int(np.log2(M)) constellation, labels build_qam_constellation(M) es_n0_db eb_n0_db 10 * np.log10(k) # 符号信噪比 n0 10 ** (-es_n0_db / 10) # 复噪声方差Es1 n_symbols n_bits // k bits_tx rng.integers(0, 2, n_symbols * k) tx qam_modulate(bits_tx, M, constellation, labels) noise np.sqrt(n0 / 2) * (rng.standard_normal(n_symbols) 1j * rng.standard_normal(n_symbols)) rx tx noise bits_rx qam_demodulate_ml(rx, constellation, labels, k) error_bits np.sum(bits_tx ! bits_rx) return error_bits / (n_symbols * k)参数说明eb_n0_db横轴是每比特信噪比单位 dB工程报告习惯用它。内部必须换算到 Es/N0因为星座能量和噪声方差对齐的是符号级功率。n0平均符号能量为 1所以 N0 1 / (Es/N0 线性值)。复噪声生成时用 sqrt(N0/2) 做标准差实部虚部分别独立采样。n_bits决定误码率估计的置信度。目标误码率在 1e-5 量级时至少需要 1e7 比特否则统计误差会大到无法和理论曲线对齐。我一般先在低信噪比跑通逻辑再对关键点单独加大样本量。调用示例for eb_n0 in range(0, 13): ber simulate_ber_qam(16, eb_n0, n_bits1000000, seed42) theory theoretical_ber_qam(16, eb_n0) print(fEb/N0{eb_n0:2d} dB sim{ber:.3e} theory{theory:.3e})16-QAM 下 Eb/N0 从 0 扫到 12 dB仿真值和理论公式的差值应该在统计波动范围内。想改 64-QAM把 M 换成 64 即可但 Eb/N0 扫描范围要整体上移约 5 dB否则 BER 会停在接近 0.5 的平段看起来像代码坏了。提示符号级仿真和波形级仿真不要混在同一个脚本里。符号级模型只关心星座和噪声不需要成型滤波器一旦加了根升余弦滤波器判决时刻就成了新变量曲线对不上时先分清是哪层引入的问题。4. 误码率仿真里常见的 5 个排查点现象、原因和改法4.1 理论曲线整体平移噪声功率定义错了现象仿真 BER 和理论公式在同一 Eb/N0 下差几个 dB差值几乎不随信噪比变化整条曲线像是被左右平移过。原因最常见的有两个。一是星座图没归一化平均能量不是 1N0 还按 1 算整条曲线就偏了二是把复噪声的总功率当成了 N0/2实部虚部各用 N0/2 还不够等于噪声加倍。解决检查星座构建代码里有没有除以 sqrt(avg_power)加噪声时确保复噪声总方差是 N0也就是实部虚部每个维度方差不大于 N0/2。再把理论公式里的 Es/N0 换算对一遍确认 Eb/N0 到 Es/N0 加了 10·log10(k)。4.2 高信噪比出现误码率地板相位没校正现象Eb/N0 拉到 12 dB 以上BER 不再下降停留在某个平台像地板一样顶住曲线。这是很多人在仿真里翻车的地方。原因信道模型里混入了固定相偏或残留频偏星座图整体旋转。最大似然判决假设星座对齐旋转之后接收点落在相邻星座点的势力范围高信噪比下这个相位误差变成主导项曲线自然下不去。解决确认信道是纯 AWGN 还是引入了相偏。如果只是固定相偏乘上 exp(-jθ) 补偿如果是残留频偏接收前加最大似然相位估计或逐符号相位跟踪。别指望 ML 判决本身纠偏它是在星座对齐之后才成立的判决规则。4.3 低误码率点抖动严重蒙特卡洛样本量不足现象同一组参数多跑几次Eb/N0 较高时 BER 在 1e-5 附近跳来跳去今天 2e-5明天 8e-6。原因100 万比特在 BER0.1 时约有 10 万个错误统计误差很小到 BER1e-5 只有 10 个错误相对误差超过 30%。这不是代码 bug是蒙特卡洛仿真的固有统计特性。解决每个 Eb/N0 点单独指定比特数。低误码率的点用到 1e7 或 1e8 比特或者用半解析法只在低信噪比做蒙特卡洛高信噪比段用理论公式外推。我习惯先把整条曲线跑通再挑几个关键点补大样本量能省不少时间。4.4 Gray 映射不对称误码率莫名偏高现象仿真 BER 在中等信噪比下比理论值高出约一倍曲线形状没问题但整体向上抬。原因调制端用 Gray 码贴标签解调端还原比特时按自然二进制顺序解释或者标签和星座点顺序定义不一致。一次符号错误统计到多个比特错误就不满足 P_b ≈ P_s/k 的假设了。解决先跑往返一致性测试无噪声时调制→解调 BER 必须严格为 0再加噪声抽查几个符号确认单符号错误只翻转 1 个比特。这两条过了Gray 映射基本对齐。网络接口误码率测试里经常出现这种“看起来差一倍”的报告多半就是映射定义不一致。4.5 判决时刻偏离最佳采样点成型滤波器带来的 ISI现象仿真里加了根升余弦滤波器和匹配滤波器后BER 曲线和理论对不上而且恶化量随滚降因子变小而变大跟信噪比关系不大。原因脉冲成型后匹配滤波输出需要按符号周期找峰值采样。用连续波形仿真时如果只在整数位置抽样码间干扰没有被消除等效于在噪声上又叠了一层 ISI。解决分场景处理。验证“QAM 调制 ML 检测”逻辑时不要加成型滤波直接符号级建模验证带限信道时必须把匹配滤波输出按最佳采样点抽取并明确滚降因子和滤波器时延。两个目标不要放在同一个脚本里否则曲线对不上时很难定位。5. 最大似然检测的工程边界复杂度、软信息和验证习惯5.1 从 16-QAM 到 256-QAM 的复杂度增长与裁剪ML 检测的复杂度随星座点数线性增长。16-QAM 每次判决算 16 个距离256-QAM 要算 256 个。低速接收机这不算什么但 5G 基带或高速光模块里每秒数百万符号乘几百次距离计算硬件资源撑不住。常见做法是分两步先按幅度粗筛再在候选点里精判。256-QAM 可以先根据 |y|² 判断落在哪个环带把候选星座点缩到十几个再做最小距离选择。性能损失用蒙特卡洛验证一般控制在 2~3 dB 以内就可以接受。5.2 把欧氏距离变成 LLR软判决的接法实际系统后面通常跟着 LDPC 或卷积码硬判决输出会丢掉软信息。把 ML 判决改成对数似然比输出并不复杂对每个比特取该比特为 0 的星座点集合和 1 的星座点集合各自最小距离之差就是近似 LLR。LLR(m) ≈ (1/N0) · ( min_{s: b_m1} |y-s|² - min_{s: b_m0} |y-s|² )代码实现如下def qam_soft_llr(rx, constellation, labels, k, n0): 计算每个比特的近似对数似然比。 LLR 越大表示该比特越可能为 0具体符号约定由后续解码器决定。 dist np.abs(rx[:, None] - constellation[None, :]) ** 2 llr np.zeros((len(rx), k), dtypefloat) for bit in range(k): mask0 ((labels bit) 1) 0 mask1 ((labels bit) 1) 1 min_dist0 np.min(dist[:, mask0], axis1) min_dist1 np.min(dist[:, mask1], axis1) llr[:, bit] (min_dist0 - min_dist1) / n0 return llr这个近似省略了星座点内部的概率求和性能和小数点后几位无关工程上够用。接 FEC 解码器前先对一下 LLR 的符号约定不同链路可能定义相反接反了性能直接崩掉。5.3 三种必跑的验证用例我自己的习惯是写了新调制器或新解调器第一个用例永远是零噪声往返调制→解调比特必须完全一致。第二步加一个很小的噪声复核 ML 判决结果符合最近邻原则。第三步才是跑整条 BER 曲线和理论公式逐点对比。这三步能过滤掉大部分因为标签映射、索引反查、比特顺序不一致引起的返工。特别提醒改 M 值时星座构建、比特分组、理论公式三处都要同步改少改一处曲线就不会落在理论值上。希望这些排查思路能帮到你少走一段弯路祝调通曲线顺利。本文还有配套的精品资源点击获取
返回列表