news 2026/9/16 2:19:13

QPSK基带仿真全链路解析:从调制映射到载波恢复

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
QPSK基带仿真全链路解析:从调制映射到载波恢复

简介:一套基于MATLAB的QPSK调制解调仿真代码包,适合通信工程专业学生、科研初学者及相关技术人员快速学习和复现QPSK核心原理。包内包含调制、解调、脉冲成型、星座图绘制、LMS均衡、早迟门与过零定时恢复、四倍频载波恢复等多个m脚本,并附带一份QAM调制解调系统中定时和载波恢复的仿真报告doc文档。全部文件共12个,以m源码为主,另有2个asv备份文件,压缩包约214KB,结构清晰,便于按功能对照阅读。已有175人学习下载。通过运行和修改上述代码,读者可以完整掌握从二进制序列到相位映射、加噪信道仿真、匹配滤波解调及误码率统计的实现流程,还能进一步理解定时同步与载波同步的重要设计思路,为后续无线通信系统仿真与课程实验提供可直接修改和扩展的参考模板。

1. 一套能跑通的QPSK仿真,远比“画星座图”有用

很多人在学QPSK时,第一反应是翻通信原理教材,把“四相位映射”背得滚瓜烂熟,但一进MATLAB就卡在“信号到底该怎么表示”上。这套zip包里放着的qpsk_mod.m、pulse.m、constel.m、QPSK_demod.m、lms.m,以及两组定时恢复和一组载波恢复脚本,覆盖了从比特生成、脉冲成型、加噪声、相干解调、自适应均衡到时钟同步的完整基带仿真链路。它不只是一份作业源码,更是一个可以拿来改参数、加信道模型、跑误码率曲线的原型平台。适合正在做数字通信课程设计、或者想验证QPSK工程实现细节的人。下面按实际仿真链路的顺序,把这几个脚本逐个拆开讲清楚。

2. QPSK调制链路:相位映射、脉冲成型与星座图观察

2.1 从比特到相位:QPSK映射表与I/Q正交载波

QPSK的本质是把串行比特流按两位一组切分,每一组映射成四个相位点之一。这里的核心不是“用相位表示信息”这个结论,而是I/Q两路正交分量的拆分方式。发送端一般把奇偶比特分别送入同相支路和正交支路,每个符号周期内两路各出一个比特,合成一个复数符号。实际常见映射方式有两种:一种是自然码映射,00、01、10、11对应0°、90°、180°、270°;另一种是Gray码映射,相邻相位只差一个比特。做仿真时我常用Gray映射,因为在高信噪比下误码率更接近理论值。

qpsk_mod.m里的调制核心,大致逻辑如下面的代码所示:

