news 2026/9/17 14:38:15

MATLAB CDMA扩频通信仿真:从卷积编码到维特比译码与误码率分析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MATLAB CDMA扩频通信仿真:从卷积编码到维特比译码与误码率分析

简介:面向通信工程、电子信息类专业学生及从事无线通信仿真的研究人员,这份资料围绕MATLAB环境下的CDMA系统建模、仿真与性能分析展开,可用于课程设计、毕业设计或通信系统仿真入门参考。压缩包仅含1个doc文件,大小约435KB,内容以正文文档形式集中呈现,便于检索与二次整理。文档从CDMA研究背景、发展现状与趋势切入,依次讲解卷积编码、扩频、调制、信道传输及解调恢复等模块,并涉及AWGN、Rayleigh等信道模型和MATLAB仿真语句、参数设置方法。读者可据此了解完整系统链路搭建思路,掌握各功能模块的实现方式,并借助数据分析与可视化手段评估抗干扰能力和传输效果。目前已有146人学习下载,适合需要快速获取CDMA仿真框架与报告素材的读者参考。

1. CDMA仿真链路里那些「看起来对、跑起来错」的坑

不少人用 MATLAB 搭 CDMA 系统仿真,发射端波形画出来有模有样,可误码率曲线一出来就露馅:信噪比都加到 10 dB 了,BER 还挂在零点几下不去。问题往往不在算法本身,而藏在几个不起眼的接口上——卷积编码的尾比特补没补、交织是按矩阵行读还是列读、扩频码周期有没有和数据长度对齐、解扩之前定时偏差清没清。这些都是仿真里最容易被跳过、又最影响结果的地方。这篇拿一套完整的直接序列扩频 CDMA 链路做样本,从卷积编码一路走到维特比译码,把每个模块的参数含义、MATLAB 实现写法、出错时该看哪个变量逐一拆开。做通信课程设计、毕设仿真的同学可以照着复现,想快速验证扩频体制参数边界的熟手也能从参数表和排错段落里拿到东西。环境用 MATLAB R2018b 及以上都行,涉及的函数在常规安装里都有。

2. CDMA收发链路拆解与关键参数选型

2.1 从信源到信宿的模块串联顺序

一条标准的 DSSS-CDMA 仿真链路,发送方向是:信源产生二进制比特流 → 卷积编码引入纠错冗余 → 交织打散突发错误 → 加扰把数据随机化 → 扩频用高速伪随机码把窄带信号摊到宽带 → QPSK 调制搬上载波 → 送入 AWGN 信道。接收方向严格反过来:相干解调 → 抽样判决 → 解扩 → 解扰 → 解交织 → 维特比译码 → 误码统计。顺序看着简单,但每一步的输入输出维度必须严丝合缝。我通常的做法是先把每一级的「进多少比特、出多少符号、速率变几倍」算清楚再写代码,比边写边调省事得多。速率账大概是:卷积编码(2,1,7)码率 1/2,输出翻倍;交织不改长度;加扰不改长度;扩频每比特乘 N 位扩频码,长度翻 N 倍;QPSK 每两比特合成一个符号,长度减半。走完一趟,符号数 = 信息比特数 × 2 × N / 2 = 信息比特数 × N。

2.2 卷积编码与维特比译码的参数选择

卷积码用 (n, k, m) 描述,k 是每次输入比特数,n 是输出比特数,m 是寄存器级数,编码率 R = k/n,约束长度 L = m+1。工程仿真里最常用的就是 (2,1,7),即码率 1/2、约束长度 7,生成多项式八进制写作 [171 133]。这个组合在纠错能力和译码复杂度之间比较平衡,约束长度再往上加,维特比译码的状态数按 2^(L-1) 指数增长,仿真时间吃不消。N 越大纠错性越好、差错率越低,这是趋势,但不是无脑加大——L=9 时状态数就到 256 了,跑一次长码流会明显变慢。MATLAB 里用poly2trellis描述网格,convenc编码、vitdec译码。要注意尾比特:卷积编码器有记忆,最后要把寄存器清零,发送端末尾补 (L-1) 个 0,接收端译码时用'trunc'模式截断,否则最后几个比特必错,BER 永远压不到零。

2.3 交织与加扰:把突发误码拆成随机误码

