
简介这份MATLAB程序资源围绕DSSS直接序列扩频信号生成展开面向通信工程专业学生、射频研发人员及无线通信算法学习者可配合通信原理课程或扩频实验使用。压缩包共4个文件全部为.m脚本体积仅2KB结构精简。主要脚本串联扩频流程分别负责主控、m序列生成与DSSS信号构造能从代码层面直接观察PN码、扩频因子与基带调制的结合方式PN码由线性反馈移位寄存器产生的思想也体现在m序列源码中便于理解伪随机特性与频谱扩散机制。解扩频、码片同步等接收端原理可对照发送端参数设计一并领会。已有361人学习适合作为课程设计或MATLAB仿真的入门样本对于需要掌握抗干扰通信机制、快速搭建扩频模型的读者这份小巧实例提供了可运行参考稍加改动即可扩展Gold码、多址接入或FEC编码等进阶功能。1. DSSS扩频信号到底怎么生成一套能直接跑的Matlab源码做无线通信仿真这些年我拆过不少扩频相关的代码包有个很深的体感DSSS扩频信号的理论讲起来不算难但真要把PN码、扩频因子、基带调制这几块拼成一个能跑的仿真大多数人会在“PN码怎么来的”第一步就卡住。这套资源正好补上这个断层它包含gen_mseq.m、gen_dsss.m、main.m和gen_fhss.m四个文件从m序列生成到DSSS信号输出是一条完整链路末尾还带了一个FHSS对照实验。它适合两类人一是刚接触扩频通信、想在Matlab里把“扩频”两个字变成波形看个究竟的学生二是做通信系统仿真、需要一份干净可改的DSSS参考实现来嵌入自己链路的工程师。你不需要先啃完通信原理跟着代码走一遍扩频信号的一生就能看明白。2. m序列与LFSR实现PN码不是随机数是算出来的2.1 为什么DSSS里几乎都用m序列DSSS扩频的核心操作是把每个数据比特乘上一段高速的伪随机码序列也就是PN码。PN码里的“伪”字提醒我们它只是看起来随机实际上完全确定接收端能复制出一份一模一样的这才是后面解扩的基础。PN码的候选方案有m序列、Gold码、Kasami序列几种而m序列是最简单也最常用的一种因为它只需要一个线性反馈移位寄存器LFSR就能产生硬件实现极其便宜且周期特性非常强。m序列的全称是最大长度序列由n级寄存器产生的m序列长度为2的n次方减1这个长度恰好是LFSR能走完的所有非零状态总数。它的自相关特性非常好完全对齐时相关峰高错开一位以上旁瓣恒为固定小值。这种尖锐的相关峰决定了接收端只要把本地PN码和收到的信号做相关运算就能在正确对齐的位置看到一个明显的凸起。正是由于“能产生、能复制、能对齐”这三个特点m序列被大量用于点对点扩频和低速控制信道。Gold码则是从两个优选m序列异或派生出来的互相关性能更好常用于CDMA多用户场景但在单用户DSSS仿真里先用m序列把链路跑通是更务实的做法。2.2 gen_mseq.m完整实现与参数说明直接看代码。gen_mseq.m的作用是输入寄存器级数n、本原多项式poly和初始状态init输出一个完整的m序列。下面是我拆包后重写并加了注释的版本% gen_mseq.m - 由线性反馈移位寄存器LFSR生成m序列 % 输入参数 % n : 移位寄存器级数序列长度 2^n - 1 % poly : 本原多项式系数长度 n1 的向量 % 例如 [1 0 0 1 0 1] 表示 1 x^2 x^5 % init : 初始状态长度 n 的0/1向量不能为全零 % 输出结果 % mseq : 生成的m序列元素为0/1长度 2^n - 1 function mseq gen_mseq(n, poly, init) if nargin 3 init [1, zeros(1, n-1)]; % 默认初相最高位置1 end len 2^n - 1; mseq zeros(1, len); reg init; for i 1:len mseq(i) reg(n); % 从末级输出当前码片 feedback mod(sum(reg .* poly(2:end)), 2); % 抽头异或模2求和 reg [feedback, reg(1:end-1)]; % 反馈值移入最低位 end end这段代码里有几个地方容易看懵。首先是poly(2:end)的取法poly的第一个元素是x的0次方系数恒等于1真正参与反馈计算的是从x的1次方到x的n次方这部分系数所以我把首位跳过只让剩余系数和寄存器做逐位点乘。其次是feedback的计算它把当前寄存器的每一位和对应抽头系数相乘后累加再模2本质就是把抽头位置上的寄存器值全部做异或运算。得到feedback之后把它放到寄存器最前面其他位依次后移一格这就完成了一次移位。调用时要注意本原多项式的正确写法。比如n等于5时多项式x的5次方加x的2次方加1对应的系数向量是[1 0 0 1 0 1]寄存器级数正好是5向量长度是6。我一般会在草稿纸上先把寄存器链画出来对照着写poly向量能避免一半以上的低级错位。init的改变会改变m序列的相位也就是得到原序列的循环移位版本。接收端捕获时需要搜索这个相位偏移这一点在后面的避坑章节会着重说。2.3 自相关验证用xcorr证明PN码能用光生成出来还不够必须验证它能不能用。m序列最核心的特征是二值自相关特性完全对齐时相关值等于N也就是2的n次方减1错开一位以上时相关值恒等于负1。把序列映射为正负1之后用Matlab自带的xcorr函数就能直接验证% 验证m序列自相关性 n 5; % 5级移位寄存器 poly [1 0 0 1 0 1]; % x^5 x^2 1本原多项式 init [1 0 0 0 0]; % 初始状态 mseq gen_mseq(n, poly, init); % 生成0/1序列 p 2*mseq - 1; % 映射为±1 [r, lags] xcorr(p, p, coeff); % 归一化自相关 stem(lags, r, filled); xlabel(码片偏移); ylabel(归一化自相关值); title(m序列自相关特性);运行后观察结果零偏移位置会出现一个明显的峰值1其余所有偏移处的值应该接近负1除以N。以n等于5为例N等于31旁瓣值大约是负0.032主峰和旁瓣的落差接近33倍在图上看起来就是一根尖刺立在一条平线上到处都低于它。这个尖峰就是接收端捕获码相位的依据滑动相关搜索时最大相关值出现的位置就是PN码对齐的位置。还有一个小技巧是同时验证互相关特性让两组不同初始状态生成的m序列互相做xcorr。如果互相关旁瓣也低说明这套PN码在后续多用户场景里有一定抗多址干扰能力。眼下先把自相关的尖峰确认好我一般就会进入下一章正式做扩频调制。3. gen_dsss.m扩频与调制处理增益和带宽怎么落地3.1 扩频因子到底取多少一个参数表就清楚进入核心环节之前先花一分钟把扩频因子SF讲透。SF的中文叫法很多扩频因子、扩频比、处理增益指的都是同一个东西一个数据比特被拆成多少个码片。举个例子你要传1Mbps的数据扩频后码片速率变成8Mchip/sSF就是8。SF越大每个比特分配到的码片越多抗干扰能力和隐蔽性越强代价是同样数据量占用的带宽变大传输速率下降。这不是玄学是带宽和信噪比之间的直接权衡。SF在代码里就是一个整数但要注意它和PN码周期的关系。最常见的做法是让每个数据比特对应PN码的一个完整周期这样SF就等于PN码长度也就是2的n次方减1。好处是每个比特的相关特性都是完整的自相关峰不会被中途截断。下表是几个常用参数组合直接照着用就可以跑仿真级数nPN码长度N扩频因子SF处理增益(dB)5313114.9712712721.0951151127.1处理增益的计算公式是10乘以log10(SF)。SF每翻一倍增益涨3dB直观感受是同样的误码率要求下系统所需信噪比更低。选SF时要先看带宽预算数据速率乘以SF就是码片速率码片速率又决定了扩频信号的主瓣带宽三者绑在一起动一个另外两个跟着动。仿真初期建议从n等于5或7开始序列短好调试波形算得快等链路稳定了再换长序列。3.2 扩频加BPSK调制的核心实现下面这段是gen_dsss.m的核心逻辑。输入数据按位取出映射为正负1后与PN码相乘再把扩频后的码片序列调制到载波上。基带调制我选BPSK因为DSSS最常见的搭档就是对相位敏感、实现又最简单的BPSK扩频和调制叠加在一起时信号模型不会变得过于复杂% gen_dsss.m - 生成DSSS扩频信号 % 输入参数 % data : 1×M的0/1数据向量 % pn : 1×N的0/1 PN码序列 % SF : 扩频因子通常是PN码长度N % fc : 载波频率 % Fs : 采样率 % chipT : 单个码片持续时间 % 输出结果 % t : 时间轴 % dsss_rf: 调制后的DSSS射频信号 function [t, dsss_rf] gen_dsss(data, pn, SF, fc, Fs, chipT) M length(data); N length(pn); pn_bipolar 2*pn - 1; % 0映射为-11映射为1 % 第一步扩频每个数据比特复制SF个码片并与PN逐位相乘 spread zeros(1, M*SF); for i 1:M seg (i-1)*SF 1 : i*SF; spread(seg) (2*data(i)-1) .* pn_bipolar(1:SF); end % 第二步BPSK上变频码片流直接乘余弦载波 chipRate 1/chipT; totalTime length(spread) * chipT; t 0:1/Fs:totalTime - 1/Fs; baseband repmat(spread, ceil(Fs*chipT), 1); baseband baseband(:); % 零阶保持码片电平保持一个chipT dsss_rf baseband .* cos(2*pi*fc*t); end第一步扩频里我用2乘data(i)减1把数据0变成负1、1变成正1这样和PN码相乘的结果刚好满足同号输出正、反号输出负的物理含义。第二步上变频用了零阶保持近似每个码片电平在整个chipT内不变再和载波相乘。载波频率fc要结合采样率Fs来选工程上常用准则是Fs至少大于两倍的fc加上信号带宽否则频谱会混叠时域波形也会出现明显失真。如果你的仿真只关心基带处理直接输出spread即可上变频那步可以跳过去。需要接射频信道模型时再保留载波部分。main.m里调用gen_dsss的典型写法大致是这样的先用randi生成一批随机数据比特再用gen_mseq生成PN码最后调用gen_dsss得到时间轴和射频信号后续可以交给加噪、解扩和误码率统计模块。3.3 从频谱确认扩频成功功率谱密度一目了然扩频有没有做对不要用眼睛空猜最直接的办法是观察信号频谱。扩频前数据信号的能量集中在窄带范围扩频后同一个能量被摊开到以码片速率决定的宽带上功率谱密度明显下降这就是扩频两个字在物理层面的含义% 观察扩频前后的频谱对比 Fs 100e3; fc 10e3; chipT 0.001; SF 31; data randi([0 1], 1, 100); % 随机数据 pn gen_mseq(5, [1 0 0 1 0 1], [1 0 0 0 0]); % m序列 [t, dsss_rf] gen_dsss(data, pn, SF, fc, Fs, chipT); % 用周期图法估计功率谱 [Pxx, f] pwelch(dsss_rf, 256, [], 1024, Fs); plot(f/1e3, 10*log10(Pxx)); xlabel(频率 (kHz)); ylabel(功率谱密度 (dBHz)); title(DSSS扩频信号功率谱);运行后会看到能量不再集中在fc附近一个窄峰上而是铺开成以fc为中心的sinc函数形状主瓣宽度由chipT决定大约是2倍码片速率。频谱铺得越平说明PN码随机性越好扩频效果越“干净”。看完这张图从数据比特到DSSS信号的整条链路就确认通了。这个习惯我坚持了很久每次改完参数都先看一眼频谱再往下走能拦住大多数链路层的低级错误。4. DSSS仿真避坑指南PN相位、初相和模2运算的经典翻车现场这一章写的都是我在拆这个项目和跑DSSS仿真时实际遇到或帮别人排查过高频问题每条都按现象、原因、解决三段式展开。提前看完至少能帮你省两个晚上的调试时间。4.1 初始状态设成全零LFSR输出整条全零序列现象调用gen_mseq时把init写成全零向量生成的mseq全部是0后续扩频输出也都是0误码率直接爆表但代码逻辑看起来没有任何问题。原因LFSR遇到全零状态后会被永久锁住。反馈异或运算里任何值异或0都等于它自己寄存器一旦全部归零无论怎么移位和反馈输出永远是0。这是LFSR的结构性缺陷不是Matlab的问题。解决初始化时保证至少有一级寄存器为1。我习惯在函数内部加一道兜底判断状态全零时自动改成默认初相if all(init 0) init [1, zeros(1, n-1)]; % 强行把最高位置1避免锁死 end4.2 生成多项式没选本原m序列变成短周期序列现象生成的序列长度理论上是2的n次方减1实际却短了一截自相关曲线除了主峰之外还出现多个等高的旁瓣捕获时容易误判同步点。原因m序列要求生成多项式必须是本原多项式所谓本原是说多项式不可约且能让LFSR遍历所有非零状态。很多人从网上随手抄一个多项式系数就直接用遇到非本原多项式周期就会缩短序列特性随之劣化。解决直接查通信原理教材后面的本原多项式表不要自己凑系数。表格列出几个最常用的组合级数n常用本原多项式周期31 x x^3751 x^2 x^53171 x^3 x^7127拿到多项式后先做一遍自相关验证如果旁瓣乱七八糟优先怀疑多项式选错了。4.3 解扩时PN相位差一个码片相关峰直接消失现象发送端和接收端用的是同一个gen_mseq生成的PN码但解扩后恢复的数据和原始数据相比完全错乱而且是系统性错位不是零星错误。原因DSSS解扩要求收发两端的PN码不只是“同一个序列”码相位还必须严格对齐。只要差一个码片相关峰就没有了。这和m序列自相关尖锐的特性是配套的峰越尖对相位越敏感。解决接收端先做滑动相关捕获搜索相关峰位置把本地PN码对齐到峰值处再开始解扩% 码相位捕获滑动相关找峰值 received_cp 2*received - 1; % 收到的码片映射为±1 pn_local 2*pn - 1; % 本地PN映射为±1 xc zeros(1, N); for shift 0:N-1 xc(shift1) sum(received_cp(1:N) .* circshift(pn_local, -shift)); end [~, bestShift] max(xc); % 峰值位置即同步点 pn_aligned circshift(pn_local, -bestShift);这一步是DSSS仿真的“后悔药”宁可在这里多算几下也别跳过。实际通信系统里码同步是一个专门模块一般会用早迟门跟踪环持续校准仿真里滑动搜索已经够用。4.4 0/1运算混入浮点误差判决边界翻车现象自相关结果出现0.9999和负0.002这类数值误码率测试里边界比特忽对忽错同样的代码跑两次结果不一样。原因Matlab里0/1逻辑运算和浮点运算混在一起用比如直接用乘法代替异或乘出来的中间值带浮点小数后续sum或比较时精度就会漂。m序列生成和PN相关都必须严格停留在整数与模2语义里。解决凡是0/1运算统一用mod(a加b2)或xor(ab)不要用乘法。PN码内部保持0/1整数只有到了扩频入口才做映射成正负1的转换。转换时用round处理一次别让浮点误差流进判决。4.5 省掉捕获逻辑直接解调仿真全对实测全懵现象仿真链路直接把发送端PN复制到接收端使用不写任何捕获步骤误码率完美但同样代码换成带时延或频偏的信道模型后系统瞬间崩溃。原因仿真里的捷径掩盖了同步问题。发送端PN和接收端PN相位天然一致真实系统里信号经过信道后码片边界、载波相位全都变了不捕获直接解扩等于盲人摸象。解决就算是纯仿真也强制在信号路径里加一步捕获逻辑。至少保留滑动相关的代码框哪怕当前场景下偏移是零也要让信号走一遍这个流程。这个习惯能让你在从仿真往硬件或实时系统迁移时少掉一层皮。5. 解扩验证与FHSS对照相关峰之外还能学到什么5.1 自检模板自相关和互相关一起看把第2章的验证代码整理成一个固定脚本每次更换PN参数时先跑一遍再进入链路仿真。我的自检模板是同时打印自相关峰值和互相关峰值autoPeak max(xcorr(pn1, pn1)); crossPeak max(xcorr(pn1, pn2)); fprintf(自相关峰值: %.3f, 互相关峰值: %.3f\n, autoPeak, crossPeak);自相关和互相关的比值越大抗干扰能力越强。这个模板单独看很简单但能提前拦住一大半序列层面的问题比等到整个链路跑完再回头找原因省事得多。5.2 解扩三步走乘回去、积分、阈值判决解扩就是再做一次扩频的逆运算把接收信号乘以同一段PN码然后按SF长度积分最后和零阈值比较。注意判决前不要round保留浮点值做能量比较y received_dsss .* pn_aligned; % 乘以本地PN完成相关 for i 1:M energy(i) sum(y((i-1)*SF1 : i*SF)); % 按SF积分 end bits_rx energy 0; % 零阈值判决这里received_dsss必须是码片级的浮点序列pn_aligned必须是与发送端相位对齐的正负1序列顺序错了结果全乱。验证时把这个流程跑通DSSS的核心收发链路就算真正闭环了。5.3 gen_fhss.m对照实验DSSS和FHSS的差别一目了然资源里还带了gen_fhss.m是调频扩频FHSS的信号生成文件。DSSS和FHSS经常被拿来对比DSSS在宽频带上把能量摊开同一时刻所有用户共享整个宽信道FHSS是在很多窄频带之间快速跳变同一时刻只占用一个窄信道跳频图案由PN码决定。跑gen_fhss.m时重点看载波频率随时间切换的规律再回到gen_dsss.m的频谱上对比一次两种扩频思路的差别就非常直观了。对初学者来说这两个文件放在一起本身就是一份很好的扩频技术对照实验。说实话这套资源跑通不难跑通之后真正留在我脑子里的不是那几行Matlab代码而是“先验证序列、再进链路”的顺序。做扩频仿真以来我踩得最深的坑几乎都集中在PN码生成和相位同步环节而这些坑几乎都能在自相关和滑动相关的图形上提前发现。从那以后我每次做DSSS相关实验都强制自己走一遍“生成PN码、画自相关、做滑动捕获、再扩频调制”的流程宁可在这上面多花十分钟也不想把问题一路带到解扩端再回头翻车。这套流程建议你也保留下来。希望帮到你。本文还有配套的精品资源点击获取