news 2026/10/28 5:50:26

DSSS扩频通信MATLAB仿真:BPSK链路、处理增益与BER避坑

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
DSSS扩频通信MATLAB仿真:BPSK链路、处理增益与BER避坑

简介:面向通信原理课程学习与扩频通信入门实践者的一份 MATLAB 仿真设计文档,围绕直接序列扩频(DSSS)系统展开,可用于课程设计、实验报告撰写与建模入门。文档先讲直扩系统模型,梳理信源、伪随机码产生器、BPSK 调制与相关解扩的完整链路,再对比 m 序列、GOLD 码、WALSH 码与 OVSF 码的自相关、互相关特性及工程使用场景,并归纳抗同频干扰、抗衰落、抗多径三项优势。仿真部分给出信源速率 2kbps、扩频因子 64、高斯信道下的参数设置与关键程序片段,配合误码率随信噪比变化的趋势讨论,便于读者复现并调整参数验证处理增益。整包只有 1 个 doc 文件,约 320KB,体量轻便,属于思路讲解加代码片段的实验型资料。已有 142 人学习,适合需要快速建立直扩系统框架、对照代码排查仿真问题的读者参考。

1. 一段「跑起来全是平线」的直扩脚本:这套仿真到底在验证什么

打开这份文档,第一眼看到的不是论文式推导,而是一段能直接粘进 MATLAB 命令窗的脚本:信源 → 扩频 → 载波调制 → 加噪 → 相干解调 → 解扩 → 积分判决 → 数误码,八个环节一个不落,每个环节还配一张 figure。它做的事很朴素——把直接序列扩频(DSSS)这条链路每一步都用波形摊开给你看,让你肉眼确认「1」被一串伪随机码替换掉之后长什么样,再在高斯白噪声里把它捞回来。适合两类人:一类是通信原理课要交仿真报告的在校同学,另一类是想快速搭个扩频链路原型、又不想一上来就动射频硬件的工程师。整套东西离线跑,不依赖互联网,一台装了 MATLAB 的笔记本就够。缺点也摆在明面上:原脚本参数前后打架,直接回车大概率是报错,或者给你一条毫无信息的平线,得先把它理顺。

2. 链路骨架与技术选型:64 位扩频码配 BPSK 是怎么搭起来的

2.1 从信源到判决:直扩系统的六段式结构

直扩的核心动作只有一个:把窄带的信息码元,乘上一路码速率远高于它的伪随机码。信息码元宽度记作 Ta,伪随机码码片宽度记作 Tc,要求 Tc << Ta,两者一乘,时域上信息被「切碎」,频域上能量被摊到一个宽得多的频带上。接收端做的事正好相反:用一路与发端严格同相位、同码型的 PN 码再乘一次,因为 c(t)·c(t)=1,信息就被还原回来。

把这个过程落到具体的信号上,整条链路的每一环都能写成一个乘法或一个积分:

环节数学动作原脚本里的变量
信源a(t) ∈ {+1,−1},码元宽 Tax/d
扩频a(t)·c(t),码片宽 Tc = Ta/SFb = BPSK.*k_code
载波调制乘 cos(2πf_c t)s
信道叠加高斯白噪声 n(t)r = awgn(s,...)
相干解调再乘 cos(2πf_c t)rs
解扩再乘同相位 c(t)jk_code
低通 + 判决一个比特周期内积分,取符号zl→xo

处理增益是这条链路最该记住的一个数:Gp = 10·lg(Ta/Tc) = 10·lg(SF)。扩频因子取 64,理论上就能把信噪比抬 18 dB 左右;取 7,只有 8.45 dB。文档里写「处理增益达到 10 dB 以上」,代码里却是 3 级移位寄存器产生的 7 位码,这个矛盾后面第 5 章会专门算一笔。