交织编码干的事就是把序列顺序打乱。信道上的突发干扰会让一长段连续比特同时出错,纠错码对连续错误很吃力,因为它的纠错能力是按一段窗口内可纠正的错误数设计的。交织把这段连续错误在时间上摊开,落到译码器眼里就变成了分散的随机错误,刚好落在纠错码能力范围内。实现上用矩阵最简单:发送端按列写入、按行读出,接收端反向操作。MATLAB 一行代码就行:interleaved = reshape(reshape(data, rows, cols).', 1, []),解交织再做一次转置还原。加扰则用 PN 序列和数据进行模二加,作用是避免长串 0 或长串 1 导致定时恢复失锁,同时也让频谱更接近白噪声。加扰是可逆的,接收端用同一段 PN 序列再异或一次就能还原,前提是收发两端 PN 序列严格同步——这是整个仿真里最容易埋雷的地方。

2.4 扩频码选型:Walsh、Gold、m 序列怎么选

扩频码直接决定多址能力和抗干扰表现,三种常见序列的差别值得说清楚:

序列类型正交性可用码数典型用途
Walsh 码同周期内严格正交与码长相同前向链路信道区分
Gold 码准正交,互相关有界远多于 m 序列多用户地址码
m 序列自相关尖锐,互相关一般单用户扩频、加扰

Walsh 码的良好正交性体现在同一周期 T 内不同码字积分为零、相同码字积分为 T,所以适合做前向扩展频谱、区分信道类型。但 Walsh 码数量受码长限制,用户一多就不够分。Gold 码由一对优选 m 序列模二加生成,可以产生的地址码数量远多于 m 序列,更适合大型通信系统,代价是互相关不再严格为零,存在多址干扰。单用户仿真阶段用 Walsh 或 m 序列都行,一旦要做多用户 MAI 分析,就得换 Gold 码。

2.5 QPSK调制与AWGN信道参数

调制选 QPSK 而不是 BPSK,同样发射功率下 QPSK 只需一半带宽就能拿到和 BPSK 相同的误码率,而且对某些干扰不敏感。QPSK 用正交调制法产生:二进制码两位一组串行输入,串并转换后宽度翻倍并行输出,极性变换后用两路相互正交的载波分别相乘再相加。设计时两路扩频码 x1(t) 和 x2(t) 码速率同步且相干,但码结构不同、彼此独立,取值为 ±1。信道用 AWGN 是最基础的验证场景,噪声瞬间值服从正态分布、功率谱在很大频带内均匀。想更贴近实际可以换 Rayleigh 或 Rician 衰落信道,但建议先把 AWGN 下的链路跑通、BER 曲线对上理论值,再加衰落,否则出了问题分不清是链路写错还是信道模型引入的。

3. MATLAB发射端建模:从信源到扩频调制

3.1 参数集中配置,别散落在各处

仿真脚本最容易失控的地方就是参数满天飞,改一个码长要翻五个文件。习惯做法是开头写一个参数结构体,后面全部引用它。

% CDMA 仿真全局参数 cfg.Nbits = 10000; % 信息比特数 cfg.k = 1; % 卷积码输入比特数 cfg.n = 2; % 卷积码输出比特数 cfg.L = 7; % 约束长度 cfg.trellis = poly2trellis(cfg.L, [171 133]); % (2,1,7) 网格描述 cfg.SF = 16; % 扩频因子(每比特扩频码片数) cfg.EbN0 = 6; % 每比特信噪比(dB) cfg.rows = 20; % 交织矩阵行数 % 信源:等概二进制序列 rng(2024); % 固定随机种子,保证结果可复现 txBits = randi([0 1], cfg.Nbits, 1);

参数说明:SF是扩频因子,取 16 意味着每个信息比特被 16 个码片承载,扩频增益 10*log10(16) ≈ 12 dB。rows决定交织深度,行数越大抗突发能力越强,但交织时延也越大——实时系统里这是个需要权衡的量。固定rng种子是仿真基本功,不然每次跑出来的 BER 都在抖,根本没法对比参数改动前后的效果。

3.2 卷积编码与交织的MATLAB实现

编码时先把末尾补上 (L-1) 个零清空寄存器,交织用矩阵转置法。

% 卷积编码:末尾补尾比特使寄存器归零 tailBits = zeros(cfg.L-1, 1); txEncoded = convenc([txBits; tailBits], cfg.trellis); % 交织:按列写入、按行读出 nCols = length(txEncoded) / cfg.rows; assert(mod(length(txEncoded), cfg.rows) == 0, '编码后长度必须能被交织行数整除'); txInterleaved = reshape(reshape(txEncoded, cfg.rows, nCols).', 1, []).'; % 加扰:与等长 PN 序列模二加 pnScramble = randi([0 1], length(txInterleaved), 1); txScrambled = mod(txInterleaved + pnScramble, 2);

逻辑说明:convenc按网格对补尾后的比特流编码,输出长度是输入的两倍。reshape两次转置完成矩阵交织,这里加assert是防止长度不是行数整数倍时静默产生错误维度——MATLAB 不会报错,只会悄悄给你一个错误结果。加扰用模二加,接收端异或同一序列即可还原。注意pnScramble必须保存下来给接收端用,或者用同一个种子在两端各生成一次,两者效果等价但后者更省内存。

3.3 扩频码生成与信号扩频

扩频就是把每个比特和一整段扩频码相乘。用 Walsh 码时直接调hadamard,注意把 0/1 映射到 +1/-1 域再运算。

% 生成 Walsh 扩频码并做极性变换(0->+1, 1->-1) walshMat = hadamard(cfg.SF); spreadCode = walshMat(2, :).'; % 取第 2 行作为一个正交码字 spreadCode = 1 - 2*spreadCode; % 映射到 ±1 % 扩频:每个比特重复 SF 次后与扩频码逐片相乘 txSpread = reshape(repmat(txScrambled.', cfg.SF, 1), [], 1) .* ... repmat(spreadCode, length(txScrambled), 1);