function [sym, s] = qpsk_mod(bits, fs, fc, sps) % bits: 二进制数据流 fs: 采样率 fc: 载波频率 sps: 每符号采样点数 bits = bits(:); % 按2比特分组,不足补零 if mod(length(bits), 2) ~= 0 bits = [bits; 0]; end symBits = reshape(bits, 2, [])'; % Gray映射:00->1+1j, 01->-1+1j, 11->-1-1j, 10->1-1j mapTable = [1+1j, -1+1j, -1-1j, 1-1j]; symIndex = bi2de(symBits, 'left-msb') + 1; sym = mapTable(symIndex).'; % 上变频:I路乘cos,Q路乘sin,组成实信号 t = (0:length(sym)*sps-1) / fs; symUp = repmat(sym.', sps, 1); symUp = symUp(:); s = real(symUp) .* cos(2*pi*fc*t) - imag(symUp) .* sin(2*pi*fc*t); end

这段代码里有几个容易忽略的点。bi2deleft-msb参数决定了比特组的位序,映射表顺序必须与之配合,否则解调端会全部判错且很难排查。sps(samples per symbol)是基带仿真里最重要的参数之一,它决定了脉冲成型的过采样倍数,取4或8比较常见,太低会导致眼图闭合,太高会拖慢仿真。symUp通过repmat把每个符号重复sps次,这是最简单的零阶保持成形,实际工程里不会这么干,但适合先验证映射和解调逻辑是否正确。

2.2 pulse.m 脉冲成型:为什么要限带与升余弦滤波器

直接用零阶保持波形发送,频谱会包含大量高频分量,在带限信道里会造成邻近符号的能量泄漏,也就是码间串扰(ISI)。pulse.m这个脚本解决的就是这个问题。升余弦滤波器是QPSK仿真里最常用的脉冲成型选择,它的频域滚降特性可以用一个参数beta(滚降因子)来控制。beta=0时是最陡峭的奈奎斯特滤波器,带宽利用率最高,但时域拖尾衰减慢,对定时误差极度敏感;beta=1时带宽扩展一倍,但拖尾衰减快很多,工程上常在0.2到0.35之间取值。

MATLAB里设计根升余弦滤波器的最直接方式是使用rcosdesign函数,pulse.m里可以这样组织:

function [txFilter, delay] = pulse(beta, sps, span) % beta: 滚降因子(0~1) sps: 每符号采样点数 span: 滤波器跨越的符号数 txFilter = rcosdesign(beta, span, sps, 'sqrt'); delay = span * sps / 2; end

注意这里使用了'sqrt'选项,得到的是根升余弦滤波器。原因是收发两端各用一个根升余弦滤波器,级联之后等效于一个完整的升余弦滤波器,在采样点处满足奈奎斯特准则,理论上零ISI。这一点在链路调试时非常关键——如果发端用升余弦、收端不用滤波器,或者收发都用完整升余弦,频谱和时域特性都会不对劲。滤波器跨度span一般取6到10个符号周期,span越大滤波器越陡峭,近似越准,但引入的群延迟delay也越大。这个延迟在后面的定时恢复和误码率统计里必须补偿,不然会看到“星座图完美但误码率很高”的怪现象。

2.3 constel.m 星座图:观察调制质量的第一个窗口

constel.m的作用很简单,但价值很高——它把解调后的复信号画在复平面上,让你一眼看出信号质量。接收端在做完匹配滤波和定时恢复之后,每个符号采样点应该落在四个理想点附近。如果星座图上的点围绕着理想点呈圆形散开,说明噪声主导;如果点沿着某个方向拉长,说明存在残余频偏或定时偏差;如果四个簇旋转了一定角度,说明本地载波和发送端存在固定相位差。

一个快速绘制星座图的脚本可以这样写:

function constel(symRx) % symRx: 经过定时恢复后的符号序列 figure; plot(real(symRx), imag(symRx), '.'); hold on; plot([-1 1 1 -1 -1], [-1 -1 1 1 -1], 'r-'); axis equal; grid on; xlabel('I 支路'); ylabel('Q 支路'); title('QPSK 接收星座图'); end

在调试里我一般会把调制端的理想符号矩阵标出来,用红框或者红点叠加在上面的代码中,方便直接观察判决余量。比如理想点分别是(1,1)、(-1,1)、(-1,-1)、(1,-1),如果接收簇的中心明显偏出这些位置,就说明前面的增益控制或者相位补偿有问题,而不是“加个噪声就完事”那么简单。

3. QPSK解调与LMS均衡:从相位差还原到误码率统计

3.1 QPSK_demod.m 相干解调:从相位差还原比特

QPSK_demod.m做的事是调制的逆过程。相干解调的前提是接收端能生成和发送端同频同相的本地载波,但在仿真里我们通常先假设理想同步,把精力放在判决逻辑上。实际做法是把接收信号分别乘上cos(2πfct)-sin(2πfct),得到I/Q两路基带分量,再经过低通滤波去掉二倍频分量。这里有个陷阱:低通滤波器的截止频率和群延迟必须与发送端的脉冲成型滤波器匹配。

一个可运行的解调核心片段如下:

function bitsRx = qpsk_demod(s, fs, fc, sps, rxFilter, delay) % s: 接收到的实信号 t = (0:length(s)-1) / fs; % 正交下变频 iBase = 2 * s .* cos(2*pi*fc*t); qBase = -2 * s .* sin(2*pi*fc*t); % 匹配滤波 iFilt = filter(rxFilter, 1, iBase); qFilt = filter(rxFilter, 1, qBase); % 补偿滤波器延迟,按sps抽取 startIdx = delay + 1; idx = startIdx : sps : length(iFilt); symRx = iFilt(idx).' + 1j * qFilt(idx).'; % 判决:按实部虚部符号位恢复 bitsI = real(symRx) > 0; bitsQ = imag(symRx) > 0; bitsRx = reshape([bitsI(:).'; bitsQ(:).'], [], 1); end

乘系数取2是为了补偿下变频时损失的一半功率,不乘也能做判决,但会影响后面信噪比的量纲统一。匹配滤波这里用filter而不是conv,因为filter的延迟特性和卷积略有差别,配合上面用filter设计的发射滤波器时,收发两端延迟才能精确对冲。抽样时刻idx必须从delay+1开始,否则整个序列相位偏移,星座图会旋转45°或出现簇间混叠。

3.2 噪声信道与awgn():信噪比的正确换算

仿真加噪声,最容易翻车的是信噪比单位。通信仿真里说的SNR通常有两种口径:一是符号信噪比Es/N0,二是比特信噪比Eb/N0。QPSK每个符号携带2比特,所以Es/N0 = Eb/N0 + 3dB。MATLAB的awgn函数默认输入信号功率按1计算时,它的SNR参数是信号功率与噪声功率的比值,单位dB。如果你想让它对应Eb/N0,就要先换算,再考虑信号的实际平均功率。

常见做法是先把基带符号序列做功率归一化,再调用awgn:

symPower = mean(abs(symTx).^2); symTxNorm = symTx / sqrt(symPower); EbN0dB = 8; EsN0dB = EbN0dB + 10*log10(2); % 每符号2比特 snrLin = 10^(EsN0dB/10); symRxNoisy = awgn(symTxNorm, 10*log10(snrLin * sps), 'measured');

这里为什么乘sps?因为awgn处理的是采样点序列,而基带信号的每符号能量被分散到了sps个采样点上。如果不乘sps,实际加进去的噪声会偏低sps倍,导致仿真结果比理论误码率好很多。这个细节在很多二手代码里都是错的,排查误码率不对时先查这里。

3.3 lms.m 自适应均衡:对抗残余码间串扰

当信道存在多径时,接收信号会混入延迟副本,光靠匹配滤波无法完全消除ISI。lms.m实现的LMS均衡器是解决这个问题的标准入门方案。LMS的思想很直接:用一个自适应FIR滤波器逼近信道逆响应,误差信号驱动抽头系数迭代更新。

function w = lms(x, d, mu, order) % x: 接收符号序列 d: 期望符号(训练序列) mu: 步长 order: 滤波器阶数 N = length(x); w = zeros(order, 1); for n = order:N xVec = x(n:-1:n-order+1); y = w' * xVec; e = d(n) - y; w = w + mu * conj(e) * xVec; end end

这里w是抽头系数向量,最短可收敛长度取决于信道时延扩展。步长mu的取值直接决定收敛速度和稳态误差的折中:mu太大,迭代发散或稳态失调大;mu太小,收敛要几千个符号,训练序列都不够用。工程上常用mu = 1/(信号功率 × order × 2)作为初值,然后观察误差曲线和星座图微调。训练阶段用已知序列,收敛后切换到判决导引(DD-LMS)模式,即把判决器的输出当作期望信号来持续跟踪信道变化。整套zip里的shixian.asv里保留了这种切换的痕迹。

LMS均衡器对收端信号的幅度非常敏感,如果前端AGC没有把信号幅度归一化,步长就得跟着变。我一般会在调用lms.m之前先x = x / rms(x),让进入均衡器的信号功率稳定在1附近,再用固定的mu做仿真对比。

4. 定时恢复与载波恢复:Early-Late、Zero-Crossing与四次方环

4.1 Early-Late定时恢复:用能量差锁定采样时刻

在解调端,如果抽样时刻偏离符号中心,眼图张开度会降低,误码率上升。早期-迟门(Early-Late Gate)是经典的非数据辅助定时恢复方案,它通过比较“早采样点”和“晚采样点”的能量来判断抽样时刻是偏早还是偏晚。当两个点的能量差为零时,说明采样时刻对准了符号峰值。

early_late_clock_recovery.m里处理的正是这个偏差闭环。核心逻辑写出来如下:

function symOut = early_late_clock_recovery(sig, sps, mu, omega) % mu: 定时误差 omega: 采样间隔(小数部分) for n = 1:length(sig)/sps earlyIdx = round(mu); lateIdx = round(mu + 0.5*sps); early = sig(earlyIdx); late = sig(lateIdx); err = abs(late)^2 - abs(early)^2; mu = mu - mu_step * err; symOut(n) = sig(round(mu + n*sps)); end end

误差信号err用早晚两个采样的模平方差,环路滤波器的积分作用会把mu推向误差为零的平衡点。这里注意,sps要取整数且最好为4以上,否则“早半个采样点”和“晚半个采样点”在整数索引上无法实现。实际工程中常用分数延时滤波器来把采样点移到非整数位置,但仿真里用整数索引加上线性插值已经能看到定时恢复的效果。如果星座图上的点呈椭圆形状且长轴沿对角线方向,基本就是定时偏差的症状。

4.2 Zero-Crossing定时恢复:过零点与定时误差

zero_crossing_clock_recovery.m是另一种定时恢复策略,它的依据是奈奎斯特脉冲在相邻符号中间时刻的幅值为零。如果抽样时刻偏离理想位置,符号间过渡带的波形会在过零点附近产生非零幅值,这个值可用来反馈调整采样时钟。

它的误差检测公式比较简洁:

function err = zero_crossing_error(sig, sps, n) % 取当前符号采样、前一个符号采样、两者之间的过零采样 s0 = sig(n*sps); s1 = sig((n+1)*sps); sMid = sig(n*sps + sps/2); err = real(sMid) * (real(s0) - real(s1)) + imag(sMid) * (imag(s0) - imag(s1)); end

为什么用real(sMid)*(real(s0)-real(s1))?因为如果定时正确,相邻符号同相分量相等,s0-s1近似为零,过零采样处信号本身接近零,误差项为零。若定时偏早,过零采样点处符号波形还没完成过渡,会留下与符号间差值成比例的残留分量。这种方法的优点是判决和误差检测可以共用同一个采样序列,不需要额外的插值器,但在低信噪比下噪声会直接污染过零点估计,所以工程上常与锁相环配合使用,而不是单独开环工作。

4.3 times_four_carrier_recovery.m 四次方载波恢复与相位模糊

QPSK信号包含载波的四次方谐波分量,原因是QPSK相位的四个取值0°、90°、180°、270°乘以4之后全部变成360°的倍数,即调制信息被“消去”,剩下一个纯净的四倍频载波分量。times_four_carrier_recovery.m的实现思路就是:对接收信号取四次方,提取四倍频分量,再用锁相环锁定这个分量,最后分频还原出本地载波。

典型实现框架如下:

function [carrier, phaseEst] = times_four_carrier_recovery(rx, fs, fc, Kp, Ki) % rx: 接收的QPSK实信号 Kp、Ki: 环路滤波器比例/积分系数 x4 = rx.^4; t = (0:length(rx)-1)/fs; % 混频到基带后经低通提取误差 mix = x4 .* exp(-1j*4*2*pi*fc*t); phaseErr = angle(mix); % 二阶环路滤波 v = 0; omega = 2*pi*fc*4; for n = 2:length(phaseErr) v = v + Ki * phaseErr(n); omega = omega + Kp*phaseErr(n) + v; phaseEst(n) = omega(n) / 4; % 分频还原载波相位 end carrier = cos(2*pi*fc*t + phaseEst); end

这里有个必须注意的耦合问题:QPSK四次方后载波相位被放大了4倍,环路锁定点是任意整数倍π/2的相位,分频后产生四重相位模糊。也就是说,即使定时恢复完美,解调出来的比特也可能是原始比特的循环移位置换。解决相位模糊的常见办法有两种:一种是在数据帧里插入已知独特字,接收端检测到独特字后再做相位去模糊;另一种是用差分QPSK编码,每个符号的相位差携带信息,解调时比较相邻符号相位差即可,代价是误码率性能损失约1-2dB。zip包自带的报告正文里也提到了这一点,运行times_four_carrier_recovery.m时如果不做去模糊处理,会发现误码率总有一个固定下限,这不是算法错,而是相位模糊没处理。

5. 把整套仿真跑起来:脚本组织、参数调整与结果验证

5.1 建议运行顺序与脚本依赖关系

这套文件的典型执行顺序是:先跑qpsk_mod.m生成符号和波形,再用awgn或自带脚本加噪声,经过定时恢复和载波恢复之后进QPSK_demod.m,最后用lms.m做均衡并统计误码率。两个.asv文件分别是实验脚本的历史备份,可以对照看调试过程。给一个统一调度的主控脚本骨架:

% QPSK 全链路仿真主控 clear; clc; fs = 200000; fc = 20000; sps = 8; EbN0dB = 8; bits = randi([0,1], 4000, 1); [symTx, s] = qpsk_mod(bits, fs, fc, sps); [txFilter, delay] = pulse(0.25, sps, 8); sFilt = filter(txFilter, 1, s); % 加噪声 sNoisy = awgn(sFilt, EbN0dB + 10*log10(2) + 10*log10(sps), 'measured'); % 定时与载波恢复 sRec = early_late_clock_recovery(sNoisy, sps, 0, 1); [carrier, ~] = times_four_carrier_recovery(sRec, fs, fc, 0.1, 0.01); % 解调与均衡 symRx = myDemod(sRec .* carrier, delay); w = lms(symRx, symTx(1:500), 0.05, 11); symEq = filter(w, 1, symRx); bitsRx = slice(symEq); ber = sum(bits ~= bitsRx(1:length(bits))) / length(bits); fprintf('BER = %e\n', ber);

5.2 参数扫描技巧与三个验证点

验证一套QPSK仿真是否正常,只看星座图不够。建议按以下三件事逐一确认:第一,无噪声时误码率必须为零,如果不是,优先检查滤波器延迟补偿、映射表位序和定时恢复的收敛方向;第二,加噪声后星座图散开程度随Eb/N0单调变化,BER曲线斜率与理论误码率曲线基本一致;第三,关闭定时恢复模块,人为引入1到2个采样点的偏移,星座图应明显恶化,打开恢复模块后应恢复正常。参数扫描时把Eb/N0从0dB扫到10dB,每点仿真的比特数至少1e6,才能保证6dB处误码率曲线的统计波动不掩盖真实性能。把filterDelay、sps、mu这三个参数做成脚本开头的变量,比每次改函数内部代码高效得多。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 2:19:09

UGUI受击触发血条制作:状态机、FillAmount与性能优化

开始就像是做U3D的朋友聊天,直接聊项目、聊代码、聊踩坑。做游戏UI,血条大概是绕不过去的一个东西。不管是打BOSS、打小怪,还是玩家自己掉血,屏幕上方或者头顶这几个格子,基本就成了玩家判断战斗状态的“晴雨表”。很多…

作者头像 李华
网站建设 2026/9/16 2:19:03

3个关键步骤搞定中国做的手机系统下载网站SEO哪家好

3个关键步骤搞定中国做的手机系统下载网站SEO哪家好 备案流程一头雾水,很多站长在搭建“中国做的手机系统下载网站”时卡在第一步。别急,今天不聊虚的,直接拆解如何在这个细分领域找到 哪家好 的服务商,并手把手教你搞定SEO。…

作者头像 李华
网站建设 2026/9/16 2:18:51

WPS疯狂占用C盘空间?原因分析与彻底清理方法

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/16 2:18:18

C盘空间告急?从临时文件清理到微信瘦身的实战指南

1. C 盘告急之前,先搞明白你的 256G 到底被谁吃掉了我这台电脑就是 256G 的 SSD 当系统盘,某天开机突然发现 C 盘只剩 1G 不到,系统直接变卡,连微信文件都收不了。打开磁盘属性一看:微信 16G、临时文件 42G、Windows 更…

作者头像 李华
网站建设 2026/9/16 2:17:57

降重总踩坑咋解决?2026年论文AIGC率超标:三个误区务必避开!

毕业季的焦虑又如期而至!不少同学对着两份检测报告愁得头大——查重率超标也就算了,明明是自己熬了好几个大夜改出来的论文,AIGC检测率居然冲破30%?花钱找校外机构改,要么改得语句不通、逻辑断裂,要么被导师…

作者头像 李华
网站建设 2026/9/16 2:16:53

智能写作系统如何提升学术论文效率与质量

1. 项目背景与核心价值去年指导研究生论文时,我发现一个有趣现象:超过80%的学生在开题阶段要花费2-3周时间反复修改框架,而其中60%的修改都集中在文献综述和方法论部分。这正是PaperXie智能写作系统要解决的核心痛点——通过结构化拆解学术写…

作者头像 李华