☰
BPSK+LDPC+扩频链路MATLAB仿真完整指南与误码率调试
2026/10/7 4:47:49 网站建设 项目流程

做通信链路级仿真的同学,十有八九会遇到这样一条经典链路:BPSK调制解调+LDPC信道编码+扩频解扩,最后用MATLAB统计误码率。我第一次完整跑通这套系统时,最大的感受是——单独看每个模块都不算难,但把三个模块串到一条链路上之后,信噪比口径、LLR符号约定、扩频功率归一化这些细节能把人折腾到怀疑人生。这篇博文就整条链路的系统设计、参数选择、代码实现和调试心得完整展开,希望能给正在做课程设计、毕业设计,或者刚接触通信仿真想快速上手的朋友一条清晰的路线。整篇文章覆盖从发射端信源比特产生到接收端误码统计的完整闭环,工程上可以直接复现。

1. 链路设计与模块选型思路

1.1 这条链路到底在仿真什么

整条链路的结构其实是先把信息比特做LDPC编码,增加冗余度来抵抗信道噪声;编码后的比特再做BPSK映射,把0和1变成+1和-1的实信号;然后做扩频,用一个高速的PN序列把每个符号扩展成多个码片,展宽频谱;信号经过AWGN信道后,接收端先解扩恢复出符号级软信息,再送入LDPC译码器,最后统计误码率。

这三个模块的组合不是随便拼的。LDPC负责纠错,逼近香农限的信道编码;扩频负责抗干扰、抗截获以及多址接入,也就是通过带宽扩展换取处理增益;BPSK则是整个链路的物理层底座,提供最简单可靠的星座映射。三者组合起来,就是一套典型的直接序列扩频通信系统,CDMA、WiFi等系统的物理层都能看到类似影子。作为课程设计或毕设课题,这个题目的好处是每个模块都可以单独验证,模块之间接口清晰,而且每一层都有理论曲线可以做对照,非常适合用来建立“链路级仿真”的整体概念。

1.2 为什么选BPSK而不是QPSK或者16QAM

BPSK虽然频谱效率只有1bit/s/Hz,但在AWGN信道下它的抗噪声性能是最好的调制方式之一,误码率理论公式也直观,可以直接写成Q函数的闭式解。在仿真实现时,BPSK的软解调尤其省事,LLR计算只需要一次乘法和一次除法,不需要像高阶调制那样做星座点距离遍历或者复杂的近似计算。

用BPSK还有一个非常实际的好处:可以把课题的注意力聚焦在LDPC和扩频上。很多同学做这个题目的时候容易在一个地方翻车,就是调制阶数一高,软信息计算本身就引入一堆bug,结果误码率曲线的锅完全分不清是调制的问题还是编码/扩频的问题。用BPSK把底层底座做稳,后面每个环节的问题都能单点定位。如果想在报告里体现深度,可以把QPSK作为扩展对比实验,但主链路用BPSK是最稳妥的选择。

1.3 LDPC编译码选型:直接用标准库还是自己写算法

LDPC的译码算法核心是置信传播,也就是BP译码,以及它衍生出来的最小和、归一化最小和等简化版本。如果课程要求不高,强烈建议直接使用MATLAB Communications Toolbox提供的comm.LDPCEncoder和comm.LDPCDecoder,码型用dvbs2ldpc函数生成。DVB-S.2标准定义了多种码率和码长,推荐使用n=16200、码率1/2的码型,这个配置在纠错性能和仿真速度之间比较平衡。

如果非要自己写BP译码算法,工作量会大很多,而且容易因为初始化信息没做对、校验节点更新公式写错等原因,导致编码增益完全体现不出来。我的建议是分两步走:先用MATLAB自带函数把整条链路跑通,拿到正确曲线,再去实现自写译码器做对比,这样即使自写版本出了问题,也有一个标准答案可以对照。

1.4 扩频方案选型:m序列还是随机序列

扩频这块最常用的是直接序列扩频,把每个BPSK符号与一个高速PN序列逐码片相乘。PN序列理论上应该用m序列,m序列的自相关性尖锐,生成方式也很简单,通过线性反馈移位寄存器就能产生。MATLAB里可以用comm.PNSequence直接生成,也可以简单用随机±1序列代替。对于单用户AWGN仿真,随机序列和m序列的性能没有实质差别,因为这里不需要考虑多用户之间的互相关性;但如果后续扩展成多用户系统,就一定要换回m序列或者Gold序列。