值得说清楚的是,BPSK 条件下「模二加」和「相乘」是一回事。信源取 ±1、PN 码取 ±1,模二加的异或表映射到 ±1 上,结果与直接相乘完全一致。所以脚本里从头到尾没有出现xor,全是.*,这不是偷懒,是等价变换。

2.2 技术选型:为什么是 BPSK + 相干解扩 + AWGN

调制方式为什么挑 BPSK 而不是 QPSK、DPSK、MPSK?三个理由,都很实际。

第一,判决门限最干净。BPSK 是二维信号里一维的退化物,接收端积分出来的判决量理论上就是 ±√Eb,门限卡在 0,一个sign()就完事,不需要估计幅度、不需要锁相环算相位模糊。第二,理论曲线有闭式解,BER = Q(√(2Eb/N0)),你跑出来的仿真点可以直接往这条曲线上怼,对不对一眼就知道——这在调试阶段太重要了,没有参照物的仿真等于黑匣子。第三,实现代价最低,一个乘法器加一个积分器。QPSK 在同样 Eb/N0 下虽然频带效率翻倍,但要多一路正交支路,仿真代码量上去,调试成本也跟着上去,对一份以「看懂原理」为目标的仿真来说不划算。

解扩方式必须是相干解扩,这是直扩和跳频最本质的区别。跳频可以非相干,直扩不行——PN 码的相位必须严格对齐,差一个码片,解扩后的相关峰就掉下去,差到半个码片以上基本等于没解扩。原脚本里jk_code = rs.*k_code这一行,乘的就是发射端那一路完全相同的k_code,中间没有任何同步、捕获、跟踪的环节,属于「理想同步假设」。做原理验证够用,做工程原型这就是第一个要补的模块。

信道选 AWGN 也是同样的逻辑:先在最干净的条件下把链路跑通,再加多径、加窄带干扰。awgn(s, SNR(k), 'measured')这个调用里,'measured'是个关键参数,它让函数先测一遍输入信号的实际功率,再按这个功率反推噪声功率。不加'measured'的话,函数默认信号功率是 0 dBW,你的信号只要不是这个量级,加出来的信噪比就是错的,而且错得很隐蔽——波形看起来还挺正常。

2.3 伪随机码选择:m 序列、GOLD、WALSH 在仿真里的分工

脚本里那段switch seq其实把四类常用码都摆出来了,只是默认走的是case 1。

m 序列是最基础的一类,由线性反馈移位寄存器产生,n 级寄存器能给出周期 2^n − 1 的最长序列,所以叫「最长线性反馈移位寄存器序列」。它的自相关函数是双值的,主瓣尖锐、旁瓣平坦,这一条性质直接决定了直扩系统的抗多径能力——不同时延到达的信号,只要错开一个码片以上,相关输出就掉到旁瓣,很容易被判决电路区分开。缺点是互相关特性不够理想,任意两条 m 序列之间的互相关峰值不可控,多址场景下互相干扰大。

GOLD 码就是冲这个来的。取两条码长、码速率都相同的 m 序列优选对,模二加,每滑动一次相对位移就得到一条新码,一共能凑出 2^n + 1 条,互相关是三值的、有明确上界。地址数比 m 序列多得多,工程上用得最广。脚本里ptap1=[1 3]、ptap2=[2 3]就是两路寄存器的不同抽头,一个产生 m1,一个产生 m2,再goldseq(m1,m2,user)移位相加。

WALSH 码是正交函数系,两两互相关严格为 0,代价是自相关很差、序列也不平衡(0 和 1 的个数不相等),所以它一般不单独当扩频码用,而是当信道化码——同一路扩频之后,再用不同阶的 WALSH 码区分不同用户或不同信道。OVSF 码是 WALSH 的可变扩频因子版本,核心约束是「不同扩频因子的码之间也要正交」,父子码不能同时分配。

对这份仿真来说,用哪一路都行,因为只有一个用户、一条链路,正交性根本用不上。真正影响结果的是码长——码长即扩频因子,直接决定处理增益。

