简介:CDMA技术说明及仿真压缩包,面向通信专业学生、3G移动通信学习者与MATLAB仿真入门者,围绕码分多址这一3G核心技术,将原理讲解和代码仿真有机结合,帮助读者系统理解扩频通信、码片序列及多址接入机制。包内共9个文件,压缩后仅626KB,包含doc格式技术文档、m格式仿真脚本、c源码与dll动态库以及dat数据文件,覆盖卷积编码、维特比译码、下行多径Rake接收等功能模块,可支撑完整的CDMA链路仿真。配套技术文档对CDMA的原理、抗干扰优势、容量特性做了梳理;MATLAB脚本涉及码片序列生成、调制解扩、信道衰落模拟和误码率统计等典型步骤,适合课程设计、通信原理实验或毕业设计直接参考。目前已有119人学习查看,小巧实用,便于快速搭建从发射端到接收端的CDMA仿真框架。
1. CDMA不只是一堆公式:想真正看懂3G就得手推一遍仿真
网上下到的“CDMA技术说明及仿真.rar”这类包,打开后往往是几页技术说明加几个.m脚本;能完整跑通的人不多,但跑通了的人,对3G和通信的理解会比看十遍书都深。CDMA(码分多址)是3G多址技术的根,WCDMA和CDMA2000都从这一套扩频原理演化出来;用MATLAB做链路级仿真,是把这个原理从“看懂公式”变成“能自查结论”最直接的手段。这篇笔记按“原理→仿真代码→参数避坑→结果验证”展开,适合通信专业学生、刚转3G方向的开发者和所有想把CDMA仿真真正跑起来的人。
2. CDMA仿真先立原理:扩频、正交码、多径接收怎么一步步落到代码
2.1 直接序列扩频:为什么所有用户挤在同一个频段还能分清彼此
CDMA和FDMA、TDMA最大的区别是:频率不分成、时隙不分,所有用户在同一频段同时发。区分用户靠的是扩频码——每个用户拿一组互不相同的码片序列,把自己的信息比特展开成码片来发。
这里的关键参数是扩频因子SF(Spreading Factor)。一个“1”比特原本占1个符号周期,经过64倍扩频就变成64个码片,每个码片周期是原来的1/64,带宽摊成64倍。接收端知道该用哪一组码,把收到的混合信号与目标码片序列做相关运算,就能把想解的那个用户“提”出来;其他用户的码和目标码不相关,在相关运算里被摊平,成了类似噪声的残差。
把这一步落到MATLAB,是整条链路最核心的运算:
% 参数定义 N = 2000; % 发送比特数 SF = 64; % 扩频因子,码片长度 user_code = hadamard(SF); % 64阶Walsh矩阵 walsh_user1 = user_code(2, :); % 用户1的扩频码 % 生成0/1比特并映射为双极性 +1/-1 data_bits = randi([0, 1], 1, N); data_bipolar = 2 * data_bits - 1; % 扩频:每个比特复制SF次,再逐码片乘上Walsh码 tx_spread = kron(data_bipolar, walsh_user1);randi([0,1],1,N)生成N个随机0/1,2*data_bits-1把0变成-1、1变成+1,因为扩频码是±1序列,信息也必须是±1才能直接乘。kron在这里把每个信息比特展开成长度为SF的码片块,例如data_bipolar=[+1,-1]会变成[+1*walsh_user1, -1*walsh_user1],展开后的总长度是N*SF。SF越大,抗噪声和多址干扰能力越强,但占用带宽也越大;仿真里取16到128都比较合理,64是3G仿真里最常见的默认值。
如果数据量很大,kron会额外申请一块尺寸为N*SF的大矩阵。改成reshape(repmat(data_bipolar, SF, 1), 1, N*SF) .* repmat(walsh_user1', 1, N)可以得到完全相同的扩频序列,内存表现更稳。先用kron跑通正确性,再考虑优化内存,这是比较稳妥的顺序。
CDMA还有一个让运营商心动的地方——软容量。GSM一个载频8个时隙,容量是硬上限;CDMA里的干扰就是容量,用户多一点信噪比就降一点,没有清晰的“断崖”边界,这就是所谓“软容量”。对应到仿真里,这种性质表现为多用户干扰项引发的性能缓慢下降,而不是系统突然发不出数据。3G时代的WCDMA和CDMA2000正是这条思路的工程化产物:WCDMA是5MHz带宽、3.84Mcps码片速率;CDMA2000是1.25MHz带宽、1.2288Mcps码片速率。仿真里的SF可以自由选择,但带宽不同直接决定了多径时延能不能被分辨开。
2.2 Walsh码正交与PN扰码:用户区分的两把钥匙
扩频不等于正交。真正把用户分开的是Walsh码:同一组Walsh码里任意两行逐点相乘再求和等于0,这就是“正交”。MATLAB里hadamard(SF)直接生成Walsh矩阵,第一行全是+1,从第二行开始彼此正交;我给用户1取第二行,给用户2取第三行,两个用户在同一时刻同一频段发,接收端各自做相关,对方信号贡献为零。
正交有一个严厉的前提:码片必须严格对齐。仿真里所有用户由同一个时钟驱动,这个前提自然满足;真实网络要靠基站的时间提前量来对齐上行信号。一旦对齐被破坏,Walsh码的正交优势会迅速衰减,这也是后面避坑章节里最典型的问题。
Walsh码只管区分同一基站下的用户,还要区分基站,于是引入PN扰码。PN序列是伪随机序列,常用m序列或Gold码,自相关尖锐、互相关接近0,且数量远多于Walsh码,适合给不同基站分配。通信工具箱的comm.PNSequence可以生成工程级m序列,但如果你用的MATLAB版本没有通信工具箱,直接生成随机±1序列也能用:
% 用随机±1作为简化PN扰码(不依赖通信工具箱) pn_seq = (randi([0, 1], 1, N*SF) * 2 - 1); % 加扰:扩频后的码片流乘以PN序列 tx_scrambled = tx_spread .* pn_seq;randi换成comm.PNSequence的计算差别不大,但概念上有区别:随机±1没有固定周期,严格的PN序列有周期且自相关特性是设计过的。单小区单用户仿真里,两种结果几乎一样;多小区仿真必须换真PN序列,因为小区间干扰靠PN的互相关来均匀化。
pn_seq的长度必须是N*SF,才能和tx_spread对齐;在哪个位置生成,就在哪个位置乘,接收端同样要用这一把“钥匙”解扰。把Walsh码和PN序列放在一起看,差别很清晰:
| 码型 | 作用 | 数量 | 对齐要求 |
|---|---|---|---|
| Walsh码 | 区分同一小区内的用户 | SF个(有限) | 码片必须严格同步 |
| PN扰码 | 区分小区/扇区 | 数量远大于SF | 需要周期对齐 |
2.3 RAKE接收与多径合并:多径不是故障,是多出来的性能
无线信道里的反射、绕射会带来多径:同一信号沿不同路径到达接收机,幅度、时延各不相同。窄带系统把多径视为符号间干扰,CDMA因为码片速率高,时延差可以大于一个码片周期,接收机就能把每条路径“单独捞出来”再合并。这就是RAKE接收机,名字取“耙子”的意思,每根齿对准一条路径。
仿真里我常用一个两径模型:第一径直达,幅度1,时延0;第二径反射,幅度0.5,时延3个码片。接收端开两个相关器,各自解扩后加权合并。合并算法有三类:选择合并、等增益合并和最大比合并(MRC);MRC按各径幅度给权重,理论信噪比最大。
% 两径信道:直达径 + 延迟3个码片的反射径 path1 = tx_scrambled; path2 = 0.5 * [zeros(1, 3), tx_scrambled(1:end-3)]; rx_multi = path1 + path2; % 加噪声在信道部分统一处理 % RAKE两抽头相关 corr1 = reshape(rx_multi .* pn_seq, SF, N); corr2 = reshape(([rx_multi(4:end), zeros(1, 3)]) .* pn_seq, SF, N); x1 = sum(corr1 .* repmat(walsh_user1', 1, N), 1); x2 = sum(corr2 .* repmat(walsh_user1', 1, N), 1); % MRC合并权重:0.5对应第二径幅度估计 x_combine = x1 + 0.5 * x2; recv_bits = double(x_combine > 0);path2的移位拼接把反射径放在3个码片之后,[rx_multi(4:end), zeros(1, 3)]则是把同一段时延补偿回来,好让相关器对准第二径。相关值x1和x2都是N维向量,每个元素对应一个比特的判决度量。repmat(walsh_user1', 1, N)把Walsh码复制成SF×N矩阵,与码片矩阵逐列相乘再列求和,完成相关积分。
RAKE最容易出问题的地方是抽头数:抽头比实际路径多,多出来的抽头只是在积分噪声,性能不升反降;抽头时延对不准路径,也等于白开一路噪声。仿真里先做“已知信道参数”的理想RAKE,再改成估计信道,曲线形状会逐步接近真实收发机。
2.4 功率控制与近远效应:仿真里不做这一条就等着误码率飙升
CDMA还有一个绕不开的机制:功率控制。原因在于所有用户同时同频,如果基站近端的手机用大功率发,远端手机的信号会被完全淹没,这就是近远效应。
GSM靠频率规划规避这个问题,CDMA必须靠功率控制。3G系统里有开环和闭环两条路:开环是手机根据测量到的下行信号强度估计发射功率,反应快但不准;闭环是基站测量上行信噪比,下发功率控制命令,手机按步长(通常0.5dB或1dB)调整。两者对比:
| 功控方式 | 依据 | 反应速度 | 精度 |
|---|---|---|---|
| 开环 | 下行信号强度测量 | 快 | 粗 |
| 闭环 | 基站下行信令反馈 | 慢 | 细 |
仿真里不会实现整套信令流程,但至少要建模“功率补偿”这个行为。一个简化的理想闭环功控:
% 近端用户A幅度1.0,远端用户B幅度0.3 tx_A = kron(data_A_bipolar, walsh_user1) .* pn_seq; tx_B = kron(data_B_bipolar, walsh_user1); % 无功率控制:B几乎被淹没 rx_no_pc = tx_A + tx_B; % 理想功控:按接收能量比例把B抬到与A接近 E_A = sum(abs(tx_A).^2); E_B = sum(abs(tx_B).^2); gain_B = sqrt(E_A / E_B); % 相对增益 tx_B_pc = tx_B * gain_B;gain_B是理想化的能量比补偿;真正闭环功控有步长、有延迟、有调整周期,动态信道下会来回震荡甚至发散。仿真里建议先跑理想功控确认曲线上限,再加2dB步长、10ms周期逼近实际。这一条如果不做,多用户CDMA仿真的误码率曲线往往没法看。
功率控制在协议实现里几乎是个黑匣子,没有直观公式,但仿真落点反而是最简单的能量关系。建议在新手阶段先接受理想模型,理解“为什么需要功控”,再去看实际系统的收敛性能。
3. 用MATLAB写CDMA链路:从比特到误码率的最小可运行代码
3.1 发射端:比特→Walsh扩频→PN扰码→BPSK调制
现在把第二章的原理合成一个能跑的链路。整个脚本分三段:发射端生成波形,信道注入噪声和多径,接收端解扩并统计误码率。先看发射端。
% CDMA单用户AWGN链路仿真 —— 发射端 N = 2000; % 每试次比特数 SF = 64; % 扩频因子 EbN0_dB = 0:2:12; % 扫描的比特信噪比 num_trials = 50; % 每个信噪比下的重复次数 walsh_code = hadamard(SF); walsh_user1 = walsh_code(2, :); pn_seq = (randi([0, 1], 1, N*SF) * 2 - 1); % 简化PN序列 for idx = 1:length(EbN0_dB) err_total = 0; for trial = 1:num_trials % 随机比特生成与双极性映射 data_bits = randi([0, 1], 1, N); data_bipolar = 2 * data_bits - 1; % 扩频 + 加扰 tx_spread = kron(data_bipolar, walsh_user1); tx = tx_spread .* pn_seq; % tx是等效基带BPSK信号,码片幅度为±1 end end这里“等效基带BPSK”的意思是:±1序列本身就是基带符号,不需要真的去乘一个载波。如果要表现脉冲成型,可以用rcosdesign加根升余弦滤波器,但那个只改变频谱形状,不改变AWGN下的判决结果,初学阶段可以省掉——大多数资料包里的仿真也是这么处理的。
发射端的关键就是kron加扰,它等价于“每个比特模二加Walsh码,再模二加PN码”,只是把0/1映射成±1后,模二加变成了乘法。参数选择上,N太小时统计抖动大,N太大内存开销大;2000比特加50次重复足够把误码率压到1e-3附近的统计噪声水平。
3.2 信道注入:AWGN与两径衰落怎么选参数
信道这一段的核心是噪声功率的换算。Eb/N0表示每比特能量和噪声功率谱密度的比值;扩频以后1个比特的信息被拆成SF个码片,所以码片能量是Eb/SF。接收端相关解扩会把SF个码片能量累加回Eb,噪声也按带宽被“去扩”了。如果直接拿码片能量当Eb,误码率曲线会整体右移约10*log10(SF)dB,也就是64倍扩频时右移18dB——这是新手最常踩的坑。
% 加噪前先算当前比特信噪比对应的噪声方差 Eb = SF * mean(abs(tx).^2); % 每比特能量 N0 = Eb / (10^(EbN0_dB(idx) / 10)); noise_var = N0 / 2; % 实数基带方差 % AWGN信道 rx = tx + sqrt(noise_var) * randn(1, N*SF);Eb之所以要乘SF,是因为mean(abs(tx).^2)算的是每个码片的平均能量,而1个比特含SF个码片。noise_var = N0/2对应实数基带模型下噪声功率N0/2;如果改用复数基带模型(IQ两路),噪声要做成(randn + 1i*randn)*sqrt(N0/2)。
多径信道在这个基础上用卷积实现:
% 两径信道冲激响应:主径 + 延迟3个码片的反射径,幅度0.5 h = [1, zeros(1, 2), 0.5]; % zeros数量为delay-1,这里对应3码片时延 rx_multi = conv(tx, h); rx_multi = rx_multi(1:N*SF); % 截断到原始长度 rx = rx_multi + sqrt(noise_var) * randn(1, N*SF);conv会输出长度为N*SF+3的序列,第4个采样开始才有反射径的能量,所以截掉末尾3个点,让主径位置保持对齐。h = [1, zeros(1, 2), 0.5]表示时延为3个码片,别把数组长度和时延数字搞混。如果只跑AWGN,把rx_multi直接赋成tx即可。码片时延必须至少是1——如果设为0,两径在采样上完全重叠,RAKE也无法区分。
这里给出一个常用的参数范围表,照着设置可以少走弯路:
| 参数 | 推荐范围 | 说明 |
|---|---|---|
| N | 2000~10000 | 太小BER抖动大,太大会消耗内存 |
| SF | 16~128 | 越大抗干扰能力越强,但带宽占用越高 |
| num_trials | 20~100 | 控制BER统计稳定性 |
| 多径时延 | 1~5 chips | 过小分不开,过大截断误差增大 |
| 功控步长 | 0.5~2 dB | 越小越精准,但收敛时间更长 |
3.3 接收端:解扰解扩+判决+误码率统计
接收端是发射端的逆过程:先乘回PN序列做解扰,再把码片块和Walsh码做相关,最后硬判决。代码可以进一步封成函数,方便扫EbN0曲线。
function ber = simulate_cdma_awgn(EbN0_dB, N, SF, num_trials) % simulate_cdma_awgn 单用户CDMA链路AWGN信道误码率仿真 % EbN0_dB : 标量或向量,目标比特信噪比(dB) % N : 每试次发送比特数 % SF : 扩频因子(Walsh码长度) % num_trials: 每个信噪比下的重复试次 walsh_code = hadamard(SF); walsh_user1 = walsh_code(2, :); pn_seq = (randi([0, 1], 1, N*SF) * 2 - 1); ber = zeros(size(EbN0_dB)); for i = 1:length(EbN0_dB) err_total = 0; bit_total = 0; for trial = 1:num_trials data_bits = randi([0, 1], 1, N); data_bipolar = 2 * data_bits - 1; tx = kron(data_bipolar, walsh_user1) .* pn_seq; Eb = SF * mean(abs(tx).^2); N0 = Eb / (10^(EbN0_dB(i) / 10)); noise_var = N0 / 2; rx = tx + sqrt(noise_var) * randn(1, N*SF); % 解扰 derand = rx .* pn_seq; % 解扩:重排成SF×N矩阵,每列是一个比特的码片 chip_block = reshape(derand, SF, N); corr = walsh_user1 * chip_block; % 1×N相关值 recv_bits = double(corr > 0); err_total = err_total + sum(recv_bits ~= data_bits); bit_total = bit_total + N; end ber(i) = err_total / bit_total; end endwalsh_user1 * chip_block是1×SF向量乘SF×N矩阵,一步完成所有比特的相关——比逐比特循环快,也省去repmat那步内存操作。corr每个元素是某个比特的判决度量:为正判成比特1,为负判成0。
调用函数画曲线:
EbN0_vec = 0:2:10; ber = simulate_cdma_awgn(EbN0_vec, 2000, 64, 50); semilogy(EbN0_vec, ber, 'o-'); hold on; EbN0_lin = 10.^(EbN0_vec/10); theory = 0.5*erfc(sqrt(EbN0_lin)); semilogy(EbN0_vec, theory, '-'); legend('仿真', '理论BPSK');erfc是补误差函数,0.5*erfc(sqrt(EbN0))就是BPSK在AWGN下的理论BER。仿真曲线如果贴在理论曲线附近,偏差在0.5dB以内,链路模型就是可信的。想跑多用户或RAKE,把信道和发射端按第二章片段替换即可。
4. CDMA仿真常见问题与避坑:Eb/N0换算、正交性失效和RAKE抽头
4.1 误码率曲线比理论值偏很多:先查Eb/N0换算
现象:AWGN信道下仿真BER曲线整体向右偏,差出来的dB数还正好等于10*log10(SF),比如SF=64时约18dB。
原因:把mean(abs(tx).^2)当成了每比特能量,而它只是每码片能量。1个比特摊到SF个码片,Eb = SF * Ec;噪声功率按码片带宽计算,信噪比因此被低估。举个例子:SF=64,码片能量为1,那么Eb=64;如果目标Eb/N0是10dB,N0应该是6.4,而不是1/10=0.1。差出来的这部分,正好通过BER曲线整体平移表现出来。
解决:加噪前先用Eb = SF * mean(abs(tx).^2)算出比特能量再做功率匹配。这个问题几乎每次新写一个CDMA仿真脚本都会遇到,属于最典型的一类翻车现场。建议在代码里单独写一行注释,把“码片能量”和“比特能量”的换算锁死,别省这一步。
4.2 用户间干扰怎么都压不下去:Walsh码的正交前提失效了
现象:用户A和用户B用了正交Walsh码,可B一发射,A的误码率明显抬升,而且B的功率越大影响越大,不像理论推导那样完全不相关。
原因:Walsh码正交的前提是码片对齐。如果两个用户的发射时刻相差哪怕一个码片,正交性就开始裂开;对应到仿真代码,常见原因是一个用户带PN扰码、一个不带,或者两个用户用了不同时刻生成的PN序列,导致码片图案失去对齐关系。
解决:多用户仿真里,发射端必须让所有用户从同一个码片时钟出发,扩频用同一套Walsh矩阵的不同行,加扰用同一段PN序列的不同偏移。需要模拟不同用户时延差异时,要明确做“时延偏移”,而不是让索引错位;若追求对抗异步场景,应该改用Gold码靠互相关特性抗干扰,而不是死守正交性。
4.3 RAKE抽头数和多径数不匹配:合并出来的性能反而更差
现象:本身两径信道,结果不加RAKE性能还行,加了两个抽头反而更差。
原因:RAKE抽头对准的位置不是真实路径,把噪声当信号给合并进来了。仿真里常见原因是时延对齐错了——比如h = [1, zeros(1, 2), 0.5]是3个码片时延,但接收端却用[rx(3:end), zeros(1, 2)]去对齐,偏移了一个码片。
解决:先打印信道冲激响应确认路径位置,再逐一核对抽头索引。一个简单做法是:
% 检查信道抽头位置 h = [1, zeros(1, 2), 0.5]; idx = find(abs(h) > 0.1); % idx=[1, 4],接收端抽头必须对准1和4,不能想当然用1和3RAKE抽头数的原则是“不多于实际可分辨路径数”;不确定路径数时,用信道估计相关器输出能量来筛选,能量低于阈值(比如相对主径-10dB)的不要并入。合并权重用估计幅度而不是固定值,这样信道一变,性能也不会崩。
4.4 仿真慢到没法调参:码片率、采样率和等效基带的取舍
现象:一个EbN0点跑几分钟,10个点下来半天没了;有些脚本把载波、下变频、脉冲成型全部带上,数据量膨胀好几个数量级。
原因:直接把真实发射机的流程“照抄”进MATLAB,每个码片又采8个点,序列长度膨胀到NSF8,再加滤波器卷积,整个仿真变成一次大计算。
解决:链路级仿真应使用等效基带模型——实数基带符号±1就是一个码片,一个码片一个采样点。多径时延用整数码片表示,脉冲成型先省掉。两者对比:
| 建模方式 | 波形形式 | 每码片采样数 | 相对耗时 | BER结论一致性 |
|---|---|---|---|---|
| 带通波形 | 已调载波采样 | 8~16 | 高 | 一致 |
| 等效基带 | ±1实数/复数 | 1 | 低 | 一致 |
这样计算量能降几十倍,而BER结论不变。等链路正确之后再按需加带通细节,这才是仿真速度和解bug之间的平衡。
4.5 同一段代码跑两次结果不一样:PN序列和随机种子的锅
现象:参数一个没改,跑两次误码率曲线,曲线却明显不同,甚至某一两个点像“断了一截”。
原因:脚本里PN序列和噪声都用randi/randn生成,每次运行都是新的随机序列;PN序列一变,等效于换了一套扰码和种子,在小样本下统计波动被放大。
解决:在脚本开头固定随机种子,比如rng(2024);或者把PN序列作为参数传入函数,只在信道噪声层保留随机性。设计上建议固定PN序列,让唯一的随机源是信道噪声,这样同一条BER曲线可以复现,调试也容易。
5. 验证CDMA仿真结论值得信赖的四个习惯
仿真跑通和结论可信是两回事。我自己验证一条CDMA仿真曲线,会按四个习惯来把关。
习惯一:永远先画理论曲线对照。AWGN单用户BPSK的理论BER是0.5*erfc(sqrt(EbN0_lin)),MATLAB里一行就能画出来。如果仿真和理论偏差超过0.5dB,先怀疑Eb/N0换算和噪声方差,而不是怀疑CDMA模型本身。理论上匹配后再动多径、多用户这些变体,心里才有底。
习惯二:固定随机种子、保持单一随机源。我通常rng(2024)置顶,PN序列固定,只有信道噪声随机。这不是为了“好看”,而是为了把变量拆干净——改动一个参数时,曲线变化只由这个参数引起,而不是随机波动淹没结论。
习惯三:单用户到多用户分级验证。多用户仿真的失败原因往往同时叠加了功率控制、非正交干扰、信道估计算法等;一旦翻车,很难说清是哪一环。我习惯先跑通不带功控的单用户AWGN,然后加多径、加RAKE,最后加多用户和功控。每加一层,都要保证上一层结论对得上。
习惯四:看解扩后的判决统计,而不是只看BER。用histogram(corr)画相关值分布,它会给我瞬时反馈:相关值分布应该围绕0对称,正负两簇分得越开,判决越可靠。如果分布出现整体偏移,多半是双极性映射做错了;如果两侧拖尾不对称,就要回头查PN序列对齐。
这四个习惯做完,CDMA仿真才算真正立住了。回到开头的CDMA技术说明及仿真.rar,技术说明给你的是公式和图纸,仿真给你的是可复现的结论——两者的价值在工程里必须互相验证。我自己的原则是:任何新改动都先从“关闭功控、关闭多径”的最低配置跑一遍,再做增量;这条原则帮我省掉了大量解bug的时间,希望帮到你。
本文还有配套的精品资源,点击获取