这里要重点理解处理增益的概念。扩频因子SF决定了处理增益,G=10log10(SF)dB。比如SF=31时大约15dB,SF=63时大约18dB。这个增益不是凭空冒出来的,它是以占用更宽带宽为代价换来的。正因为扩频增大了带宽,导致相同SNR下单位带宽内的噪声能量更高,所以扩频系统的性能对比必须用Eb/N0这个口径,否则会出现“扩频后性能反而变差”的奇怪结论。这个坑在4.2节会专门展开。

2. 核心模块实现与参数细节

2.1 BPSK软解调:从似然比到LLR的一步推导

BPSK的软解调,说到底是计算对数似然比LLR。我们定义映射关系为:信息比特0映射为+1,信息比特1映射为-1,接收信号r=s+n,其中n是均值为0、方差为σ²的高斯白噪声。那么LLR可以写成:

LLR = ln [ P(b=0|r) / P(b=1|r) ] = 2r/σ²

推导过程其实很简短:两个条件概率分别正比于exp(-(r-1)²/(2σ²))和exp(-(r+1)²/(2σ²)),两者取对数相减,中间的二次项正好抵消,剩下来的就是2r/σ²。这意味着一行代码就能完成软解调,而且输出的LLR直接携带了信道可信度信息,这在后面接入LDPC译码器时是决定性能的关键。

为什么用软信息而不是硬判决?LDPC译码本质上就是一个软信息迭代更新的过程。如果你在解调端做硬判决再送入译码器,等于把信道信息提前量化成0或1,丢失了可靠性权重,性能会损失约2dB。对LDPC这种追求逼近香农限的编码来说,这2dB非常宝贵。所以链路里的原则是:不到最后一刻,尽量不丢软信息。

2.2 LDPC编译码配置的几个关键点

LDPC编译码在MATLAB里的标准配置非常简洁,核心是三行:

H = dvbs2ldpc(1/2); % 码率1/2的校验矩阵 enc = comm.LDPCEncoder(H); dec = comm.LDPCDecoder(H, ... 'DecisionMethod', 'Soft decision', ... 'MaximumIterationCount', 50);

这里有几个容易忽略的点。第一,comm.LDPCDecoder的DecisionMethod参数,如果是Soft decision,输入应该是LLR软信息,输出也是LLR形式的软值;如果选了Hard decision,输入输出都变成硬比特0/1。很多同学把硬判决输出直接送入译码器,结果译码性能差得离谱,还以为是LDPC本身不行。

第二,dvbs2ldpc(1/2)生成的校验矩阵对应码长16200、信息位长度8100,编码器的输入必须是8100比特的信息块,输出是16200比特的码字。如果长度对不上,MATLAB会直接报错,但有时候你会因为信息位长度弄错,导致每帧数据量看似正常,实际统计误码时却对不上号。每次构造数据帧前,先确认kLdpc=size(H,2)-size(H,1),这才是真实的输入长度。

第三,MaximumIterationCount建议设30到50。LDPC译码有早停机制,达到收敛条件会提前退出,所以上限设大一些并不会让每帧都跑到上限。但如果设太小,高信噪比下可能会出现“明明差几个比特就能收敛,却因为迭代次数耗尽而失败”的情况,曲线会留下难看的底噪。

2.3 扩频与解扩:功率归一化是最大的坑

扩频在MATLAB里的直观实现就是复制+点乘:把每个BPSK符号复制成SF份,逐码片乘以PN序列。但这里面藏着一个特别容易出错的点——功率归一化。

一个常见的错误做法是:直接把BPSK符号(幅度1)复制成SF个码片,码片幅度仍然保持1,然后加AWGN。这样做等于把发射总功率放大了SF倍,仿真出来的误码率曲线会比理论上限还“好”15dB以上,而且你很难察觉哪里错了,因为曲线形状看起来完全正常。正确做法是保持扩频前后每个编码符号的总能量不变:设扩频码片幅度A=sqrt(Rc/SF),其中Rc是LDPC码率,SF是扩频因子,同时假定信息比特能量Eb归一化为1。

解扩端对应有两种实现方式:可以相关累加再除以SF求平均,也可以直接累加不归一化。求平均的好处是软值幅度不会随SF增大而爆炸,数值稳定性好。解扩之后的等效噪声方差变成σ²/SF,这意味着处理增益确实体现出来了:信号幅度不变,噪声方差被压缩了SF倍,等效信噪比提升。LLR计算也要用压缩后的噪声方差,才能和LDPC译码器对接正确。