参数说明:hadamard(SF)生成 SF×SF 的正交矩阵,每一行是一个正交码字,行与行之间内积为零。这里取第 2 行避免用全 1 的第 1 行,全 1 码字在实际系统里通常留给导频。repmat把每个比特复制 SF 份,再和扩频码逐片相乘,输出长度变成原来的 SF 倍。扩频增益就是从这个倍率来的:信号带宽被展宽 SF 倍,窄带干扰在解扩时只被展宽 SF 分之一,信噪比因此改善。

3.4 QPSK调制与AWGN加噪

% 串并转换 + QPSK 调制 assert(mod(length(txSpread), 2) == 0, '扩频后长度必须为偶数'); txSym = txSpread(1:2:end) + 1j*txSpread(2:end); % I/Q 两路合成复符号 txSym = txSym / sqrt(2); % 归一化到单位功率 % 过 AWGN 信道:按 Eb/N0 加噪 snrDb = cfg.EbN0 + 10*log10(2*cfg.SF); % 符号信噪比 = Eb/N0 + 10log10(码率*SF) rxSym = awgn(txSym, snrDb, 'measured');

逻辑说明:QPSK 每个符号携带两比特,I 路和 Q 路各取一比特,合成复数符号后归一化到单位平均功率,这样awgn的功率假设才成立。'measured'选项让函数先测量输入信号实际功率再加噪,比手动指定信号功率省事且不易出错。符号信噪比和 Eb/N0 的换算关系是仿真里另一个高频错误点:扩频因子把每比特能量摊到 SF 个码片上,换算时必须把 SF 和码率都算进去,写错了 BER 曲线整体平移,形状对但位置不对。

4. 接收端解调、解扩与维特比译码的实测

4.1 相干解调与抽样判决

接收端第一步是把复符号还原成 ±1 的码片序列。理想 AWGN 下没有相位旋转,直接取实部和虚部即可;如果中间加了衰落或频偏,就得先做相位估计再判决。

% 相干解调:复符号拆回 I/Q 两路 rxI = real(rxSym); rxQ = imag(rxSym); % 硬判决:大于零判 +1,否则判 -1 rxChips = zeros(2*length(rxSym), 1); rxChips(1:2:end) = sign(rxI); rxChips(2:2:end) = sign(rxQ);