3. 核心代码逐段拆解:一条比特从产生到判决复现的完整链路

原脚本有不少历史包袱:变量名前后不一致、时间轴单位混乱、载波频率超过奈奎斯特。我按它原有的结构重排了一版能直接跑的,逻辑一环没改,参数理顺了。

3.1 参数区与信源:M、L、dt 三个数字决定一切

先看参数。原脚本写的是Rb=0.25; Tb=1/Rb;,也就是 Tb = 4 秒,可文档里明明白白写着「信源速率 2 kbps」。这类前后矛盾在学生脚本里非常常见——参数是抄来的,注释是后来补的,两边的单位体系根本没对齐。

我的建议是:所有参数从采样率往上推,别单独拍脑袋定。

close all; clear; clc; Rb = 1e3; % 信源速率 1 kbps SF = 64; % 扩频因子:每比特 64 个码片 sps = 8; % 每码片采样点数 fs = Rb * SF * sps; % 采样率 = 512 kHz fc = 100e3; % 载波 100 kHz,必须 < fs/2 = 256 kHz M = 1000; % 仿真比特数,统计误码至少要这个量级 sps_bit = SF * sps; % 每比特采样点数 = 512 t = (0 : M*sps_bit-1) / fs; % 统一时间轴,单位秒 SNR_dB = 1 : 1 : 15; % 信噪比扫描点

几个参数的关系是绑死的:码片速率 = Rb × SF,采样率 = 码片速率 × sps。改任何一个,fs 都得跟着重算,否则后面cos(2*pi*fc*t)里的 fc 和 t 就不在一个时间基准上。原脚本dt=Tb/L算出来是 0.01,等效采样率只有 100 Hz,而载波写的是fc=4e4——40000 Hz 对 100 Hz 的采样率,相位每步跳 400 圈,早混叠成一团噪声了,画出来的「扩频调制信号」其实是一堆随机起伏。

信源部分,sign(randn(1,M))比sign(rand(1,M)-0.5)更稳妥:

data = sign(randn(1,M)); % ±1 等概 data(data == 0) = 1; % randn 恰好取 0 时兜底 base = reshape(repmat(data, sps_bit, 1), 1, []); % 矩形脉冲成型

repmat把每个比特沿列方向复制sps_bit份,reshape再摊平成一行,得到一个长度 M × 512 的基带波形。这一步就是矩形脉冲成型,等价于每个码元持续期间输出恒定电平。用rand(1,M)-0.5的问题在于rand返回 [0,1),减 0.5 之后取 sign,端点值偶尔会落到 0 上,虽然概率极低,但一旦出现就会在波形里留一个零值点,积分判决那里容易出玄学问题。

3.2 扩频与载波调制:模二加在 BPSK 里就是乘法

PN 码这一段是原脚本里最需要小心的部分。原代码用mod(i,7)做循环索引,取的是code(7)和code(mod(i,7)),两个分支之间还差一个偏移,写得很绕。等价但清楚得多的写法是按码片维度生成,再上采样:

pn7 = mseq(3, [1 3], [1 1 1]); % 长度 7 的 m 序列,取值 0/1 pn7 = 2*pn7 - 1; % 映射到 ±1,方便直接相乘 chip_idx = 0 : M*SF-1; % 一共 M*SF 个码片 pn_chip = pn7(mod(chip_idx, 7) + 1); % 周期延拓到整帧 pn = reshape(repmat(pn_chip, sps, 1), 1, []); % 每码片重复 sps 个采样点 tx = base .* pn; % 扩频:BPSK 下模二加 == 相乘 s = tx .* cos(2*pi*fc*t); % 搬移到载波

mod(chip_idx,7) + 1是 MATLAB 里做周期索引的标准套路:mod的结果是 0~6,加 1 变成 1~7 才能当下标。这里扩频因子实际是 7(m 序列周期),不是文档写的 64——想凑够 64,得把寄存器级数提到 6 拿到 63 位码,或者直接用 64 阶 WALSH。这个差异在第 5 章会单独算。

