简介:这份资源是一套基于MATLAB的扩频通信(DSSS)仿真代码,聚焦直序扩频系统对抗窄带干扰的设计与验证,面向无线通信、信号处理方向的学生与研究人员,帮助理解扩频调制、干扰建模与抗干扰策略的实现思路。压缩包共3个文件,包含2个m脚本与1个md说明文档,体积约3KB,其中脚本分别承担主流程仿真与误码率性能评估,说明文档用于交代项目结构与运行方式。资源围绕伪随机码生成、信息信号扩频调制、窄带干扰模型、解扩与匹配滤波、BER与SNR性能分析等环节展开,可帮助读者直观观察窄带干扰对通信链路的影响,并对比不同抗干扰方法的误码表现。目前已有467人学习,适合作为扩频通信课程实验或相关课题的入门参考,便于在此基础上修改参数、扩展干扰类型并优化系统设计。
1. 直扩通信里那根“窄带刺”:为什么 DSSS 抗干扰仿真值得你亲手跑一遍
做无线通信仿真的同行大多有过这种体验:链路在纯高斯信道下误码率曲线漂亮得像教科书,一旦在频谱里塞进一个窄带干扰源,接收端星座图立刻散成一团。直接序列扩频(DSSS)被反复提及的核心价值,就是拿一条伪随机码把窄带信号的能量摊到宽频带上,让落在某个频点的窄带干扰只污染一小部分频谱,解扩时再把这部分损伤摊薄。这套逻辑听起来顺理成章,但真正动手在 MATLAB 里搭一遍,你会发现扩频增益、处理增益、干扰容限这几个词之间的关系,和论文里写的并不完全是一回事。
这篇笔记面向的是已经懂一点通信原理、想在 MATLAB 里把 DSSS 抗窄带干扰这条链路跑通的人。我会从信号模型讲起,把扩频、加窄带干扰、解扩、判决这条链路一步步搭出来,给出可复现的代码和参数,再重点讲几个我踩过的坑——尤其是窄带干扰的频率位置、带宽、功率怎么设,以及为什么你的误码率曲线有时候会“玄学”地不降反升。目标很明确:读完你能自己搭一套能出图的仿真,并且知道每个参数动了之后曲线会往哪走。
2. 把 DSSS 抗窄带干扰的信号模型拆开:从扩频到解扩的完整链路
2.1 为什么窄带干扰在解扩后会被“摊薄”
先把这个反直觉的结论说清楚。假设发送端每个信息比特用长度为 N 的伪随机序列(PN 码)扩展,码片速率是比特速率的 N 倍。一个窄带干扰如果带宽远小于扩频带宽,它在时域上近似是一个单频正弦或窄带调制信号。接收端做解扩,本质是把接收信号再乘一遍本地同步的 PN 码,然后在一个比特周期内积分。
关键在这里:有用信号乘上本地 PN 码后,重新变回窄带,积分时能量相干累加;而窄带干扰乘上 PN 码后,被“二次扩频”,能量被摊到整个扩频带宽上,积分时只有落在信息带宽内的那一小部分能量被累加。这就是处理增益的来源,数值上等于扩频带宽与信息带宽之比,用码长表示就是 10·log10(N) dB。N 越大,干扰被摊得越薄,但代价是占用带宽和同步复杂度上升。
需要提醒的是,这个“摊薄”只在干扰带宽确实远小于扩频带宽时成立。如果干扰带宽接近甚至超过信息带宽,处理增益带来的好处会迅速缩水,这也是后面避坑章节要重点讲的。
2.2 最小可复现链路的模块划分
我一般把这条链路拆成五个模块,每个模块对应一段独立可测的代码,方便单独调:
| 模块 | 作用 | 关键参数 |
|---|---|---|
| 信源 | 生成随机比特 | 比特数、帧长 |
| 扩频 | 比特与 PN 码相乘 | 码长 N、码片速率 |
| 信道 | 叠加窄带干扰与噪声 | 干扰频率、带宽、干信比 |
| 解扩 | 本地码相乘并积分 | 同步位置、积分区间 |
| 判决 | 抽样判决算误码率 | 判决门限 |
这样拆的好处是,当误码率曲线不对时,你可以逐模块打印中间信号的频谱,快速定位是扩频没做对、干扰加错了位置,还是解扩同步偏了。
2.3 用 MATLAB 搭出扩频与解扩的最小代码
下面这段是核心链路,我把它写成函数形式,方便改参数反复跑。注意 PN 码用 m 序列生成,保证自相关特性。
function [ber, tx_sig, rx_sig] = dsss_link(N, num_bits, JSR, f_int, fs, snr_db) % N : 扩频码长(处理增益 = 10*log10(N) dB) % num_bits : 信息比特数 % JSR : 干信比 (dB),窄带干扰功率 / 信号功率 % f_int : 窄带干扰中心频率 (Hz) % fs : 采样率 (Hz) % snr_db : 信噪比 (dB) % ---- 1. 信源:双极性比特 ---- bits = randi([0 1], 1, num_bits); symbols = 2*bits - 1; % 映射到 +1 / -1 % ---- 2. 生成 m 序列作为 PN 码 ---- pn = m_sequence(N); % 长度 N 的 +1/-1 序列 pn = pn(:).'; % ---- 3. 扩频:每个比特重复整段 PN 码 ---- tx_sig = zeros(1, num_bits * N); for k = 1:num_bits tx_sig((k-1)*N+1 : k*N) = symbols(k) * pn; end % ---- 4. 上采样到采样率,模拟连续波形 ---- sps = fs / N; % 每码片采样点数 tx_up = rectpulse(tx_sig, sps); % ---- 5. 信道:加窄带干扰 + 高斯白噪声 ---- t = (0:length(tx_up)-1) / fs; sig_power = mean(tx_up.^2); % 窄带干扰用单频正弦近似(带宽极窄) jam_power = sig_power * 10^(JSR/10); jam = sqrt(2*jam_power) * cos(2*pi*f_int*t); % 高斯噪声按 SNR 加 noise_power = sig_power / 10^(snr_db/10); noise = sqrt(noise_power) * randn(size(tx_up)); rx_up = tx_up + jam + noise; % ---- 6. 解扩:下采样回码片率,乘本地码,按比特积分 ---- rx_chip = downsample(rx_up, sps); rx_sig = zeros(1, num_bits); for k = 1:num_bits seg = rx_chip((k-1)*N+1 : k*N); rx_sig(k) = sum(seg .* pn); % 相干积分 end % ---- 7. 判决与误码率 ---- decided = rx_sig > 0; ber = sum(decided ~= bits) / num_bits; end function pn = m_sequence(N) % 生成长度 N 的 m 序列,N 取 2^m - 1 m = log2(N+1); regs = [1 zeros(1, m-1)]; taps = [m 1]; % 本原多项式抽头,按需替换 pn = zeros(1, N); for i = 1:N pn(i) = regs(end); fb = mod(sum(regs(taps)), 2); regs = [fb regs(1:end-1)]; end pn = 2*pn - 1; % 映射到 +1/-1 end逻辑说明:信源用双极性映射是为了让扩频乘法直接对应 BPSK 调制,省掉额外的载波搬移。扩频段把每个比特乘以整段 PN 码,这是最直观的时域扩频写法。信道段把窄带干扰建模成单频正弦,这是最“毒”的一种干扰——能量全部集中在一个频点,对扩频系统是最坏情况之一。解扩段先下采样回码片率再乘本地码积分,等价于匹配滤波。
参数说明:N决定处理增益,常用 31、63、127;JSR是干信比,仿真里从 0 dB 一路加到 20 dB 看曲线变化;f_int是干扰频率,建议先取 0(直流附近)和取扩频带宽中心两个位置对比;fs要满足采样定理,一般取码片速率的 8 到 16 倍。
2.4 跑出第一条误码率曲线
把上面的函数包一层循环,扫 JSR,就能得到抗干扰性能曲线:
N = 63; num_bits = 1e5; fs = 1e6; snr_db = 10; jsr_list = 0:2:20; ber_list = zeros(size(jsr_list)); for i = 1:length(jsr_list) ber_list(i) = dsss_link(N, num_bits, jsr_list(i), 0, fs, snr_db); end semilogy(jsr_list, ber_list, '-o'); xlabel('干信比 JSR (dB)'); ylabel('误码率 BER'); grid on;跑完你会看到一条随 JSR 上升而缓慢抬升的曲线,而不是像无扩频系统那样迅速崩掉。这条曲线的斜率就是处理增益在起作用。如果曲线在某个 JSR 之后突然陡升,多半是干扰功率已经大到让积分器输出饱和或判决门限失效,这时候要检查你的信号功率归一化是不是做对了。
3. 窄带干扰的参数怎么设:频率、带宽、功率三个旋钮
3.1 干扰频率落在哪,结果差别很大
很多人第一次做这个仿真,随手把干扰频率设成 0,发现误码率几乎不涨,于是得出结论“DSSS 抗干扰很强”。这个结论是片面的。干扰频率落在直流附近时,解扩后乘 PN 码,低频分量被搬到高频,积分时被抑制得最狠。真正麻烦的是干扰频率落在扩频信号频谱的某个旁瓣或主瓣边缘,这时候部分能量会落在积分带宽内。
我一般会扫一遍干扰频率,画出“干扰频率—误码率”的二维图,找到最坏干扰频点。做法是把f_int从 0 扫到码片速率,步进取码片速率的 1/50 左右:
N = 63; fs = 1e6; chip_rate = fs / 8; % 假设每码片 8 个采样点 f_int_list = 0 : chip_rate/50 : chip_rate; ber_freq = zeros(size(f_int_list)); for i = 1:length(f_int_list) ber_freq(i) = dsss_link(N, 2e4, 15, f_int_list(i), fs, 10); end plot(f_int_list/chip_rate, ber_freq); xlabel('归一化干扰频率 f_{int}/R_c'); ylabel('误码率 BER'); grid on;这张图能帮你判断:你的系统对哪个频段的窄带干扰最敏感。工程上做抗干扰设计时,这个最坏频点就是你要重点防护的位置。
3.2 干扰带宽从“单频”放宽后会怎样
单频正弦是最理想的窄带干扰,实际干扰往往有一定带宽。把干扰从单频改成带限噪声,只需要把正弦换成带通滤波后的高斯噪声:
% 带限窄带干扰:中心 f_int,带宽 B_jam B_jam = chip_rate / 20; % 干扰带宽,远小于扩频带宽 [b, a] = butter(4, [f_int - B_jam/2, f_int + B_jam/2]/(fs/2), 'bandpass'); jam = filter(b, a, randn(size(tx_up))); jam = jam / std(jam) * sqrt(jam_power); % 归一化到目标功率这里有个容易翻车的点:butter的归一化频率必须除以fs/2,而且当f_int - B_jam/2接近 0 或f_int + B_jam/2接近fs/2时,滤波器设计会不稳定。我一般会把干扰中心频率限制在扩频带宽的 20% 到 80% 之间,避开边界。
带宽放宽后你会发现,误码率对干扰频率的敏感度下降了,但整体误码率抬升了。原因是带限干扰的能量分布更宽,解扩后落在积分带宽内的比例更高。这解释了为什么实际对抗中,宽带噪声干扰比单频干扰更难对付。
3.3 干信比与处理增益的换算关系
处理增益 Gp = 10·log10(N) dB,这个值决定了系统能容忍多大的干信比。理论上,当 JSR 小于 Gp 时,解扩后干扰功率被压到信号功率以下,误码率接近无干扰情况;当 JSR 超过 Gp,误码率开始明显恶化。
但仿真里你会发现这个“拐点”并不锐利,原因是噪声也在起作用,而且积分器的有限字长和采样率都会影响实际抑制效果。我一般会同时画三条曲线:无干扰、JSR = Gp - 5 dB、JSR = Gp + 5 dB,对比看拐点位置是否符合预期。如果 JSR = Gp - 5 dB 时误码率还很高,说明你的解扩同步或功率归一化有问题。
提示:干信比的定义要和信号功率的统计方式一致。上面代码里用
mean(tx_up.^2)估计信号功率,如果你的信号有脉冲成形或加窗,这个估计会偏,建议用长时平均。
4. 避坑与排查:DSSS 抗窄带干扰仿真里最容易翻车的五件事
4.1 误码率曲线不降反升
现象:加大扩频码长 N,误码率反而比小 N 时更高。
原因:最常见的是 PN 码生成有误,码的自相关性差,导致解扩时有用信号没有相干累加。另一个可能是采样率没跟着 N 调整,码片率变了但fs没变,导致每码片采样点数不是整数,下采样时相位错位。
解决:先单独验证 PN 码的自相关,xcorr(pn)应该在零延迟处出现尖峰、其他位置接近零。再检查fs / N是否为整数,不是整数就把fs调成 N 的整数倍。
4.2 干扰频率设成 0 时几乎无影响,误以为仿真成功
现象:JSR 加到 20 dB,误码率还是接近零。
原因:干扰落在直流附近,解扩时被 PN 码搬到高频,积分器把它抑制掉了。这不代表系统抗干扰强,只是你选了一个最有利的干扰频点。
解决:按 3.1 节扫一遍干扰频率,找到最坏频点再评估性能。工程评估必须用最坏情况,不能用最有利情况。
4.3 带限干扰滤波器设计报错或结果异常
现象:butter报错,或者滤波后干扰功率和设定值差很多。
原因:归一化频率越界,或者滤波器阶数太高导致数值不稳定。另外filter的瞬态响应会让前几个样本功率异常,影响功率归一化。
解决:把干扰中心频率限制在(0.1, 0.9) * fs/2范围内,阶数降到 2 到 4 阶。功率归一化前先丢掉前 100 个样本,用稳态段估计标准差。
4.4 解扩同步偏移导致性能骤降
现象:误码率比理论值高一个数量级,但频谱看起来正常。
原因:本地 PN 码和接收信号的码相位没对齐,哪怕偏一个码片,相干积分就会大幅损失。
解决:在解扩前加一个滑动相关同步,扫一遍码相位,取相关峰最大的位置作为积分起点。仿真里可以先用已知的发送码相位做理想同步,确认链路无误后再加同步模块。
4.5 蒙特卡洛次数不够导致曲线抖动
现象:误码率曲线在高 JSR 段上下跳,重复跑结果不一致。
原因:高 JSR 时误码率本身很低,num_bits不够,统计涨落大。
解决:低误码率段把num_bits加到 1e6 以上,或者用重要性采样。我一般会保证每个误码率点至少统计到 100 个错误比特,否则曲线不可信。
5. 进阶技巧:用处理增益反推干扰容限,并验证你的仿真边界
5.1 从误码率曲线反推干扰容限
干扰容限的定义是:在保证一定误码率的前提下,系统能承受的最大干信比。有了前面的仿真链路,你可以固定一个目标误码率(比如 1e-3),扫 JSR 找到对应的临界值,再和理论处理增益对比。
target_ber = 1e-3; N_list = [31, 63, 127]; for i = 1:length(N_list) N = N_list(i); jsr = 0; while true ber = dsss_link(N, 1e5, jsr, chip_rate/4, 1e6, 10); if ber > target_ber fprintf('N=%d, 干扰容限约 %d dB, 理论处理增益 %.1f dB\n', ... N, jsr, 10*log10(N)); break; end jsr = jsr + 1; end end跑出来你会看到干扰容限通常比理论处理增益低几个 dB,差值来自噪声、积分损失和判决门限。这个差值就是你的仿真链路“效率”的度量。如果差值超过 5 dB,说明链路里有明显的实现损失,值得回头查同步和功率归一化。
5.2 用频谱图验证“二次扩频”是否真的发生
理论说窄带干扰解扩后被摊开,这个结论可以直接在频谱上看。把解扩前和解扩后的信号分别做 FFT,对比干扰分量的带宽:
% 解扩前频谱 N_fft = 4096; spec_before = abs(fftshift(fft(rx_up(1:N_fft)))).^2; % 解扩后(乘本地码再变换) rx_despread = rx_up .* rectpulse(repmat(pn, 1, num_bits), sps); spec_after = abs(fftshift(fft(rx_despread(1:N_fft)))).^2; f = (-N_fft/2 : N_fft/2-1) * fs / N_fft; plot(f, 10*log10(spec_before), 'b', f, 10*log10(spec_after), 'r'); legend('解扩前', '解扩后'); xlabel('频率 (Hz)'); ylabel('功率谱 (dB)'); grid on;如果解扩后干扰峰明显展宽、峰值下降,说明二次扩频生效了。如果峰值没怎么变,检查本地码是否和发送码对齐——码不对齐时,乘上去的不是同一个 PN 序列,扩频效果就出不来。
5.3 一个我常犯的错误
早期做这个仿真,我总想把num_bits设小一点跑得快,结果高 JSR 段曲线全是噪声,白白浪费一下午调参数。后来养成习惯:先用小num_bits快速扫参数范围,确定大致趋势后,再用大num_bits在关键点上精算。另外,每次改完参数一定先看时域波形和频谱,别直接看误码率数字——波形不对,数字再好看也是假的。希望帮到你。
本文还有配套的精品资源,点击获取