基于MATLAB的CDMA链路仿真:从扩频原理到BER验证

发布时间:2026/9/13 17:39:51
基于MATLAB的CDMA链路仿真:从扩频原理到BER验证
简介面向通信与信号处理方向学习者这套基于 MATLAB 的 CDMA 仿真代码包可用于课程设计、科研验证与入门实践。资源核心由 20 个 m 脚本组成主程序 main.m 将信号生成、扩频调制、解调、衰落信道、分集接收和跳频等模块串接起来运行时能直接输出效果图直观反映不同环节对系统误码性能的影响。配套的 PDF 使用说明、Markdown 笔记和少量文本文件对脚本用途、代码结构和运行顺序进行了说明可帮助使用者快速掌握仿真流程减少从零排错的时间。压缩包共 24 个文件整体约 80KB轻量简洁以 Matlab 2020b 为参考环境基本能满足开箱即用的需求。目前已有 112 人学习浏览。仿真点涵盖直接序列扩频、跳频、多径衰落、分集合并等常见内容既适合理解 CDMA 系统工作原理也可用于算法修改与二次开发。文档内运行步骤清晰按主函数入口执行即可复现结果模块化设计让使用者能将精力集中在信号模型与场景调整上适合课程报告、原型验证或作为进一步研究的基础代码。1. 拿到 CDMA 仿真项目时先别急着找扩频函数很多人拿到「基于MATLAB实现的CDMA仿真」这个课题第一反应是去搜 Gold 码生成器或现成的 BPSK 调制代码。但真正让仿真结果可信的往往在接收端解扩前码片怎么对齐、噪声功率怎么按 E_b/N_0 而不是 SNR 换算、用户数逼近码长时误码率为什么突然失控。这篇文章把 CDMA 链路仿真的完整路径拆开讲——从扩频与相关接收的数学模型到 MATLAB 里可复现的发射-信道-接收代码再到参数怎么设、结果怎么验证。适合通信方向的学生做课程设计和课题验证也适合刚接手物理层链路仿真的工程师快速搭出一套不依赖 Simulink、能批量扫描误码率的脚本框架。2. 先立住模型扩频增益、Gold 码与相关接收2.1 扩频到底扩的是什么直序扩频DSSS的做法很朴素把一个信息比特复制成 N 个码片每个码片再乘以伪随机序列的 ±1 电平。信息速率 R_b 变成码片速率 R_c N × R_b占用的带宽也同步扩大 N 倍这个 N 就是处理增益 G_p。处理增益是 CDMA 系统里最核心的一个数字。它决定了三件事一是抗窄带干扰的能力二是多用户同时通信时的隔离度三是接收端解扩后信噪比能恢复多少。换个说法扩频本身并不改善单用户 AWGN 信道下的误码率因为信号和噪声会被一起「平均」它的价值在于让多个用户共用同一段频带时彼此只把对方看成强度可控的干扰。2.2 为什么 CDMA 用 Gold 码而不是单个 m 序列m 序列的自相关特性很好但互相关不够理想不同相位的 m 序列之间互相关峰值可能很高多用户场景下谁碰到峰值谁就被压垮。Gold 码的出现就是为了解决这个问题。Gold 码的生成规则取两个同阶的 m 序列必须是优选对逐位异或就得到一个 Gold 码。改变其中一个 m 序列的相对相位偏移比如整体循环移位 s 位再异或就得到另一个不同的 Gold 码。两个 5 阶 m 序列可以生成 N2 33 个 Gold 码这 33 个码之间的互相关值有明确上界不会出现个别用户干扰特别大的情况。MATLAB 里生成双极性 Gold 码时「异或」可以换成乘法两个只取 ±1 的 m 序列对应位相乘效果和 0/1 域上的异或完全一致这个技巧在写向量化代码时非常省事。2.3 相关接收乘法再累积信噪比是怎么恢复的接收端拿到的是叠加了噪声和干扰的码片序列解扩操作只有两步乘以同一个 Gold 码然后在一个比特周期内累加。设码片能量为 1N 个码片累加后有用信号的幅度变为原来的 N 倍功率变为 N² 倍噪声每个码片独立累加后功率只增加 N 倍。于是解扩后的信噪比正好恢复成 N ×码片信噪比这个 N 就是扩频增益。这部分想明白后面仿真里所有参数换算都不会错。下表是几个常用码长对应的指标关系码长 NGold 码周期处理增益 G_p带宽扩展倍数码片速率 / 信息速率可用 Gold 码数量3114.9 dB31 倍31336318.0 dB63 倍636512721.0 dB127 倍127129提示用户数超过 Gold 码数量后系统进入过载状态误码率会快速恶化仿真曲线直接发散。这不是代码 bug是 CDMA 的容量边界。3. 用 MATLAB 搭 CDMA 链路从最小可运行代码到可调参模型3.1 脚本路线还是 Simulink 路线常见做法是选脚本.m 文件而不选 Simulink。原因有三脚本天然适合批量参数扫描改一个循环就能把 E_b/N_0 从 0 到 10 dB 全跑一遍脚本便于和 git 配合做版本管理交付时对方能直接 diff 改动脚本可以随时插入断言做自动验证Simulink 模型做同样的回归测试成本高得多。3.2 Gold 码生成与最小发射-信道-接收链路先把 m 序列生成器写出来这是 Gold 码的基础。以下代码用反馈移位寄存器实现不依赖通信工具箱function seq mseq(stg, taps, init) % mseq 生成双极性 m 序列 % stg: 寄存器长度 % taps: 反馈抽头位置向量例如 [5 2] 表示第5级和第2级参与反馈 % init: 初始状态长度必须等于 stg元素为 0/1 reg init(:).; len 2^stg - 1; seq zeros(1, len); for i 1:len seq(i) 2 * reg(stg) - 1; % 最高位输出0/1 转成 -1/1 fb mod(sum(reg(taps)), 2); % 反馈位 抽头异或 reg [fb, reg(1:stg-1)]; % 右移 end end这段代码把线性反馈移位寄存器的每一步都显式写出来了输出最高位反馈位由指定抽头异或得到然后整体右移。参数taps对应特征多项式5 阶 m 序列常用 x^5 x^2 1对应taps [5 2]。Gold 码由两个优选 m 序列异或得到。以 5 阶为例m1 用特征多项式 x^5 x^2 1m2 用 x^5 x^4 x^3 x^2 1两者都是课程设计里常用的优选对。下面是完整的最小链路脚本% CDMA 最小链路单用户 AWGN 信道 N 31; % Gold 码长度5阶 EbN0_dB 0:2:10; % 信息比特信噪比 numBits 10000; % 仿真比特数 % 生成两个 m 序列m2 循环移位 s 位得到不同 Gold 码 m1 mseq(5, [5 2], [1 0 0 0 0]); m2 mseq(5, [5 4 3 2 1], [1 0 0 0 0]); s 0; goldCode m1 .* circshift(m2, s); % 双极性域相乘等价于异或 % 发射端BPSK 符号复制 N 份并逐码片相乘 bits randi([0 1], numBits, 1); sym 2 * bits - 1; % 0 - -1, 1 - 1 txMatrix sym * goldCode; % numBits x N每行一个比特 txSignal reshape(txMatrix., [], 1); % 按行展开成码片序列 % AWGN 信道按 Eb/N0 换算码片噪声方差 for idx 1:length(EbN0_dB) EbN0_lin 10^(EbN0_dB(idx) / 10); noiseVar N / (2 * EbN0_lin); % 每个码片的噪声方差 rxSignal txSignal sqrt(noiseVar) * randn(size(txSignal)); % 接收端每 N 个码片重排成矩阵相关解扩 rxMatrix reshape(rxSignal, N, []).; decSym rxMatrix * goldCode. / N; % 累加并归一化 decBits decSym 0; ber(idx) sum(decBits ~ bits) / numBits; end逻辑说明发射端的sym * goldCode是矩阵乘法每一行是一个符号复制 N 次再逐码片相乘的结果等价于对每个比特做扩频。reshape(txMatrix., [], 1)必须转置后再展开否则码片顺序会错。接收端的累加是用矩阵乘rxMatrix * goldCode.一次算完所有比特的相关值除以 N 是为了把信号幅度归一化到 ±1。参数说明noiseVar N / (2 * EbN0_lin)这一行的含义是——每个符号能量是 NN 个码片各带能量 1要模拟特定的 E_b/N_0噪声单边功率谱密度 N0 N / EbN0_lin实部噪声方差取一半所以是N / (2 * EbN0_lin)。这里的N / 2不是随便写的去掉它 BER 曲线会整体偏移约 3 dB。调制方式换成 QPSK 时每个符号带两个比特噪声方差换算要再除以 2。3.3 把代码重构成可调参模型最小链路跑通后下一步是把散落的常量收进一个 struct方便后续批量扫描。常见做法是这样组织参数params.N 31; % Gold 码长度可选 31 / 63 / 127 params.numUsers 2; % 同时通信的用户数必须小于 N params.EbN0_dB 0:2:10; % 信噪比扫描范围 params.numBits 1e5; % 每用户仿真比特数低误码率时加大 params.goldShift [0 7]; % 每个用户对应的 m2 循环移位量 params.m1Taps [5 2]; % 第一个 m 序列的特征多项式 params.m2Taps [5 4 3 2 1]; % 第二个 m 序列的特征多项式 params.initState [1 0 0 0 0];为什么用 struct 不用全局变量仿真脚本一旦变成函数全局变量在批量扫描时会造成状态污染不同的params实例之间互相干扰排错极其痛苦。改成 struct 后每个仿真任务对应一个独立参数快照曲线出问题可以直接回查当时用的配置。推荐从 N31 起步周期短单次仿真秒级完成先验证链路逻辑确认无误后再把params.N改成 63 或 127 观察扩频增益的影响。用户数从 1 加到 3 或 4 就足够看出多用户干扰的趋势不需要一上来就模拟几十个用户。4. 仿真结果解读误码率曲线、多用户干扰与参数边界4.1 先拿单用户 AWGN 的 BER 当标尺单用户 AWGN 信道下扩频不会改变误码率所以理论曲线就是 BPSK 在 AWGN 下的误码率BER 0.5 × erfc(sqrt(E_b/N_0))MATLAB 里对应qfunc(sqrt(2 * EbN0_lin))。跑完上一节的代码把仿真的ber和理论值画在同一张图上berTheory qfunc(sqrt(2 * 10.^(EbN0_dB / 10))); semilogy(EbN0_dB, ber, o-, EbN0_dB, berTheory, x-); grid on; xlabel(E_b/N_0 (dB)); ylabel(误码率 BER); legend(CDMA 仿真, BPSK 理论, Location, southwest);如果两条曲线对不上问题一定在链路实现而不是系统设计。常见错误有两个一是noiseVar里忘了乘 N导致仿真曲线整体右偏二是发端扩频后码片顺序在 reshape 时搞反接收端解扩等于在解一段随机序列BER 会接近 0.5。把这两条曲线重合当作「最小链路验收标准」后续所有参数调整都在这个基准上做回归。4.2 多用户场景干扰是加性且同功率的多用户和单用户唯一的区别在发射端每个用户用自己的 Gold 码扩频信号在信道里叠加。假设两个用户功率相等接收端只解扩用户 1% 用户 1 使用 goldShift0 的 Gold 码用户 2 使用 goldShift7 的 Gold 码 gold1 m1 .* circshift(m2, 0); gold2 m1 .* circshift(m2, 7); sym1 2 * randi([0 1], numBits, 1) - 1; sym2 2 * randi([0 1], numBits, 1) - 1; tx1 reshape((sym1 * gold1)., [], 1); tx2 reshape((sym2 * gold2)., [], 1); % 信号叠加后过 AWGN 信道噪声换算仍按码长 N 计算 rxSignal tx1 tx2 sqrt(noiseVar) * randn(size(tx1)); % 只用 gold1 解扩用户 2 的信号在这里是干扰 rxMatrix reshape(rxSignal, N, []).; decSym1 rxMatrix * gold1. / N; decBits1 decSym1 0;把这条曲线和单用户对比会看到误码率明显抬高。注意一个细节增加用户数时信道里的总功率也在增加如果numBits不变同样的 E_b/N_0 下多用户的 BER 会随用户数单调上升。这不是近远效应而是多址干扰的本质——每个用户的信号对别人来说都是一种加性干扰干扰功率和用户 1 的信号功率同量级单纯提高发射功率解决不了。多用户干扰的典型特征是误码率曲线出现地板效应SNR 继续增大BER 却降不下去。教材里叫 error floor。遇到这种情况第一步不是怀疑代码而是确认用户之间的 Gold 码相位偏移是否两两不同。两个用户如果用了同一个goldShift干扰直接退化成同码干扰地板会高得离谱。4.3 参数边界的三个硬约束第一个硬约束是用户数 K 必须小于码长 N。5 阶 Gold 码最多 33 个超过这个数以后新增用户拿不到正交性足够好的码序列系统过载BER 曲线发散。仿真里见过最典型的「发散」是把 numUsers 设到 50 而 N 只有 31这种跑法没有任何物理意义。第二个硬约束是 E_b/N_0 和码片 SNR 的换算。扩频后码片信噪比 E_b/N_0 - 10log10(N)以 dB 计。如果直接把 E_b/N_0 当作信道 SNR 传给噪声生成函数等于少算了处理增益单用户 BER 会比理论值差出十几 dB。第三个约束是用户间功率差。两个用户功率差超过 10 dB 时近远效应强用户会把弱用户完全压掉弱用户的 BER 曲线会在所有 SNR 点上接近 0.5。仿真里要模拟这种情况只需要把tx2乘一个幅度系数再叠加。真实 CDMA 系统靠功率控制把各用户接收功率拉平仿真里如果没做这一步就默认所有用户E_b相等。下表是仿真中常用的参数组合按目标区分仿真目标N用户数 KE_b/N_0 范围numBits链路正确性验证3110 ~ 10 dB1e4 ~ 1e5多用户干扰观察633 ~ 50 ~ 15 dB1e5低误码率对比12710 ~ 12 dB1e6过载发散演示31400 ~ 10 dB1e45. 一个验证技巧把「使用说明文档」变成能自检的脚本5.1 用「扩频无损」做整链路冒烟测试CDMA 链路最容易犯的错不是理论不懂而是收发两端的小细节互相抵消最后仿真结果碰巧对了。要防止这种情况最有效的验证方法不是多看几次代码而是跑一个「扩频无损」测试单用户 AWGN 下无论 N 取 31 还是 127BER 曲线都应该和 BPSK 理论曲线重合。N 变了代码里的扩频、解扩、噪声换算全都要跟着变如果这些环节有隐性错误两条曲线必然分离。把这个验证写进脚本让交付物自己会说话function validate_cdma_sim() % 冒烟测试单用户 BER 必须逼近 BPSK 理论值 params.N 31; params.numUsers 1; params.EbN0_dB 0:2:10; params.numBits 1e5; berOpt run_cdma_link(params); % 复用主体仿真函数 berTheory qfunc(sqrt(2 * 10.^(params.EbN0_dB / 10))); % 每个 SNR 点上仿真值与理论值的比值应在 0.7 ~ 1.3 之间 ratio berOpt ./ berTheory; assert(all(ratio 0.7 ratio 1.3), 单用户 BER 偏离理论值链路有误); % 再用 N127 复测一次确认扩频路径正确 params.N 127; berLong run_cdma_link(params); assert(max(abs(berLong - berTheory)) 0.3 * max(berTheory), N 改变后结果不一致); disp(冒烟测试通过单用户 BER 与理论曲线吻合); end这段脚本里run_cdma_link是按 struct 参数运行的仿真主函数validate_cdma_sim只做断言不画图。把它设成交付包的入口函数对方拿到后什么都不用配置直接敲validate_cdma_sim如果链路有问题MATLAB 会明确报出「偏离理论值」而不是给一张看不出问题的曲线。这个习惯对「使用说明文档」的落地特别有用——文档可以不写满十页但必须有一个 30 秒内能跑完的验证命令。5.2 使用说明文档里最该写的一段配套的使用说明文档不需要罗列每个函数的接口但必须有三个部分一是运行顺序从validate_cdma_sim开始再到单用户曲线最后到多用户扫描二是参数表把params里每个字段改到什么值会出现什么现象写清楚尤其要写明「增加numUsers会看到地板效应这是正常的」三是结果对照给出典型输出比如 N31、单用户、E_b/N_08 dB 时 BER 应在 2e-4 附近方便使用者判断自己的运行结果是否正常。最后说一个具体的落地动作在仿真脚本的第一行自动打印参数快照用fprintf输出 N、用户数、E_b/N_0 范围和当前时间戳。曲线出问题时截图里的参数打印就是你排错的第一份证据再配合validate_cdma_sim的自检断言CDMA 仿真从交付到二次开发都不需要靠「猜」来定位问题。本文还有配套的精品资源点击获取