简介这份资源面向通信工程、无线通信课程学习者及CDMA仿真入门者聚焦IS-95前向链路系统的建模与验证帮助理解扩频调制、多径衰落与RAKE接收等核心机制。压缩包共13个文件以10个m脚本、2个mat数据文件和1份doc说明文档为主脚本覆盖PN序列生成、调制解调、维特比编译码及软硬判决接收等模块mat文件保存仿真中间结果doc文档提供整体说明包体约3.83MB。已有148人学习下载。读者可借助这些模块搭建从基站发送、AWGN信道传播到移动台接收的完整链路观察误码率与数据速率等指标并深入理解RAKE接收机对多径信号的同步、解扩与合并思路为后续3G及更复杂无线通信系统的仿真设计打下基础。1. IS95 前向链路与 Rake 接收从一份仿真压缩包说起如果你手头正好有一个叫IS95前向链路系统.zip的工程打开后看到IS95、前向链路、rake这几个关键词大概率会有点懵——它到底是一个完整的链路级仿真还是只做了接收端的一部分我最初接触这类工程时也踩过同样的坑以为解压就能跑结果发现缺参数、缺信道模型、缺测试向量最后只能自己从头把 IS95 前向链路的物理层逻辑重新捋一遍。IS95 是 CDMA 体系里非常经典的一套空中接口标准前向链路指的是基站到移动台这条方向而 Rake 接收机则是对抗多径衰落的核心手段。这三者凑在一起本质上就是一个完整的「发射—信道—接收」闭环仿真。这篇文章不打算复述教科书而是按我实际复现这类工程的顺序把 IS95 前向链路的帧结构、扩频调制、多径信道建模、Rake 接收机实现以及仿真验证方法一步步拆开讲。适合正在做链路级仿真、通信算法验证或者需要从零搭建 CDMA 接收机的工程师。2. IS95 前向链路的物理层结构先搞清楚数据怎么走2.1 前向链路的信道组成与速率匹配IS95 前向链路并不是一条单一的数据流而是由导频信道、同步信道、寻呼信道和业务信道复用而成的。导频信道不携带信息只发送全零的 Walsh 码作用是让移动台做相干解调和信道估计同步信道负责传递系统时间和配置参数寻呼信道承载控制消息业务信道才是真正传用户数据的地方。每个信道用不同的 Walsh 函数做正交扩频前向链路一共 64 个 Walsh 码码长 64速率 1.2288 Mcps。业务信道的处理链比较长先做卷积编码IS95 用的是约束长度 9、码率 1/2 的卷积码然后做符号重复根据数据速率决定重复次数接着是块交织交织深度 20 ms再经过长码扰码、Walsh 扩频、I/Q 支路 PN 短码扩频最后成型滤波输出。这里最容易翻车的地方是速率匹配——IS95 支持 9600 bps 和 14400 bps 两种速率集不同速率下重复次数和调制方式都不一样。如果你拿到的工程里没有明确标注速率集跑出来的误码率曲线会完全对不上。参数速率集 1速率集 2最高速率9600 bps14400 bps卷积码率1/21/2调制方式BPSKBPSK扩频带宽1.2288 MHz1.2288 MHz帧长20 ms20 ms2.2 扩频与调制Walsh 码和 PN 码的分工前向链路的扩频分两步走。第一步是 Walsh 扩频用 64 阶 Walsh 函数把不同信道分开这一步实现的是正交复用。第二步是 PN 短码扩频I 路和 Q 路分别用两个不同的 PN 序列周期是 2^15速率 1.2288 Mcps。PN 短码的作用是区分基站不同基站用不同的 PN 偏置。在代码实现上Walsh 码可以直接用 Hadamard 矩阵生成PN 短码一般用移位寄存器生成。下面是一个生成 Walsh 码和 PN 短码的 Python 片段import numpy as np def generate_walsh(n): 生成 n 阶 Walsh 码矩阵n 必须是 2 的幂 H np.array([[1]]) while H.shape[0] n: H np.block([[H, H], [H, -H]]) return H def generate_pn_short(seed0b100000000000001): 生成 IS95 前向链路 I 路 PN 短码周期 2^15-1 taps [0, 1, 2, 3, 5, 6, 8, 9, 11, 12, 13, 14] # 特征多项式抽头 state seed pn [] for _ in range(2**15 - 1): pn.append(state 1) feedback 0 for t in taps: feedback ^ (state t) 1 state ((state 1) | feedback) 0x7FFF return np.array(pn) * 2 - 1 # 映射到 1/-1 # 生成 64 阶 Walsh 码 walsh generate_walsh(64) print(fWalsh 矩阵形状: {walsh.shape}) # 生成 I 路 PN 短码 pn_i generate_pn_short() print(fPN 短码长度: {len(pn_i)})这段代码里generate_walsh用递归方式构造 Hadamard 矩阵得到的行向量就是各个信道的 Walsh 码。generate_pn_short用线性反馈移位寄存器生成 PN 序列抽头位置对应 IS95 标准里定义的特征多项式。注意 PN 序列长度是 2^15-1不是 2^15因为全零状态被排除了。实际工程里为了对齐符号边界通常会在序列末尾补一个码片。参数上需要留意Walsh 码的索引决定了信道号导频信道固定用第 0 号 Walsh 码PN 短码的偏置决定了基站身份偏置步长是 64 个码片。如果你在仿真里把这两个参数搞混接收端解出来的就是噪声。3. 多径信道建模Rake 接收机为什么需要它3.1 多径衰落的数学描述与仿真参数Rake 接收机的存在意义完全建立在多径信道上。无线信号从基站到移动台会经过反射、散射、绕射到达接收端时变成多个不同时延、不同幅度、不同相位的副本。IS95 的码片速率是 1.2288 Mcps对应码片周期约 0.814 微秒而典型城市环境的多径时延扩展在 1 到 3 微秒量级也就是说多径副本之间会相隔 1 到 4 个码片。这个时间差正好让 Rake 接收机可以用多个相关器分别捕获不同路径。常见的信道模型是 Jakes 模型或者它的简化版本。Jakes 模型描述的是瑞利衰落适用于没有直射路径的场景如果存在直射路径就要用 Rician 模型。在链路级仿真里我一般用抽头延迟线模型来近似每条路径用一个复数增益表示增益的幅度服从瑞利分布相位服从均匀分布增益随时间变化的相关性由多普勒频移决定。参数典型值说明码片速率1.2288 McpsIS95 标准多径时延扩展1~3 μs城市环境多普勒频移5~200 Hz对应不同车速路径数2~6 条仿真常用路径功率分布指数衰减第一条路径最强3.2 用 Python 搭建一个可复现的多径信道下面这段代码实现了一个离散抽头延迟线信道模型支持多普勒频移和指数功率衰减import numpy as np def rayleigh_fading(N, fd, fs): 生成瑞利衰落序列 N: 采样点数 fd: 多普勒频移 (Hz) fs: 采样率 (Hz) # 用两个独立高斯过程经过多普勒滤波器 from scipy.signal import butter, lfilter b, a butter(2, fd / (fs / 2), btypelow) g1 lfilter(b, a, np.random.randn(N)) g2 lfilter(b, a, np.random.randn(N)) return (g1 1j * g2) / np.sqrt(2) def multipath_channel(signal, delays, powers, fd, fs): 多径信道仿真 signal: 输入信号 delays: 各路径时延采样点数 powers: 各路径功率线性值 fd: 多普勒频移 fs: 采样率 N len(signal) output np.zeros(N, dtypecomplex) for delay, power in zip(delays, powers): fading rayleigh_fading(N, fd, fs) delayed np.zeros(N, dtypecomplex) if delay N: delayed[delay:] signal[:N - delay] output np.sqrt(power) * fading * delayed return output # 示例3 条路径时延分别为 0, 2, 4 个采样点 fs 4 * 1.2288e6 # 4 倍过采样 fd 50 # 多普勒频移 50 Hz delays [0, 2, 4] powers [1.0, 0.5, 0.25] # 指数衰减 # 假设输入是一个简单的 BPSK 符号序列 symbols np.random.choice([-1, 1], 1000) 0j rx_signal multipath_channel(symbols, delays, powers, fd, fs) print(f接收信号长度: {len(rx_signal)}) print(f前 5 个采样点: {rx_signal[:5]})rayleigh_fading用低通滤波法生成瑞利衰落滤波器的截止频率由多普勒频移决定。multipath_channel把输入信号按不同时延和功率加权叠加每条路径独立生成衰落系数。这里用 4 倍过采样是为了模拟真实接收机的采样过程实际仿真里可以根据需要调整。参数delays的单位是采样点数不是绝对时间换算时要注意采样率。powers用线性值而不是 dB如果拿到的信道模型给的是 dB记得先转换。这个模型跑通之后你可以把接收信号和原始信号对比观察多径造成的码间干扰。接下来 Rake 接收机要做的就是从这些混叠的副本里把能量重新收集起来。4. Rake 接收机实现从相关器到合并器4.1 Rake 接收机的结构选择相干还是非相干Rake 接收机的核心思想是「分集合并」用多个相关器也叫 finger分别跟踪不同时延的路径每个 finger 解调出一路符号最后按某种准则合并。合并方式主要有三种最大比合并MRC、等增益合并EGC和选择式合并。IS95 前向链路有导频信道所以移动台可以做相干解调MRC 是最优选择。结构上每个 finger 包含一个本地 PN 码生成器、一个相关器和一个信道估计模块。PN 码的相位要对准对应路径的时延信道估计用导频信道做。合并时把各 finger 的输出按信道估计的共轭加权求和。这里有个容易忽略的点finger 的搜索和跟踪。实际系统里多径时延是变化的finger 要能动态调整相位。仿真里如果信道是静态的可以固定时延如果信道是时变的就要加搜索算法。4.2 用 Python 实现一个三 finger Rake 接收机下面是一个简化但可运行的 Rake 接收机实现假设已经完成了 PN 码同步和信道估计import numpy as np class RakeReceiver: def __init__(self, pn_code, delays, num_fingers3): pn_code: 本地 PN 短码序列 delays: 各 finger 的时延采样点数 num_fingers: finger 数量 self.pn_code pn_code self.delays delays[:num_fingers] self.num_fingers num_fingers self.channel_est np.zeros(num_fingers, dtypecomplex) def estimate_channel(self, rx_pilot, pilot_symbols): 用导频信道做信道估计 for i, delay in enumerate(self.delays): # 取对应时延的导频段 segment rx_pilot[delay:delay len(pilot_symbols)] # 用导频符号做相关 corr np.sum(segment * np.conj(pilot_symbols)) self.channel_est[i] corr / len(pilot_symbols) return self.channel_est def demodulate(self, rx_signal, symbol_len): 解调业务信道 rx_signal: 接收信号 symbol_len: 每个符号的采样点数 num_symbols len(rx_signal) // symbol_len finger_outputs np.zeros((self.num_fingers, num_symbols), dtypecomplex) for i, delay in enumerate(self.delays): # 对齐时延 aligned rx_signal[delay:delay num_symbols * symbol_len] # 用 PN 码解扩 despread aligned * np.conj( np.tile(self.pn_code[:symbol_len], num_symbols) ) # 按符号长度累加 for s in range(num_symbols): finger_outputs[i, s] np.sum( despread[s * symbol_len:(s 1) * symbol_len] ) # 最大比合并 combined np.zeros(num_symbols, dtypecomplex) for i in range(self.num_fingers): combined np.conj(self.channel_est[i]) * finger_outputs[i] return combined # 示例用法 pn np.random.choice([-1, 1], 64) # 简化 PN 码 rake RakeReceiver(pn, delays[0, 2, 4], num_fingers3) # 模拟导频和业务信号 pilot np.ones(100) 0j rx_pilot pilot * 0.8 0.1 * np.random.randn(100) # 加噪声 rake.estimate_channel(rx_pilot, pilot) # 解调 rx_data np.random.choice([-1, 1], 640) 0j result rake.demodulate(rx_data, symbol_len64) print(f合并后符号数: {len(result)}) print(f信道估计: {rake.channel_est})estimate_channel用导频符号和接收导频段做相关得到每个 finger 的复增益估计。demodulate先按 finger 时延对齐信号再用 PN 码解扩然后按符号周期累加最后用信道估计的共轭做最大比合并。参数symbol_len是一个符号对应的采样点数如果过采样倍数是 4Walsh 码长 64那symbol_len就是 256。delays要和信道模型的时延对齐否则 finger 采到的是噪声。这个实现省略了 PN 码同步和 finger 搜索实际工程里这两块占的代码量比解调还大。如果你拿到的IS95前向链路系统.zip里没有同步模块那基本可以判断它只做了理想同步下的性能验证。5. 避坑与排查IS95 前向链路仿真里最容易翻车的 5 个地方5.1 误码率曲线始终不下降现象跑完仿真Eb/N0 从 0 dB 加到 10 dB误码率一直在 0.1 附近晃完全不收敛。原因最常见的是 PN 码相位没对齐。前向链路接收端需要知道基站的 PN 偏置如果偏置设错解扩出来的全是噪声。另一个可能是 Walsh 码索引用错把导频信道的码用到了业务信道上。解决先单独验证导频信道用已知的导频符号做相关看相关峰是否出现在预期时延位置。如果导频都解不出来业务信道不用看了。确认 PN 偏置和 Walsh 索引与发射端一致。5.2 Rake 合并后性能反而变差现象单 finger 解调误码率 0.05加到三 finger 后误码率变成 0.15。原因信道估计不准或者 finger 时延没对准。如果某个 finger 的信道估计幅度很小但相位误差很大MRC 合并时会把噪声放大。另外如果多条路径功率差异太大弱路径的 finger 贡献的基本是噪声。解决检查每个 finger 的信道估计幅度把幅度低于主路径 10 dB 的 finger 关掉。确认 finger 时延和信道模型的时延一致误差不要超过半个码片。5.3 卷积编码和交织顺序搞反现象误码率比理论值高 2 到 3 dB但曲线形状是对的。原因IS95 的发送链是先卷积编码、再符号重复、再块交织。如果仿真里把交织放在编码前面或者忘了符号重复译码器拿到的软信息就是错的。解决对照标准里的发送链顺序逐级打印中间数据。卷积编码后的符号数、重复后的符号数、交织后的符号数应该和标准一致。用全零序列测试编码后的输出应该是确定的。5.4 多普勒频移参数设得太大现象误码率曲线抖动严重每次跑结果都不一样。原因多普勒频移设得过大导致一个帧内信道变化太快信道估计跟不上。IS95 的帧长是 20 ms如果多普勒频移 200 Hz一个帧内信道变化了 4 个周期导频估计就不准了。解决根据实际车速设置多普勒频移。步行速度对应 5 到 20 Hz车载速度对应 100 到 200 Hz。仿真时先用低多普勒验证算法正确性再逐步加大多普勒测试鲁棒性。5.5 采样率不匹配导致解扩增益损失现象误码率比理论值差 1 dB 左右怎么调参数都上不去。原因发射端和接收端的采样率不一致或者过采样倍数不是整数。IS95 的码片速率是 1.2288 Mcps如果接收端采样率是 2 倍那每个码片只有 2 个采样点解扩时累加的长度不对。解决确保收发采样率一致过采样倍数建议用 4 或 8。解扩时的累加长度要等于 Walsh 码长乘以过采样倍数。用单径无噪声场景验证解扩后的星座点应该清晰可辨。6. 进阶验证用导频信道做信道估计的精度提升技巧导频信道是 IS95 前向链路里最干净的一路信号它不经过卷积编码和交织直接扩频发送。这意味着导频的符号是已知的接收端可以用它做信道估计。但导频信道的功率分配是有讲究的标准里导频信道的功率通常占总功率的 20% 左右业务信道占大头。如果导频功率太低信道估计精度不够如果太高业务信道功率被压缩。我一般用两种方法提升信道估计精度。第一种是滑动平均在一个帧内对导频相关值做滑动平均平滑噪声。第二种是插值如果 finger 的时延不是采样点整数倍可以用相邻采样点的信道估计做线性插值。下面是一个滑动平均的示例def smooth_channel_est(channel_est, window5): 对信道估计做滑动平均 channel_est: 原始信道估计序列 window: 滑动窗口长度 smoothed np.convolve(channel_est, np.ones(window)/window, modesame) return smoothed # 假设每个 finger 的信道估计是一个时间序列 raw_est np.random.randn(100) 1j * np.random.randn(100) smoothed_est smooth_channel_est(raw_est, window5) print(f原始估计方差: {np.var(raw_est):.4f}) print(f平滑后方差: {np.var(smoothed_est):.4f})滑动窗口的长度要折中窗口太长信道时变跟踪不上窗口太短噪声抑制不够。在 20 ms 帧内如果多普勒频移 50 Hz窗口取 5 到 10 个符号比较合适。另外信道估计的相位比幅度更重要MRC 合并时相位误差会直接导致合并损失。如果发现合并增益不理想先检查相位估计。验证 Rake 接收机性能的另一个方法是看合并前后的信噪比增益。理论上N 条独立路径的 MRC 合并能带来 N 倍的信噪比增益也就是 10log10(N) dB。三 finger 合并应该有约 4.8 dB 的增益。如果实测增益远低于这个值说明路径之间相关性太强或者信道估计有问题。我在实际项目里养成了一个习惯每加一个模块先用理想信号验证再逐步加噪声和衰落。Rake 接收机尤其如此PN 码同步、信道估计、合并权重每一步都要单独验证。直接跑完整链路出了问题根本不知道是哪一级的锅。希望这些经验能帮你在复现 IS95 前向链路系统时少走点弯路。本文还有配套的精品资源点击获取