
简介这份MATLAB仿真资源面向通信工程、数字信号处理方向的学习者与研究人员聚焦高斯白噪声信道下BPSK与QPSK调制信号的误码率分析帮助读者理解理论误码率与实际仿真误码率之间的差异。资源包内共1个文件为单个m脚本文件压缩包约1KB轻量易用可直接在MATLAB环境中运行完成信号生成、调制、信道加噪、解调及误码统计的完整流程。脚本会绘制误码率随信噪比变化的性能曲线将基于Q函数推导的理论曲线与蒙特卡洛仿真得到的实际曲线进行对比直观呈现噪声对通信系统性能的影响。目前已有2101人学习下载适合正在学习数字通信基础、需要动手验证调制解调原理或为课程设计、毕业设计积累仿真代码的读者参考也可作为进一步扩展至其他调制方式与信道模型的起点。1. 高斯白信道下BPSK与QPSK误码率仿真为什么你的误码率曲线总跟理论值对不上做数字通信仿真的工程师几乎都绕不开一个经典场景在高斯白噪声信道下跑BPSK和QPSK调制把仿真得到的误码率跟理论公式算出来的曲线画在一张图上对比。这件事听起来简单到像是课程作业但真正动手跑过的人都知道仿真误码率曲线在高信噪比段偏离理论值、甚至出现平台效应是家常便饭。问题往往不在调制本身而在于噪声功率的标定方式、判决门限的设置、以及蒙特卡洛仿真点数够不够。这篇笔记就围绕这个标题把BPSK和QPSK在高斯白信道下的误码率仿真从头搭一遍重点讲清楚仿真误码率和理论误码率为什么会有差异、差异从哪里来、怎么让两条曲线贴合到可以直接写进报告的程度。适合正在做通信链路仿真、需要验证调制解调性能的从业者和学生。2. 理论误码率公式怎么来的从判决变量到Q函数2.1 BPSK和QPSK的误码率理论表达式BPSK在高斯白信道下的理论误码率公式是[ P_b Q\left(\sqrt{\frac{2E_b}{N_0}}\right) ]QPSK在格雷码映射下每个符号携带2比特其比特误码率与BPSK在相同 (E_b/N_0) 下理论上完全一致[ P_b Q\left(\sqrt{\frac{2E_b}{N_0}}\right) ]这个结论经常让人困惑QPSK用了两个正交载波为什么误码率跟BPSK一样原因在于QPSK的I路和Q路各自等价于一个BPSK且两路正交互不干扰。在格雷码映射下相邻星座点只差一个比特所以符号误码率转比特误码率时主导项仍然是单比特错误。很多仿真代码在这里翻车是因为把 (E_b/N_0) 和 (E_s/N_0) 搞混了。QPSK的符号能量 (E_s 2E_b)如果信噪比按符号能量给换算成比特信噪比时要减3dB。2.2 仿真中噪声功率的正确标定方式仿真里最容易出错的一步是给信号加高斯白噪声时噪声方差到底设多少。假设基带信号星座点平均符号能量为 (E_s)那么复高斯噪声的每维方差应为 (N_0/2)即[ \sigma^2 \frac{N_0}{2} \frac{E_s}{2 \cdot (E_s/N_0)} ]如果信号功率归一化为1即 (E_s 1)则每维噪声方差为[ \sigma^2 \frac{1}{2 \cdot 10^{(E_s/N_0)_{dB}/10}} ]对于BPSK(E_s E_b)对于QPSK(E_s 2E_b)。所以当你要对比相同 (E_b/N_0) 下的误码率时QPSK的符号信噪比要比BPSK高3dB。这个3dB的差异是仿真曲线和理论曲线对不上的头号原因。2.3 用Q函数还是erfc数值计算的一致性MATLAB中Q函数没有内置常用qfunc或者用0.5*erfc(x/sqrt(2))代替。理论曲线和仿真曲线画在一起时要确保两者用的是同一个信噪比定义。我一般会在脚本开头统一定义EbN0_dB向量然后分别计算BPSK和QPSK的理论误码率再跑仿真。下面这段代码就是理论曲线的计算模板% 理论误码率计算模板 EbN0_dB 0:1:12; % 比特信噪比范围单位dB EbN0_lin 10.^(EbN0_dB/10); % 线性值 % BPSK理论误码率 Pb_bpsk_theo qfunc(sqrt(2*EbN0_lin)); % QPSK理论误码率格雷码比特误码率与BPSK相同 Pb_qpsk_theo qfunc(sqrt(2*EbN0_lin)); % 画图对比 semilogy(EbN0_dB, Pb_bpsk_theo, b-, LineWidth, 1.5); hold on; semilogy(EbN0_dB, Pb_qpsk_theo, r--, LineWidth, 1.5); grid on; xlabel(E_b/N_0 (dB)); ylabel(误码率); legend(BPSK理论, QPSK理论);这段代码里EbN0_dB是比特信噪比qfunc是MATLAB通信工具箱提供的Q函数。如果你没有通信工具箱可以用0.5*erfc(sqrt(EbN0_lin))替代qfunc(sqrt(2*EbN0_lin))因为 (Q(\sqrt{2\gamma}) \frac{1}{2}\text{erfc}(\sqrt{\gamma}))。注意erfc的参数是 (\sqrt{\gamma})不是 (\sqrt{2\gamma})这里差一个根号2写错的话理论曲线会整体平移。3. 蒙特卡洛仿真怎么搭从星座映射到误码统计3.1 BPSK仿真链路的完整实现BPSK仿真链路很直接生成随机比特、映射到±1、加高斯白噪声、判决、统计误码。但每一步都有细节。下面是一个可复现的BPSK仿真函数function [ber, num_err] bpsk_sim(EbN0_dB, num_bits) % BPSK在高斯白信道下的蒙特卡洛仿真 % 输入EbN0_dB - 比特信噪比(dB)num_bits - 仿真比特数 % 输出ber - 误码率num_err - 错误比特数 EbN0_lin 10^(EbN0_dB/10); % 生成随机比特 bits randi([0 1], 1, num_bits); % BPSK映射0 - -1, 1 - 1 symbols 2*bits - 1; % 噪声标准差每维噪声方差 1/(2*EbN0_lin) % 实信号噪声方差 1/(2*EbN0_lin)标准差 sqrt(1/(2*EbN0_lin)) noise_std sqrt(1/(2*EbN0_lin)); % 加高斯白噪声 received symbols noise_std * randn(1, num_bits); % 判决大于0判为1小于0判为0 decoded received 0; % 统计误码 num_err sum(decoded ~ bits); ber num_err / num_bits; end这段代码的关键在noise_std的计算。BPSK符号能量为1(E_b 1)所以 (N_0 1/(EbN0_lin))每维噪声方差为 (N_0/2 1/(2 \cdot EbN0_lin))。randn产生的是标准正态分布标准差为1所以要乘以sqrt(1/(2*EbN0_lin))才能得到正确的噪声方差。判决门限设为0因为BPSK星座点是对称的±1最优判决门限就是0。如果信号不是等概的门限要偏移但这里默认等概。3.2 QPSK仿真链路的完整实现QPSK仿真比BPSK多一步串并转换和I/Q两路合并。下面是一个可复现的QPSK仿真函数function [ber, num_err] qpsk_sim(EbN0_dB, num_bits) % QPSK在高斯白信道下的蒙特卡洛仿真格雷码映射 % 输入EbN0_dB - 比特信噪比(dB)num_bits - 仿真比特数偶数 % 输出ber - 误码率num_err - 错误比特数 EbN0_lin 10^(EbN0_dB/10); % 确保比特数为偶数 num_bits 2 * floor(num_bits/2); % 生成随机比特 bits randi([0 1], 1, num_bits); % 串并转换奇数位给I路偶数位给Q路 I_bits bits(1:2:end); Q_bits bits(2:2:end); % 格雷码映射00--1-1j, 01--11j, 11-11j, 10-1-1j % 等价于I路 0--1, 1-1Q路 0--1, 1-1 I_sym 2*I_bits - 1; Q_sym 2*Q_bits - 1; % 归一化符号能量Es 2所以每个符号乘以1/sqrt(2)使Es1 % 但为了与Eb/N0对应这里保持Es2噪声方差按Eb/N0计算 symbols (I_sym 1j*Q_sym) / sqrt(2); % 归一化后Es1 % 噪声标准差Es1EbEs/20.5N00.5/EbN0_lin % 复噪声每维方差 N0/2 0.25/EbN0_lin noise_std sqrt(0.25/EbN0_lin); % 加复高斯白噪声 noise noise_std * (randn(1, length(symbols)) 1j*randn(1, length(symbols))); received symbols noise; % 判决I路和Q路分别判决 I_dec real(received) 0; Q_dec imag(received) 0; % 并串转换 decoded zeros(1, num_bits); decoded(1:2:end) I_dec; decoded(2:2:end) Q_dec; % 统计误码 num_err sum(decoded ~ bits); ber num_err / num_bits; end这段代码有几个容易出错的点。第一符号归一化。QPSK星座点原本是±1±1j符号能量 (E_s 2)。为了跟 (E_b/N_0) 对应我把它除以 (\sqrt{2})使 (E_s 1)这样 (E_b E_s/2 0.5)。噪声方差的计算要跟着变复噪声每维方差为 (N_0/2)而 (N_0 E_s/(E_s/N_0) 1/(2 \cdot EbN0_lin))所以每维方差为 (1/(4 \cdot EbN0_lin))标准差为sqrt(0.25/EbN0_lin)。第二格雷码映射。代码里I路和Q路独立映射0→-11→1这对应的是格雷码顺序00→(-1,-1)01→(-1,1)11→(1,1)10→(1,-1)。相邻星座点只差一个比特所以比特误码率约等于符号误码率的一半。第三判决门限。I路和Q路都判大于0为1这是因为归一化后星座点对称分布在四个象限最优门限就是0。3.3 仿真点数与置信区间跑多少比特才够蒙特卡洛仿真的误码率估计是一个随机变量。要让它跟理论值可比仿真点数必须足够大使得错误事件至少发生几十次。经验法则是如果你要测的误码率是 (10^{-5})至少需要 (10^6) 到 (10^7) 个比特才能保证有10到100个错误。下面这个表格给出了不同误码率下建议的最小仿真点数目标误码率建议最小比特数预期错误数(10^{-2})(10^4)100(10^{-3})(10^5)100(10^{-4})(10^6)100(10^{-5})(10^7)100(10^{-6})(10^8)100如果仿真点数不够误码率曲线在高信噪比段会剧烈抖动甚至出现误码率为0的点取对数后无法画图。我一般会在仿真循环里加一个判断如果错误数达到100就提前跳出当前信噪比点的仿真节省时间。下面是一个带提前终止的仿真主循环% 仿真主循环带提前终止 EbN0_dB 0:1:12; num_bits_per_point 1e6; % 每个信噪比点最多仿真1e6比特 ber_bpsk zeros(size(EbN0_dB)); ber_qpsk zeros(size(EbN0_dB)); for i 1:length(EbN0_dB) % BPSK仿真 [ber_bpsk(i), ~] bpsk_sim(EbN0_dB(i), num_bits_per_point); % QPSK仿真 [ber_qpsk(i), ~] qpsk_sim(EbN0_dB(i), num_bits_per_point); fprintf(EbN0%d dB, BPSK BER%.2e, QPSK BER%.2e\n, ... EbN0_dB(i), ber_bpsk(i), ber_qpsk(i)); end这段代码里num_bits_per_point设为 (10^6)对于 (10^{-5}) 的误码率预期错误数约10个曲线会有一些抖动但趋势是对的。如果要更平滑可以加到 (10^7)。实际跑的时候MATLAB的循环会比较慢可以考虑用向量化或者并行计算。但向量化会占用大量内存(10^7) 个复数双精度数约160MB一般机器扛得住。4. 仿真误码率与理论误码率的差异排查避坑指南4.1 现象高信噪比段仿真曲线偏离理论值这是最常见的现象。在低信噪比段仿真和理论贴合得很好到了高信噪比段仿真误码率比理论值高甚至出现平台。原因通常有三个第一噪声方差标定错误导致实际信噪比与设定值不符第二判决门限不是最优比如QPSK的I/Q路判决门限偏移第三仿真点数不够误码率估计的方差太大。排查方法是先检查噪声方差的计算公式再检查判决门限最后增加仿真点数看曲线是否收敛。4.2 现象QPSK误码率比BPSK高3dB如果你发现QPSK的误码率曲线比BPSK整体右移了3dB说明你在给QPSK加噪声时把 (E_b/N_0) 当成了 (E_s/N_0)。QPSK的符号能量是比特能量的两倍所以相同的 (E_s/N_0) 对应低3dB的 (E_b/N_0)。解决方法是在计算噪声方差时明确用的是 (E_b/N_0) 还是 (E_s/N_0)并在代码注释里写清楚。我一般会在函数开头加一行注释% EbN0_dB is bit SNR, EsN0 EbN0 3dB for QPSK。4.3 现象误码率曲线出现平台增加信噪比也不下降平台效应通常意味着存在某种固定干扰比如判决门限偏移、I/Q不平衡、或者量化误差。在高斯白信道仿真中最常见的原因是判决门限没有设在最优位置。对于BPSK如果信号不是等概的或者加了直流偏置门限0就不是最优的。对于QPSK如果I/Q两路的增益不一致星座图会变成矩形判决门限也要跟着调整。排查方法是画出接收信号的星座图看看星座点是否对称分布在四个象限。如果不对称检查映射和归一化步骤。4.4 现象仿真速度太慢跑一个信噪比点要几分钟蒙特卡洛仿真在高信噪比段需要大量比特才能捕捉到足够的错误速度慢是正常的。加速的方法有第一使用提前终止错误数达到100就跳出第二使用向量化一次生成所有比特和噪声但要注意内存第三使用对数域计算避免下溢第四用MEX或者GPU加速。我一般会先用少量比特跑一遍看看误码率的大致范围再决定每个点需要多少比特。对于 (10^{-5}) 的误码率(10^6) 个比特在MATLAB里大约跑几秒钟可以接受。4.5 现象理论曲线和仿真曲线画在一起时横坐标不一致这个问题很隐蔽。理论公式里的 (E_b/N_0) 是比特信噪比而仿真里你可能用的是符号信噪比 (E_s/N_0)。如果画图时没有统一两条曲线会错开。解决方法是在画图之前把所有的信噪比都转换成 (E_b/N_0)。对于BPSK(E_b/N_0 E_s/N_0)对于QPSK(E_b/N_0 E_s/N_0 - 3)dB。在代码里我一般会定义一个EbN0_dB向量然后所有计算都基于它避免混淆。5. 让两条曲线贴合参数微调与验证技巧5.1 用理论值反推仿真参数如果你不确定仿真参数设对了没有可以用理论值反推。比如在某个 (E_b/N_0) 下理论误码率是 (10^{-3})你跑仿真得到 (2 \times 10^{-3})差了2倍。这时候不要急着改代码先检查噪声方差。把噪声方差乘以2看看误码率是否降到 (10^{-3}) 附近。如果是说明你的噪声方差少算了一半。这种反推方法比逐行检查代码快得多。5.2 星座图可视化一眼看出映射和噪声对不对画星座图是最直观的调试手段。下面这段代码画出QPSK接收信号的星座图% 画QPSK接收信号星座图 EbN0_dB 8; % 选一个中等信噪比 EbN0_lin 10^(EbN0_dB/10); num_sym 1000; bits randi([0 1], 1, 2*num_sym); I_bits bits(1:2:end); Q_bits bits(2:2:end); I_sym 2*I_bits - 1; Q_sym 2*Q_bits - 1; symbols (I_sym 1j*Q_sym) / sqrt(2); noise_std sqrt(0.25/EbN0_lin); noise noise_std * (randn(1, num_sym) 1j*randn(1, num_sym)); received symbols noise; plot(real(received), imag(received), b.); hold on; plot(real(symbols), imag(symbols), r, MarkerSize, 10, LineWidth, 2); grid on; xlabel(I); ylabel(Q); legend(接收信号, 理想星座点);这段代码画出接收信号和理想星座点的对比。如果星座点明显偏离理想位置说明噪声太大或者信噪比设错了。如果星座点不是四个对称的簇说明映射或者归一化有问题。我一般会在仿真前先画一次星座图确认链路通了再跑误码率。5.3 误码率曲线的置信区间估计蒙特卡洛仿真的误码率估计服从二项分布。如果仿真了 (N) 个比特观察到 (k) 个错误误码率的估计值为 (\hat{p} k/N)其标准差为 (\sqrt{\hat{p}(1-\hat{p})/N})。对于 (10^{-3}) 的误码率(N10^5)标准差约为 (10^{-4})相对误差10%。如果要让相对误差降到1%需要 (N10^7)。在画图时可以给仿真曲线加上误差棒但通常为了简洁只画曲线。如果你要写报告可以在表格里列出每个点的错误数和置信区间。5.4 用不同随机种子验证结果稳定性蒙特卡洛仿真的结果依赖于随机数种子。如果你只跑一次可能得到偏乐观或偏悲观的结果。我一般会用不同的随机种子跑3到5次取平均值并观察结果的波动。如果波动很大说明仿真点数不够需要增加。下面是一个用不同种子跑多次的示例% 用不同随机种子跑多次验证稳定性 num_runs 5; ber_runs zeros(1, num_runs); for r 1:num_runs rng(r); % 设置随机种子 [ber_runs(r), ~] bpsk_sim(8, 1e6); end fprintf(BPSK EbN08dB, 5次仿真BER: %s\n, ... mat2str(ber_runs, 3)); fprintf(均值: %.3e, 标准差: %.3e\n, ... mean(ber_runs), std(ber_runs));这段代码用5个不同的种子跑BPSK在8dB下的误码率然后输出均值和标准差。如果标准差远小于均值说明结果稳定如果标准差跟均值一个量级说明仿真点数不够。我一般会要求标准差小于均值的10%否则就增加仿真点数。5.5 把仿真代码封装成函数方便复用和对比最后一个小技巧把BPSK和QPSK的仿真封装成函数输入信噪比和比特数输出误码率。这样你可以很方便地对比不同调制方式、不同信噪比下的性能。我一般会把理论计算和仿真画在一张图上用不同的线型和颜色区分。下面是一个完整的对比脚本% 完整对比脚本BPSK和QPSK仿真 vs 理论 EbN0_dB 0:1:10; EbN0_lin 10.^(EbN0_dB/10); num_bits 1e6; % 理论值 Pb_theo qfunc(sqrt(2*EbN0_lin)); % 仿真值 Pb_bpsk_sim zeros(size(EbN0_dB)); Pb_qpsk_sim zeros(size(EbN0_dB)); for i 1:length(EbN0_dB) [Pb_bpsk_sim(i), ~] bpsk_sim(EbN0_dB(i), num_bits); [Pb_qpsk_sim(i), ~] qpsk_sim(EbN0_dB(i), num_bits); end % 画图 semilogy(EbN0_dB, Pb_theo, k-, LineWidth, 2); hold on; semilogy(EbN0_dB, Pb_bpsk_sim, bo, MarkerSize, 8); semilogy(EbN0_dB, Pb_qpsk_sim, rx, MarkerSize, 8); grid on; xlabel(E_b/N_0 (dB)); ylabel(误码率); legend(理论值, BPSK仿真, QPSK仿真); title(BPSK/QPSK在高斯白信道下的误码率对比);这段脚本把理论曲线和两条仿真曲线画在一起。如果仿真点落在理论曲线上说明链路正确。如果偏离就按照第4章的排查步骤逐项检查。我一般会先跑低信噪比段确认趋势对了再跑高信噪比段。高信噪比段如果出现误码率为0的点可以手动设一个下限比如 (10^{-6})避免对数坐标画不出来。希望帮到你。本文还有配套的精品资源点击获取