简介:这份MATLAB仿真资源面向通信原理学习者与数字调制入门者,围绕QPSK调制解调全流程展开,帮助读者打通从理论公式到代码实现之间的理解断层。包内共5个文件,以4个m脚本和1个txt说明为主,压缩包约6KB,其中主函数负责调制解调整体流程,其余脚本分别承担低通滤波、升余弦滚降成型滤波以及双边与单边频谱绘制,结构清晰、便于分模块阅读。代码覆盖二进制随机序列生成、双极性变换、串并转换、上采样、成型滤波、载波调制、高斯白噪声、相干解调、匹配滤波、抽样判决与并串转换等环节,并输出时域波形、功率谱、频谱图、星座图、眼图及理论与仿真误码率曲线,每步均附详细注释。已有3800余人学习,作者在2018与2023版MATLAB均验证可运行,适合作为课程设计、实验报告与自学调试的参考脚本。
1. QPSK调制解调仿真:为什么你跑出来的星座图总是一团糊
刚接触数字通信仿真的工程师,十有八九是从 QPSK 入手的。原因很直接:它足够简单,只有四个相位状态,映射关系一眼能看懂;又足够典型,I/Q 两路正交调制、相干解调、判决门限这些概念它全占了。但真正动手写 MATLAB 代码的时候,很多人会发现一个尴尬的现象——代码跑通了,波形也画出来了,可星座图上的点不是四个清晰的簇,而是一团糊在一起的散点,误码率曲线更是和理论值差了十万八千里。
这个问题不是 MATLAB 的锅,也不是 QPSK 本身有多难,而是仿真链路里几个关键环节没对齐:根升余弦滤波器的滚降系数设了多少、收发两端滤波器是否匹配、采样率与符号率的比例关系、判决前有没有做正确的相位补偿。这些参数之间是耦合的,改一个不动另一个,星座图立刻就散。这篇笔记就按实际做仿真的顺序,从信号生成、脉冲成型、信道加到接收端解调判决,把每个环节的代码和参数逻辑拆开讲清楚,让新手能照着复现,熟手能对照检查自己的参数边界。
2. QPSK 基带信号生成:符号映射与差分编码怎么选
2.1 格雷映射与自然映射对误码率的影响
QPSK 每个符号携带 2 bit 信息,四个相位状态对应四种符号组合。映射方式直接决定了相邻符号之间误一位还是误两位。常见做法是格雷映射,让相邻相位的比特组合只差一位,这样在信噪比不高、判决容易落到相邻区域时,每个符号错误只对应一个比特错误。
MATLAB 里生成 QPSK 符号最直接的方式是用pskmod函数,但做仿真时我更倾向于手写映射表,因为这样能完全控制 I/Q 两路的数值,后面加脉冲成型和载波调制时不容易乱。下面是一段基础映射代码:
% 参数设置 M = 4; % QPSK 调制阶数 k = log2(M); % 每符号比特数 numSymbols = 10000; % 仿真符号数 bits = randi([0 1], numSymbols*k, 1); % 生成随机比特流 % 格雷映射表:00->(1+1j), 01->(-1+1j), 11->(-1-1j), 10->(1-1j) % 映射前先归一化功率,除以 sqrt(2) mappingTable = [1+1j, -1+1j, -1-1j, 1-1j] / sqrt(2); % 将比特流每2位一组转成十进制索引 bitPairs = reshape(bits, k, []).'; symbolIndex = bi2de(bitPairs, 'left-msb') + 1; % 查表得到复符号 txSymbols = mappingTable(symbolIndex).';这段代码里mappingTable的顺序决定了映射规则。bi2de把每两个比特转成 0 到 3 的整数,加 1 之后作为 MATLAB 的数组索引。注意'left-msb'这个参数,它保证第一个比特是高位,和映射表的顺序对应上。如果这里搞反了,后面解调出来的比特顺序会整体错位,误码率直接接近 0.5。
归一化除以sqrt(2)这一步容易被忽略。QPSK 四个符号的平均功率是 2(实部虚部各为 ±1),归一化之后平均功率变成 1,这样后面加噪声时信噪比的定义才和理论公式对得上。不归一化也能跑,但算信噪比的时候要额外补偿这个因子,容易出错。
2.2 差分编码在相位模糊场景下的必要性
相干解调需要一个本地载波,如果本地载波和发送载波之间存在固定相位差,星座图会整体旋转。对于 QPSK,相位模糊通常是 90 度的整数倍。如果信道中存在多径或者接收端载波恢复环路有延迟,这个相位模糊很难完全消除。
差分编码的思路是不直接传绝对相位,而是传相邻符号之间的相位变化。接收端即使有固定相位旋转,相邻符号的相位差不变,解调时用前一符号的相位做参考就能恢复。代价是误码率会略有恶化,因为一个符号的判决错误会影响相邻符号。
% 差分编码:当前符号相位 = 前一符号相位 + 当前映射相位 % 用复数乘法实现相位相加 diffSymbols = zeros(size(txSymbols)); prev = 1 + 0j; % 初始参考相位 for n = 1:length(txSymbols) diffSymbols(n) = txSymbols(n) * prev; prev = diffSymbols(n); end这里用复数乘法代替角度相加,避免了angle和exp来回转换带来的数值误差。初始参考相位设为1+0j,接收端做差分解码时也用同样的初始值,这样第一个符号的判决才正确。
提示:如果仿真里没有载波恢复环节,或者信道是纯 AWGN 没有相位旋转,差分编码不是必须的。加了反而让误码率曲线比理论值高一点,写报告的时候要能解释这个差异。
3. 脉冲成型与匹配滤波:滚降系数和滤波器延迟怎么定
3.1 根升余弦滤波器的参数计算
基带符号是冲激串,直接送进信道会占用无限带宽。实际系统里要用脉冲成型滤波器把每个符号的能量限制在有限带宽内。QPSK 仿真里最常用的是根升余弦滤波器,收发各放一个,合起来就是升余弦响应,满足无码间串扰条件。
MATLAB 里用rcosdesign生成滤波器系数,关键参数有三个:滚降系数beta、符号跨度span、每符号采样数sps。滚降系数决定频谱的陡峭程度,取值在 0 到 1 之间。太小了频谱边沿陡,实现困难;太大了占用带宽宽,频谱效率低。工程上常用 0.35 或者 0.5,仿真里用 0.35 比较多。
beta = 0.35; % 滚降系数 span = 6; % 滤波器跨度,单位是符号数 sps = 8; % 每符号采样数 % 生成根升余弦滤波器系数 rrcFilter = rcosdesign(beta, span, sps, 'sqrt'); % 对发送符号做上采样和滤波 txUpsampled = upsample(txSymbols, sps); txFiltered = conv(txUpsampled, rrcFilter, 'same');span决定了滤波器的截断长度,跨度越大,逼近理想升余弦的效果越好,但计算量也越大。6 个符号跨度在仿真里够用了,再大对误码率的改善微乎其微。sps是每符号采样数,至少要 4 才能让波形看起来平滑,8 或者 16 更常见。注意conv的'same'参数让输出长度和输入一致,但这样会引入滤波器延迟,后面接收端要补偿。
滤波器延迟是span*sps/2个采样点。如果收发两端都用'same'卷积,总延迟是span*sps个采样点。做误码率统计的时候,必须把接收端采样时刻对准这个延迟,否则采到的值全是错的。
3.2 匹配滤波与采样时刻对齐
接收端先通过匹配滤波器,再降采样。匹配滤波器的系数和发送端一样,也是根升余弦。这一步的目的是最大化信噪比,同时把发送端的脉冲成型响应补成完整的升余弦,消除码间串扰。
% 接收端匹配滤波 rxFiltered = conv(rxSignal, rrcFilter, 'same'); % 计算总延迟并补偿 totalDelay = span * sps; % 收发各贡献一半 rxAligned = rxFiltered(totalDelay/2 + 1 : end - totalDelay/2); % 降采样,取最佳采样点 rxSymbols = rxAligned(1:sps:end);采样时刻的选取是仿真里最容易翻车的地方。理论上最佳采样点是眼图张开最大的时刻,对应匹配滤波器输出的峰值。如果采样点偏了半个符号周期,星座图立刻散开。上面代码里用1:sps:end取点,前提是延迟补偿已经做对了。实际调试的时候,可以先画眼图确认最佳采样位置,再调整取点偏移。
注意:
conv的'same'模式在 MATLAB 里对奇数长度和偶数长度滤波器的处理略有不同。如果发现星座图整体旋转或者偏移,先检查滤波器长度和延迟补偿量是否匹配。
4. AWGN 信道与误码率统计:信噪比定义和理论曲线对比
4.1 加噪声时的信噪比换算
AWGN 信道就是在发送信号上叠加高斯白噪声。MATLAB 里用awgn函数最方便,但它的信噪比参数默认是按采样点算的,不是按符号算的。如果信号已经上采样了sps倍,直接传符号信噪比进去,实际噪声功率会偏小,误码率曲线会好看得不真实。
% 假设 Eb/N0 设为 10 dB EbN0_dB = 10; EbN0 = 10^(EbN0_dB/10); % 符号信噪比 = Eb/N0 * 每符号比特数 * 每符号采样数 % 因为噪声带宽是按采样率算的 SNR_dB = EbN0_dB + 10*log10(k) + 10*log10(sps); % 加噪声 rxSignal = awgn(txFiltered, SNR_dB, 'measured');'measured'参数让awgn先测量信号功率再按指定信噪比加噪声,避免手动计算信号功率的麻烦。如果不加这个参数,函数会假设信号功率为 0 dBW,对于归一化后的 QPSK 信号来说这个假设不成立,噪声功率会加错。
另一种做法是手动生成噪声:
signalPower = mean(abs(txFiltered).^2); noisePower = signalPower / (10^(SNR_dB/10)); noise = sqrt(noisePower/2) * (randn(size(txFiltered)) + 1j*randn(size(txFiltered))); rxSignal = txFiltered + noise;手动加噪声的好处是每一步都透明,调试的时候能单独检查信号功率和噪声功率是否合理。复数噪声的实部和虚部各占一半功率,所以除以 2 再开根号。
4.2 误码率统计与理论值对比
解调判决之后,把接收比特和发送比特逐位比较,统计错误比特数除以总比特数,就是误码率。为了和理论曲线对比,通常要扫多个信噪比点,每个点跑足够多的符号数。
EbN0_range = 0:2:12; ber_sim = zeros(size(EbN0_range)); ber_theory = berawgn(EbN0_range, 'psk', 4, 'nondiff'); for idx = 1:length(EbN0_range) % 每个信噪比点重新生成数据,避免相关性 bits = randi([0 1], numSymbols*k, 1); % ... 完整链路 ... % 统计误码 bitErrors = sum(rxBits ~= bits); ber_sim(idx) = bitErrors / (numSymbols*k); end % 画对比曲线 semilogy(EbN0_range, ber_sim, 'o-', EbN0_range, ber_theory, 'r--'); grid on; xlabel('Eb/N0 (dB)'); ylabel('BER'); legend('仿真', '理论');berawgn是 MATLAB 通信工具箱里的理论误码率计算函数,对于 QPSK 它给出的就是相干解调的理论值。仿真曲线在低信噪比时通常比理论值略高,因为脉冲成型和匹配滤波会引入少量码间串扰,差分编码也会恶化性能。高信噪比时仿真曲线下降速度可能变慢,这时候要检查是不是符号数不够,错误事件太少导致统计不收敛。
提示:每个信噪比点至少跑 10 万个符号,误码率才能稳定到 10^-4 以下。如果只跑几千个符号,高信噪比点的误码率可能是 0,画在半对数坐标上会断线。
5. QPSK 仿真避坑:星座图散、误码率不收敛、滤波器延迟错位
5.1 星座图四个簇糊在一起
现象:接收端降采样后的符号画在复平面上,四个簇分不开,或者簇的中心偏离了理论位置。
原因:最常见的是采样时刻没对准。脉冲成型和匹配滤波加起来有span*sps个采样点的延迟,如果降采样时没有补偿这个延迟,取到的就是过渡带上的值。另一个原因是收发滤波器的滚降系数不一致,发送端用了 0.35,接收端用了 0.5,匹配滤波不成立,码间串扰严重。
解决:先画匹配滤波器输出的眼图,找到眼图张开最大的采样时刻,再调整降采样偏移。确认收发两端rcosdesign的参数完全一致。如果用了conv的'same'模式,检查延迟补偿量是否等于span*sps。
5.2 误码率曲线在高信噪比处不下降
现象:低信噪比时仿真曲线和理论值贴合,但 Eb/N0 超过 8 dB 之后,仿真误码率下降变慢,甚至出现平台。
原因:可能是相位模糊没有处理。如果信道引入了固定相位旋转,而接收端没有做相位补偿,判决区域整体偏转,高信噪比时错误主要来自相位偏移而不是噪声。另一个可能是符号数不够,高信噪比下错误事件太少,统计涨落大。
解决:检查是否有载波相位偏移,必要时加差分编码或者简单的相位估计算法。增加每个信噪比点的符号数,至少 10 万个。如果用了差分编码,确认理论曲线也换成了差分 QPSK 的理论值。
5.3 滤波器延迟补偿后符号数对不上
现象:延迟补偿之后,接收符号数比发送符号数少几个,误码率统计时数组维度不匹配。
原因:conv的'same'模式输出长度和输入一致,但延迟补偿时如果直接截掉首尾,截取量计算错误就会导致符号数变化。收发各一次卷积,总延迟是span*sps,但'same'模式已经居中截取了一次,实际需要补偿的量要仔细推导。
解决:不要用'same',直接用'full'卷积,然后手动截取。发送端滤波后截掉前span*sps/2个点,接收端匹配滤波后截掉前span*sps个点,这样延迟关系清晰,符号数也容易对齐。
% 发送端 full 卷积后截取 txFiltered = conv(txUpsampled, rrcFilter, 'full'); txFiltered = txFiltered(span*sps/2 + 1 : end - span*sps/2); % 接收端匹配滤波后截取 rxFiltered = conv(rxSignal, rrcFilter, 'full'); rxFiltered = rxFiltered(span*sps + 1 : end);5.4 信噪比扫描时误码率曲线抖动
现象:相邻信噪比点的误码率不是单调下降,偶尔出现低信噪比点的误码率反而更小。
原因:每个信噪比点用了同一组随机比特,噪声也用了相同的随机种子,导致不同信噪比点之间的噪声实现相关。或者符号数太少,统计涨落超过了信噪比间隔带来的差异。
解决:每个信噪比点重新生成比特和噪声,不要复用。符号数增加到 10 万以上。如果曲线仍然抖动,检查噪声生成方式,确保实部和虚部独立。
5.5 理论曲线和仿真曲线整体平移
现象:仿真曲线形状和理论曲线一致,但整体高或者低几个 dB。
原因:信噪比换算时漏掉了上采样因子或者比特数因子。awgn函数默认按采样点算信噪比,如果信号上采样了 8 倍,符号信噪比和采样点信噪比差了10*log10(8)约 9 dB。漏掉这个补偿,仿真曲线会整体偏移。
解决:用'measured'参数让awgn自动测量信号功率,或者手动换算时把10*log10(sps)和10*log10(k)都加上。画图之前先确认横轴是 Eb/N0 还是 SNR,两者差一个10*log10(k)。
6. 用 MATLAB 通信工具箱加速验证:从脚本到 App 的过渡技巧
手写完整链路能帮你理解每个环节,但调参数的时候反复跑脚本效率不高。MATLAB 通信工具箱里有一些现成的对象和 App,可以用来快速验证参数是否合理,再回到脚本里细化。
一个实用的做法是用comm.RRCFilter系统对象代替rcosdesign加conv的组合。系统对象内部维护滤波器状态,支持流式处理,不用手动补偿延迟。对于 QPSK 这种简单调制,还可以直接用comm.QPSKModulator和comm.QPSKDemodulator,把映射和判决逻辑封装起来,减少手写出错的机会。
% 用系统对象搭建链路 modulator = comm.QPSKModulator('BitInput', true, 'PhaseOffset', pi/4); demodulator = comm.QPSKDemodulator('BitOutput', true, 'PhaseOffset', pi/4); % 发送端 txBits = randi([0 1], 20000, 1); txSym = modulator(txBits); % 接收端 rxBits = demodulator(rxSym);PhaseOffset参数设成pi/4让星座点落在对角线上,这样判决门限就是坐标轴,实现简单。默认的0偏移让星座点在坐标轴上,判决时需要比较实部和虚部的绝对值,两种方式等价,看个人习惯。
验证完基本链路之后,可以用bertool工具画理论曲线,把自己仿真的误码率点叠上去。bertool支持多种调制方式和信道类型,QPSK 在 AWGN 下的理论曲线可以直接生成,不用手动调berawgn的参数。
最后一个习惯:每次改完参数,先跑短数据看星座图,确认四个簇清晰分开,再跑长数据统计误码率。星座图是黑匣子,链路里任何一环出问题它都会告诉你,比盯着误码率数字猜原因快得多。我早期做仿真的时候总想一步到位跑完整曲线,结果一个延迟补偿错误查了一下午,后来养成先看星座图的习惯,调试时间至少省了一半。希望帮到你。
本文还有配套的精品资源,点击获取