简介面向码分多址通信与移动通信课程学习者这份资源聚焦IS-95前向链路系统仿真实现完整覆盖扩频调制、伪随机序列生成、匹配滤波、软硬判决译码及RAKE接收分集等关键模块适合需要理解第三代移动通信基础理论或复现链路仿真的学生与研究人员。压缩包共13个文件含10个MATLAB脚本、2个仿真结果数据文件和1份说明文档整体大小3.83MB脚本覆盖调制、解调、维特比译码、RAKE合并等子功能文档辅助梳理算法流程目录分类存放便于检索。已有148人浏览学习。通过运行主仿真脚本可直观观察直接序列扩频、多径合并和高斯白噪声信道下的误码率表现也能结合文档核对算法实现支持调整参数以对比不同信噪比下的接收性能用于课程设计或毕业设计参考价值较高。1. IS95前向链路系统到底在仿真什么一个RAKE接收机的最低可用闭环第一次拿到 IS95前向链路系统这套仿真时大多数人的关注点是 CDMA 的扩频与解扩真正拉开差距的是一个能处理多径的 RAKE 接收机。IS-95 前向链路之所以把导频信道单独设出来就是为了让接收端可以从多条时延路径里分别做信道估计再把能量合并起来。对刚接触无线物理层仿真的从业者而言这套系统最适合用来回答三个问题正交 Walsh 码为什么在前向链路里成立多径为什么不全都是坏事以及 RAKE 的合并增益到底是怎么算出来的。下面不展开协议帧格式按发射端、信道、接收端 RAKE 这条链把可复现的仿真结构、关键参数和容易踩的坑一次讲透。2. 前向链路信道结构与RAKE接收机原理先从码域和多径说起2.1 四种信道导频、同步、寻呼、业务谁的功率大IS-95 前向链路是从基站到手机的方向采用 1.2288 Mcps 码片速率信道化用的是 64 阶 Walsh 码也就是每个 Walsh 码字占用 64 个码片来表示一个符号。码域上被分成若干并行信道导频信道固定用 W0同步信道用 W32寻呼信道常用 W1其余编号分给业务信道。一个仿真里最容易被忽略的动作是给这些信道分配发射功率。导频不是“加一点意思一下”它的功率占比直接影响 RAKE 的信道估计质量。在前向链路里导频信道通常占总功率的 15% 到 20%同步信道和寻呼信道各占几个百分点剩余功率几乎全部给业务信道。导频功率占比过小手机在小区边缘就搜不到你RAKE 合并时的信道估计会跟着抖动占比过大业务信道被压低用户速率和容量都上不去。仿真参数表里常见的做法是设置“导频增益”而不仅仅是“导频功率占比”因为代码里经常拿幅度相加幅度和功率差着一个平方关系。下表是我习惯上先填入仿真的一组基准值实际项目里应根据覆盖场景再调。信道Walsh 码字典型功率占比在 RAKE 里的用途导频W015% ~ 20%路径搜索、时延锁定、信道估计同步W322% ~ 5%帧定时和系统参数寻呼W15% ~ 10%呼叫控制业务按分配剩余功率语音/数据复用功率分配在仿真里也不是一次性到位的。先固定导频和业务的最小幅度把其他信道全部暂时关掉等 RAKE 把单用户链路跑通再逐步加入同步、寻呼和其他业务信道。前向链路正交性的前提是所有信道码片严格对齐这个对齐在仿真里靠 Walsh 矩阵生成顺序和切片位置保证一旦 Walsh 编号写错后续所有解扩都会跟着错。2.2 RAKE的三个手指相关器、搜索器、合并器RAKE 接收机名字很形象但“手指”不是物理天线而是接收端里一组并行工作的相关器。常见配置是三个手指每个手指负责锁定一条多径先让本地短 PN 码对准这条多径的时延然后用对应的 Walsh 码做解扩得到一个带信道幅相信息的数据符号最后由合并器把这些符号做最大比合并。如果只讲数学一条多径的基带形式可以写成 h_l·x(t-τ_l)。三根手指做的事情就是分别估算 (h_0, τ_0)、(h_1, τ_1)、(h_2, τ_2)再把解扩后的结果按权重叠加。最大比合并的标准形式是 y Σ h_l^* · r_l这里 h_l 来自导频信道估计r_l 是业务信道解扩后的符号。很多第一次做仿真的人把“合并”理解成直接把几个相关性求和忽略权重中的共轭导致相位没有对齐合出来还不如单径。搜索器是 RAKE 里容易被忽略的模块。接收端不可能知道多径时延是多少需要先用导频信道做滑动相关找到相关峰的位置。前向链路因为所有基站短 PN 码相同只是相位偏移不同搜索器要处理的时延范围通常不会太大一段 64 到 256 码片的搜索窗就够。仿真里如果直接用已知时延喂给接收机能快速验证算法但做完一定要回到搜索器结构否则你看不到“路径分配”这个环节给实际接收机带来的差异。2.3 为什么前向链路比反向链路更适合用RAKEIS-95 反向链路是多用户共享同一频率每个用户由长 PN 码区分基站接收端需要做非相干或导频辅助解调RAKE 也能用但训练和功率控制限制更多。前向链路则天然适合 RAKE原因是基站只发一套公共导频所有用户共享手机的 RAKE 可以直接拿导频做相干信道估计。同时前向链路靠短 PN 码加 Walsh 码区分信道只要路径时延对准路径间正交性就能恢复。多径在这个系统里实际上是资源不是纯粹的干扰。没有 RAKE 时多径会造成符号间干扰有 RAKE 时每条路径都被解扩出来变成对判决有用的能量。这也是为什么在 IS95 前向链路仿真里增加多径数量经常能提升 BER而不是恶化 BER前提是 RAKE 的搜索器和 finger 数量跟得上。理解了这一点再看后面仿真里的各种“诡异”现象就会轻松一些。3. 把IS95前向链路系统跑起来最小可复现的Python仿真3.1 参数清单码片速率、Walsh长度、RAKE路数一套能说明问题的前向链路仿真不需要把 IS-95 协议全部实现。我习惯先按物理层最小闭环来搭一个业务信道、一个导频信道、三条多径、一个三 finger RAKE。先把这套基带数字模型跑通再往里面补同步、寻呼、功率控制、帧结构。下面是仿真里的基础参数可以直接抄走。参数典型值本仿真取值说明码片速率1.2288 Mcps1.2288e6IS-95 固定值仿真可归一化Walsh 长度6464前向链路信道化码长业务 Walsh 编号1010不与导频、寻呼冲突即可导频增益幅度比 0.2~0.50.3对应功率占比约 9%更接近低导频配置多径时延0~5 码片[0, 1, 3]城市环境典型时延扩展多径幅度随距离衰减[0.9, 0.45, 0.25]幅度比不是功率比RAKE finger 数2~43与多径数保持一致码片速率在 Python 仿真里通常不需要真的每秒生成 1.2288e6 个点关键是把码片速率和时间关系保持正确。比如 64 个码片承载一个符号符号周期就是 64 个码片多径时延写成“几个码片”而不是绝对时间后面调整时延扩展时更直观。先用随机数据跑不能直接说明 BER 就达标但能验证所有模块连接是否完整。3.2 发射端代码利用Walsh正交性与PN扩频发射端先做 Walsh 正交调制再做 PN 扩频。注意 Walsh 码和 PN 码是两次不同的操作Walsh 用来区分前向信道PN 用来区分基站相位并抹平频谱。仿真里最容易出错的是把两者顺序颠倒或者把 Walsh 码当成扩频码。下面这段是发射端的核心代码。import numpy as np SF 64 # Walsh 码长 NUM_SYM 800 # 仿真符号数 N_CHIPS SF * NUM_SYM # 总码片数 TRAFFIC_WALSH 10 # 业务信道使用的 Walsh 编号 PILOT_GAIN 0.3 # 导频幅度增益 # 生成 Hadamard-Walsh 矩阵 walsh np.array([[1, 1], [1, -1]]) while walsh.shape[0] SF: walsh np.block([[walsh, walsh], [walsh, -walsh]]) w_pilot walsh[0] # 导频信道 W0全 1 w_traf walsh[TRAFFIC_WALSH] # 业务信道 # 简化版 15 位 m 序列用于仿真与 IS-95 标准抽头不同 # 但结构一致发射端和接收端共用同一个序列即可复现 def lfsr_pn(nchips, seed0x7fff): reg seed out np.zeros(nchips, dtypenp.int8) for n in range(nchips): out[n] reg 1 # m 序列反馈抽头15 位长度 fb ((reg 14) 1) ^ ((reg 13) 1) reg ((reg 1) | fb) 0x7fff return 2.0 * out - 1.0 pn lfsr_pn(N_CHIPS) rng np.random.default_rng(42) bit rng.choice([-1.0, 1.0], sizeNUM_SYM) # 发射基带业务信道 导频信道 tx_traffic np.zeros(N_CHIPS) tx_pilot np.zeros(N_CHIPS) for i, b in enumerate(bit): base i * SF tx_traffic[base:baseSF] b * w_traf tx_pilot[base:baseSF] w_pilot # 导频符号固定为 1 # 叠加导频并按总功率归一化 tx_unscaled tx_traffic PILOT_GAIN * tx_pilot tx_unscaled tx_unscaled / np.sqrt(np.mean(tx_unscaled**2)) tx tx_unscaled * pn # PN 扩频逻辑说明tx_traffic把每个业务符号先复制成 64 个码片再逐位乘上 Walsh 码字tx_pilot直接铺 W0。W0 全 1所以导频部分没有编码数据只提供参考相位。tx_unscaled做完功率归一化后再乘 PN 序列这一步必须放在 Walsh 调制之后。参数说明PILOT_GAIN0.3是幅度0.3平方对应约 9% 的功率占比如果项目里要求导频功率 20%幅度应设置为np.sqrt(0.2)左右不要直接用功率值加在幅度上。这段里的 PN 用的是简化 m 序列不是 IS-95 标准短码。标准的 15 位短码还包含一个特殊补零逻辑周期接近 2^15 码片仿真里用同样结构的 PRBS 足以验证 RAKE 算法。真正做设备级仿真时再把生成多项式换成 3GPP2 文档里的抽头定义其他代码结构不用变。3.3 多径信道代码给RAKE提供分集素材信道模块把发射码片序列分别延迟、衰减、叠加再加入白噪声。多径延迟用码片整数倍来切是最直接的做法实际信道里时延不是码片整数倍通常还要过一组分数时延滤波器或重采样器。仿真初期用整数倍最容易排错跑通后再换分数时延效果差异主要反映在 finger 抽头位置的精度上。# 多径条件时延单位是码片幅度是线性幅度 DELAYS [0, 1, 3] GAINS [0.9, 0.45, 0.25] rx np.zeros(N_CHIPS) for d, g in zip(DELAYS, GAINS): if d 0: rx g * tx else: rx[d:] g * tx[:-d] # 右移 d 个码片等价于时延 # 加白噪声这里按码片级 SNR 粗略设置 SNR_DB 2.0 noise_power np.mean(rx**2) / (10 ** (SNR_DB / 10)) rx np.sqrt(noise_power) * rng.standard_normal(N_CHIPS)逻辑说明rx[d:] g * tx[:-d]是纯 Python 数组右移表示路径 d 的输出到达时间比主径晚 d 个码片。写这段时要注意发射序列末尾的截断真实仿真里应在帧头留一段保护间隔否则最后一条路径会截掉尾部符号。参数说明GAINS是幅度不是功率三路功率关系是 0.9²、0.45²、0.25²这样第一条路径占绝对主导适合先验证三 finger 是否能把小径能量也合并进来。加噪基准SNR_DB2.0看起来偏低但注意这是码片级 SNR。IS-95 处理增益是 64 倍一个符号解扩后信噪比会提高大约 18 dB所以码片级 2 dB 对应符号级约 20 dB对 BPSK 来说已经能跑出接近无误码的结果。如果没有这个概念直接把符号级 SNR 填到码片上你会发现输出 BER 全零以为系统没问题实际上是在掩盖信道损伤。3.4 接收端RAKE代码导频估计与最大比合并接收端要做三件事去掉 PN、解 Walsh、从导频里提取信道估计然后合并。下面这段是三 finger RAKE 的实现骨架采用与发射端相同结构的 PN 序列。注意这里先固定多径时延已知省去搜索器目的是把合并算法单独验证一遍。# RAKE 接收机每个 finger 对齐一条多径 sym_data np.zeros((NUM_SYM, len(DELAYS))) sym_pilot np.zeros((NUM_SYM, len(DELAYS))) for f, d in enumerate(DELAYS): # 本地 PN 对齐时延np.roll 是简化写法边缘有 wrap pn_local np.roll(pn, -d) rxd rx * pn_local for i in range(NUM_SYM): seg rxd[i*SF d : i*SF d SF] sym_data[i, f] np.dot(seg, w_traf) # 业务解扩 sym_pilot[i, f] np.dot(seg, w_pilot) # 导频解扩 # 最大比合并导频解扩结果作为信道估计权重 hard np.zeros(NUM_SYM) for i in range(NUM_SYM): weight sym_pilot[i, :] # 复数场景取 conj() comb np.sum(weight * sym_data[i, :]) hard[i] 1.0 if comb 0 else -1.0 BER np.mean(hard ! bit) print(fRAKE BER {BER:.4f})逻辑说明每个 finger 先乘本地 PN 码把扰码去掉切片位置从i*SFd开始这样符号边界对齐的是这条路径的到达时刻然后分别与业务 Walsh 码和导频 Walsh 码做相关。导频解扩结果在路径增益和相位已知的情况下就代表信道系数 h_l用它去乘业务解扩结果就是最大比合并的权重相乘。最后按符号判决并统计 BER。参数说明np.roll(pn, -d)是循环移位对多径时延长到接近帧长度时不安全正式仿真应在发射数据尾部丢弃最后max(DELAYS)个码片接收也只用有效区间。复数场景里权重需要取共轭本文里是实数信道所以省略。把这三段代码连起来保存为同一脚本就能得到一个完整的前向链路 RAKE 最小仿真。如果 BER 在 0.01 以下基本说明发射、信道、接收这条链路是通的可以进入参数调优阶段。4. 把RAKE接收机调好搜索窗口、finger数与导频滤波系数4.1 搜索器如何决定finger分配搜索窗和门限实际 RAKE 不知道路径时延需要靠搜索器在导频信道里做相关峰扫描。搜索窗长度要根据环境时延扩展来定城区环境下前向链路多径时延扩展通常在几微秒量级对应到 1.2288 Mcps 就是 3 到 10 个码片。我一般先开 64 码片的窗半码片步进扫描这样能用 128 个相关值覆盖整段多径。找到相关峰后不是把前三个最大的峰直接分配而是要加一个门限比如峰值的十分之一。低于门限的峰大多是噪声或旁瓣强行分配给 finger 只会往合并器里灌噪声。路径分配还有个常见原则任意两个 finger 的时延差不能小于一个码片。因为 RAKE 的分集收益来自不相关性隔得太近的路径本质上是同一条路径的旁瓣分配过去只是浪费资源。如果搜索器扫出两个相距 0.7 码片的峰直接保留能量更高那个另一个舍掉。这个“最小间隔”参数在仿真里一般设为 64 个码片样本的若干倍具体值取决于你仿真采用的过采样倍数。4.2 finger数多少合适仿真和工程的不一样的取舍仿真里很多人一上来就把 finger 数设为 6其实没这个必要。IS-95 前向链路的典型移动台用 3 个 finger基站侧也不会把所有路径都分给一个用户。分集增益随着 finger 数增加是递减的主径占 80% 能量时加第二根手指能带来明显收益加到第三根手指边际变小再加第四根基本属于浪费硬件和功耗。做设计时先看你的多径能量分布用搜索器统计出强路径数量再定 finger 数。如果我的仿真多径参数是 [0.9, 0.45, 0.25]三根手指就是合适的第一根吃主径第二根吃中等路径第三根收小路径能量。如果把 finger 改成两根第三径完全丢失BER 会有可见恶化改成四根MERGE 时第四根可能只收到噪声甚至拉低性能。对比试验很有说服力在相同信道下分别跑 1 finger、2 finger、3 finger你会看到 BER 差异非常直观这就是给你的 RAKE 必要性留下证据。finger 数对应配置预期效果1只收主径受多径衰落影响最明显2主径 一条强径增益明显BER 降低一个量级3主径 两条强径适合 3 条路径的仿真4多收噪声对三径而言几无增益4.3 导频估计的平滑系数对移动速度很敏感RAKE 里最大比合并的权重来自导频信道估计而单次导频解扩的瞬时值噪声很大需要平滑。常见的做法是一阶 IIR 滤波h_est (1-α) * h_est α * h_instant。α 选大选小是个纠结点α 太大估计值追得上快变信道但噪声滤不干净α 太小估计跟不上移动速度一快就出现相位滞后合并权重错位。在 IS-95 前向链路里导频符号率是 19.2 ksymbol/s一个导频符号持续 64 个码片约 52 μs。低速场景我用 α0.1 到 0.2高速场景会提高到 0.3 到 0.5具体要看 BER 随速度的拐点。还有一种做法是滑动窗平均窗口长度取 4 到 8 个导频符号简单且不容易出现 IIR 滤波器的初始化问题。注意做相干合并时信道估计不要做太深的滤波否则路径相位在快衰落里已经漂移了你还在用上一轮的估计去合并。5. 避坑与排查IS95前向链路仿真中最常见的6个翻车点5.1 现象把所有信道的 BER 都统计成业务信道结果差异巨大原因没有先确认业务 Walsh 编号和接收端解扩用的是同一个编号。Walsh 矩阵生成方式不一致尤其在使用np.block拼接 Hadamard 矩阵时矩阵的行顺序和 IS-95 标准定义不一定相同W10 在不同矩阵里对应的码字完全不同。解决在发射端和接收端之间只传w_traf这个向量不要传“10”这个编号跑通后再去核对标准 Walsh 序号。5.2 现象RAKE 的 BER 比单径 RAKE 还差原因导频功率设置过高或过低。如果导频幅度达到 0.5业务信号被压缩数据解扩的 SNR 下降最大比合并权重虽然更准但数据符号本身太弱合并后还是不如单径。解决把导频幅度和业务幅度一起归一到总功率不要分别给两个信道都按绝对幅度叠加。常见做法是先固定数据幅度为 1导频幅度在 0.2 到 0.4 之间扫一遍找到 BER 最低点。5.3 现象时延改成 0.5 码片之后RAKE 合并性能剧烈下降原因整数码片延时代码只能平移整数倍样本0.5 码片需要过采样后做多相滤波或重采样直接用整点采样代替分数时延会丢失能量。解决先对基带波形做 4 倍或 8 倍过采样再把“延迟 d 个码片”换算成“延迟 d*采样倍数 个采样点”。过采样同时也解决了匹配滤波和指纹抽头精度问题代价是仿真长度增加但这是值得的。5.4 现象明明信噪比很高BER 却下不来看起来像有误码地板原因没有把信道估计和业务路径对齐或者np.roll循环移位导致第一条路径的尾部数据卷到了帧头。循环移位在短帧里会把末尾码片挪到开头相关器跳到错误符号边界。解决发射时留出max(DELAYS)码片的前导和尾随零接收只统计有效段不让 wrap-around 进入符号窗。5.5 现象复数信道下把权重直接乘业务符号星座图乱转原因最大比合并要求权重取信道估计的共轭。很多人在实数仿真里写weight * data复数场景忘了加np.conj(weight)结果两条路径相位的共轭不匹配叠加后互相抵消。解决统一在合并器里写成np.sum(np.conj(weight) * data)实数信道下结果不变复数信道下自然正确。5.6 现象加寻呼和同步信道后业务信道 PERFORMANCE 突然下降原因这几路信道叠加到发射端以后总功率归一化改变了导频功率占比。发射端在加法后直接除均方根导致导频实际功率低于预期接收端导频估计变差。解决先把所有信道的幅度配置好统一设计成目标功率比再做归一化不要临时往加法器里面塞没有功率预算的信道。6. 进阶验证用BER曲线和finger开关验证你的RAKE实现验证一套 RAKE 仿真光看一个 BER 数值不够。我习惯做一组“finger 开关”实验在同一个信道条件下分别关闭第 2、3 个 finger记录 BER。如果打开第 3 个 finger 后 BER 没有明显降低要么是这条路径在该 SNR 下已低于合并门限要么是搜索器把路径分配错了。这个对照实验能直接反映你的 RAKE 资源是不是都用在刀刃上。# finger 开关对照只有第一根手指参与合并 ber_1 [] for trial in range(10): # 重新生成同一信道参数只保留 DELAYS[0] # 合并时 sym_pilot[:, 1:] 和 sym_data[:, 1:] 置零即可 pass # 实际脚本里用循环参数化完成代码示意里不需要真去重写整个链路而是在合并循环里加一个启用掩码。比如active [True, True, False]合并时只把active[f]对应的权重和数据纳入。这样能快速看到手指增多带来的增益曲线。如果三 finger 比单 finger 在 BER1e-3 附近能省出大约 3 dB 到 5 dB你的 RAKE 实现就是合格的。最后的习惯性检查把发射端 PN 序列和接收端本地 PN 序列随机错开几个码片看 BER 是不是立刻变成 0.5。这个测试能避免你在把难度逐步加到 4 倍过采样、64 个业务信道、动态信道估计之后突然发现基本功没打牢。先跑通最小闭环再做复杂度扩展这套顺序我用了很多年希望帮到你少踩几个坑。本文还有配套的精品资源点击获取