tx = base .* pn这一行就是整个直扩的灵魂。逐点相乘之后,原本宽 512 个采样点的比特,被切成 64 段各自翻转极性,频谱自然被摊宽 64 倍。乘载波那一步只是把这个基带信号搬到 fc 附近,不改变带宽。

3.3 解扩与积分判决:判决门限为什么要用一整个比特周期求和

接收端这一段,原脚本走了频域:T2F变换 →lpf低通 → 再变换 → 再低通,一路滤波下来。能跑通,但对新手极不友好,因为中间任意一个滤波器参数错了,波形看着还像那么回事,结果就是不对,排查起来很费劲。

相干解调 + 矩形脉冲这个组合,其实用不着频域滤波,时域积分就够了:

for k = 1 : length(SNR_dB) r = awgn(s, SNR_dB(k), 'measured'); % 加噪,按实测信号功率定噪 rd = r .* cos(2*pi*fc*t); % 相干解调:乘回同频同相载波 rd = rd .* pn; % 解扩:乘回完全同相位的 PN 码 metric = sum(reshape(rd, sps_bit, M), 1); % 每比特周期内求和 == 积分 rx = sign(metric); % 零门限判决 ber(k) = mean(rx ~= data); end

为什么sum就能代替低通?把rd展开看:r里是±cos(2πfct) + n(t),乘一次 cos 得到±cos²(2πfct) = ±(1+cos(4πfct))/2,再乘 PN 码不改变频率成分。一个比特周期 Tb = 1 ms 里,cos(4πfct) 在 fc = 100 kHz 下正好走完 200 个整周期,求和结果趋近于 0,只剩下直流分量±0.5 × 512 ≈ ±256。这就是「积分型低通」——积分区间取信号周期的整数倍,高频项自然清零。噪声项因为与 PN 码不相关,乘完之后被摊宽,落进积分带宽里的只是一小部分,这一进一出就是处理增益的来源。

reshape(rd, sps_bit, M)是按列切分的,第 i 列正好是第 i 个比特的 512 个采样点,sum(...,1)沿列求和得到 1×M 的判决量向量。这个写法比原脚本里嵌套两层for手动累加快一个数量级,M 提到 10 万的时候差距非常明显。

提示:解扩用的pn必须和发射端那一路逐点对齐,包括起始相位。原脚本里这个条件靠「同一个变量用两次」天然满足,一旦你把收发拆成两个函数,第一个翻车点就在这里。

3.4 误码率统计:为什么样本量比曲线好不好看更重要

ber(k) = mean(rx ~= data)这一行做了两件事:逐比特比较,再取均值。看起来简单,但统计可靠性完全取决于 M。

M = 100 的时候,一个误码就是 1% 的 BER,曲线是一格一格跳的台阶;M = 1000 也只到 0.1% 的分辨率。BER 要下到 1e-3 以下,M 至少得 1e5 量级,否则你在图上看到的「曲线下降」有一半是随机波动。原脚本M=100、z1(a)=z/100,15 个 SNR 点每个点只有 100 个样本,画出来的折线基本没法看。

还有一个容易忽略的点:awgn在循环里每次重新生成噪声,但data只生成了一次。这本身是对的——固定发送序列、只换噪声,统计的才是纯信道影响。如果连data也在循环里重新生成,误码率的方差会更大,曲线更抖。

想直接看误码率随 SNR 的变化,一句semilogy(SNR_dB, ber, 'o-'); grid on;就够了,别学原脚本axis([0,15,10^(-4),1])那样硬设坐标范围——BER 一旦掉到 1e-4 以下,点就被裁掉了,看着像曲线突然断了。

4. 三个自造轮子:mseq、T2F、lpf 的实现与替代方案

原文档贴了主脚本,却没贴这几个被反复调用的函数。缺了它们,代码就是跑不起来的半成品。这一章把三个函数补齐,顺便说清楚什么时候该自己写、什么时候该用工具箱。