3. 完整仿真链路搭建与误码率统计

3.1 参数配置一览与信噪比换算

先给出一套可以直接复用的参数配置表格,这套参数我在多个版本的MATLAB上跑过,曲线稳定且仿真时间可以接受:

参数取值说明
调制方式BPSK0映射+1,1映射-1
LDPC码长16200DVB-S.2短帧
LDPC码率1/2信息位8100比特
LDPC译码迭代50软判决输出
扩频因子SF31处理增益约15dB
PN序列随机±1或m序列单用户场景下等价
Eb/N0扫描范围-2dB到8dB步进1dB
每信噪比帧数150每帧8100信息比特

信噪比换算是整个仿真最容易翻车的地方。这里给出我的换算逻辑:信息比特能量Eb设为1,则给定Eb/N0的线性值后,N0=1/EbN0_linear,噪声单边功率谱密度确定,AWGN噪声方差σ²=N0/2。在码片域,因为总功率归一化,码片幅度是A=sqrt(Rc/SF),噪声方差还是σ²。这样加完噪声后,解扩、软解调、LLR计算,所有量纲是一致的。

为什么强调这个口径?因为很多现成代码直接把awgn函数拿来用,awgn的SNR参数指的是信号功率和噪声功率的比值,它并不知道你的扩频因子是多少、编码码率是多少。一旦信号功率归一化方式不同,曲线就会整体偏移。手动算方差虽然多写两行代码,但每个参数都在自己掌控里,排查问题能少掉一大半麻烦。

3.2 主循环仿真代码逐段拆解

下面是仿真核心代码,按整条链路的顺序逐段解释:

SF = 31; % 扩频因子 Rc = 1/2; % LDPC码率 nLdpc = 16200; % LDPC码长 kLdpc = nLdpc * Rc; % 信息位长度8100 H = dvbs2ldpc(Rc); enc = comm.LDPCEncoder(H); dec = comm.LDPCDecoder(H, ... 'DecisionMethod', 'Soft decision', ... 'MaximumIterationCount', 50); % PN序列,随机±1;实际系统可替换为comm.PNSequence pn = 2 * (rand(SF, 1) > 0.5) - 1; EbN0dB = -2:1:8; BER = zeros(size(EbN0dB)); numFrames = 150; for k = 1:length(EbN0dB) EbNoLin = 10^(EbN0dB(k)/10); N0 = 1 / EbNoLin; sigma2 = N0 / 2; % 码片域噪声方差 ampChip = sqrt(Rc / SF); % 扩频码片幅度,总功率归一 errTotal = 0; bitTotal = 0; for frame = 1:numFrames data = randi([0 1], kLdpc, 1); % 信息比特 coded = enc(data); % LDPC编码输出16200比特 sym = 1 - 2 * coded; % BPSK映射:0->+1, 1->-1 % 扩频:每个符号复制SF份并乘以PN序列 spread = repmat(sym', SF, 1) .* repmat(pn, 1, nLdpc); spread = ampChip * spread(:); % 展平为码片序列 % 加AWGN rx = spread + sqrt(sigma2) * randn(size(spread)); % 解扩:按SF个码片一组,相关累加后求平均 rxMat = reshape(rx, SF, nLdpc); softSym = mean(rxMat .* repmat(pn, 1, nLdpc), 1); softSym = softSym(:); % 解扩后等效噪声方差变成 sigma2/SF sigmaSoft2 = sigma2 / SF; llr = 2 * ampChip * softSym / sigmaSoft2; % BPSK的LLR % LDPC译码,输出为LLR软值 decoded = dec(llr); % 符号约定:请先用无噪声链路验证,我的约定是LLR<0判为1 estBits = double(decoded < 0); errTotal = errTotal + sum(estBits ~= data); bitTotal = bitTotal + kLdpc; end BER(k) = errTotal / bitTotal; end

这段代码里最需要注意的几个位置:扩频时的repmat操作生成了SF×nLdpc的矩阵,每一列对应一个符号的SF个码片;加噪声用手动方式,而不是awgn函数,因为噪声方差完全由Eb/N0推导而来,不受信号幅度显示值的影响;解扩后的LLR计算里,sigmaSoft2用的是压缩后的噪声方差,这才是扩频增益真正进入数学表达式的环节。

3.3 误码率统计口径与绘图

统计误码率之前,先想清楚一个问题:你想看的是哪一层BER?是LDPC编码后的信息比特BER,还是编码前信道层的硬判决BER?加了LDPC的链路,最终关心的当然是信息比特层面的编码后BER,也就是estBits和原始data对比的结果。如果你想同时画一条信道层BER做对照,需要在解调硬判决之后立刻统计一次,而不是等译码完再统计。

蒙特卡洛统计的帧数要足够多。经验法则是每个Eb/N0点至少积累100个错误比特,BER曲线才不至于到处乱跳。在BER=10的-5次方这个量级,意味着至少需要传输10的7次方个信息比特,按每帧8100比特来算,大概要1300帧。实际课程设计里如果时间有限,可以积累到30到50个错误就停,曲线在低误码率段会有抖动,但整体趋势还在。绘图用semilogy,把理论BPSK曲线一起叠上去,方便判断仿真实现是否正确。

4. 常见问题与调试实录

4.1 LDPC译码输出全乱?先查LLR符号约定

我见过最多的问题,就是译码器输出和原始信息比特对比时,错误率接近0.5,看起来像完全随机。这种情况下90%的概率是LLR的符号约定和BPSK映射不一致。比如你的BPSK映射是0对应+1、1对应-1,但LDPC译码器内部默认的软判决约定是LLR为正表示比特1,两者一冲突,译码器等于拿错误的信息做迭代,结果自然全乱。

最稳妥的排查方法是跑一个无噪声链路自检:把扩频因子设为1,噪声方差设成0,走一遍编码、映射、解调、译码全流程,然后用disp打印出几个位置的LLR值和译码输出的符号。你很快就能看出是正负号反了,还是幅度量级不对。记住这个经验:任何带软信息对接的系统,第一件事就是确认符号约定,不要靠猜。

4.2 扩频后性能不升反降?八成是横轴口径错了

扩频系统的处理增益是实打实的,但如果绘图的横轴用的是SNR而不是Eb/N0,扩频后的曲线一定会比未扩频的曲线差。原因很简单:扩频把信号带宽扩展了SF倍,同样的信号功率下,噪声带宽也变宽了,SNR天然被稀释。这是物理规律,不是仿真错误。但在Eb/N0坐标下,信息比特能量和噪声功率谱密度的比值与带宽无关,扩频前后的性能应该基本一致,甚至因为扩频带来的处理增益,在实际系统中会有抗窄带干扰的额外好处。

另一个容易搞混的是功率归一化。如果扩频时码片幅度没按sqrt(1/SF)缩放,而是保持了原始符号幅度,那么发射能量被放大SF倍,曲线的位置会整体左移约10log10(SF)dB,这个差距正好等于处理增益。很多同学看到“扩频后曲线比未扩频好15dB”,还以为是扩频的功劳,其实是能量被偷偷放大了。判断方法简单:未扩频的理论BPSK曲线作为基准,扩频后的曲线如果显著优于理论上限,说明功率归一化一定出了问题。

4.3 高信噪比曲线拖尾,BER收敛不下去

BER曲线在高信噪比段容易出现“平底”或者拖尾不降的现象。最常见原因是错误样本数不够,蒙特卡洛统计在低误码率区域需要海量比特才能体现出真实性能。举个例子,BER在1e-6量级时,一帧8100比特可能一帧里一个错误都没有,但偶尔来一帧错误就会让平均误码率跳一个量级。解决办法是多跑帧数,或者设定错误计数达到阈值才停止仿真。

另一个可能原因是LDPC译码的迭代上限太小。高信噪比下,大部分帧能够快速收敛,但个别帧需要更多迭代才能把残留错误纠完。可以把MaximumIterationCount从50提高到100试试,如果曲线有明显改善,说明是迭代上限的锅。这里多说一句,LDPC译码的早停机制比较智能,提高上限通常只增加个别帧的耗时,整体仿真时间不会成倍增加,所以放心加大。

4.4 仿真跑太慢的优化手段

16200码长配合SF=31,每帧的数据量已经超过50万码片,150帧跑11个信噪比点,数据量上亿,确实会让人等到怀疑人生。优化方向有几个。

第一个方向是把矩阵化做到位。上面代码里用repmat做扩频,每一帧都重新分配大规模矩阵,比较耗时。可以改成预分配一张固定的扩频模板矩阵,每次只需要点乘和reshape。更彻底的优化是把扩频操作写成一个矩阵乘法:用一个大小为nLdpc×nLdpc*SF的稀疏矩阵映射,但Matlab里稀疏矩阵乘法未必比reshape快,实际测试下来repmat方案已经足够。

第二个方向是并行化。Matlab的parfor可以直接用来替换外层信噪比循环,每个Eb/N0的蒙特卡洛仿真相互独立,天生适合并行。需要注意parfor里不能像普通循环那样动态写入BER数组,可以先在循环内收集结果,循环结束后再组装。

第三个方向是降低LDPC译码的迭代上限。在调试阶段用20次迭代定位bug,完全没问题,最终出图时再改回50或100。还有一个实用技巧:如果你的目的是验证链路正确性,先跑3个信噪比点,每个点30帧,确认趋势正常后再全量跑出最终曲线,能节省大量等待时间。

下面把上述问题整理成一个速查表,方便随时对着排查:

现象可能原因定位方法解决方向
译码输出错误率约0.5LLR符号约定与映射不一致无噪声链路自检打印LLR统一符号约定,必要时取负号
扩频后曲线明显优于理论扩频未做功率归一化叠加理论BPSK曲线作基准码片幅度改为sqrt(Rc/SF)
扩频后曲线不如未扩频横轴错用了SNR比对横轴物理含义横轴统一到Eb/N0
高信噪比BER拖平底错误样本不足统计当前错误数增加帧数或积累到100错误
个别帧残错多LDPC迭代上限过低提高迭代上限对比MaximumIterationCount设100

5. 结果验证与扩展方向

5.1 三条自检曲线,让仿真结果更可信

拿到仿真曲线后,不要急着交报告。做三个自检实验,能帮你确认整条链路每个环节都是对的。

第一个自检:去掉LDPC、去掉扩频,只跑纯BPSK+A WGN,把仿真BER和理论曲线Q(sqrt(2Eb/N0))叠在一起。如果两条线完全重合,说明调制、噪声、统计口径这套基座是稳的。这一步特别重要,因为后续所有问题排查都要建立在“底层没问题”这个前提下。

第二个自检:只加LDPC不加扩频,对比未编码BPSK曲线。由于码率是1/2,在低信噪比区域曲线应该比未编码曲线明显左移,这就是编码增益的体现。LDPC在AWGN下逼近香农限,理论上码率为1/2的BPSK极限阈值大约在0.2dB附近,你的曲线应该在0.5到1dB附近达到10的-5次方量级,如果差太多,就要考虑软信息是否真的接对了。

第三个自检:加LDPC加扩频,在Eb/N0坐标下应该和“只加LDPC不加扩频”的曲线基本重合。因为扩频本身不改变Eb/N0下的理论性能,存在的微小差距来自PN序列非理想相关特性和同步误差。如果两条曲线差得离谱,优先检查扩频功率归一化。这三条曲线叠在一张图里,既验证了每个模块,也直接展示了扩频和编码各自的作用,报告会很有说服力。

5.2 从毕设课题到进一步研究的方向

如果做完这套基础链路还想继续深入,扩展空间其实很大。调制端可以把BPSK换成QPSK、16QAM,比较高阶调制在同样频谱效率下的性能变化,这时候需要把LLR计算扩展成星座点距离计算,工程量适中但能学到不少东西。信道编码端可以对比Turbo码、Polar码和LDPC在同样码率下的纠错性能,这正好贴合5G时代信道编码演进的话题。扩频端可以把随机PN序列换成Gold序列、Walsh码,进一步做成多用户CDMA系统,研究多址干扰下的误码性能。信道模型也可以从AWGN扩展到平坦瑞利衰落、频率选择性衰落,加入信道估计和均衡模块。

每条扩展路径都不是简单堆工作量,而是能在报告里形成一个完整的对比实验矩阵。从一个基础链路出发,逐步改变一个维度、控制其他维度不变,这种研究思路本身远比“跑通一条曲线”更有价值。

最后分享我的一点实际体会。做这种多模块联动仿真,最忌讳一上来就全链路跑通后直接画图。我的建议是先模块化验证,再串联集成,顺序是:纯BPSK基线曲线→加入LDPC→加入扩频,每一步都保存一张曲线图。这样每新增一个模块,你都知道性能变化的来源是什么。万一曲线出了问题,回退查错的范围也小得多。我当时第一次做LDPC加扩频联动仿真,整整花了两天才把曲线做干净,事后复盘发现,大部分时间都耗在LLR幅度对齐和扩频因子跨级换算这两个细节上。把这两个点想透,后面再跑任何编码通信链路,都会顺利很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询