
做通信链路级仿真的同学十有八九会遇到这样一条经典链路BPSK调制解调LDPC信道编码扩频解扩最后用MATLAB统计误码率。我第一次完整跑通这套系统时最大的感受是——单独看每个模块都不算难但把三个模块串到一条链路上之后信噪比口径、LLR符号约定、扩频功率归一化这些细节能把人折腾到怀疑人生。这篇博文就整条链路的系统设计、参数选择、代码实现和调试心得完整展开希望能给正在做课程设计、毕业设计或者刚接触通信仿真想快速上手的朋友一条清晰的路线。整篇文章覆盖从发射端信源比特产生到接收端误码统计的完整闭环工程上可以直接复现。1. 链路设计与模块选型思路1.1 这条链路到底在仿真什么整条链路的结构其实是先把信息比特做LDPC编码增加冗余度来抵抗信道噪声编码后的比特再做BPSK映射把0和1变成1和-1的实信号然后做扩频用一个高速的PN序列把每个符号扩展成多个码片展宽频谱信号经过AWGN信道后接收端先解扩恢复出符号级软信息再送入LDPC译码器最后统计误码率。这三个模块的组合不是随便拼的。LDPC负责纠错逼近香农限的信道编码扩频负责抗干扰、抗截获以及多址接入也就是通过带宽扩展换取处理增益BPSK则是整个链路的物理层底座提供最简单可靠的星座映射。三者组合起来就是一套典型的直接序列扩频通信系统CDMA、WiFi等系统的物理层都能看到类似影子。作为课程设计或毕设课题这个题目的好处是每个模块都可以单独验证模块之间接口清晰而且每一层都有理论曲线可以做对照非常适合用来建立“链路级仿真”的整体概念。1.2 为什么选BPSK而不是QPSK或者16QAMBPSK虽然频谱效率只有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标准定义了多种码率和码长推荐使用n16200、码率1/2的码型这个配置在纠错性能和仿真速度之间比较平衡。如果非要自己写BP译码算法工作量会大很多而且容易因为初始化信息没做对、校验节点更新公式写错等原因导致编码增益完全体现不出来。我的建议是分两步走先用MATLAB自带函数把整条链路跑通拿到正确曲线再去实现自写译码器做对比这样即使自写版本出了问题也有一个标准答案可以对照。1.4 扩频方案选型m序列还是随机序列扩频这块最常用的是直接序列扩频把每个BPSK符号与一个高速PN序列逐码片相乘。PN序列理论上应该用m序列m序列的自相关性尖锐生成方式也很简单通过线性反馈移位寄存器就能产生。MATLAB里可以用comm.PNSequence直接生成也可以简单用随机±1序列代替。对于单用户AWGN仿真随机序列和m序列的性能没有实质差别因为这里不需要考虑多用户之间的互相关性但如果后续扩展成多用户系统就一定要换回m序列或者Gold序列。这里要重点理解处理增益的概念。扩频因子SF决定了处理增益G10log10(SF)dB。比如SF31时大约15dBSF63时大约18dB。这个增益不是凭空冒出来的它是以占用更宽带宽为代价换来的。正因为扩频增大了带宽导致相同SNR下单位带宽内的噪声能量更高所以扩频系统的性能对比必须用Eb/N0这个口径否则会出现“扩频后性能反而变差”的奇怪结论。这个坑在4.2节会专门展开。2. 核心模块实现与参数细节2.1 BPSK软解调从似然比到LLR的一步推导BPSK的软解调说到底是计算对数似然比LLR。我们定义映射关系为信息比特0映射为1信息比特1映射为-1接收信号rsn其中n是均值为0、方差为σ²的高斯白噪声。那么LLR可以写成LLR ln [ P(b0|r) / P(b1|r) ] 2r/σ²推导过程其实很简短两个条件概率分别正比于exp(-(r-1)²/(2σ²))和exp(-(r1)²/(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会直接报错但有时候你会因为信息位长度弄错导致每帧数据量看似正常实际统计误码时却对不上号。每次构造数据帧前先确认kLdpcsize(H,2)-size(H,1)这才是真实的输入长度。第三MaximumIterationCount建议设30到50。LDPC译码有早停机制达到收敛条件会提前退出所以上限设大一些并不会让每帧都跑到上限。但如果设太小高信噪比下可能会出现“明明差几个比特就能收敛却因为迭代次数耗尽而失败”的情况曲线会留下难看的底噪。2.3 扩频与解扩功率归一化是最大的坑扩频在MATLAB里的直观实现就是复制点乘把每个BPSK符号复制成SF份逐码片乘以PN序列。但这里面藏着一个特别容易出错的点——功率归一化。一个常见的错误做法是直接把BPSK符号幅度1复制成SF个码片码片幅度仍然保持1然后加AWGN。这样做等于把发射总功率放大了SF倍仿真出来的误码率曲线会比理论上限还“好”15dB以上而且你很难察觉哪里错了因为曲线形状看起来完全正常。正确做法是保持扩频前后每个编码符号的总能量不变设扩频码片幅度Asqrt(Rc/SF)其中Rc是LDPC码率SF是扩频因子同时假定信息比特能量Eb归一化为1。解扩端对应有两种实现方式可以相关累加再除以SF求平均也可以直接累加不归一化。求平均的好处是软值幅度不会随SF增大而爆炸数值稳定性好。解扩之后的等效噪声方差变成σ²/SF这意味着处理增益确实体现出来了信号幅度不变噪声方差被压缩了SF倍等效信噪比提升。LLR计算也要用压缩后的噪声方差才能和LDPC译码器对接正确。3. 完整仿真链路搭建与误码率统计3.1 参数配置一览与信噪比换算先给出一套可以直接复用的参数配置表格这套参数我在多个版本的MATLAB上跑过曲线稳定且仿真时间可以接受参数取值说明调制方式BPSK0映射11映射-1LDPC码长16200DVB-S.2短帧LDPC码率1/2信息位8100比特LDPC译码迭代50软判决输出扩频因子SF31处理增益约15dBPN序列随机±1或m序列单用户场景下等价Eb/N0扫描范围-2dB到8dB步进1dB每信噪比帧数150每帧8100信息比特信噪比换算是整个仿真最容易翻车的地方。这里给出我的换算逻辑信息比特能量Eb设为1则给定Eb/N0的线性值后N01/EbN0_linear噪声单边功率谱密度确定AWGN噪声方差σ²N0/2。在码片域因为总功率归一化码片幅度是Asqrt(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); % 符号约定请先用无噪声链路验证我的约定是LLR0判为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曲线才不至于到处乱跳。在BER10的-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码长配合SF31每帧的数据量已经超过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设1005. 结果验证与扩展方向5.1 三条自检曲线让仿真结果更可信拿到仿真曲线后不要急着交报告。做三个自检实验能帮你确认整条链路每个环节都是对的。第一个自检去掉LDPC、去掉扩频只跑纯BPSKA 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幅度对齐和扩频因子跨级换算这两个细节上。把这两个点想透后面再跑任何编码通信链路都会顺利很多。