4.1 m 序列发生器:移位寄存器加反馈抽头

mseq(stage, ptap, regi)的三个入参分别是寄存器级数、反馈抽头位置、初始状态。核心就是「输出末级、抽头模二加、整体右移、反馈位补到最前」这一个循环:

function code = mseq(stage, tap, reg) % stage : 寄存器级数 % tap : 反馈抽头位置,如 [1 3] 表示第 1 级和第 3 级异或 % reg : 初始状态,长度必须等于 stage,不能全 0 N = 2^stage - 1; % m 序列周期 code = zeros(1, N); for n = 1:N code(n) = reg(end); % 输出末级 fb = mod(sum(reg(tap)), 2); % 抽头模二加得到反馈位 reg = [fb, reg(1:end-1)]; % 右移,反馈位塞最前 end end

初始状态不能全零,全零的话反馈永远是零,寄存器卡死,输出一条直流。抽头怎么选不是随便来的,得对应本原多项式,选错了周期会缩短。3 级取 [1 3] 得到周期 7,序列是1 1 1 0 1 0 0,跑满 7 步寄存器回到初态,可以自己验一遍。

工程上常备的几个配置:

级数 stage抽头 tap周期 2^n−1
3[1 3]7
4[1 4]15
5[2 5]31
6[1 6]63
7[3 7]127
8[2 3 4 8]255
9[4 9]511

想要「扩频因子 64」这个整数,m 序列给不了——它只能是 2^n−1。要么用 63 近似,要么改用 64 阶 WALSH 码,要么就在 63 位码后面补一位凑数(原脚本case 3里[goldseq(...), zeros(user,1)]干的就是补零这件事,但补零会破坏序列的平衡性,处理增益要打折)。

4.2 T2F 与 lpf:频域滤波这条路怎么走通

T2F是时域转频域,lpf是频域理想低通。两个函数必须约定同一套频率轴,否则截止频率就是玄学常数。

function [f, X] = T2F(t, x) % 返回双边频谱,f 单位 Hz N = length(x); dt = t(2) - t(1); X = fftshift(fft(x)) / N; % 归一化,保证幅度与时域可比 f = (-N/2 : N/2-1) / (N * dt); % 频率轴,步进 1/(N*dt) end function [t, y] = lpf(f, X, fc) % 理想低通:只保留 |f| <= fc 的分量 N = length(X); Y = X .* (abs(f) <= fc); y = real(ifft(ifftshift(Y))) * N; % 反变换并还原幅度 dt = 1 / (N * (f(2) - f(1))); % 由频率分辨率反推采样间隔 t = (0:N-1) * dt; end

fftshift把零频搬到中间,是为了让频率轴从 −fs/2 到 +fs/2 单调递增,这样abs(f) <= fc才是关于零频对称的一段。少了这一步,滤的是一段首尾相接的乱频带。

原脚本调用写的是lpf(f, rf, fc/10000),那个 10000 是怎么来的?无非是把 Hz 换算成「万赫兹」好让数值落在 0~5 的小区间里。这种写法极其脆弱:fs 一改、fc 一改,这个 10000 立刻失效,而且不会报错,只会默默滤错。血泪经验——滤波器参数永远跟着采样率走,不要手写魔数。

注意:ifft的结果默认是复数,实信号滤完取real是必要的;乘回 N 是因为前面fft除了 N 做归一化,这里得乘回去,否则幅度会小 N 倍,判决门限全部偏移。

4.3 更省事的替代:工具箱与直接时域积分

如果只是要验证原理,第 3 章那种「乘载波 → 乘 PN → 比特周期求和」的写法比频域滤波短得多,也快得多,还不容易出错。频域那条路的价值在画图——你想看扩频前后的频谱对比、想看干扰被摊宽的过程,T2F就派上用场了。