参数说明:sign在这里做的是硬判决,输出 ±1。如果后面要用软判决维特比译码,就不能在这一步把信息压成硬比特,应该保留rxIrxQ的模拟值,在解扩后按符号给出软信息。软判决译码通常比硬判决有 1~2 dB 的增益,代价是译码器输入位宽变大、计算量增加。这个链路先走硬判决,把流程跑通再说优化。

4.2 解扩和解扰的顺序与同步问题

解扩就是把每个比特对应的 SF 个码片和扩频码相乘再累加,本质是一个相关运算。

% 解扩:分段相关,每 SF 片恢复一个比特 rxDespread = zeros(length(rxScrambled)/cfg.SF, 1); for idx = 1:length(rxDespread) seg = rxScrambled((idx-1)*cfg.SF + (1:cfg.SF)); rxDespread(idx) = sum(seg .* spreadCode) / cfg.SF; % 归一化相关 end rxBitsScrambled = double(rxDespread > 0); % 解扰:与发送端同一 PN 序列异或 rxInterleaved = mod(rxBitsScrambled + pnScramble, 2);

逻辑说明:解扩的相关运算除以 SF 是为了归一化幅度,不除也能判决,但后续和门限比较时容易搞混。这里扩频码spreadCode必须和发送端严格对齐,包括起始位置和周期相位——差一个码片,相关峰值就掉到接近零,解扩输出全是噪声。实际系统里靠同步捕获和跟踪来对齐,仿真里如果收发共用同一个变量,天然同步;一旦你为了验证同步算法故意引入时延,就必须在解扩前补上这个时延,这是最容易忘的一步。解扰在解扩之后做,顺序不能颠倒,否则异或操作会破坏码片级别的相关性。

4.3 解交织与维特比译码

