简介:一份基于Matlab的扩频通信系统仿真源码包,面向通信工程专业学生、科研人员及对直接序列扩频(DSSS)感兴趣的开发者,用于理解扩频通信原理、验证同步性能并开展参数优化研究。包内共9个M文件,涵盖扩频码生成、沃尔什码构造、发送接收模块、高斯白噪声模拟及信噪比计算等功能模块,整体仅4KB,结构紧凑、便于逐模块阅读和二次修改。已有2298人学习参考。通过运行这些脚本,读者可直观查看信号扩频、传输、解扩及SNR评估的完整流程,也能在此基础上调整扩频码长度、扩频比和信道条件,深入分析不同因素对系统误码与同步性能的影响,为课程设计或实际系统预研提供可复现的仿真基础。
1. 基于 Matlab 的扩频通信系统仿真,为什么同步比解扩更值得关注
写这个源码包时,我遇到的反直觉结论是:基于 Matlab 的扩频通信系统仿真里,真正决定误码率上限的不是扩频码本身,而是接收端的码相位同步。只要本地伪码与发送端错开一个码片,解扩结果直接变成噪声,误码率掉到 0.5 附近。这套扩频通信系统仿真包正是围绕这个问题设计的,提供 send_moudle.m、receive_module.m、SNR.m 等独立脚本,覆盖直接序列扩频(DSSS)从伪码生成、扩频调制、信道加噪到同步解扩的完整链路,同时把同步误差对相关峰和误码的影响变成可量化的曲线。对想理解 m 序列、沃尔什码、扩频增益如何落到代码里的人,它是没有 Simulink 包装的裸版本:每个环节都是一个函数,能看清信号在每个阶段的形态。适合通信课程设计、DSSS 算法验证,以及不想拖动模块、想直接用脚本控制每个采样点的工程师。
2. 扩频链路框架与 m 序列发生器:先让伪随机码可用
2.1 直接序列扩频的信号模型与处理增益
DSSS 的本质是信息序列与高速率伪随机序列相乘。设信息比特为 d(k) ∈ {+1, -1},伪随机码片序列为 c(n) ∈ {+1, -1},扩频后的基带信号为 x(n) = d(k) * c(n),其中每个信息比特持续 L 个码片,L 就是扩频因子。接收端解扩时再次乘以同步伪码 c(n),由于 c²(n)=1,信号被还原成 d(k);窄带干扰则被伪码的谱扩展特性摊平到整个带宽,从而在解扩后被低通滤波器滤除。
处理增益 G = 10log10(L) dB 直接决定系统对干扰的抑制能力,在后续的 SNR.m 和同步性能分析中会反复出现。实际仿真时,如果 L=31,G 约为 14.9 dB,意味着解扩前信噪比很低时,解扩后信噪比依然可以支撑可靠判决。理解这一点,才知道为什么扩频通信能工作在噪声电平以下,也才知道 SNR.m 测出来的数值应该落在哪个区间。
2.2 从源码包映射到链路各环节
拿到这套源码,我建议先按功能给文件归类,而不是直接追 main.m 的执行顺序。一个比较实用的映射关系是下表:
| m 文件 | 链路角色 | 关键输出 |
|---|---|---|
| m_sequence.m / m_seq.m | 伪码生成 | m 序列(单极性或双极性) |
| walsh.m | 正交码生成 | 沃尔什函数矩阵 |
| send_moudle.m | 发送端 | 扩频后的基带信号 |
| yongwuAwumlvjisuan.m | 扩频运算 | 信息与码片相乘后的序列 |
| gngauss.m | 信道噪声 | 高斯白噪声样本 |
| receive_module.m | 接收端 | 解扩结果与同步偏移 |
| SNR.m | 性能测量 | 信噪比估计值 |
| main.m | 顶层调度 | 误码率、同步性能曲线 |
这里容易混淆的是 m_sequence.m 与 m_seq.m 的关系:前者输出移位寄存器产生的 0/1 逻辑序列,后者做极性映射,把 0 变成 -1、1 变成 +1。后续所有扩频相乘都要求双极性,所以真正参与运算的是 m_seq.m 的输出。这样拆分调试也清晰:想单测伪码性质时用 m_sequence,想跑链路时用 m_seq。
2.3 复现 m 序列生成逻辑
m 序列是最大长度线性反馈移位寄存器序列,周期是 2^n - 1。工程上最常用查本原多项式表确定抽头位置,例如 n=4 时抽头是 [4 3],n=5 时是 [5 3]。复现包的 m_sequence.m 时,我一般写成:
function seq = m_sequence(n, taps) % n : 移位寄存器阶数 % taps : 反馈抽头下标,从1开始 % seq : 0/1序列,长度 2^n - 1 if nargin < 2 switch n case 4 taps = [4 3]; case 5 taps = [5 3]; otherwise error('需要显式指定抽头位置'); end end reg = ones(1, n); seq = zeros(1, 2^n - 1); for i = 1:(2^n - 1) seq(i) = reg(n); % 输出最高位 fb = mod(sum(reg(taps)), 2); % 抽头异或,等价于模2加法 reg = [fb reg(1:end-1)]; % 右移并反馈 end end参数说明:n决定伪码周期,周期越长自相关性越好,但接收端捕获时间随之变长。taps里的下标对应本原多项式中非零项的幂次,选错会退化为短周期序列,造成相关峰旁瓣升高。实现里mod(sum(...), 2)等价于对抽头位做异或,是移位寄存器最直接的数学表达。拿到真实文件后,可以用sum(m_sequence(5, [5 3]))验证序列中 1 的个数为 16,与理论值 2^(n-1) 一致。
3. 沃尔什码与扩频调制:用正交性分离用户与信号
3.1 沃尔什码的构造逻辑
沃尔什码由 Hadamard 矩阵按行提取。2 阶 Hadamard 矩阵是 [[1 1]; [1 -1]],更高阶按 Kronecker 积递归扩展:H(2m) = [H(m) H(m); H(m) -H(m)]。walsh.m 在 Matlab 里最简单的实现是用hadamard(N),但更贴近链路建模习惯的是自己迭代矩阵,便于控制码长和行索引。沃尔什码的核心价值是任意两行正交,相关值为 0;在多用户场景中,不同用户的扩频码不互相干扰,接收端解扩时能把目标用户信号完整恢复出来。代码可以这样复现:
function walsh_codes = walsh(N) % N 必须是2的幂 w = 1; while size(w, 1) < N w = [w w; w -w]; end walsh_codes = w; end调用walsh_codes = walsh(4)会得到四行码,每行对应一个用户的扩频码。使用时要把 0/1 逻辑映射成双极性,即 0→+1、1→-1,正交关系在双极性下依然严格成立。N越大,处理增益越高,可支持的用户数越多,但码片速率被抬高,同步要求也越苛刻。码长选择可以参考下表:
| 码长 N | 处理增益 dB | 正交码数量 | 适用场景 |
|---|---|---|---|
| 4 | 6.0 | 4 | 教学演示 |
| 8 | 9.0 | 8 | 简单多用户 |
| 16 | 12.0 | 16 | 小规模接入 |
| 32 | 15.1 | 32 | 同步性能研究 |
实战中做误码率仿真用 N=31 或 63 的 m 序列更普遍,因为沃尔什码的自相关旁瓣不一定低;但研究多址干扰时,沃尔什码不可替代。
3.2 send_moudle 的扩频与极性映射
发送模块完成三件事:信息比特极性映射、码片复制、扩频相乘。下面的 send_moudle 结构能复现源码包的行为:
function [tx, pn] = send_moudle(data, code, code_index) % data : 0/1 信息比特行向量 % code : 扩频码矩阵或行向量 % code_index : 选第几个码作为扩频码,默认1 if nargin < 3 code_index = 1; end info = 2*data - 1; % 0/1 -> -1/+1 pn = code(code_index, :); % 取一行双极性码 info_up = reshape(repmat(info, length(pn), 1), 1, []); tx = info_up .* repmat(pn, 1, length(info)); end这里repmat将每个比特扩展成length(pn)个码片,扩频倍数等于码长。info_up是扩展后的信息序列,repmat(pn, 1, length(info))把伪码重复到与扩展序列等长,两者逐点相乘完成扩频。双极性表示下没有真正的“乘”硬件实现,工程里对应 FPGA 的异或门操作,Matlab 里用点乘结果更直观。如果 data 是 0/1 序列而忘了转极性,会出现扩频后各码片之间缺少对应的抑制作用,误码率曲线整体异常。
3.3 yongwuAwumlvjisuan.m 的运算本质
yongwuAwumlvjisuan.m 承担扩频运算,输入两个双极性序列,输出乘积序列。实际包里可能还有额外的边界处理,但核心逻辑可以抽象成:
function out = yongwuAwumlvjisuan(info_seq, pn_seq) info_rep = kron(info_seq, ones(1, length(pn_seq))); out = info_rep .* pn_seq; endkron的语义是先将每个信息比特重复length(pn_seq)次,形成串行码片流,再与伪码逐点相乘。这段代码与 send_moudle 里的展开方式等价,区别在于它是底层算子,send_moudle 负责把用户参数准备好。参数上要强制输入已经是双极性序列,否则点乘结果失去正负号信息。在仿真链路里,yongwuAwumlvjisuan.m 可以同时被发送端和同步测试复用,避免写两份重复的扩频代码。
4. 高斯噪声、接收解扩与 SNR 估计:把仿真做到可信
4.1 gngauss.m 的噪声模型
信道噪声建模为加性高斯白噪声时,gngauss.m 用于生成指定方差的高斯样本。它通常采用 Box-Muller 变换,将两个均匀分布随机数转成标准正态分布样本,再乘标准差。代码可以写成:
function n = gngauss(sigma) % sigma : 噪声标准差 u1 = rand; u2 = rand; z = sqrt(-2*log(u1)) * cos(2*pi*u2); n = sigma * z; end这个函数每次只返回一个样本,因此在实际链路中需要循环调用。几千个码片规模下开销可接受;如果要仿真 10^6 个码片,更推荐向量化写法noise = sigma * randn(1, N)。标准差sigma与噪声功率直接相关,噪声功率为sigma^2。在 5.3 节会看到,从 Eb/N0 换算 sigma 是一个容易出错的点:必须把扩频码长考虑进去,否则生成的信道噪声与理论曲线对不上。
4.2 receive_module 的滑动相关同步策略
接收端的首要任务是找到本地伪码和接收信号的相对延迟,工程上最简单可靠的策略是滑动相关:把本地码循环移位 k 个码片,与接收信号相乘累加,相关值最大的位置就是码相位估计。receive_module.m 可以按下面的方式理解:
function [data_hat, offset, peak, corr] = receive_module(rx, pn) % rx : 接收基带信号,长度必须为 length(pn) 的整数倍 % pn : 双极性扩频码 chip_len = length(pn); mat = reshape(rx, chip_len, [])'; % 每行对应一个信息比特的码片块 corr = zeros(1, chip_len); for shift = 1:chip_len pn_shift = circshift(pn, [0, shift-1]); corr(shift) = abs(mean(mat * pn_shift')); end [peak, offset] = max(corr); pn_sync = circshift(pn, [0, offset-1]); despread = mat * pn_sync'; data_hat = despread > 0; end这里circshift完成伪码循环移位,reshape(rx, chip_len, [])把连续码片流分割成每个信息比特一个块。相关值最大的offset就是同步位置估计。注意循环移位适用于周期性的 m 序列;对非周期性的突发帧,需要用前导码做线性移位相关,否则边缘效应会引入误差。如果每码片有多个采样点,需要先把接收信号按码片抽取成单样本序列,或把搜索步长改为采样点间隔,两种做法对应不同的代码路径。
4.3 SNR.m 估计的信噪比口径
SNR.m 负责计算接收信号信噪比,输入一般是一段含噪信号和一段噪声样本,输出 dB 值:
function s = SNR(signal, noise) Ps = mean(signal.^2); % 含噪信号总功率 Pn = mean(noise.^2); % 噪声功率 s = 10*log10((Ps - Pn) / Pn); % 扣除噪声功率得到信号功率 endPs是含噪信号的总功率,减去Pn得到信号自身功率,再除以Pn就是线性信噪比。这个测量口径与理论上的 Eb/N0 之间有固定换算关系:解扩前 SNR 比解扩后低处理增益 dB。码长 31 的 m 序列处理增益约 14.9 dB,接收端若测得解扩前 SNR=-10 dB,解扩后应接近 4.9 dB。如果 SNR.m 输出的解扩增益小于这个值,说明伪码没有同步或相关峰搜索失败。调参时可以参考下面的配置表:
| 参数 | 含义 | 推荐值 |
|---|---|---|
| sigma | 噪声标准差 | 由 Eb/N0 换算 |
| samps_per_chip | 每码片采样点 | 2 ~ 4 |
| search_step | 同步搜索步长 | 1/samps_per_chip |
| pn_len | 扩频码长度 | 31 / 63 |
其中search_step必须与内插后的采样率匹配,否则会出现相邻搜索位置都找不到峰值的情况。
5. 同步性能仿真:主程序参数与误码率曲线
5.1 main.m 里的关键参数
main.m 是整个仿真程序的调度入口。在跑通之前,必须把参数确定下来:信息比特数、扩频码类型、码长、每码片采样点、信噪比扫描范围。每码片采样点对同步性能的影响最明显:取 1 时只能检测整数码片偏移,取 2 时能分辨半码片误差,取 4 可以做到 0.25 码片。参数建议如下:
| 参数 | 值 | 说明 |
|---|---|---|
| num_bits | 1000 | 统计 BER 需要足够样本 |
| code_len | 31 | m 序列周期,处理增益约 14.9 dB |
| samps_per_chip | 2 | 同步分析时使用,BER 主流程跑码片级 |
| SNR_dB | -10:2:10 | 常规 AWGN 扫描范围 |
| code_type | 'm_seq' | 可选 'walsh',用于多址对比 |
这组参数下,单条 SNR 点产生 31000 个码片,普通笔记本跑完 11 个信噪比点约一分钟,方便反复调参。
5.2 码相位偏差对相关峰的影响
同步性能仿真的核心观察对象是相关峰峰值与码相位误差的关系。m 序列自相关函数在零偏移处等于码长 L,其他整数偏移为 -1/L。当接收端本地码与发送端的相对偏差超过一个码片时,相关峰几乎消失,判决输出趋于随机。为了量化这个效应,我在链路中加入可控延迟并做上采样:
samps_per_chip = 2; tx_up = reshape(repmat(tx, samps_per_chip, 1), 1, []); % 2倍上采样 tau = 0.3 * samps_per_chip; % 0.3码片延迟的采样点数 delayed_tx = [zeros(1, round(tau)), tx_up]; corr_peak = zeros(1, 50); for shift = 1:50 seg = delayed_tx(shift:shift + length(tx_up) - 1); chip_seg = seg(1:samps_per_chip:end); % 抽取到码片级 [~, ~, corr_peak(shift)] = receive_module(chip_seg, pn); end plot(1:50, corr_peak); xlabel('搜索起始位置'); ylabel('相关峰');这里的tau是定时偏移模拟值,corr_peak展示不同初相下相关峰的衰减。实际绘制后会看到:偏移 0.5 码片时相关峰已明显下降,1 码片时降至旁瓣水平,此时 BER 必然接近 0.5。这个曲线是判断接收端同步算法是否有效的直接证据。源码包里的 main.m 一般会用类似手段扫描不同偏移,得到同步性能随相位误差变化的曲线。
5.3 跑通全链路并绘制 BER 曲线
完整链路把发送、噪声、同步和误码统计串起来。5.3 的 BER 主流程在码片级仿真,每个码片一个样本,避免噪声功率换算歧义。代码可以写成:
EbN0_dB = -10:2:10; code_len = 31; pn = 2*m_sequence(5, [5 3]) - 1; for idx = 1:length(EbN0_dB) data = randi([0 1], 1, 1000); tx = send_moudle(data, pn, 1); EbN0_lin = 10^(EbN0_dB(idx)/10); sigma = sqrt(1 / (2 * EbN0_lin * code_len)); noise = zeros(size(tx)); for m = 1:length(tx) noise(m) = gngauss(sigma); end rx = tx + noise; data_hat = receive_module(rx, pn); ber(idx) = mean(data ~= data_hat); end semilogy(EbN0_dB, ber, 'o-'); hold on; semilogy(EbN0_dB, qfunc(sqrt(2*10.^(EbN0_dB/10))), '--'); xlabel('Eb/N0 (dB)'); ylabel('BER'); legend('仿真','理论');这里sigma的换算是关键:BPSK 的每比特能量 Eb 在扩频后等于码长code_len,所以噪声方差要除以code_len,否则噪声功率被高估。qfunc(sqrt(2*EbN0_lin))是 BPSK 理论误码率,扩频不改变 AWGN 下的误码性能,两条曲线应该基本重合。若仿真曲线在高信噪比区域出现平台,优先怀疑同步搜索步长过粗或相关峰判决门限问题,而不是噪声生成问题。
6. 调参技巧:让仿真结果更接近理论值
6.1 扩频码平衡性检查
m 序列每个周期有 2^(n-1) 个 1 和 2^(n-1)-1 个 0,转换为双极性后正负样本数量差 1,直流分量约为 1/L。这个不平衡会在频谱上留下离散谱线,导致 SNR.m 计算信号功率时把直流分量也算进去,使信噪比估计偏高。调试第一步是检查平衡性:
pn_bipolar = 2*m_sequence(5, [5 3]) - 1; balance = sum(pn_bipolar); % 期望为 -1如果 balance 明显不是 ±1,说明反馈抽头不是本原多项式,必须重新查表确定 taps。这个检查在跑 main.m 之前做,成本几乎为零,却能避免后面所有结果被污染。
6.2 同步搜索步长的选择
当 BER 曲线在低信噪比区域比理论值差,最常见原因是同步搜索步长太大。步长为 1 码片时,同步误差最大可达 0.5 码片,相关峰损失约 3 dB;步长减小到 0.5 码片,损失降到约 0.9 dB。但步长减小会线性增加运算量。实用做法是先按 1 码片步长跑通链路,确认各模块没有错误,再提高samps_per_chip到 2 或 4,观察同步误差分布是否收敛。main.m 中的相关峰搜索循环会随步长细化而变慢,因此只对重点信噪比点做细扫,全范围扫描保持粗步长。
6.3 用峰均比自动判定同步失败
同步模块最怕的是把旁瓣当主瓣,静默地解出随机数据。调试时把 receive_module 的corr数组拿出来算峰均比:
[~, ~, ~, corr] = receive_module(rx, pn); peak_to_avg = max(corr) / (mean(abs(corr)) + eps);m 序列同步成功时peak_to_avg通常大于 5,未同步时接近 1。在 main.m 中加一个阈值判断,凡是peak_to_avg < 2的帧标记为同步失败,并排除在误码统计之外。这样能快速定位是信道噪声过大还是伪码本身出了问题。最后建议在 main.m 开头固定随机种子:rng(2024);。伪码序列、噪声样本和误码位置完全可复现,尤其在低 SNR 区域误码数量少时,随机波动会让 BER 曲线出现毛刺影响判断,固定种子后同一组参数每次运行结果一致,不同参数之间的差异才能真实反映到曲线上。
本文还有配套的精品资源,点击获取