版本较新的 MATLAB 里,PN 码可以直接用comm.PNSequence生成,不用自己写移位寄存器:

pnGen = comm.PNSequence('Polynomial', 'x^6+x+1', ... 'InitialConditions', ones(1,6), ... 'SamplesPerFrame', M*SF); pn = 2 * pnGen() - 1; % 输出 0/1,映射到 ±1

'x^6+x+1'对应抽头 [1 6],SamplesPerFrame一次给够整帧长度,省掉了循环延拓。滤波同样有现成的LowpassFilter或dsp.FIRFilter,但它们用的是真实滤波器(有过渡带、有群时延),跟理想低通的行为不完全一样,做 BER 对比时要留意这层差异。自己写的lpf是砖墙响应,理论上更「干净」,代价是时域会有振铃。

5. 避坑与排查:仿真结果不对时先看这 5 个地方

仿真类代码有个共同特点——错了不一定报错,很可能安安静静给你一张图,只是那张图没意义。下面五条是我在不同场合反复见到的翻车现场,按「现象 → 原因 → 解决」记下来,下次遇到能省几个小时。

5.1 波形层:图能出来但形状不对

第一条:figure(2)一跑就报「无法识别函数或变量 QPSK」。现象很直接,命令行红字一片,前面的图全白画。原因在变量命名:上一行明明写的是BPSK=(I.*cos(...)-Q.*sin(...))/sqrt(2);,plot的时候顺手敲成了plot(tq,QPSK)。这种错误在变量名和函数名、调制方式名混着用的时候特别高发。解决办法是统一收敛到语义无关的名字,比如基带信号一律叫tx、接收一律叫rd,调制方式只写在注释里,能规避掉一大半手误。

第二条:扩频调制信号的波形是一团密集的糊状带,看不出周期性。现象是plot(td,s)出来的曲线密密麻麻、完全随机,调整axis也没用。根因是载波频率超过了奈奎斯特频率。原脚本dt=Tb/L算下来等效采样间隔 0.01,采样率 100 Hz,而fc=4e4,每个采样点之间相位跳 400 圈,cos的值已经完全混叠,画出来就是噪声。解决顺序是先定 fs,再取 fc 落在 fs/8 到 fs/4 之间,最后用axis只放大一两个比特周期看细节——不放大根本看不出码片翻转的痕迹。

第三条:解扩之后的波形里还有明显的高频毛刺,判决量忽正忽负。这是没用低通、或者低通截止频率设错的典型表现。解调后r.*cos(2πfct)里有cos(4πfct)这个二倍频分量,幅度和基带分量一样大,不滤掉的话判决量会被它带偏。如果用积分代替滤波,必须保证积分区间是载波周期的整数倍——这一点在 fc 和 Rb 不成整数比的时候会失效,所以参数尽量取整倍数关系,别搞出 100 kHz 载波配 1.3 kbps 这种组合。

5.2 统计层:误码率曲线不下降或恒为零

第四条:BER 恒等于 0,或者恒等于 0.5。恒 0 一般两种情况:一是没加噪(awgn的 SNR 参数被传成了很大的数,或者压根没调用),二是接收端直接拿data去比较,等于自己跟自己比。恒 0.5 更有意思,说明判决量完全是随机的——最常见的原因是解扩时 PN 码相位没对齐,收发用了两段不同起始位置的码,相关峰对不上,输出就是纯噪声;其次是判决前没做积分或低通,二倍频分量主导,sign出来一半对一半错。排查手法是先固定 SNR = 0 dB 单点跑,把metric的直方图画出来,双峰分离清晰说明链路通了,混成一片就是上面两个问题之一。