% 解交织:交织的逆操作,按行写入、按列读出 rxDeinterleaved = reshape(reshape(rxInterleaved, nCols, cfg.rows).', [], 1); % 注意:这里行列数和发送端互换了 % 维特比译码:截断模式,去除尾比特 rxDecodedFull = vitdec(rxDeinterleaved, cfg.trellis, cfg.L-1, 'trunc', 'hard'); rxBits = rxDecodedFull(1:cfg.Nbits); % 去掉尾比特对应的输出 % 误码率统计 [errNum, ber] = biterr(txBits, rxBits); fprintf('Eb/N0 = %d dB, 误码数 = %d, BER = %.4e\n', cfg.EbN0, errNum, ber);

参数说明:vitdec的第三个参数是回溯深度,一般取 5 倍约束长度左右,这里给 L-1 是简化写法,长码流下建议加大到 5*L 以接近最优性能。'trunc'表示收发两端都按截断处理、不做收尾,配合发送端补尾比特使用;如果用'term'模式则不补尾比特,两者不能混用。解交织的矩阵维度必须和发送端交织严格互逆,行数列数写反了不会报错,但译码输出会全是错的——这个坑很隐蔽,建议对照发送端代码逐字核对 reshape 的参数顺序。

4.4 各级中间结果与误码率对照

调试时把每一级的比特和发送端对应节点逐一比对,能快速定位问题出在哪一级。

观测节点期望现象异常时的排查方向
解调后码片与发送码片大部分一致判决门限、相位旋转
解扩输出相关峰值明显、噪声段平坦扩频码相位未对齐
解扰输出与交织后序列一致PN 序列不同步
解交织输出与编码后序列一致reshape 行列写反
译码输出高信噪比下 BER 趋近 0尾比特、回溯深度

按这张表逐级打印比对,比盯着最终 BER 猜问题快得多。通常前三级就有一级已经全错,根本走不到译码。

5. 多用户MAI、SNR扫描与仿真提速

5.1 多用户扩频与多址干扰的观察方法

单用户链路跑通后,加第二个用户进来观察 MAI 是最有价值的实验。做法是给每个用户分配不同的扩频码字(Walsh 第 2 行、第 3 行……),各自扩频后叠加再送信道,接收端用自己那行的码字解扩。

% 两用户叠加:各自扩频后相加 user1 = spreadTx(txBits1, walshMat(2,:).', cfg.SF); user2 = spreadTx(txBits2, walshMat(3,:).', cfg.SF); txSum = user1 + user2; % 多址叠加,功率各占一半 % 接收端用 user1 的码字解扩,user2 变成干扰 rx1 = despreadRx(rxSum, walshMat(2,:).', cfg.SF);

Walsh 码在同一周期内严格正交,如果两用户完全同步、码字未受信道破坏,解扩后 user2 的贡献理论上为零,BER 曲线和单用户重合。但一旦引入相对时延、或者换成互相关不为零的 Gold 码,解扩输出里就会混入 user2 的残留分量,这就是 MAI。把两用户功率比调成 10:1 再跑一次,能看到弱用户被强用户压制,这正好对应实际系统里需要功率控制的原因。做这个实验时记得把用户数、码字类型、功率比记下来,MAI 的严重程度和这三个量都强相关。

5.2 信噪比扫描画误码率曲线

单个 SNR 点的 BER 说明不了什么,扫一条曲线才能看出链路是否健康。

ebn0List = 0:2:10; berCurve = zeros(size(ebn0List)); for k = 1:length(ebn0List) cfg.EbN0 = ebn0List(k); [~, berCurve(k)] = runOneShot(cfg, txBits, spreadCode, pnScramble); end semilogy(ebn0List, berCurve, 'o-'); grid on; xlabel('Eb/N0 (dB)'); ylabel('BER');

判断曲线是否正常的经验法则:加了 (2,1,7) 卷积码和硬判决译码的 BPSK/QPSK,在 Eb/N0 = 6 dB 附近 BER 应该落到 1e-4 量级,曲线斜率大概每 dB 下降接近一个数量级。如果曲线整体右移,先查信噪比换算;如果高 SNR 段下不去、出现误码平台,十有八九是同步或尾比特问题,而不是噪声太大。把有编码和无编码两条曲线画在一起,编码增益一目了然,这也是毕设报告里最能说明问题的图。

5.3 常见报错与提速技巧

现象原因处理
reshape维度不匹配长度不能被行数整除调交织行数或补零
BER 始终约 0.5扩频码相位错位检查解扩起始索引
高 SNR 有误码平台未补尾比特或回溯深度不够补 L-1 个零、加大回溯
仿真跑几十分钟循环解扩、向量逐点操作改矩阵运算、预分配数组
每次 BER 都不同未固定随机种子开头rng(seed)

提速上最有效的一招是把解扩的 for 循环换成矩阵乘法:把接收码片按 SF 重排成矩阵,和扩频码做一次矩阵向量乘,速度能差一个数量级。另外 MATLAB 里通信工具箱的comm.*系列系统对象支持流式处理,处理长码流时比函数调用省内存。跑参数扫描前先预分配berCurve数组,别在循环里动态增长,这个习惯在扫描点数多的时候能省不少时间。最后一点,仿真时间主要花在维特比译码上,如果只是调链路通不通,先把码长压到几千比特验证逻辑,确认无误再放大到完整长度跑最终曲线。

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

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

DX12入门避坑:从Device到贴图三角形的25集实战

1. 老教程一上手就卡住,问题多半出在环境判断上如果你最近在搜索引擎里敲过DX12入门,八成会翻到那批2016年前后写的老文章。它们有个共同特点:代码框架看着完整,但照着敲下来,你会在D3D12CreateDevice这一步就收到一个…

作者头像 李华
网站建设 2026/9/17 14:36:11

Cadence SIP Layout设计本质:从PCB思维到系统级物理建模

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/17 14:36:05

UniApp跨端文件下载保存预览全链路解决方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/17 14:35:39

RedHat 7.7部署Oracle 19c实操指南:避坑、静默安装与systemd服务

1. 这不是“点下一步”的安装指南,而是我在三台RedHat 7.7物理服务器上亲手装了七遍19c后,把所有血泪教训揉进来的实操手册你搜“RedHat 7.7 安装19c”,页面上全是零散的命令片段、缺参数的截图、没上下文的报错截图,甚至还有把12…

作者头像 李华
网站建设 2026/9/17 14:33:05

大数据毕设项目:基于 Django 的多景区旅游数据整合与可视化展示系统 基于 Django 的旅游点评数据挖掘与可视化分析平台 (源码+文档,讲解、调试运行,定制等)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

作者头像 李华
网站建设 2026/9/17 14:31:02

小红书数据采集实战:公开页面解析与反爬应对全指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华