简介:面向通信系统设计与分析场景,一套基于MATLAB的信道仿真模型适合通信工程专业学生与研究人员,用于模拟平坦衰落、频率选择性衰落、多径传播及多普勒频移等典型信道,支撑链路级性能评估。压缩包共22个文件,以17个m脚本、4个mat数据文件及1个pdf说明文档组成,大小308KB;其中m脚本覆盖信道相关函数、高斯/拉普拉斯/均匀分布下的相关系数计算与归一化处理,mat文件提供预生成的相关矩阵数据,pdf则为链路级仿真说明。已有647人学习下载。读者既能对照PDF理清信道建模思路,也可直接运行脚本观察不同分布参数对相关性的影响,或基于几何映射关系扩展MIMO、OFDM等应用实验,是快速上手MATLAB信道仿真的一份紧凑实用资料。
1. 为什么基于 MATLAB 的信道仿真模型要先于编解码器成型?
刚开始做通信仿真的工程师,容易把注意力全部放在调制解调或信道编码上,等误码率曲线迟迟不下沉,才回头检查链路中间的信道仿真模型是不是少了该有的物理条件。基于 MATLAB 的信道仿真模型,本质是在发射机和接收机之间插入一组可重复的传递函数:路径损耗定标信号的功率,多径时延制造符号间干扰,多普勒频移让冲激响应随时间变化,最后叠加噪声和干扰,使仿真行为接近现场测量。它的价值不是让曲线变得更难看,而是让均衡、编码和检测算法在真实性能边界内得到评价。下面这套做法,适合刚接手物理层仿真的人,也适合要把工具箱对象接入既有链路的工程师。
2. 信道仿真模型的物理基础:路径损耗、多径与多普勒
要建立信道仿真模型,第一步不是打开 MATLAB,而是先把三类物理因素转化为可仿真参数。它们作用于不同尺度:路径损耗和阴影衰落决定接收功率的平均水平,多径决定频率选择性,多普勒决定时间选择性。分开建模,后面调参时才能分清误码率曲线斜率变缓是因为深衰落,还是因为符号间干扰,还是仅仅因为噪声功率算错了。
2.1 用 dB 写路径损耗和阴影衰落:仿真功率别直接乘
自由空间路径损耗按距离和载波频率的平方增长。常用公式写成 PL = 20log10(d) + 20log10(fc) - 147.55,单位 dB,d 用米,fc 用 Hz。后面那个 -147.55 来自 4π/c 的平方项;若 fc 用 GHz,常数会变成 32.44,这很容易被人忽略。工程上更多用对数距离模型,PL(d) = PL(d0) + 10nlog10(d/d0),环境不同 n 值变化很大:室内视距 n 约 1.8,办公楼穿墙 n 可到 3.5,城市宏蜂窝一般取 4 左右。阴影衰落是在路径损耗均值上叠加一个对数正态随机量,dB 域里就是加 N(0, sigma^2),sigma 取 4~12 dB。
d = 10:10:1000; % 距离,单位m fc = 2.4e9; % 载波频率,单位Hz PL_fs = 20*log10(d) + 20*log10(fc) - 147.55; % 自由空间损耗 PL_office = PL_fs(1) + 10*3.5*log10(d/10); % 以10m为参考点 figure; semilogx(d, PL_fs, '-'); hold on; semilogx(d, PL_office, '--'); xlabel('距离 (m)'); ylabel('路径损耗 (dB)'); grid on; legend('自由空间 n=2', '办公环境 n=3.5');参数说明:d是距离向量,fc是载频;PL_fs(1)取 10 米处的自由空间损耗作为参考点,避免公式在 d 接近 0 时崩掉。真正把损耗放进链路时,要用db2mag(-PL/20)转成线性幅度增益,再与信号相乘;直接拿 dB 值相乘,幅度会差几个数量级。阴影衰落则要在一个“位置”或一个数据帧的持续时间内生成一次sigma*randn,而不是每个样本都重新抽样,否则会制造出没有任何物理来源的快速闪烁。
2.2 多径冲激响应:为什么说它是抽头延迟线
多径在基带的表现是一条抽头延迟线。假设存在 L 条可分辨路径,第 l 条路径有复增益 h_l(t) 和时延 tau_l,接收基带信号可写成 y(t) = sum_l h_l(t)x(t - tau_l) + n(t)。当时延差小于一个采样周期时,这些路径在采样点上无法区分,通常合并成一个抽头。判断信道是否频率选择性,主要看符号周期相对最大时延扩展的关系:若符号周期明显大于时延扩展,信道是平坦衰落;若一个符号周期内有多个可分辨抽头,就会产生符号间干扰,需要均衡或改用多载波。
numTaps = 6; avPowdB = [0 -2 -6 -10 -14 -18]; % 各抽头平均功率,单位dB h = zeros(1, 21); % 总冲激响应,21个采样点长度 h(1) = 1; h(2) = db2mag(avPowdB(2)) * exp(1i*2*pi*rand); h(4) = db2mag(avPowdB(3)) * exp(1i*2*pi*rand); h(8) = db2mag(avPowdB(4)) * exp(1i*2*pi*rand); h(13) = db2mag(avPowdB(5)) * exp(1i*2*pi*rand); h(21) = db2mag(avPowdB(6)) * exp(1i*2*pi*rand);这里非零元素的位置就是可分辨时延,db2mag(avPowdB)把平均功率从 dB 转成线性幅度。每个抽头的相位先随机初始化,实际传播中相位还会随时间变化,所以后续要通过多普勒让抽头系数持续旋转。这样做的好处是,可以从h的形状直接看出信道是二径还是多径,也能看出最大时延是否超过符号周期。
2.3 多普勒频移和相干时间:仿真更新步长怎么定
最大多普勒频移由移动速度、载频和光速共同决定:fd = v * fc / c。真正的多普勒会在 -fd 到 +fd 之间铺开,而不是只有一个点。相干时间 Tc 是信道冲激响应近似不变的时间尺度,工程上常用 0.423 / fd 估算。若 fd = 133 Hz,Tc 约 3.2 ms;采样率 25 kHz 时,意味着信道状态大约在 80 个采样点内保持稳定。
v = 60 * 1000 / 3600; % 60 km/h 转成 m/s fc = 2.4e9; % 载波频率 2.4 GHz c = 3e8; fd = v * fc / c % 最大多普勒频移 Tc = 0.423 / fd % 相干时间下表直接用 2.4GHz 载频计算,方便你快速判断自己的仿真参数是否合理。表格里的“采样点约数”是基于 25 kHz 基带采样率,换成你自己的采样率后按比例缩放即可。
| 场景 | 速度 | fd @2.4GHz | 相干时间 | 采样点约数 @25kHz |
|---|---|---|---|---|
| 步行 | 3 km/h | 6.7 Hz | 63 ms | 约 1575 |
| 车载 | 60 km/h | 133 Hz | 3.2 ms | 约 80 |
| 高铁 | 300 km/h | 667 Hz | 0.63 ms | 约 16 |
在仿真里,如果每个样本都重新生成瑞利系数,信道变化会过猛,误码率被高估;如果一个大帧只生成一次系数,又会把时变信道错误地当成静态信道。手动抽头模型建议在 1/4 到 1/2 个相干时间内更新一次冲激响应;用工具箱对象时则把MaximumDopplerShift设置成 fd,内部多普勒滤波器会连续处理,不需要自己计算步长。
3. 用 MATLAB 把瑞利多径信道仿真模型跑通:最小脚本和参数设置
很多教程一上来就调comm.RayleighChannel,但里面隐藏了不少默认参数,出了问题不好判断。我自己的习惯是先在纯 MATLAB 里用抽头延迟线把链路搭起来,再用 Communications Toolbox 对象做对照。两步走的另一个好处是,当误码率不对时,能立刻知道问题是出在多径定义上,还是出在接收机均衡上。
3.1 从 QPSK 信号和三抽头信道起步
先构造一个最简 QPSK 发射端,采样率设成 25 kHz,每符号 4 个采样点。这样做的目的是让多径时延具备“采样点数”这个直观单位,后面的路径时延也能直接换算。
fs = 25e3; % 基带采样率 sps = 4; % 每个符号采样数 symbolRate = fs / sps; % 6250 符号/秒 data = randi([0 3], 2000, 1); sym = pskmod(data, 4, pi/4, 'gray'); % QPSK,格雷映射 tx = upsample(sym, sps); tx = conv(tx, ones(sps,1), 'same'); % 矩形脉冲成型这段代码中,symbolRate决定了符号周期约 160 us;upsample把每个符号插值成 4 个采样点,conv与ones(sps,1)卷积起到最简单的脉冲成型作用。之所以不用专门的滤波器组,是因为最小信道模型的重点在信道而非成形;等信道验证完,再换根升余弦滤波器即可。
接下来定义三抽头信道:第一径无时延,第二径时延 2 个采样点,第三径时延 5 个采样点。用filter做卷积,输出就是带符号间干扰的接收信号。
h = zeros(1, 6); h(1) = 1; % 直射径 h(3) = 0.5 * exp(1i*2*pi*rand); % 时延2个采样点 h(6) = 0.2 * exp(1i*2*pi*rand); % 时延5个采样点 rx = filter(h, 1, tx); e0 = tx(1:4:end); % 抽取发送符号位置 e1 = rx(1:4:end); % 抽取接收符号位置 figure; plot(real(e0), imag(e0), 'x'); hold on; plot(real(e1), imag(e1), '.'); legend('发送符号', '经过信道后的采样点');参数说明:h(3)的首元素索引 3,表示第二径相对第一径延后 2 个采样点;h(6)表示第三径延后 5 个采样点。filter(h,1,tx)是做 FIR 滤波,输出长度与输入相同,不会像conv那样把信号变长。如果第二径时延超过了符号间隔的采样点数,就会在接收星座图上看到明显的散点;若时延很小,散点可能只是整体缩小,不会出现跨符号干扰。
3.2 换用 comm.RayleighChannel 和 comm.RicianChannel 做时变信道
静态三抽头模型不能反映多普勒,所以正式仿真里更常见的是用工具箱对象。comm.RayleighChannel最大优点是把每条路径的复增益随时间连续变化,不用自己维护滤波器状态。
fd = 133; % 最大多普勒频移 pathDelays = [0 80e-6 160e-6]; % 路径时延,单位秒 avgPathGains = [0 -6 -15]; % 各路径平均功率,单位dB rayChan = comm.RayleighChannel(... 'SampleRate', fs, ... 'PathDelays', pathDelays, ... 'AveragePathGains', avgPathGains, ... 'MaximumDopplerShift', fd, ... 'RandomStream', 'mt19937ar with seed'); rxFading = rayChan(tx);这里必须注意单位:PathDelays以秒为单位,就要和SampleRate保持一致;如果是[0 2 5]这样的整数,则会被解释成采样点数。AveragePathGains是各条路径的平均功率,单位 dB,对象内部会自动做功率归一化。RandomStream指定随机数流,用于重复试验时得到相同信道实现。
Rician 信道的区别是存在一个较强的直视分量,视距与散射功率之比由KFactor表达。KFactor是线性值,10 相当于 10 dB,K 接近 0 时退化到瑞利。
ricChan = comm.RicianChannel(... 'SampleRate', fs, ... 'PathDelays', pathDelays, ... 'AveragePathGains', avgPathGains, ... 'KFactor', 10, ... 'MaximumDopplerShift', fd); rxRician = ricChan(tx);对象调用之间会维护内部状态,也就是说分段处理长信号时,下一帧会从上一帧的信道状态继续演化。若希望每次测试都从同一状态开始,在循环里用一次reset(rayChan)或reset(ricChan)。
3.3 最小系统里要盯住的五个参数
新手最容易在参数单位上翻车。每个参数单独看都不难,组合起来经常互相矛盾,尤其是时延、采样率和符号率三者。建议把下表贴在仿真脚本旁边,调参时逐条核对。
| 参数 | 取值例子 | 它影响什么 | 常见误设 |
|---|---|---|---|
SampleRate/ fs | 25e3 | 时延与采样点的换算基准 | 把秒写成了采样点数 |
PathDelays | [0 80e-6 160e-6] | 多径时延,决定频率选择性 | 忽略了第一径要设为 0 |
AveragePathGains | [0 -6 -15] | 各径平均功率分配 | 忘记用 dB,直接写线性比例 |
MaximumDopplerShift | 133 | 时变速度,决定相干时间 | 和符号率混淆,设得过大或过小 |
KFactor | 10 | 视距分量占比 | 把 dB 值直接当成线性值传入 |
一个快速判断法则是:最大时延扩展远小于符号周期时,可以只保留第一径或把多径合并成一个平坦衰落系数;最大时延扩展接近或超过符号周期时,必须保留多抽头,否则后续的误码率曲线即使能画出来,也只代表了一个根本不存在的信道条件。
4. 用误码率曲线校准信道仿真模型:AWGN、瑞利和莱斯的对照实验
信道模型做对了没有,不能只看接收波形像不像,最好用误码率曲线和下界理论做对照。最常见的做法是把 AWGN、瑞利平坦衰落、莱斯衰落放在同一个发射和接收框架里做对比,谁的 BER 曲线偏离正常范围,谁就在归一化或均衡上出了问题。
4.1 为什么不能只用 AWGN 代替信道模型
AWGN 只描述噪声叠加,不包含信号幅度随机衰落。在平坦瑞利信道中,即使接收端知道完美信道,每个符号依然可能落到信道幅度接近 0 的深衰落里,这时瞬时信噪比很低,错误集中爆发。结果是瑞利信道的 BER 曲线斜率大约只有 AWGN 的一半,而且信噪比越高,差距越明显。若只加 AWGN,你会高估接收机性能好几个 dB,这在实际系统里会直接导致覆盖距离判断失误。
4.2 在同一个脚本里比较 AWGN、瑞利和莱斯
下面的脚本用 BPSK,原因很简单:理论 AWGN 误码率可用berawgn或qfunc(sqrt(2*EbN0))直接验证,瑞利衰落也有一阶近似,方便判断结果是否合理。脚本对每一个 Eb/N0 点重新生成一次独立的发射序列和信道实现,保证概率统计的独立性。
EbNoVec = 0:2:20; numBits = 2e5; berAWGN = zeros(size(EbNoVec)); berRay = zeros(size(EbNoVec)); berRic = zeros(size(EbNoVec)); for k = 1:numel(EbNoVec) noiseVar = 0.5 * 10^(-EbNoVec(k)/10); % 复噪声中每个实部虚部方差的对应关系 txBits = randi([0 1], numBits, 1); txSym = 2*txBits - 1; % BPSK 映射到 +/-1 % AWGN 参考信道 rxAWGN = txSym + sqrt(noiseVar) * randn(numBits,1); berAWGN(k) = mean(rxAWGN < 0 ~= txBits); % 瑞利平坦衰落,每比特一个信道系数 hRay = (randn(numBits,1) + 1i*randn(numBits,1)) / sqrt(2); rxRay = hRay .* txSym + sqrt(noiseVar) * (randn(numBits,1) + 1i*randn(numBits,1)); berRay(k) = mean(real(rxRay ./ hRay) < 0 ~= txBits); % 莱斯信道,K=10(线性值) K = 10; hRic = sqrt(K/(K+1)) + sqrt(1/(K+1)) * (randn(numBits,1) + 1i*randn(numBits,1))/sqrt(2); rxRic = hRic .* txSym + sqrt(noiseVar) * (randn(numBits,1) + 1i*randn(numBits,1)); berRic(k) = mean(real(rxRic ./ hRic) < 0 ~= txBits); end代码里的noiseVar是复噪声每个实部或虚部分量的方差,BPSK 符号能量为 1,所以Eb/N0与这个方差之间有 0.5 的系数。瑞利衰落中,接收端用rxRay ./ hRay做零强迫均衡,这样信号部分恢复为原始符号,但噪声会被小幅度信道放大,这正是衰落信道与 AWGN 性能差异的来源。莱斯信道分解成直射项和散射项,直射项功率占比是 K/(K+1),散射项占比 1/(K+1),两者相加后平均功率仍为 1,没有改变噪声方差定义。
画图部分可以继续用semilogy把三条 BER 曲线叠在一张图上,同时画出 AWGN 理论值:
figure; semilogy(EbNoVec, berAWGN, 'o-'); hold on; semilogy(EbNoVec, berRay, 's-'); semilogy(EbNoVec, berRic, 'd-'); semilogy(EbNoVec, berawgn(EbNoVec, 'psk', 2, 'nondiff'), 'k--'); grid on; xlabel('Eb/N0 (dB)'); ylabel('BER'); legend('AWGN 仿真', '瑞利衰落 仿真', '莱斯 K=10dB 仿真', 'AWGN 理论', 'Location', 'southwest');这里berawgn(EbNoVec, 'psk', 2, 'nondiff')需要 Communications Toolbox;如果不想依赖工具箱,可以用qfunc(sqrt(2*10.^(EbNoVec/10)))代替。运行后应当能看到:AWGN 曲线和理论曲线重合;瑞利曲线更陡,并且在高信噪比处仍有明显错误;莱斯曲线介于两者之间。若 AWGN 曲线就不对齐,问题基本在噪声方差,而不是信道。
4.3 从误码平台倒推信道模型参数问题
曲线一旦在高信噪比出现平坦趋势,也就是“误码平台”,通常不是随机现象,而是某个结构性因素没有建模好。下面这张表是排查时最有效的顺序。
| 现象 | 检查项 | 常见修正 |
|---|---|---|
| 低信噪比段与 AWGN 重合 | 信道没有衰落,或随机系数被固定住 | 检查hRay是否真的随机,观察abs(hRay)分布 |
| 高信噪比出现平台 | 时延扩展接近或超过符号周期 | 缩小符号率或增加时域均衡 |
| 曲线抖动很大 | 仿真位数不足 | 增大numBits,保证至少统计到 100 个错误事件 |
| 多次运行结果差异大 | 随机流未固定 | 设置随机数种子,或对信道对象执行reset |
出现误差平台不代表仿真代码一定错了:若你的系统本身没有均衡器,而信道又设成频率选择性,那平台就是真实存在的性能天花板。此时应当先回头算一下max(pathDelays)与符号周期的比值,如果最大时延已经接近甚至超过符号周期,先减少多径抽头数或降低传输速率,确认平坦衰落曲线正常后,再逐步增加抽头复杂度。
5. 把信道仿真模型升级成 OFDM/MIMO:先守住 3 个接口约定
在 SISO 模型上做完验证后,升级到 OFDM 或 MIMO 时不需要推翻重写。常见做法是把信道模型当成一个帧级对象,固定几个接口约定,后续换天线数量、换路径参数都只改对象名和配置,不改误码率统计代码。
第一个约定是按“列 = 天线”组织矩阵。comm.MIMOChannel的输入输出都是二维矩阵,输入每一列对应一个发射天线,输出每一列对应一个接收天线。把发送帧组织成txFrame = [txSymLeft, txSymRight]这种形式,MIMO 对象会为每个收发天线组合维护独立的多径抽头。
mimoChan = comm.MIMOChannel(... 'SampleRate', fs, ... 'PathDelays', pathDelays, ... 'AveragePathGains', avgPathGains, ... 'MaximumDopplerShift', fd, ... 'NumTransmitAntennas', 2, ... 'NumReceiveAntennas', 2, ... 'SpatialCorrelationSpecification', 'None'); txFrame = [txSymLeft, txSymRight]; % 每列一个发射天线 rxFrame = mimoChan(txFrame); % 每列一个接收天线第二个约定是让帧长等于一个 OFDM 符号或一个时隙的采样数,而不是把整个发射文件一次性灌进去。这样既能看到同一信道内部的状态演化,也方便与导频位置对齐。若在帧中间调用reset,信道时间相关性会被切断,多普勒效应的统计就会失真。
第三个约定是在 OFDM 仿真中把路径时延直接换算成频域信道系数。OFDM 每个子载波可以近似看成一条窄带平坦信道,时域的抽头延迟线在频域里体现为相位旋转。
numSC = 64; % 子载波数 tauNum = [0 2 4]; % 路径时延,以采样点为单位的整数 gainLin = [1 0.5 0.2]; % 各径线性幅度 idx = (0:numSC-1).'; H = gainLin .* exp(-1i*2*pi*idx*tauNum/numSC); H = sum(H, 2); % 得到每个子载波上的复信道系数这段代码中,idx是子载波索引列向量,exp(-1i*2*pi*idx*tauNum/numSC)把时延转成对应子载波上的相位旋转,gainLin乘进去后按路径求和,得到每个子载波上的复增益H。这比在 OFDM 仿真里继续做时域卷积要快得多,也更容易和导频估计结果做交叉验证。若你希望验证这个频域转换,最直接的办法是把H当做均衡系数应用到 OFDM 接收符号上,并和经过时域抽头滤波器生成的结果对比,差异应只在数值误差范围内。固定住“矩阵列 = 天线、帧长按符号对齐、频域 H 由时延换算得到”这三个约定后,后面再替换成comm.RicianChannel、comm.MIMOChannel或工业标准的 TDL 模型,都不会破坏整套仿真脚本的结构。
本文还有配套的精品资源,点击获取