第五条:BER 曲线画出来是折线,或者被裁掉一半。axis([0,15,10^(-4),1])这行是元凶之一,BER 掉到 1e-4 以下的点直接被裁出画面,看着像曲线「断了」。另一个原因是样本量——M=100时每个 SNR 点只有 100 个比特,一个误码就是 1%,曲线自然一格一格跳。解决方法是把 M 提到 1e5 量级,绘图换成semilogy让纵轴自动适应,同时别硬设axis的纵轴下界。顺带一提,跑 1e5 比特 × 15 个 SNR 点,for循环里那两层手写累加会慢到让人怀疑人生,换成reshape + sum能快十几倍,这就是后悔药。

第六条(附带):文档说扩频因子 64,实际处理增益只有 8.45 dB。现象是仿真出来的 BER 曲线比理论值差一大截,怎么调都对不上。算一下就清楚了:代码里stage=3,m 序列周期 2³−1 = 7,扩频因子实际是 7,处理增益 10·lg(7) ≈ 8.45 dB;而文档写的是 64,对应 18 dB。中间差了将近 10 dB,曲线当然对不上。要凑够 64,把stage提到 6 拿到 63 位码(Gp ≈ 18 dB),或者换 64 阶 WALSH。这类「文档写一套、代码跑一套」的坑,在这类仿真资料里出现的频率高得离谱,拿到手第一件事就是拿length(pn)打印一下实际码长。

6. 二次开发:把它从一次性作业脚本改成可复用的 BER 实验台

原脚本最大的问题是「一次性」——参数写死在开头,跑完出几张图,换一组参数就得改代码。想拿它做点正经事,比如比较不同扩频因子对误码率的影响、验证理论曲线,第一步是把它函数化。

改造的核心是把「信噪比」这个自变量从awgn的 SNR 换成 Eb/N0。这两者差一个带宽比:SNR = (Eb/N0)·(Rb/fs),而 fs/Rb = SF × sps,所以

snr_dB = EbN0_dB - 10*log10(SF * sps); % Eb/N0 换成采样带内的 SNR

sps是每码片采样点数,它不该影响误码率性能——如果你改了 sps 而 BER 曲线跟着动,说明换算写错了。这是个很好用的自检手段。

包成函数之后,主体不到二十行:

function ber = dsss_sim(SF, sps, EbN0_dB, M) % SF : 扩频因子 % sps : 每码片采样点数 % EbN0_dB : 每比特信噪比,dB % M : 仿真比特数 Rb = 1e3; fs = Rb*SF*sps; fc = fs/8; t = (0 : M*SF*sps-1) / fs; data = sign(randn(1,M)); data(data==0) = 1; base = reshape(repmat(data, SF*sps, 1), 1, []); pn = 2*mseq(log2(SF+1), [1 log2(SF+1)], ones(1,log2(SF+1))) - 1; pn = reshape(repmat(pn(mod(0:M*SF-1, numel(pn))+1), sps, 1), 1, []); s = base .* pn .* cos(2*pi*fc*t); r = awgn(s, EbN0_dB - 10*log10(SF*sps), 'measured'); rd = r .* cos(2*pi*fc*t) .* pn; rx = sign(sum(reshape(rd, SF*sps, M), 1)); ber = mean(rx ~= data); end

外面套一层 SNR 扫描,同时算一条理论曲线做参照:

EbN0 = 0:2:12; for i = 1:numel(EbN0) ber_sim(i) = dsss_sim(63, 8, EbN0(i), 1e5); ber_th(i) = qfunc(sqrt(2*10^(EbN0(i)/10))); % BPSK 理论值 end semilogy(EbN0, ber_sim, 'o', EbN0, ber_th, '-'); grid on;

扩频因子取 63 而不是 64,是因为 m 序列只能是 2^n−1,用 63 换来的是干净的码长和准确的 Gp = 18 dB,比凑整更重要。理论曲线用的是 BPSK 在 AWGN 下的闭式解,直扩在理想同步、单用户的条件下应该与它重合——如果你的仿真点系统性偏高,多半是 PN 相位、滤波或 SNR 换算三者之一出了问题;偏低则是泄漏了发送端信息,比如判决时误用了data。

再往下还有几个值得做的方向:

