简介:面向通信原理、扩频通信课程学习者与MATLAB仿真入门者的设计文档,围绕直接序列扩频系统的建模、解扩和性能评估展开,可用于课程设计、实验报告撰写与仿真复核。压缩包仅含1个doc文件,约320KB,正文以实验原理、直扩系统模型、伪随机码类型、扩频优势和仿真结果为主线,说明BPSK调制下信息码与PN码的模二加过程,并比较m序列、GOLD码、WALSH码和OVSF码的适用场景。文中给出信源速率2kbps、扩频因子64、高斯信道等参数及部分MATLAB程序片段,读者可据此观察误码率随信噪比变化的趋势,理解抗同频干扰、抗衰落和抗多径干扰的实现思路。已有143人学习下载,适合需要完整实验原理、参数设置和结果分析参考的读者。
1. 为什么值得花一周把一条直扩链路在 MATLAB 里跑通
做「基于 MATLAB 的直接序列扩频通信系统仿真设计」这个题目的人,多半一开始都觉得它简单:画一张发射机、信道、接收机的框图,信源和 PN 码模二加,加高斯白噪声,出一条误码率曲线,报告就能交。真坐下来写代码才发现处处不顺——扩频因子写 64,m 序列一个周期却是 63;载波频率设成 40 kHz,采样率只有 4 kHz;滤波器一通,判决点被群延迟拖走半个码片。曲线要么贴着 0.5 不动,要么掉下去又弹回来,怎么看都不像教科书上那条瀑布线。这篇按交付顺序把链路重搭一遍:定架构和参数、写扩频码与速率匹配、做 BPSK 调制解调与判决、摊开几个最容易翻车的点、最后给一套答辩时能自证的验收方法。
2. 总体架构设计:发射—信道—接收三段链路与参数标定
直扩系统的教科书框图只有六个方块,但落到代码里至少要拆成十二个可独立调试的步骤。拆得粗,出了问题只能整段重跑,连是哪一级坏的都不知道;拆得细,每一级都能单独出图对照,调试才有抓手。这一章先把链路切成三段、把参数钉死在一张表里,后面三章才有共同的坐标系。
2.1 发射端的三级结构:信源映射、扩频、载波搬移
发射端做三件事,顺序不能乱。第一件是把二进制信源映射成双极性不归零码,0 变 -1、1 变 +1;第二件是用伪随机码逐位相乘,把窄带信息摊到宽带上;第三件是乘载波,把基带谱搬到中频。
很多报告里写「信源与 PN 码模二加」,这是 0/1 域的说法,和 ±1 域的乘法完全等价。仿真里我更愿意用乘法,因为后面的载波调制也是乘法,整条链路可以统一成「信号 × 序列 × 载波」的连乘形式,省掉一层又一层判断,也避免了原文那种sign(code(mod(i,7))-0.5)的绕法——这种写法一旦码长和循环变量对不上,错在哪都看不出来。
% 信源:等概率二进制序列,映射成双极性不归零码 Nbit = 5000; bits = randi([0 1], 1, Nbit); sym = 2*bits - 1; % 0 -> -1, 1 -> +1 % 扩频:每个比特与整段伪随机码逐位相乘 pn = gen_mseq(6, [6 1], ones(1,6)); % 长度 63 的 m 序列,取值 ±1 spread = kron(sym, pn); % 长度 Nbit*63 的基带扩频序列 % 码片成型:每码片保持 Ns 个采样点,等价于矩形脉冲成形 Ns = 8; txS = reshape(repmat(spread, Ns, 1), 1, []);kron这一步是整个发射端的关键:它把「每个比特复制一份完整的 63 位码」这件事写成一行,得到的序列长度是Nbit*63。如果用两层 for 循环手写,很容易把内层索引写成1:7而不是1:63,跑出来的扩频比实际只有 7,处理增益凭空少了 9 dB。repmat加reshape的组合负责码片成型,Ns决定波形的采样密度,取 8 已经能看清码片边沿,取再大只是白烧内存。
2.2 接收端:相干解调、解扩与积分判决的次序
接收端的顺序是有讲究的:先乘本地载波下变频,低通滤掉二倍频分量,再乘本地伪随机码解扩,最后在一个比特周期内积分清洗并判决。这个顺序对应「中频采样 + 数字下变频」的实现路线,好处是低通滤波器只作用于基带信号,不会把扩频码的陡峭边沿磨圆——如果反过来先解扩再滤波,码片边沿被平滑之后,相关峰的高度会明显下降。
解扩的本质是一次相关运算:本地码与接收信号逐点相乘,同相位的码片乘积恒为 +1,反相位的码片乘积恒为 -1,一个比特周期内累加,得到的就是「+63 或 -63 加上噪声」这样的判决量。干扰和噪声因为与本地码不相关,相乘之后正负相消,累加结果趋近于零,这就是处理增益的来源。
Rc = 2000*63; % 码片速率 126 kcps fs = Rc*Ns; % 采样率 1.008 MHz fc = 2*Rc; % 载波 252 kHz,低于 fs/2 t = (0:numel(txS)-1)/fs; carrier = cos(2*pi*fc*t); txRF = txS .* carrier; % 发射:基带扩频序列搬移到中频 rxRF = awgn(txRF, 8, 'measured'); rxBase = rxRF .* carrier; % 相干解调 [b,a] = butter(4, 2*Rc/fs); % 截止频率约一个码片带宽 rxLPF = filtfilt(b, a, rxBase); % 零相位滤波,规避群延迟 rxDe = rxLPF .* pnS; % 与本地同步码逐点相乘butter(4, 2*Rc/fs)里的归一化截止频率是相对奈奎斯特频率的,写成2*Rc/fs而不是Rc/fs,这一点错了滤波器会把有用边带切掉一半,解扩后的眼图会变成一锅粥。filtfilt做正反两遍滤波,等效群延迟为零,代价是运算量翻倍——在几百万点的仿真里这点开销完全可以接受,换来的是判决点不用手工补偿延迟。
2.3 参数表:把比特率、扩频因子和采样率钉死在一张表里
链路的四个速率——比特率、码片率、采样率、载波频率——之间存在硬约束,任何一个改动都会牵动其他三个。我习惯在脚本最前面用一张表把它们全部算出来,后面所有模块只引用变量名,不再出现任何魔数。
| 参数 | 符号 | 取值 | 由谁决定 |
|---|---|---|---|
| 比特率 | Rb | 2000 bps | 题目给定 |
| 比特周期 | Tb | 0.5 ms | 1/Rb |
| 扩频因子 | SF | 63 | m 序列周期 2^6-1 |
| 码片速率 | Rc | 126 kcps | Rb×SF |
| 每码片采样 | Ns | 8 | 波形分辨率 |
| 采样率 | fs | 1.008 MHz | Rc×Ns |
| 载波频率 | fc | 252 kHz | 2×Rc,且远小于 fs/2 |
| 处理增益 | Gp | 18 dB | 10log10(SF) |
| 仿真比特数 | Nbit | 5000 | 决定 BER 分辨率 |
Rb = 2000; Tb = 1/Rb; SF = 63; Rc = Rb*SF; Tc = 1/Rc; Ns = 8; fs = Rc*Ns; fc = 2*Rc; Gp = 10*log10(SF); % 18.0 dB Nbit = 5000;这张表最重要的一列是「由谁决定」。比特率是题目给的,不能动;扩频因子受码长限制,也不能随便取;只有采样率Ns是自由变量,调它只影响波形精细度和内存占用,不影响任何物理结论。把自由变量和受约束变量分开,后面改参数时就不会出现「改了一个、崩了三个」的连锁反应。
3. 扩频码生成与扩频模块:m 序列、GOLD 序列与速率匹配
扩频码是这条链路里唯一一个「算错了也照样能跑出图」的模块,所以最容易被糊弄过去。码长、抽头、初相、自相关旁瓣,任意一项不对,BER 曲线不会报错,只会悄悄变差。这一章把 m 序列和 GOLD 序列的生成写清楚,再把「速率匹配」这件事讲透——很多报告里写的扩频因子和代码里实际生效的扩频比根本不是一回事。
3.1 m 序列的移位寄存器实现与抽头选择
m 序列由线性反馈移位寄存器产生,级数n决定周期2^n-1,抽头位置决定它是不是本原多项式。级数取 6,周期就是 63;级数取 7,周期 127。注意这个「减一」:全零状态是死锁态,必须排除,所以 m 序列的周期永远比 2 的整数次幂少一位。这一位之差,就是后面所有对不上的根源。
function code = gen_mseq(stage, taps, seed) % stage : 移位寄存器级数,输出序列周期为 2^stage-1 % taps : 反馈抽头位置,需对应一个本原多项式 % seed : 初相向量,长度等于 stage,且不能全为 0 reg = seed(:).'; L = 2^stage - 1; code = zeros(1, L); for n = 1:L code(n) = reg(end); % 末级输出即序列当前位 fb = mod(sum(reg(taps)), 2); % 抽头模二加得到反馈位 reg = [fb, reg(1:end-1)]; % 寄存器右移,反馈位进最高位 end code = 2*code - 1; % 0/1 映射为 -1/+1 end三个入参里最容易错的是taps。6 级寄存器常用的本原多项式是 x^6+x+1,对应抽头[6 1];如果随手写成[1 2],得到的序列周期会缩短到 3 或 7,自相关特性完全破坏。验证方法很简单:把生成的序列循环移位后与自身逐位比较,只有零位移时相关系数为 1,其余位移的旁瓣应该落在 -1/63 附近。跑一遍这个检查,比盯着 BER 曲线猜半天管用。
seed不能全零,这一点在代码里没有强制判断,实际工程里我一般会加一句assert(any(seed), 'seed 不能全零'),省得后面调半天发现序列是一条直线。
3.2 GOLD 序列与地址码:什么时候不该用 m 序列
m 序列的自相关特性很好,但互相关特性不行——同一级数下不同抽头产生的 m 序列,互相关峰值可能高到 0.5 以上,多用户场景下这种互相关就是实打实的多址干扰。GOLD 序列正是为此设计的:取一对互相关满足优选准则的 m 序列,模二加之后得到新序列,同一对优选对能生成2^n+1条互相关性有界的码,地址数比 m 序列多得多。
m1 = gen_mseq(6, [6 1], ones(1,6)); m2 = gen_mseq(6, [6 5 2 1], [1 0 1 0 1 1]); % 另一条同周期 m 序列 gold = m1 .* m2; % ±1 域相乘等价于模二加这两条序列必须来自同一级数、同一码时钟、且构成优选对,随便换一组抽头得到的 GOLD 码互相关峰值可能超过 0.7,反而比 m 序列更差。判断是否优选对的方法是把两条序列的互相关函数全算出来,取绝对值最大值,理论上应满足三值特性。单用户的高斯信道仿真里用 m 序列完全够用,只有做到多址或者抗多址干扰那一章,才需要换 GOLD 码——这一点在报告的结构安排上也要对应,别在单用户章节里大谈 GOLD 码的优势,答辩时容易被追问「你的仿真里多址在哪」。
WALSH 码是另一条路:长度可以是 2 的整数次幂,两两完全正交,互相关恒为零,代价是除了零位移之外自相关旁瓣很大,对同步误差极其敏感。如果题目明确要求扩频因子取 64,用 64 阶 WALSH 码是最省事的做法,处理增益按 10log10(64)=18.06 dB 报,数值上和 63 差别可以忽略。
3.3 扩频的本质是速率匹配,不是「加个扰码」
很多报告把扩频描述成「给信号加一层伪随机扰码」,这个说法在概念上就偏了。扩频真正做的是速率匹配:把一个Tb宽的比特,拆成SF个Tc宽的码片,时域上每个码片携带同样的信息,频域上带宽被展宽SF倍,功率谱密度被压低SF倍。这一步做完,信号的时间分辨率和带宽分辨率都变了,后面的采样率、滤波器带宽、积分长度全都要跟着变。
pnS = reshape(repmat(pn, Ns, 1), 1, []); % 一个码周期内的采样展开 pnS = repmat(pnS, 1, Nbit); % 铺满整个比特流 assert(numel(pnS) == numel(txS), '本地码长度与接收信号不匹配');assert这一行看着多余,却是我踩过坑之后加上的固定动作。只要本地码和接收信号差一个采样点,.*运算在 MATLAB 里会广播成矩阵乘法,不报错,结果全错。加一行断言,把这种沉默错误变成显式崩溃,调试时间能省掉一大半。
速率匹配还有一层含义:如果伪随机码的周期不能被扩频因子整除,每个比特对应的码段就会发生相位滑动,解扩时相关峰高度会在 63 和 62 之间来回跳,BER 曲线出现规律的锯齿。解决办法是让码周期等于扩频因子,或者让扩频因子是码周期的整数倍,最忌讳的是「码长 7、扩频因子 64」这种既不相等也不成倍数关系的组合。
4. BPSK 调制、信道与解扩判决:一条能出波形也能出星座的链路
前三章把发射端和码序列搭完了,这一章补上调制、信道和解调判决,链路才算闭环。这一段最容易出的问题不是算法错,而是「量纲错」——载波频率和采样率不在一个数量级、滤波器带宽和码片速率对不上、加噪时的功率基准被滤波器改过。逐个对齐之后,剩下的就是纯粹的统计工作。
4.1 双极性码与 BPSK 映射:相位跳变要看得出 180 度
BPSK 的映射规则只有一条:比特 1 对应载波相位 0,比特 0 对应相位 π。在 ±1 域里这件事就是一次乘法,sym .* cos(2*pi*fc*t),符号翻转直接体现为波形在比特边界上的 180 度相位跳变。看波形图时重点看两处:比特边界上有没有明显的反相,码片边界上有没有连续的相位过渡——如果整段波形光滑得像调幅信号,说明载波频率相对码片速率太低,一个码片里塞不下一个完整周期,相位信息被平均掉了。
carrier = cos(2*pi*fc*t); txRF = txS .* carrier; figure; plot(t(1:2000), txRF(1:2000)); grid on; xlabel('时间 / s'); ylabel('幅度 / V'); title('BPSK 调制信号(局部)');画局部波形时横轴范围要按「几个比特周期」来取,不要按「几个采样点」拍脑袋。Tb=0.5 ms、fs≈1 MHz,一个比特就是 504 个采样点,取 2000 点大约能看到 4 个比特,相位跳变一目了然。取 200 点只能看到噪声一样的抖动,取 20000 点又挤成一团黑,这两种图在答辩时都拿不出手。
4.2 载波频率与采样率的硬约束
fc和fs的关系是这条链路上最硬的一条约束:fc必须小于fs/2,最好再留一倍余量,让fc < fs/4。一旦越过奈奎斯特线,余弦波在采样点上会退化成低频正弦,频谱搬移的位置完全错位,解调端乘同频载波之后得到的不是基带信号,而是一个频率为fs-fc的混叠分量,低通滤波器根本拦不住。
这条约束在小数采样率下特别容易破。比如按归一化时间单位写代码,fs取 4000、fc取 4e4,数值上看着都是「几千、几万」的量级,实际上是载波频率比采样率还高十倍。波形图照样能画出来,因为 plot 只管把点连起来,但那条曲线已经不是原来的信号了。我的做法是:先把Rc算出来,再把fc写成k*Rc(k 取 2 到 4),最后让fs = Ns*Rc,三个量绑死在一起,永远不会出现量级错配。
Rc = Rb*SF; % 126 kcps Ns = 8; fs = Rc*Ns; % 1.008 MHz fc = 2*Rc; % 252 kHz assert(fc < fs/4, '载波频率过高,检查采样率或扩频因子');fc = 2*Rc意味着一个码片里正好两个载波周期,解调后每个码片能积出两个完整的半波,低通滤波的过渡带也够宽。如果fc取到Rc/2,一个码片里不到半个周期,码片边沿的相位不连续会带来严重的频谱泄漏,眼图闭合。
4.3 解扩:本地码同步、低通滤波与积分清洗
解扩这一步由三个动作串起来:本地码与接收信号逐点相乘、低通滤掉高频分量、在一个比特周期内积分。三个动作里最容易忽略的是积分区间——必须严格对齐比特边界,差一个码片,积分结果就会混入相邻比特的能量。
rxDe = rxLPF .* pnS; rxMat = reshape(rxDe, SF*Ns, Nbit); % 每列是一个完整比特周期 metric = sum(rxMat, 1); % 积分清洗 bitsHat = metric > 0; % 零门限判决 ber = mean(bitsHat ~= bits);reshape的维度顺序是全篇最容易翻车的一行。rxDe是按时间顺序排列的一维向量,前SF*Ns个点属于第 1 个比特,接着SF*Ns个点属于第 2 个比特,所以 reshape 成SF*Ns × Nbit之后,每一列才是一个完整比特。如果写成Nbit × SF*Ns,每一行变成一个比特,sum(...,1)求的就不是比特内积分而是比特间求和,判决结果会呈现一种「看起来有信号、但 BER 始终在 0.3 左右」的诡异状态。
判决门限取 0 是 BPSK 在等概信源下的最优门限,前提是直流分量已经被滤掉。如果低通滤波器是巴特沃斯型且阶数较低,通带内可能有轻微不平坦,直流附近会残留一点点偏置,这时候门限取 0 会让 BER 曲线在低 SNR 段整体抬高。稳妥的做法是先对metric做一次去均值,再判决,代价几乎为零。
5. 常见问题排查:误码率不下降的五个翻车点
仿真跑通和仿真做对是两件事。下面五条是这条链路上出现频率最高的问题,每一条都能让 BER 曲线保持「能出图但没意义」的状态,而且在 MATLAB 里全部不报错。
5.1 现象:BER 贴着 0.5,SNR 加到 15 dB 也不动
原因:本地码与发射码不是同一段,或者比特与码块的对齐错位。常见情形是把pnS按码周期铺开时从第 1 个比特开始,而txS因为kron的展开方式实际是从第 0 个码片开始,两者差了一个码片,相关峰直接塌掉。
解决:先做单比特冒烟测试——把Nbit改成 1,不加噪声,打印rxDe前 63 个点,确认它与pn.^2逐点相等(全为 +1)。这一步通过之后再恢复比特数,能排除掉九成的对齐错误。
5.2 现象:报告写 64 倍扩频,答辩被问「你的 m 序列一个周期几位」答不上来
原因:m 序列周期是2^n-1,63、127、255 都可能,唯独不可能是 64。扩频因子按 64 写、码长按 7 生成、代码里再靠repmat补到 64,三层数字互相打架,实际生效的扩频比可能是 4 也可能是 7,处理增益算出来完全对不上。
解决:把扩频因子改成 63,处理增益如实写 10log10(63)=18.0 dB;如果任务书硬性要求 64,就换 64 阶 WALSH 码,并在报告里说明「采用正交码而非 m 序列,处理增益 18.06 dB」。数字差 0.06 dB 没人计较,前后不自洽才是硬伤。
5.3 现象:波形看着像调幅,频谱图上搬移位置不对
原因:fc越过fs/2,采样后发生混叠。归一化时间单位下这个问题尤其隐蔽,因为fs和fc都是无量纲数字,量级差十倍也不影响代码运行。
解决:在参数初始化段落加一条断言,fc < fs/4,让它在脚本一开始就崩,而不是等到画频谱图才发现。同时把所有频率量统一写成码片速率的整数倍,物理意义清楚,量级也不会错。
5.4 现象:SNR 提高,BER 反而在某一段变差
原因:awgn的'measured'模式按输入信号的实际功率加噪,而信号经过滤波器之后功率已经被衰减过,如果在滤波之后调用awgn,噪声基准就跟着变了。另一种情形是不同 SNR 点用了不同的信号功率基准,曲线自然不单调。
解决:只在发射端加一次噪声,接收端全程不再加噪;或者在每个 SNR 点先计算bandpower(txRF),用'measured'模式时保证输入始终是同一段未滤波的信号。
5.5 现象:BER 曲线抖动大,10^-3 以下全是 0 或者 1
原因:统计样本太少。5000 个比特在 BER=10^-3 时平均只有 5 个错误,统计涨落能到 40% 以上,画出来就是一条锯齿。
解决:低 BER 段每个 SNR 点至少跑 10^5 个比特,或者只统计到 10^-3 就停,报告里注明「10^-3 以下样本不足,未作统计」。与其画一条假的光滑曲线,不如老老实实标出置信区间,答辩时反而是加分项。
6. 答辩验收:用三条自证曲线把「跑通了」变成「做对了」
验收这件事,重点不在代码有多长,而在能不能用三张图把「这条链路是对的」讲清楚。我一般要求学生准备下面三条曲线,缺一条就说明链路里还有没对齐的地方。
| 曲线 | 横轴 | 纵轴 | 通过标准 |
|---|---|---|---|
| 波形对齐图 | 时间(3~5 个比特) | 幅度 | 信源与解调输出的跳变沿逐个对齐,无整体平移 |
| BER 对比图 | SNR / dB | 误码率(对数) | 仿真点与0.5*erfc(sqrt(ebn0))理论线偏差 < 1 dB |
| 处理增益图 | 带内 SNR / dB | 误码率 | 扩频与不扩频两条线间距接近 10log10(SF) |
第一条曲线最简单也最能唬人。把信源bits和解调输出bitsHat画在同一张图上,用不同线型叠起来,如果两条线的跳变沿严丝合缝,说明从映射到判决的整条路径没有引入延迟;如果整体错开一个比特,那就是reshape维度反了或者积分窗口偏了一位。这一张图放在报告第一页,比十页公式更有说服力。
第二条曲线要对上理论。BPSK 在 AWGN 下的误码率是0.5*erfc(sqrt(Eb/N0)),把仿真点画上去,偏差在半 dB 以内就算合格。偏差超过 1 dB 通常是滤波器带宽太窄把有用边带切了,或者awgn的功率基准不对。
第三条曲线是这套仿真的核心结论所在。在同一带内 SNR 下,扩频系统的 BER 明显低于不扩频的 BPSK,两条线的间距应该接近处理增益 18 dB——这是因为解扩把噪声带宽压缩回了信息带宽。但如果把横轴从「带内 SNR」换算成「Eb/N0」,两条线应当完全重合,因为扩频本身不改变每比特能量与噪声谱密度之比,它改变的是抗窄带干扰和抗多径的能力。这一点讲不清楚,答辩时被追问「既然一样,扩频有什么用」,整套仿真就白做了。
ebn0 = snrList - 10*log10(fs/(2*Rc)); % 带内 SNR 换算到 Eb/N0 berTh = 0.5*erfc(sqrt(10.^(ebn0/10))); semilogy(snrList, ber, 'o-', snrList, berTh, 'k--'); legend('仿真','理论'); grid on;从那以后我每次交仿真报告前,都会先把这三条曲线跑一遍再动笔——波形对不上就不写结论,理论线贴不上就不写增益,两条线换算之后不重合就不写「扩频提升抗干扰能力」。这套动作花不了二十分钟,却能挡掉答辩现场绝大多数追问。希望帮到你。
本文还有配套的精品资源,点击获取