简介:这份资源是面向通信工程、电子信息类专业学生及扩频通信入门者的MATLAB仿真设计文档,围绕直接序列扩频(DSSS)通信系统展开,帮助读者理解扩频、解扩与抗干扰性能评估的完整流程。文档系统讲解了直扩系统模型、BPSK调制方式、m序列与GOLD码等伪随机码类型,以及抗同频干扰、抗衰落、抗多径干扰等扩频通信优势,并给出信源速率2kbps、扩频因子64、高斯信道下的仿真参数设置与误码率随信噪比变化的分析思路。资源包内含1个doc文件,大小约320KB,内容涵盖实验原理、系统组成框图说明、部分程序代码与仿真结果,结构紧凑、便于查阅。目前已有142人学习下载,适合作为课程设计、实验报告或自学扩频通信原理的参考材料,读者可据此快速搭建仿真框架并调整参数研究不同条件下的系统性能。
1. 从一份 8 页的 MATLAB 仿真文档说起:DSSS 到底能跑出什么
很多人第一次接触直接序列扩频,都是在课程设计或者毕设里被丢过来一份文档,里面公式一堆、框图一张,但真正打开 MATLAB 跑起来的时候,波形全是毛刺,误码率曲线要么一条直线要么乱跳。这份《基于 MATLAB 的直接序列扩频通信系统仿真设计》就是典型的这类资源——它不厚,8 页,但把 DSSS 从原理到代码到结果串了一遍,信源速率 2kbps、扩频因子 64、BPSK 调制、高斯信道,参数给得很具体,代码虽然有些地方写得糙,但骨架是完整的。如果你正在做扩频通信的仿真作业,或者想搞明白“扩频增益到底怎么体现在误码率上”,这份文档能让你少走不少弯路。它适合两类人:一类是刚学通信原理、需要一份能跑通的参考代码的新手;另一类是做系统仿真、想快速验证 DSSS 在不同信噪比下性能的从业者。下面我按自己拆这份资源的顺序,把原理、代码、参数和坑一个个讲清楚。
2. 直扩系统模型拆解:从信源到解扩的完整链路
2.1 为什么选 BPSK 而不是 QPSK
文档里明确写了调制方式选 BPSK,这不是随便定的。直扩系统里,扩频码和信息的模二加本质上是在做极性映射,BPSK 的相位跳变只有 0 和 π 两种状态,解扩时相关器的输出判决门限最干净。如果用 QPSK,I/Q 两路各自扩频,虽然频谱效率高,但仿真里引入的相位模糊和载波同步误差会让误码率曲线在低信噪比段出现明显的“地板效应”,新手很容易误以为是自己代码写错了。文档里信源速率 2kbps、扩频因子 64,意味着扩频后码片速率是 128kbps,BPSK 在这个速率下仿真步长和采样率的匹配比较好调。我一般建议第一次跑 DSSS 仿真就用 BPSK,等误码率曲线跟理论值对上了,再去折腾 QPSK 或者 MPSK。
2.2 扩频增益 10dB 是怎么算出来的
文档里提到“处理增益达到 10dB 以上”,这个数字来自扩频因子。处理增益 Gp = 10log10(扩频因子),当扩频因子为 64 时,Gp = 10log10(64) ≈ 18dB。但文档里写的是 10dB 以上,这里有个细节:实际仿真中,由于采样率、滤波器带宽和积分区间的影响,有效处理增益会低于理论值。文档里信源速率 2kbps、扩频因子 64,但代码里 Rb=0.25、Tb=1/Rb=4,这个 Rb 的单位其实是毫秒级的,跟 2kbps 的对应关系需要自己换算。我跑的时候一般会先确认三个频率:信源符号率、扩频码片率、载波频率。文档里 fc=4e4,也就是 40kHz,而码片率是 128kbps,载波频率比码片率还低,这在仿真里会导致频谱混叠,后面避坑章节会细说。
2.3 伪随机码生成:m 序列和 Gold 序列的代码实现
文档里的代码用了一个mseq函数和一个goldseq函数来生成扩频码,stage=3,ptap1=[1 3],ptap2=[2 3],regi1=[1 1 1]。这是 3 级移位寄存器,m 序列长度是 2^3-1=7。代码里x_code的生成逻辑是每 7 个码片循环一次,但写得很绕:if mod(i,7)~=0取code(mod(i,7)),elseif mod(i,7)==0取code(7)。这实际上是在做一个周期为 7 的重复,但code本身是 1×7 的向量,所以code(mod(i,7))在 i=7 时会取code(0),MATLAB 里索引 0 会报错,所以作者加了个判断。这种写法能跑,但可读性差,我一般会直接写成x_code(i) = code(mod(i-1,7)+1),一行搞定。
扩频码与信息的相乘在代码里是b=BPSK.*k_code,这里k_code是把x_code每个码片重复 4 次得到的,长度跟QPSK变量对齐。注意代码里变量名有点乱:QPSK实际上是 BPSK 调制后的信号,BPSK反而是 I/Q 两路合并后的结果。这种命名不一致是这份文档的一个小毛病,读代码的时候要对着注释看。
2.4 接收端解扩与积分判决
接收端的流程是:接收信号r先加高斯白噪声awgn(s,SNR(10),'measured'),然后乘以同频载波做相干解调,再乘以扩频码k_code完成解扩,接着通过低通滤波器lpf滤除高频分量,最后对每个信息符号周期内的 400 个采样点做积分判决。代码里z2累加 400 个点,大于等于 0 判为 1,否则判为 -1。这个积分区间长度 L=400 对应的是每个信息符号的采样点数,M=100 是信息符号数,N=M*L=40000 是总采样点数。参数之间的约束是:L 必须等于扩频因子乘以每个码片的采样数,文档里扩频因子 64,每个码片采样 4 次,但 L=400,64×4=256,对不上。这说明代码里的参数体系跟文档正文的描述有出入,实际跑的时候需要自己统一。
3. 仿真参数配置与代码逐段拆解
3.1 信源生成与采样率设置
文档代码开头几行是参数定义:
close all clear all SNR=[1:1:15] Rb=0.25; Tb=1/Rb; M=100; L=400; N=M*L; dt=Tb/L; df=1/(N*dt); Bs=N*df/2; T=N*dt; t=linspace(-T/2,T/2,N); x=sign(rand(1,M)-0.5); t0=linspace(0,T,M); for i=1:100 d((1+(i-1)*400):i*400)=x(i); end这里Rb=0.25对应的符号周期Tb=4,单位是秒还是毫秒取决于后续时间轴的标注。M=100是信息比特数,L=400是每个比特的采样点数,N=40000是总采样点数。x是随机生成的 ±1 序列,d是把每个比特扩展成 400 个采样点的矩形脉冲。注意for i=1:100里的 100 就是 M,但写死了,改 M 的时候这个循环也要改。我一般会把for i=1:M写成变量,避免后面调参数时漏改。
dt=Tb/L是采样间隔,df=1/(N*dt)是频率分辨率,Bs=N*df/2是奈奎斯特带宽。这几个参数决定了后续 FFT 和滤波器的频率轴。如果Tb的单位是毫秒,那dt就是微秒级,载波fc=4e4就是 40kHz,跟码片率的关系需要自己核对。
3.2 BPSK 调制与扩频码生成
BPSK 调制部分的代码:
fs=4000; z=reshape(d,2,N/2); Q=z(1,:); I=z(2,:); tq=linspace(-T/2,T/2,length(I)); BPSK=(I.*cos(2*fs*pi*tq)-Q.*sin(2*fs*pi*tq))/sqrt(2);这里reshape(d,2,N/2)把d分成两路,Q和I各占一半。然后BPSK变量实际上是 I/Q 调制后的信号,载波频率fs=4000。注意fs跟后面的fc=4e4不是同一个频率,fs是 I/Q 调制的载波,fc是扩频后二次调制的载波。这种两级调制在仿真里容易混淆,我一般会把变量名改成fc1和fc2,避免看错。
扩频码生成部分:
user=1; seq=1; stage=3; ptap1=[1 3]; ptap2=[2 3]; regi1=[1 1 1]; regi2=[1 1 1]; switch seq case 1 code=mseq(stage,ptap1,regi1,user); case 2 m1=mseq(stage,ptap1,regi1); m2=mseq(stage,ptap2,regi2); code=goldseq(m1,m2,user); case 3 m1=mseq(stage,ptap1,regi1); m2=mseq(stage,ptap2,regi2); code=[goldseq(m1,m2,user),zeros(user,1)]; endseq=1选 m 序列,seq=2选 Gold 序列,seq=3选带补零的 Gold 序列。stage=3对应 3 级移位寄存器,m 序列长度 7。ptap1和ptap2是抽头位置,regi1和regi2是初始状态。这段代码的坑在于mseq和goldseq函数没有在文档里给出实现,需要自己补。常见做法是用 MATLAB 的comm.PNSequence对象或者手写移位寄存器。我一般会手写一个简单的 m 序列生成函数,方便调试抽头。
3.3 扩频调制与信道加噪
扩频调制和加噪的代码:
k_code=zeros(1,length(QPSK)); for i=1:5000 k_code((1+(i-1)*4):i*4)=x_code(i); end b=BPSK.*k_code; fc=4e4; td=linspace(0,T,length(b)); s=b.*cos(2*fc*pi*td); r=awgn(s,SNR(10),'measured');k_code是把x_code每个码片重复 4 次,长度对齐QPSK。b是扩频后的基带信号,s是二次调制到fc=4e4的射频信号。awgn加噪时用的是SNR(10),也就是第 10 个信噪比点,对应 10dB。这里SNR数组是 1 到 15,步长 1,所以SNR(10)=10。加噪后的r进入接收端处理。
注意awgn的第三个参数'measured'表示先测量信号功率再按 SNR 加噪,这个模式在信号功率波动时比较准。如果信号功率恒定,也可以不加'measured',直接按默认功率加。
3.4 解扩、滤波与误码率计算
接收端解扩和误码率计算的代码:
rs=r.*cos(2*fc*pi*td); jk_code=rs.*k_code; [f,rf]=T2F(td,jk_code); [tl,l]=lpf(f,rf,fc/10000); liremod=cos(2*fs*pi*tq)*sqrt(2).*l; [fi,lif]=T2F(tq,liremod); [tji,iremod]=lpf(fi,lif,fs/10000); iremodo=sign(iremod); lqremod=(-1)*sin(2*fs*pi*tq)*sqrt(2).*l; [fq,lqf]=T2F(tq,lqremod); [tjq,qremod]=lpf(fq,lqf,fs/10000); qremodo=sign(qremod); zl=[qremodo,iremodo];T2F是时域到频域的变换,lpf是低通滤波器,截止频率分别是fc/10000和fs/10000。这两个截止频率的设置很关键:fc/10000=4,fs/10000=0.4,单位取决于频率轴。如果频率轴是 Hz,那截止频率就是 4Hz 和 0.4Hz,这明显不对,因为信号带宽远大于这个值。我怀疑这里的频率轴单位是 kHz 或者归一化频率,实际跑的时候需要根据T2F和lpf的实现来调整。这也是这份文档代码里最容易翻车的地方——滤波器截止频率跟信号带宽不匹配,导致解调输出全是噪声。
误码率计算部分:
for a=1:15 r=awgn(s,SNR(a),'measured'); ... z=0; for i=1:100 if(x(i)~=xo(i)) z=z+1; end end z1(a)=z/100 end对每个 SNR 点重新加噪、解调、判决,统计 100 个比特里的错误数,除以 100 得到误码率。最后plot(SNR,z1)画出误码率曲线。注意z1(a)=z/100后面没有分号,会在命令行打印每次的误码率,调试的时候可以留着,正式跑的时候建议加分号。
4. 避坑与排查:这份代码跑不通的五个常见原因
4.1 现象:运行报错“索引超出矩阵维度”
原因:code(mod(i,7))在 i=7 时mod(7,7)=0,MATLAB 索引从 1 开始,code(0)非法。文档里用if mod(i,7)~=0绕过了,但如果x_code的长度不是 7 的整数倍,或者code的长度不是 7,这个判断就会失效。
解决:把索引改成code(mod(i-1,7)+1),这样 i=1 时取code(1),i=7 时取code(7),i=8 时取code(1),逻辑清晰且不会越界。
4.2 现象:误码率曲线一直在 0.5 附近,完全不下降
原因:扩频码没有对齐。发送端k_code的长度和接收端解扩时用的k_code长度不一致,或者扩频码的相位偏移了一个码片,导致相关器输出接近零。
解决:检查k_code的生成循环,确保发送和接收用的是同一个k_code变量。如果接收端重新生成了扩频码,要保证移位寄存器的初始状态和抽头完全一致。我一般会在发送端和接收端各打印一次k_code(1:20),对比是否相同。
4.3 现象:解调输出波形全是高频毛刺,判决全错
原因:低通滤波器截止频率设置不当。文档里lpf(f,rf,fc/10000)的截止频率是fc/10000=4,如果频率轴单位是 Hz,这个截止频率远低于信号带宽,滤波器把有用信号也滤掉了;如果频率轴是归一化频率,4 又太大了,高频分量没滤干净。
解决:先确认T2F返回的频率轴范围,然后根据信号带宽设置截止频率。基带信号带宽大约是码片率的一半,即 64kHz,如果频率轴是 Hz,截止频率应该设在 64kHz 左右。我一般会先用fft看一次解扩后信号的频谱,确认有用分量在哪个频段,再定截止频率。
4.4 现象:awgn报错“信号功率为零”
原因:s信号在某个时刻全为零,或者SNR设置过低导致awgn无法正常计算。文档里s=b.*cos(2*fc*pi*td),如果b在某段为零,s的功率就会波动。
解决:检查b的生成过程,确保BPSK和k_code的长度一致且没有全零段。如果信号本身有零段,可以在awgn前先归一化功率,或者改用awgn(s,SNR(a),'measured')并确保s非零。
4.5 现象:仿真跑完但误码率曲线跟理论值对不上
原因:积分区间和判决门限不匹配。文档里每个信息符号积分 400 个点,但扩频因子是 64,每个码片采样 4 次,64×4=256,不是 400。积分区间多了 144 个点,这些点落在相邻符号的区间里,引入了符号间干扰。
解决:把L改成扩频因子 × 每码片采样数,即 64×4=256,然后重新生成d和k_code。如果一定要用 400,那就把扩频因子改成 100,或者每码片采样数改成 6.25(取整后会有误差)。我一般会先固定扩频因子和采样率,再反推L,而不是先定L再凑扩频因子。
5. 进阶技巧:把误码率曲线跑得跟理论值只差 0.5dB
5.1 用理论 BER 曲线校准仿真
BPSK 在高斯信道下的理论误码率是Q(sqrt(2*Eb/N0)),扩频系统的处理增益会让等效Eb/N0提高10log10(扩频因子)。文档里扩频因子 64,理论增益 18dB。我一般会先画一条理论曲线,再把仿真点叠上去,看差距在哪里。如果仿真曲线在低 SNR 段比理论差很多,通常是扩频码同步没做好;如果高 SNR 段出现地板,通常是滤波器或者积分区间的问题。
SNR_dB = 1:1:15; EbN0_dB = SNR_dB + 10*log10(64); % 扩频增益 EbN0_lin = 10.^(EbN0_dB/10); BER_theory = 0.5*erfc(sqrt(EbN0_lin)); semilogy(SNR_dB, BER_theory, 'k-', SNR_dB, z1, 'ro');这段代码把理论曲线和仿真点画在一起,erfc是互补误差函数,semilogy让纵轴对数显示。如果仿真点跟理论曲线平行但整体偏高,说明有固定的信噪比损失,通常是滤波器或者采样率带来的。
5.2 扩频码同步的简化处理
实际系统里扩频码同步是个大话题,但仿真里可以简化:发送端和接收端用同一个k_code变量,不引入延迟和频偏。如果要做更真实的仿真,可以在接收端加一个滑动相关器,搜索扩频码的相位。我一般会在仿真里加一个delay参数,手动偏移几个码片,看误码率恶化多少,这样能直观感受同步误差的影响。
5.3 参数扫描的批量跑法
文档里只跑了 15 个 SNR 点,每个点 100 个比特,统计精度有限。我一般会把M加到 10000,然后对每个 SNR 点跑 10 次取平均,这样误码率曲线更平滑。批量跑的代码框架:
SNR_list = 1:1:15; BER_avg = zeros(size(SNR_list)); for k = 1:length(SNR_list) err_sum = 0; for trial = 1:10 % 重新生成信源和噪声 x = sign(rand(1,M)-0.5); % ... 完整仿真流程 ... err_sum = err_sum + sum(x ~= xo); end BER_avg(k) = err_sum / (M * 10); end这样跑一次大概几分钟,但曲线质量比单次跑好很多。注意每次 trial 都要重新生成信源和噪声,否则统计的是同一个样本,没有意义。
5.4 我踩过的一个坑:滤波器群延迟
文档里的lpf函数如果是 FIR 滤波器,会引入群延迟,导致解调输出的符号边界偏移。我一开始没注意,误码率一直在 0.1 左右下不去,后来把积分区间往前挪了filter_order/2个点,曲线立刻跟理论对上了。从那以后我每次用lpf都会先看它的群延迟,然后在积分判决时做补偿。这个细节文档里没提,但实际跑的时候很容易翻车。
5.5 把这份资源用到位的一个习惯
这份文档的代码虽然有些地方写得糙,但它的价值在于把 DSSS 的完整链路串了一遍。我一般会先把它的代码原样跑一遍,确认能出图,然后逐段替换成自己写的函数,每替换一段就对比一次波形和误码率。这样既能理解每个模块的作用,又能积累自己的代码库。如果你只是把代码复制粘贴跑完交差,那这份资源就浪费了。希望帮到你。
本文还有配套的精品资源,点击获取