改造点改哪里预期效果
换扩频码mseq换成goldseq或 WALSH观察互相关对多址干扰的影响
加窄带干扰在awgn之后叠一个单音验证干扰被摊宽后功率下降
加多径用两路不同时延的s相加观察相关峰分离与 RAKE 合并收益
加码片失配PN 码索引整体偏移若干采样点量化同步误差对 BER 的影响

最后这一项是我最推荐的。前面说过,原脚本隐含了「理想同步」这个假设,而真实系统里同步是最难啃的一块。你只要把接收端的pn索引整体挪 1 个采样点、半个码片、1 个码片,跑出来的 BER 曲线会立刻告诉你这条链路有多脆弱——半个码片以内还能救,超过一个码片直接崩成 0.5。从那以后我每次做扩频仿真,都强制先跑一遍同步误差扫描,把这条曲线的拐点记在参数表里,再谈别的性能指标。希望这套东西帮到你,少走几段弯路。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/28 5:49:43

具身智能实验室建设:从仿真到物理闭环的四层能力阶梯

1. 这不是概念炒作&#xff0c;是实验室落地的真实切口“具身智能”这个词最近在朋友圈刷屏&#xff0c;但很多人点开文章只看到一堆术语堆砌&#xff1a; embodied AI、多模态感知、闭环决策、物理世界交互……看得人头皮发麻。我带团队在高校和企业跑过27个AI实验室建设咨询项…

作者头像 李华
网站建设 2026/10/28 5:49:29

社交网络中的欠揍昵称:心理学与传播学解析

1. 为什么“欠揍昵称”总能引爆社交网络&#xff1f;在社交平台上&#xff0c;我们总能看到一些让人忍不住想吐槽的昵称。这类昵称通常具备几个特征&#xff1a;要么是故意卖萌到让人起鸡皮疙瘩&#xff08;比如"小兔兔酱~"&#xff09;&#xff0c;要么是自恋到极致…

作者头像 李华
网站建设 2026/10/28 5:47:38

大模型应用:关系抽取的分析和实践

1 前言关系抽取是自然语言处理中知识图谱构建的核心任务&#xff0c;目标是从非结构化文本中识别实体以及实体之间的语义关系&#xff0c;输出(头实体&#xff0c;关系&#xff0c;尾实体)结构化三元组&#xff0c;广泛用于知识库构建、舆情分析、智能问答、文档信息挖掘等场景…

作者头像 李华
网站建设 2026/10/28 5:47:27

agent-skills:面向CLI的模型无关智能体能力协议

1. 项目概述&#xff1a;Agent-Skills 不是插件&#xff0c;而是一套可复用的智能体能力协议 “agent-skills”这个词最近在开发者社区里频繁出现&#xff0c;但它既不是某个具体软件的安装包&#xff0c;也不是某家大厂刚发布的闭源工具。我第一次在 GitHub 上看到它时&#…

作者头像 李华
网站建设 2026/10/28 5:46:56

手写四阶Runge-Kutta:从定步长实现到稳定性与误差控制

简介&#xff1a;本资源是一份面向高校理工科学生、科研人员及Matlab初学者的数值计算教学资料&#xff0c;系统讲解龙格-库塔&#xff08;Runge-Kutta&#xff09;方法的核心原理与工程实现。重点解析四阶五阶RK算法在Matlab中的应用逻辑&#xff0c;涵盖ode45等内置求解器的适…

作者头像 李华
网站建设 2026/10/28 5:45:30

外卖系统架构设计与SaaS化实践指南

1. 外卖系统选型与源码与 SaaS 实践的思考做外卖系统这些年&#xff0c;从零开始搭建过三套不同架构的平台&#xff0c;也参与过多个SaaS化改造项目。今天想聊聊在选型、源码管理和SaaS实践中的那些坑和经验。无论你是想自建外卖平台&#xff0c;还是准备做二开&#xff0c;这些…

作者头像 李华