
简介QPSK正交相移键控在无线通信和卫星通信中应用广泛其误码率性能是工程设计与课程学习中的关键指标。这份源码包面向通信专业学生、算法研究人员以及需要评估链路性能的工程师提供一套基于蒙特卡洛仿真的QPSK误码率分析工具。压缩包整体仅1KB包含3个m脚本一个负责生成加性高斯白噪声以模拟信道一个实现调制解调与误码统计的主程序另一个用于仿真结果辅助处理可清晰体现从信号生成、加噪、解调到误码计算的完整流程。包内代码结构简洁适合在此基础上调整信噪比范围、采样点数等参数快速绘制不同条件下的误码率曲线并与理论值对比验证。已有261人学习下载对理解数字调制系统在AWGN信道中的性能特征、掌握蒙特卡洛仿真方法具有直接参考价值。1. QPSK蒙特卡洛仿真到底在仿真什么一个反直觉的3dB起点很多人拿到命名为 qpsk.zip 的课程工程或开源包第一反应是先把 QPSK 的调制解调写出来再往 AWGN 信道里丢高斯噪声数一数错比特蒙特卡洛仿真的误码率曲线就算跑完。这个流程本身没有错但真正让工程师在验收时反复返工的往往不是调制解调代码而是信噪比归一化QPSK 一个符号带两个比特Eb/N0 和 Es/N0 之间差着 3dB噪声功率写错一位整条误码率曲线就横向平移一档看起来合理拿去和理论值一对就露馅。下面把 QPSK 蒙特卡洛仿真从头拆到尾星座图映射、理论误码率公式、最小可复现的 Python 脚本再到成形滤波、qpsk 眼图和网络接口误码率这类工程化延伸最后集中写五个高频踩坑点。适合刚接触基带仿真的人也适合需要核对物理层链路预算的一线工程师。2. 从星座图到及格线先定好QPSK理论误码率再谈蒙特卡洛动手写仿真之前先明确两件事仿真对象是什么理论结果是什么。QPSK 的星座图只有四个点理论误码率公式也有闭式解看起来比自适应滤波器简单得多。但正因为简单很多人跳过理论锚点直接写循环最后曲线对不上也不知道是信道模型错了还是统计口径错了。2.1 为什么误码率曲线非要用蒙特卡洛解析式只在AWGN好使AWGN 信道下 QPSK 的误码率有闭式解为什么还要仿真因为工程里要评估的信道很少是纯 AWGN。频偏、相位噪声、多径衰落、带限滤波后的码间串扰这些场景没有统一闭式解蒙特卡洛是唯一不依赖数学技巧、只靠计算量就能拿到结果的办法。它的本质是用大量随机实验的频率逼近概率实验次数越大误码率估计越稳。蒙特卡洛仿真还有第二个价值验证链路实现是否正确。成形滤波、定时同步、均衡器每一步都会改变误码率曲线形态只有把仿真曲线和理论基线放在同一张图里才知道哪一块引入了额外损伤。理论曲线不是摆设是链路健康检查的零线。2.2 QPSK符号模型与Gray映射先画对星座图QPSK 每符号带 2 比特实部放第一个比特虚部放第二个比特。最容易记住的映射是0 映射为 -11 映射为 1。四个符号就是 (-1,-1)、(-1,1)、(1,1)、(1,-1)按逆时针排列。这个顺序是 Gray 映射相邻象限之间只有 1 比特翻转符号错判到相邻点时只丢 1 比特而不是 2 比特这是仿真里默认该用的映射。import numpy as np def qpsk_gray_map(bits): # bits 是 0/1 序列长度必须是偶数 bits bits.reshape(-1, 2) # 实部对应第 1 个比特虚部对应第 2 个比特 symbols (bits[:, 0] * 2 - 1) 1j * (bits[:, 1] * 2 - 1) # 归一化让平均符号能量 Es 1 return symbols / np.sqrt(2) for b in [[0, 0], [0, 1], [1, 1], [1, 0]]: s qpsk_gray_map(np.array(b))[0] print(b, s, energy, np.abs(s) ** 2)归一化这步是关键不除以 sqrt(2) 时每个星座点能量是 2平均符号能量 Es2后面 Eb/N0 换算会跟着变。统一除以 sqrt(2) 后 Es1每个比特能量 Eb0.5这个约定让噪声功率计算变得干净。判决规则也很简单实部和虚部分别与 0 比较大于 0 判 1小于 0 判 0两条支路互不影响。2.3 理论误码率仿真前先算好及格线QPSK 可以拆成两个正交 BPSK每比特在实部或虚部独立判决。BPSK 的理论误比特率是 Q(sqrt(2*Eb/N0))所以 QPSK 在 Gray 映射下的误比特率可以直接套用。用互补误差函数写就是BER 0.5 * erfc(sqrt(Eb/N0))计算几个关键点作为仿真验收基线Eb/N0 (dB)理论BER-40.10000.078640.012162.34e-381.68e-4106.5e-60 dB 那个点值得记死Eb/N00 dB 时理论上误码率约 0.0786。如果仿真跑出来偏差很大信号模型、判决规则、噪声功率三者里必有一个错。这个检查点后面避坑章节还会反复用到。3. 跑通第一版QPSK蒙特卡洛仿真最小Python脚本与Eb/N0换算理论基线有了下面把最小脚本跑通。我一般用 Pythonnumpy 的向量化让“一次投掷百万比特”只需要几行扫一条曲线在普通笔记本上十几秒到几十秒。3.1 最小可运行脚本从随机比特到BER曲线完整脚本包含调制、AWGN 加噪、硬判决、误码统计、理论曲线对比五个部分。直接可以复制运行import numpy as np import matplotlib.pyplot as plt from scipy.special import erfc def qpsk_mod(bits): # 两比特一组映射成复符号 bits bits.reshape(-1, 2) symbols (bits[:, 0] * 2 - 1) 1j * (bits[:, 1] * 2 - 1) return symbols / np.sqrt(2) # Es 1 def qpsk_demod(symbols): # 硬判决实部虚部各自和 0 比较 bits0 (symbols.real 0).astype(int) bits1 (symbols.imag 0).astype(int) return np.column_stack([bits0, bits1]).reshape(-1) def simulate_ber(ebn0_db, n_bits, rng): # 1) 生成随机比特 bits rng.integers(0, 2, sizen_bits) # 2) QPSK 调制 symbols qpsk_mod(bits) # 3) 由 Eb/N0 换算噪声功率 ebn0_lin 10 ** (ebn0_db / 10) n0 0.5 / ebn0_lin # Es1, Eb0.5 noise np.sqrt(n0 / 2) * (rng.standard_normal(symbols.shape) 1j * rng.standard_normal(symbols.shape)) rx symbols noise # 4) 硬判决 bits_hat qpsk_demod(rx) # 5) 比特域统计误码率 n_errors np.sum(bits ! bits_hat) return n_errors / n_bits # 扫 Eb/N0从 -4 dB 到 10 dB步进 2 dB ebn0_db_list np.arange(-4, 11, 2) rng np.random.default_rng(42) # 固定种子保证可复现 n_bits 1_000_000 # 每点 100 万比特 ber_sim [simulate_ber(x, n_bits, rng) for x in ebn0_db_list] # 理论曲线 ebn0_lin 10 ** (np.array(ebn0_db_list) / 10) ber_theory 0.5 * erfc(np.sqrt(ebn0_lin)) # 画图 plt.semilogy(ebn0_db_list, ber_sim, o-, labelMonte Carlo) plt.semilogy(ebn0_db_list, ber_theory, s--, labelTheory) plt.xlabel(Eb/N0 (dB)) plt.ylabel(BER) plt.grid(True, whichboth, ls--) plt.legend() plt.show()脚本流程很直接生成随机比特两比特一组映射成复数符号按目标 Eb/N0 算出噪声方差后加在符号上解调时看实部和虚部的正负恢复比特最后在比特域做比较。这里有个容易忽略的细节n_bits 必须是偶数否则 reshape 会报错一百万比特没这个问题。rng 用 default_rng 而不是全局的 np.random.seed是为了在多函数调用时保持随机状态可控。3.2 从Eb/N0换算到噪声功率最关键的3dB换算很多人在这一步翻车。上面代码里 n0 0.5 / ebn0_lin这一行的来源是星座图归一化后 Es1 QPSK 每符号 2 比特所以 Eb Es/2 0.5 目标 Eb/N0 在线性域是 ebn0_lin 因此 N0 Eb / (Eb/N0) 0.5 / ebn0_lin复噪声的总方差是 N0实部和虚部各分一半所以每维噪声方差是 N0/2。如果写成 n0 1 / ebn0_lin等于把 Es 当成了 Eb噪声功率小了一半整条曲线会向左平移 3dB和理论曲线明显错位。注意换一种星座归一化方式这个公式也要跟着变。如果不用 sqrt(2) 归一化星座点是 ±1±1jEs2Eb1N01/ebn0_lin。两种写法等价但混着用最容易翻车建议统一用 Es1 这一套。可以加一段自检代码直接打印不同 Eb/N0 下应该用的噪声方差for db in [0, 3, 6, 10]: lin 10 ** (db / 10) print(fEb/N0{db} dB, N0{0.5/lin:.6f}, per-dim{0.5/lin/2:.6f})3.3 仿真曲线和理论曲线对比误差怎么算合理跑出来的曲线在低信噪比段应该和理论贴合得很好。Eb/N00 dB 时理论误码率约 0.0786一百万比特里预期有约 78600 个错误统计波动非常小到 8 dB 时理论误码率约 1.7e-4预期错误只有 168 个相对波动大约 8%曲线开始有明显抖动到 10 dB 时预期错误只有 6.5 个仿真结果基本不可读。这说明固定样本量的蒙特卡洛是有边界的。列一下“想统计到 100 个错误”所需的最小比特数Eb/N0 (dB)理论BER需要比特数07.86e-21.3e341.21e-28.3e362.34e-34.3e481.68e-45.9e5106.5e-61.5e7判定标准不是“完全重合”而是仿真值在统计波动范围内围绕理论曲线抖动。如果整体平移、斜率不同、或者在高信噪比突然掉不下去就要回到调制映射、噪声换算、统计口径三个方向排查。这三条分别对应后面避坑章节里的 5.1、5.2 和 5.4。4. 把仿真推进到工程链路根升余弦成形、qpsk眼图与网络接口误码率符号级 AWGN 仿真跑通后如果直接拿它指导系统设计会被带偏。真实发射机不会发矩形脉冲频谱太宽。这一章把仿真从符号级往前推一步到采样级同时也说清为什么这一步之后噪声功率最容易翻车。4.1 为什么无成形的AWGN仿真在工程里不够用矩形脉冲对应 sinc 频谱旁瓣衰减慢带外能量大。在多载波系统或邻道共存的场景里这些带外成分就是干扰源。工程上广泛用根升余弦RRC做脉冲成形发射端一个 RRC接收端一个 RRC级联后等效为升余弦滤波器满足 Nyquist 无码间串扰条件。加入成形滤波后仿真模型必须从符号域切到采样域符号先过采样再和发射滤波器卷积接收端用同样的 RRC 做匹配滤波最后在最佳采样点抽取。这个过程会引入滤波器群延迟抽取时刻对不上眼图就是斜的误码率也会劣化。4.2 采样域仿真链路的最小改动过采样、匹配滤波与抽取用 Matlab 实现这段链路比 Python 更顺手因为 rcosdesign 直接生成 RRC 滤波器不需要自己算系数。常见做法是% 参数设置 sps 8; % 每符号采样数 span 10; % 滤波器截断长度单位符号 beta 0.35; % 滚降系数 rrc rcosdesign(beta, span, sps, sqrt); % 发送端过采样 成形滤波 tx_up upsample(symbols, sps); % symbols 来自 QPSK 映射 tx conv(tx_up, rrc); % 加噪声采样域加复高斯噪声 n0 0.5 / (10^(ebn0_db/10)); % 和符号级仿真相同的 N0 换算 noise sqrt(n0/2) * (randn(size(tx)) 1j*randn(size(tx))); tx tx noise; % 接收端匹配滤波 rx conv(tx, rrc); % 抽取总群延迟 span*sps 个采样点 start_idx span*sps 1; rx_sym rx(start_idx : sps : end); % rx_sym 就是恢复出的符号序列后续判决和误码统计沿用第三章代码这段代码展示的是标准链路结构但直接拿去统计 BER 前有个系统性的坑噪声在采样域加入后经过接收端 RRC 滤波输出噪声功率和解析 N0 的关系取决于滤波器归一化方式。rcosdesign 默认输出是单位能量归一化实际用起来通常能对上但我建议先跑一个无 ISI 自检加成形滤波后的 BER 应该和符号级仿真基本一致如果明显偏了优先校噪声功率而不是怀疑滤波器。注意不要在成形前加噪声也不要在抽取后再补噪声。前者改变了噪声的频谱形状后者等于把噪声躲过了匹配滤波器都会得到错误的 BER。4.3 qpsk眼图怎么看张开度、过零点与交叉点采样域仿真跑通后第一件该做的事是画 qpsk 眼图。眼图不是最终指标但它能一次性暴露出滤波、定时、相位三类问题。% 画 QPSK 的 I 路眼图 yI real(rx); % rx 是匹配滤波后的信号 yI yI(start_idx : end); % 去掉群延迟 num_traces floor(length(yI) / sps); yI yI(1 : num_traces*sps); yI reshape(yI, sps, num_traces); plot(yI, k);判决时刻眼图张开幅度越大抗噪声裕量越大。过零点的轨迹抖动越小定时误差越小。交叉点在周期中央且左右对称说明收发滤波延迟对齐如果交叉点偏移通常是有符号定时偏置。I 路和 Q 路要分别看两路眼睛错位一般对应相位旋转或 I/Q 失衡。4.4 从基带仿真到链路指标误码率、误包率与网络接口误码率基带 BER 不是工程链路的终点。物理层后面还有 FEC 纠错、成帧、协议重传。比如有线以太网或光模块规格里常出现“网络接口误码率 1e-12”这通常是纠错后的残余误码率不是解调器硬判决误码率。做 QPSK 级仿真时要把口径明确第三章脚本画的是解调器硬判决 BER。如果想对接系统指标常规做法是先仿真硬判决 BER再按 FEC 的编码增益和纠错门限换算成纠错后误码率或误包率而不是把整个协议栈塞进蒙特卡洛循环。仿真跑不到 1e-12 没关系这个量级本来就该交给误码仪长时间灌码实测。5. 避坑QPSK误码率仿真里五个高频翻车现场这一章把前面反复出现的坑集中整理每一条按“现象—原因—解决”展开都是实际仿真中会遇到的。5.1 曲线整体移动3dB信噪比归一化把Es当成了Eb现象仿真 BER 和理论曲线形状一致但整条曲线在横轴上偏移约 3dB低信噪比段一看就知道没对上。原因QPSK 每符号 2 比特符号能量 Es 是比特能量 Eb 的 2 倍。把 Es 当成 Eb 带入 N0 换算噪声功率算错曲线必然平移 3dB。解决确认星座归一化。Es1 时 Eb0.5N00.5/10^(Eb/N0_dB/10)。固定这一套写法不要在两套星座缩放间来回切换。自检方法Eb/N00 dB 时仿真 BER 应该在 0.0786 附近差太多就先查这一行。5.2 高信噪比曲线乱跳错误比特太少现象Eb/N0 超过 8 dB 后每次重跑 BER 能差好几倍甚至一个数量级曲线像噪声。原因固定样本量在高信噪比下的错误事件太少。误码统计服从二项分布相对标准差约等于 1/sqrt(N_e)N_e 是统计到的错误比特数。错误只有几十个时波动当然大。解决改用自适应停止累积到足够错误数再报结果。目标错误数一般取 100 到 200。如果是扫描曲线可以给高信噪比点单独加大样本量而不是所有点都跑同样比特数。5.3 曲线压不下去出现地板相位残留与采样时刻现象BER 随 Eb/N0 增加先下降降到某个数量级后不再动曲线出现一个平台。原因AWGN 理论里没有地板出现平台说明链路里存在未被补偿的固定损伤。常见的有三种星座图整体旋转了一个固定角度、采样点不在最佳判决时刻、I/Q 两路增益失衡。解决逐级排查。先画星座图看是否有旋转或椭圆化再检查判决前是否需要做相位校正最后确认抽取时刻是否对齐了群延迟。高频偏场景必须加载波同步不能指望硬判决硬扛。5.4 把误符号率当误比特率格雷码下差一倍现象低信噪比仿真 BER 比理论值高约一倍看起来像“BER SER/2”。原因QPSK 的误符号率统计的是符号错误一个符号错了可能错 1 比特或 2 比特。格雷码映射下相邻符号只有 1 比特不同稳态下约等于一半的比特错所以误比特率约等于误符号率的一半。如果统计口径在符号域再命名为 BER数字自然对不上。解决误码率统计必须在比特域做。自检方法信道极差、信噪比很低时真 BER 应该趋近 0.5 而不是 1。看到趋近 1说明统计口径错了。5.5 换台机器结果变随机数种子与并行分块现象同样参数昨天跑出来曲线平滑今天换台服务器或者开了多进程曲线明显变毛糙。原因很多人依赖全局随机函数多进程时会共享或复制随机状态导致各进程产生关联序列统计独立性被破坏。解决用显式的随机数生成器对象往函数里传 rng 而不是全局状态。多进程时每个 worker 按编号分配独立种子。这样既保证可复现又保证并行分块之间不相关。6. 把仿真再往前推一步置信区间、自适应停止与半解析加速跑通并排除上述坑之后下一步是让仿真结果更可信、更省时间。BER 估计不能只给一个点要给它一个区间。误码统计近似服从二项分布95% 置信区间可以写成 p_hat ± 1.96 * sqrt(p_hat*(1-p_hat)/N)其中 p_hat N_e/NN_e 是统计到的错误比特数N 是总比特数。这个公式在 p_hat 不太极端时都够用。写报告或者评审时明确指出“统计到 N_e 个错误”比只给一条曲线有说服力得多。自适应停止可以大幅省时间。目标不是跑满固定样本量而是跑到错误数达标就停def simulate_ber_adaptive(ebn0_db, min_errors100, max_bits10_000_000, rngNone): rng rng or np.random.default_rng() total_bits 0 total_errors 0 while total_errors min_errors and total_bits max_bits: n_bits 200_000 bits rng.integers(0, 2, n_bits) symbols qpsk_mod(bits) # 加噪、解调、统计错误逻辑同第三章 total_errors n_errors total_bits n_bits return total_errors / total_bits, total_errors高信噪比下纯蒙特卡洛太慢可以用半解析加速。对 QPSK 这种恒定包络调制AWGN 下的理论公式已经存在链路里如果有衰落或残留干扰但噪声仍是高斯方法上可以对信噪比分布做数值积分只对衰落抽蒙特卡洛不对每个比特的噪声抽蒙特卡洛。这样仿真时间能缩短几个数量级。和网络接口误码率这类系统指标对接时不要试图用蒙特卡洛跑到 1e-12。常规做法是仿真硬判决 BER 到 1e-6 或 1e-7再按 FEC 增益外推最后用误码仪实测收尾。我自己的习惯是新建任何 BER 仿真工程第一件事把理论曲线画在背景里第二件事跑 Eb/N00 dB 检查点看到 0.0786 才继续写后面的信道模型。这个习惯至少帮我拦下过三次 3dB 级别的翻车希望也能帮到你。本文还有配套的精品资源点击获取