尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

16QAM星座图仿真与误码率仿真:从Gray编码到蒙特卡洛实现

发布时间:2026/9/29 19:34:44

资讯中心
01
ARTICLE

16QAM星座图仿真与误码率仿真:从Gray编码到蒙特卡洛实现

16QAM星座图仿真与误码率仿真:从Gray编码到蒙特卡洛实现
简介这是一份面向通信系统仿真学习者的MATLAB工程针对16QAM数字调制方式可实现星座图绘制与误码率BER仿真帮助理解幅度相位联合调制及AWGN信道下的系统性能。资源压缩包共8个文件全部为m脚本体积仅3KB涵盖主流程控制、随机二进制序列生成、比特与符号映射转换、调制解调函数、星座图绘制和基带成形滤波等环节结构精简适合MATLAB初学者快速部署和二次修改。已有1926人学习这套代码兼具教学演示与实验参考价值。通过运行主函数可在不同信噪比下观察接收信号星座点的分布变化并得到误码率随信噪比变化的性能曲线。同时比特转换函数展示了4位二进制码字到16QAM符号的映射/逆映射过程便于深入理解编码映射与解调判决的原理。1. 16QAM星座图仿真与误码率仿真开工前的三个判断做物理层算法验证时最直观的抓手就是星座图和误码率曲线。16QAM把4个比特映射成一个IQ符号星座图上16个点排成方阵看起来简单但真正把仿真跑起来、让误码率曲线和理论值对上涉及信噪比定义、映射表、判决边界和统计量设置一堆细节。这篇笔记围绕16QAM星座图仿真及误码率仿真展开讲清楚每一段代码为什么这么写、参数怎么定、对不上时往哪排查。适合三类人刚接触数字调制想验证理论公式的初学者、在FPGA或硬件实现前先用仿真确认算法指标的工程师、以及要给整条链路做误码率预算的系统设计者。先想明白三件事再动手用Eb/N0还是Es/N0作横坐标、Gray编码映射表有没有写对、蒙特卡洛仿真要统计到多少比特才可信。2. 16QAM调制与星座映射一张Gray编码表定全局2.1 Gray编码不是可选项相邻星座点只差1bit是误码率性能的底线16QAM的16个星座点I路和Q路各取4个电平。常见电平取值是-3、-1、1、3电平间隔为2这样星座图是均匀方阵平均符号能量可以精确算出来。关键是相邻星座点分配的比特必须使用Gray编码也就是二进制码按00、01、11、10的顺序对应到-3、-1、1、3保证任何相邻电平组合只差1个比特。如果不做Gray编码而直接用自然二进制00、01、10、11接收机判决时最容易把相邻电平搞混一次符号错误可能同时错2个甚至3个比特误码率比Gray编码差一截。这在仿真里非常容易踩中星座图画出来完全正常但BER曲线抬得很高怎么调噪声都压不回理论值。所以映射表是16QAM仿真的第一个全局参数写成常量表而不是临时拼凑。常见映射表如下2比特Gray码的十进制键值0、1、3、2分别对应电平-3、-1、1、3。4个比特为一组时高2位映射到I路低2位映射到Q路。平均符号能量Eavg(4×(19))/410为了后面方便设信噪比把星座点统一除以根号10让平均符号能量归一化为1。这个归一化动作决定了后续噪声功率的计算基准漏掉它整个误码率曲线都会错位。提示归一化基准建议固定写在映射函数里不要留在主流程里单独处理避免不同模块各归各的。2.2 用Python生成16QAM星座图最小可运行代码与坐标核对先写映射函数。这个函数直接决定发射端的符号序列也决定了接收端逆映射的查表方式。import numpy as np # Gray编码映射表2-bit十进制键 - I/Q电平 GRAY_LEVEL {0: -3, 1: -1, 3: 1, 2: 3} def map_16qam(bits): 把比特流映射为16QAM符号每4bit一组高2位为I路低2位为Q路 n len(bits) - len(bits) % 4 # 丢弃不满4bit的尾巴 symbols [] for i in range(0, n, 4): i_key bits[i] * 2 bits[i 1] # 高2位合成键值 q_key bits[i 2] * 2 bits[i 3] # 低2位合成键值 s (GRAY_LEVEL[i_key] 1j * GRAY_LEVEL[q_key]) / np.sqrt(10) symbols.append(s) return np.array(symbols)这段代码的要点在于键值的计算方式bits[i] * 2 bits[i1]把2个比特拼成一个0到3的整数正好作为GRAY_LEVEL的键。之所以用键值查表而不是直接写电平运算是因为映射关系是Gray码不是简单的线性关系查表最不容易错。np.sqrt(10)是能量归一化16个符号的平均能量算出来正好是1后面加噪声时直接用符号能量1做基准。画星座图时把16个参考星座点和仿真产生的符号点叠在一起看import matplotlib.pyplot as plt # 16个归一化参考星座点 const np.array([(i 1j * q) / np.sqrt(10) for i in [-3, -1, 1, 3] for q in [-3, -1, 1, 3]]) # 生成随机比特并映射 np.random.seed(42) bits np.random.randint(0, 2, 4000) symbols map_16qam(bits) plt.figure(figsize(4, 4)) plt.scatter(symbols.real, symbols.imag, s4, alpha0.5, labelsymbols) plt.scatter(const.real, const.imag, marker, s120, colorC3, labelconstellation) plt.axis(equal) # 强制I/Q坐标等比例否则星座图会被拉伸成椭圆 plt.grid(True, linestyle--, alpha0.5) plt.xlabel(I) plt.ylabel(Q) plt.legend() plt.title(16QAM constellation) plt.show()axis(equal)这个参数必须加。不加的话matplotlib会根据数据范围自动缩放坐标轴符号点在I方向和Q方向的视觉距离不一致会误导你对判决边界的判断。归一化后内圈4个点在坐标±1/√10(约±0.316)处外圈12个点在±3/√10(约±0.949)处用这个参照系去核对别的模块产生的星座点能快速发现是振幅缩放错误还是映射错误。3. AWGN信道下的误码率仿真蒙特卡洛与理论曲线的对齐方法3.1 信噪比定义与Eb/N0、Es/N0换算横坐标差0.5dB都是这里引起的误码率仿真的横坐标行业里最常见的是Eb/N0即每比特能量与噪声功率谱密度的比值。但噪声实际是加在符号上的仿真代码里最先拿到的是Es/N0(每符号能量与噪声功率谱密度之比)。16QAM一个符号带4个比特所以Es4Eb换算成dB就是加10log10(4)≈6.02dB。也就是说同样的信道条件下Es/N0比Eb/N0大6dB左右。理论误码率公式也要用对基准。在AWGN信道下16QAM的符号错误概率可以近似写成Ps ≈ 3Q(√(Es/(5N0)))Q函数是高斯概率积分工程上用互补误差函数erfc表示为Q(x)0.5·erfc(x/√2)。Gray编码下高信噪比时一个符号错误大约只影响1个比特所以比特误码率近似为Pb ≈ (3/4)·Q(√(4Eb/(5N0)))代码里实现理论曲线时直接用erfc版本from scipy.special import erfc def ber_theory_16qam(eb_n0_db): 16QAM在AWGN下的理论误比特率输入Eb/N0单位dB eb_n0_lin 10 ** (eb_n0_db / 10) x np.sqrt(4 * eb_n0_lin / 5) return 0.75 * 0.5 * erfc(x / np.sqrt(2))这里4 * eb_n0_lin / 5来自公式推导Es/N04Eb/N0代入Q函数自变量√(Es/(5N0))。0.75就是系数3/40.5 * erfc(...)是Q函数展开。建议把理论公式独立成函数仿真曲线和它对比时横坐标统一用dB、纵坐标统一用对数刻度避免两边单位不一致。3.2 蒙特卡洛误码率仿真的完整链路从加噪声到BER统计蒙特卡洛误码率仿真的基本流程是生成随机比特→映射成符号→按指定信噪比加AWGN噪声→判决还原成比特→和发射比特对比统计错误比例。关键在于加噪声这一步复噪声的实部和虚部要分别加方差为N0/2的高斯噪声合起来复噪声功率才是N0。def add_awgn(symbols, es_n0_db): 给符号序列叠加AWGN输入Es/N0单位dB符号能量已归一化为1 es_n0_lin 10 ** (es_n0_db / 10) n0 1.0 / es_n0_lin # Es1所以N01/(Es/N0) noise np.sqrt(n0 / 2) * ( np.random.randn(len(symbols)) 1j * np.random.randn(len(symbols)) ) return symbols noisen0 1.0 / es_n0_lin的前提是符号平均能量已经归一化为1。如果前面映射时没有除以√10这里就要额外除以符号平均能量很多人误码率曲线整体偏移就是因为漏了这一步。np.sqrt(n0 / 2)分别给实部、虚部生成噪声两个维度合起来的噪声功率才是N0。判决采用最近邻准则把接收符号和16个参考星座点逐一算欧氏距离取距离最小的点作为判决输出。逆映射时用查表还原成4个比特def demod_16qam(rx_symbols): 最近邻判决返回还原的比特序列 bits [] for s in rx_symbols: idx np.argmin(np.abs(s - const)) # 找最近星座点下标 s_raw const[idx] * np.sqrt(10) # 还原归一化前的电平 i_key {v: k for k, v in GRAY_LEVEL.items()}[int(round(s_raw.real))] q_key {v: k for k, v in GRAY_LEVEL.items()}[int(round(s_raw.imag))] bits.append((i_key 1) 1) bits.append(i_key 1) bits.append((q_key 1) 1) bits.append(q_key 1) return np.array(bits)逆映射这里临时构造了GRAY_LEVEL的反向字典实际工程里建议把正向和反向两个字典都定义成模块级常量避免每个符号都重复构造。判决逻辑本身没有玄学就是全搜索最近邻16个点规模很小不需要K-D树这类优化。主循环遍历需要的Eb/N0点每个点独立跑一批比特并统计误码率n_bits 400000 eb_n0_range np.arange(0, 15, 1) ber_sim [] for eb_n0_db in eb_n0_range: bits np.random.randint(0, 2, n_bits) tx map_16qam(bits) es_n0_db eb_n0_db 10 * np.log10(4) # Es/N0 Eb/N0 6.02dB rx add_awgn(tx, es_n0_db) dec demod_16qam(rx) ber np.sum(dec ! bits) / len(bits) ber_sim.append(ber)这个循环里最容易忽略的是es_n0_db的换算。发射端符号能量确定后噪声功率只取决于Es/N0如果直接用eb_n0_db去加噪声实际信噪比低了6dB仿真曲线会比理论曲线整体右移看起来像是“性能变差”。跑完后用plt.semilogy把ber_sim和ber_theory_16qam画在同一条坐标轴上10dB以上的区间两者差在0.3dB以内基本算正常。3.3 叠加噪声后的星座图判断链路健康的第一个窗口误码率曲线是统计结果星座图则是单样本视图两者互补。在Eb/N0等于8dB左右加噪声后画星座图能看到16个高斯簇每一簇的中心就是参考星座点簇的半径由噪声标准差决定。簇与簇之间会有粘连这正是符号错误发生的区域——落在判决边界附近的点噪声稍微一推就跨到隔壁簇。把Eb/N0调到14dB重画16个簇清晰分离误码率降到1e-5量级。用这个可视化检查链路时注意三个细节簇是否圆形对称、簇心是否落在参考点位置、是否有整体旋转。簇呈椭圆说明I/Q增益不平衡整体旋转说明存在频偏簇心偏移说明归一化或判决有系统误差。这三种现象靠误码率曲线很难定位星座图一眼就能看出来。4. 误码率仿真跑不通的5个常见坑从曲线发散到星座图翻车4.1 横坐标单位混用导致BER曲线整体偏移6dB现象仿真误码率曲线形状和理论曲线完全一致但整体差了大约6dB仿真BER比理论值在同一横坐标下“差”很多。原因几乎可以断定是单位混用。最常见的是用Eb/N0做横坐标画图加噪声时却直接把Eb/N0当成了Es/N0或者反过来加噪声用对了Es/N0画图时标成了Eb/N0。16QAM的Es/N0和Eb/N0相差6.02dB这个固定偏移最容易出现在单位换算不彻底的时候。解决把换算固定写在主循环里只在横坐标变量处写一次Eb/N0加噪声前用es_n0_db eb_n0_db 10 * np.log10(4)单独算一行。不要在图里临时换算也不要在加噪函数里做隐含换算。我习惯在代码注释里写明“此函数输入Es/N0”从源头上避免调用时传错。4.2 复噪声功率少加一半星座点抖动范围明显不对现象星座图在低信噪比下显得比预期更干净高信噪比下簇的半径偏小BER普遍低于理论值。原因AWGN信道的复噪声实部和虚部各占一半功率。如果把复噪声总功率设成N0又让实部和虚部分别取N0方差的高斯噪声实际噪声功率就是2N0等效信噪比低了3dB。反过来只给实部虚部各加N0/2才是标准做法。解决按np.sqrt(n0 / 2) * (randn 1j * randn)生成复噪声这是教科书标准写法。检查代码要点是n0的数值应该等于1 / (Es/N0线性值)且乘以sqrt(n0/2)后实部虚部的各自方差恰好是n0/2。4.3 低误码率区间的蒙特卡洛发散BER在1e-5处跳来跳去现象Eb/N0大于12dB后BER曲线不再是单调下降而是在1e-5到1e-6之间上下跳动重复跑脚本结果也不稳定。这个现象在工程里常被叫仿真发散实际上是统计样本不够。原因误码率本身就是小概率事件的频率估计。要测到1e-5量级至少要积累几十个错误事件才有统计意义。400k比特在12dB时理论上只有个位数错误随机性太强算出来的BER方差极大。解决两个方向。一是增加样本量比如把n_bits提到几千万但仿真时间会线性增长二是接受现实只对比到10dB左右的曲线段把仿真验证和理论公式的分工定清楚——理论公式负责外推低BER区仿真负责验证中低信噪比段。另外固定随机种子至少在调代码阶段保证结果可复现。4.4 映射表和逆映射表各写一套无噪声判决也出错的隐形bug现象星座图完全正常但BER高得出奇甚至无噪声时误码率不为0。原因正映射和逆映射各写了一份查表逻辑两份逻辑用的Gray编码顺序不一致。常见情况是发射端按00-01-11-10的Gray顺序接收端却按00-01-10-11的自然顺序逆查导致相邻星座点正确判决后比特还原错误。解决在仿真主循环之前先跑一遍无噪声自检随机生成几万比特映射后直接判决再逆映射对比输入输出必须完全一致。这一步5秒能跑完却能排除一大半映射类bug。我个人的做法是把正反映射表定义成同一份字典的两个视图反向字典用推导式从一个源生成杜绝手写两遍出错的可能。4.5 理论公式硬套通用近似低信噪比段误差大到没法看现象Eb/N0小于6dB时仿真点稀疏地散落在理论曲线附近系统性偏差明显越往低信噪比偏差越大。原因常见的16QAM误码率近似公式Ps≈3Q(√(Es/(5N0)))是忽略高阶项的近似结果在高信噪比下很准但在低信噪比下双符号同时出错、边界符号错误概率差异等因素开始显现近似公式的系统误差变得不可忽略。解决如果是验证系统性能把对比区间限定在BER低于1e-3的段比如Eb/N0在7dB以上。如果非要在低信噪比段对齐用精确的符号错误概率表达式把星座图边界效应逐项积分算进去。工程上一般不需要但要清楚手里用的是近似公式不要拿它当全区间真理。提示理论公式和仿真曲线的关系是“互相印证”不是“单方面校验”。仿真也要怀疑理论近似也要怀疑两边独立推导才能发现各自的错误。5. 从理想AWGN走向真实链路脉冲成形、频偏与定时误差5.1 根升余弦成形与匹配滤波矩形脉冲仿真为何不够前面几章的仿真默认每个符号是一个冲激没有考虑实际发射机里基带脉冲的频带限制。真实系统里符号序列要经过脉冲成形滤波器最常见的配对是发射端根升余弦成形、接收端根升余弦匹配滤波合起来构成升余弦脉冲满足奈奎斯特第一准则采样点处无符号间干扰。滚降系数α是这里最关键的参数。α越小占用带宽越窄但脉冲在时域的拖尾越长对定时误差越敏感α越大频谱滚降越平缓牺牲带宽换定时鲁棒性。LTE和WiFi系统里α0.22是常见的选择实验室原型机里也有用0.35的它抗定时偏差能力更强。仿真参数对比如下滚降系数占用带宽倍数定时误差敏感度典型应用0.151.15倍符号速率高卫星链路窄带场景0.221.22倍符号速率中LTE、WiFi主流选择0.351.35倍符号速率低实验室原型、教学仿真成形过程要做过采样也就是每个符号插入多个采样点再与根升余弦滤波器卷积。接收端先匹配滤波再按最佳采样时刻抽取每个符号取一个判决点。如果跳过成形直接发矩形脉冲频谱旁瓣会泄漏到相邻信道这在带限信道里会产生实质性能损失单纯AWGN仿真里很难暴露。def rrc_filter(sps, alpha, span8): 生成根升余弦滤波器系数sps为每符号采样数alpha为滚降系数 n span * sps t (np.arange(n) - n / 2) / sps h np.zeros(n) for i, ti in enumerate(t): if abs(ti) 1e-8: h[i] (1 - alpha) 4 * alpha / np.pi elif abs(abs(ti) - 1 / (4 * alpha)) 1e-8: h[i] (alpha / np.sqrt(2)) * ( (1 2 / np.pi) * np.sin(np.pi / (4 * alpha)) (1 - 2 / np.pi) * np.cos(np.pi / (4 * alpha)) ) else: num np.sin(np.pi * ti * (1 - alpha)) 4 * alpha * ti * np.cos(np.pi * ti * (1 alpha)) den np.pi * ti * (1 - (4 * alpha * ti) ** 2) h[i] num / den return h / np.sqrt(sps)这个滤波器系数的三个参数要单独解释sps是过采样率决定频谱复制间隔取值4到16之间比较常见太小滤波器逼近误差大太大计算量浪费alpha是滚降系数直接写进公式里的分子分母span是滤波器截断的符号数设成8意味着两边共截断8个符号截断太短会引入额外ISI。发射成形和接收匹配共用这组系数只是接收端滤波后要丢弃边界样本再按sps的间隔抽样判决。5.2 载波频偏与定时误差星座图旋转和拖影是怎么来的加了成形滤波之后还有两个真实链路里躲不开的问题载波频偏和定时误差。频偏的直观效果是星座图整体旋转而且是随符号序号累加的——第k个符号旋转角度是2πΔf·k符号越往后转得越多表现在星座图上就是一圈圆弧或同心圆环而不是清晰的16个点。def apply_freq_offset(symbols, norm_freq_offset): 施加归一化频偏norm_freq_offset单位为1/符号周期 n len(symbols) k np.arange(n) return symbols * np.exp(1j * 2 * np.pi * norm_freq_offset * k)仿真时这个函数要放在加噪声之前频偏作用在信号上再叠噪声才符合物理顺序。norm_freq_offset1e-3意味着每1000个符号转一整圈这个量级看起来小但在16QAM中星座点间最小距离本来就小旋转超过内圈点间距就会开始压垮判决。真实接收机里频偏靠载波同步环路估计和补偿仿真阶段可以用这个函数验证同步算法没有收敛时星座图呈环形误码率趋于50%。定时误差的影响类似但表现不同。最佳采样时刻偏离时眼图张开度变小判决点处的ISI增加星座图上的簇不再是圆形而是沿对角线方向拉长成椭圆或产生“拖影”。原因在于脉冲成形滤波的升余弦特性只在精确的采样时刻保证无ISI采样点偏移后相邻符号的拖尾叠加上来。仿真中模拟定时误差最简单的方式是在过采样序列里按小数采样点抽取用线性插值近似。5.3 仿真模型搭建顺序分步逼近真实系统才是效率最高的路径不要一上来就把成形、频偏、定时偏差全部加进仿真。链路模型每加一个模块排查难度翻一倍。我建议的顺序是第一步跑纯AWGN加理想采样把映射、逆映射、BER统计验证到和理论一致第二步加上根升余弦成形和匹配滤波确认无噪声时判决输出依然完全正确第三步加频偏先加已知固定频偏观察星座图旋转方向再考虑加同步补偿最后一步加定时误差。每一步都回归一下BER曲线确认它没有偏离理论值太多。反过来做的教训很多模块全加上之后曲线不平、星座图一团糊根本分不清是频偏没补偿还是定时偏差过大还是噪声算错。分步逼近每一步留一个可验证的中间指标翻车时能快速定位到具体模块。6. 验收误码率仿真结果的三件事对照理论、检查星座图、验证收敛性仿真跑完不是画出曲线就结束了要证明这组结果可信我一般做三件事。第一件是把蒙特卡洛结果和理论曲线叠在同一个对数坐标里看重点看Eb/N0在7到11dB这一段两者偏差应该在0.3dB以内。这一段BER覆盖1e-4到1e-2样本量足够有统计意义又是理论近似最准的区间是最可靠的对齐窗口。第二件是星座图质量检查。加噪声后的接收星座图16个簇应该近似圆形、簇心应该精确落在参考星座点位置、簇的半径应该随Eb/N0的增大按预期缩小。如果簇是椭圆形去查I/Q增益不平衡如果有环状旋转拖影去查频偏估计如果簇心偏移去查判决边界和参考星座点是不是同名不同值。第三件是收敛性验证。把一次仿真产生的比特流对半切分分别统计前半段和后半段的BER。如果两个半段的误码率差别超过2倍说明这次仿真的样本量不够BER结果不可信。这个检验要固定随机种子重跑结果就不会每次都不一样。之前见过有人用40万比特在13dB处跑出BER1e-6对半切分后两段一个为0一个2e-6显然是撞运气而不是测出了真实性能。最后说个我自己踩过的坑赶进度时跳过无噪声自检直接跑完整曲线结果曲线在10dB处比理论低了差不多半个数量级排查半天才发现是逆映射查表把Gray顺序写反了。从那以后所有调制解调仿真都先做无噪声自检再跑有噪曲线整个过程连10秒都用不上。这个习惯也推荐给你希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。