简介:面向通信工程学习者与LTE物理层研究人员的维也纳大学LTE链路级仿真平台,以MATLAB完整实现了LTE两路传输及仿真的整过程,覆盖参数加载、信道构建、多普勒多径效应、Turbo速率匹配与比特选择、CRC校验、SISO译码和Gold序列生成等关键环节。资源包共254个文件,以99个.m源码文件为核心,辅以58个.mat数据与参数文件、15个C源文件及配套头文件、多种平台的mex动态库文件(mexw64/mexmaci64/mexa64等),另有txt说明与PDF文档,压缩包大小约40.98MB。全部项目源码均经测试校正可运行,新手和有经验的开发人员均可参照其模块化结构快速上手,深入理解LTE链路级仿真的完整流程。目前已有753人学习下载,适合作为LTE通信系统课程设计、算法验证与科研仿真的基础平台。
1. 认识这套 LTE 链路级仿真平台:两路与整过程到底指什么
第一次拿到维也纳大学这套 LTE 链路级仿真平台,我做的第一件事不是急着改参数,而是把发送端到接收端的信号流程从头到尾画了一遍。标题里“LTE 两路及仿真的整过程”这八个字,极容易让人走岔路:它不是说两个小区互相发数据,也不是把两条互不相干的链路简单拼在一起,而是默认的 2×2 天线配置——发射端两个天线端口、接收端两个天线端口,从传输块的 CRC 附着一路仿真到解调、解码后的 CRC 校验,把 LTE 下行物理层的完整流水线在 MATLAB 里跑出来。对准备验证接收机算法、MIMO 预编码或者信道估计方案的人来说,这套平台的价值在于:不用搬基站、不用买综测仪,就能拿到 BLER 和吞吐量曲线。下面按我个人能复现的路径,把模块、脚本、参数和常见的坑位过一遍。
2. 链路级仿真平台的模块拆解:从传输块到两天线接收
把这套平台的结构想成一条单向流水线会清楚很多。我在代码里找入口时,先按 3GPP 36.211 和 36.212 的功能框图去核对调用关系:物理下行共享信道 PDSCH 从传输块开始,经过编码、加扰、调制、层映射、预编码、资源映射,变成时域 OFDM 信号,再过信道、加噪声,接收端再反向做一遍。你后面看到的 BLER 曲线,就是这条链上任何一环出错后,接收端 CRC 校验失败统计出来的结果。
“整过程”这三个字往往容易被低估。很多初学者误以为链路级仿真就是 OFDM 调制解调加一个 AWGN 信道,其实真正有价值的篇幅全在编码和 MIMO 这一段。没有信道编码,误码率曲线会难看一个数量级;没有层映射和预编码,标题里的“两路”就体现不出来。所以我会把它拆成发送端、接收端、边界三块来理解。
2.1 发送端链路:编码、加扰、调制与预编码的职责划分
发送端的第一步是给传输块做 CRC 附着,如果传输块大小超过 6144 比特,还要做码块分割,每个码块各自补 CRC。接下来进 Turbo 编码和速率匹配,这部分的实现直接决定链路误码性能的上限,也是整套仿真和“把信号直接发出去”之间拉开差距的地方。速率匹配会从编码后的比特池里按目标码率挑选比特,这一步如果参数不匹配,接收端解码的软信息比例就会出错。
加扰环节也值得单独说。LTE 的加扰序列由伪随机序列发生器产生,初始化值依赖 lte 小区ID、RNTI 和子帧号。这里有个很典型的坑:有一天你把仿真里的 lte 小区ID 从 0 改成 350,结果 BLER 曲线纹丝不动,原因多半不是代码没读参数,而是加扰和解扰在同一套参照系里运行,只要两边一致,CRC 能过,曲线自然不变。cellId 真正影响的是参考信号位置和干扰分布,不是直观的“小区边界”概念。
调制映射把比特变成复符号,QPSK、16QAM、64QAM 是 LTE 下行最核心的三档。经典链路级平台里,256QAM 不一定被实现,如果你拿到的分支只在配置里写了modulation = '64QAM',那就不要强行追高。层映射和预编码才是“两路”真正落地的位置:层映射决定把一个还是两个码字映射到一层或两层,预编码矩阵再把层映射到两个天线端口。开环空间复用和闭环空间复用的区别就在这里,前者按固定循环切换预编码矩阵,后者依赖接收端反馈的 PMI 和 RI。如果平台只实现了发射分集 SFBC,两路发射的就是同一数据流的正交分集版本,这也是合法的 2×2 链路。
2.2 接收端还原:信道估计、MIMO 解调与 CRC
接收端做的事情可以理解成把发送端每一步反向精确还原。先基于小区参考信号做信道估计,很多平台默认提供理想信道估计和 LS 估计两档;理想信道估计适合先验证算法框架,LS 估计会把你拉回真实性能。拿到信道矩阵后,两路接收的优势就体现在符号级解调上:每个子载波上看到的不是标量信道,而是一个 2×2 的信道矩阵,MMSE 接收机的权重计算里必须有矩阵求逆或等效的表达式。你在代码里搜inv或矩阵\运算,基本能定位接收机核心。
解调后得到的是软比特 LLR,不是硬判决。LLR 再送进解扰、解速率匹配和 Turbo 解码。最终判断一个子帧成不成功的标准,不是比特错误数,而是解码后 CRC 是否通过。所以在统计 BLER 时,你要找的是每个子帧返回的 CRC 标志,而不是 BER。很多刚接触这套平台的人在这里绕远路:去代码里数比特错误,然后发现 BLER 怎么算都不对。
2.3 链路级和系统级、外场的边界:能测什么、不能测什么
用这个平台之前,先把它的能力边界画清楚,否则后面解读曲线容易过度自信。链路级仿真关心的是单条物理链路在给定信道和干扰下的性能,输出通常是 BLER、吞吐量、频谱效率;系统级仿真关心的是多用户、小区间干扰、调度、切换对整体容量的影响;外场测试则是把前面所有假设换成真实环境和射频器件。
| 仿真层级 | 主要关心 | 典型输出 | 使用阶段 |
|---|---|---|---|
| 链路级 | 发射机与接收机算法、波形、信道模型 | BLER、吞吐量、EVM | 算法选型、参数调试 |
| 系统级 | 小区间干扰、调度、切换、资源分配 | 小区吞吐量、用户公平性 | 网络规划、容量评估 |
| 外场测试 | 真实环境与射频硬件 | 覆盖、吞吐量、时延 | 商用前最终验证 |
链路级平台通常在单小区、无调度、无切换的假设下工作,所以它回答不了“小区边缘用户速率有多低”这类系统级问题。这是平台简化,不是缺陷。如果你想要的结果牵涉到多个基站之间的干扰,就应该去找系统级仿真器,而不是在链路级代码里硬加第二个小区。
3. 在 MATLAB 里跑通最小链路:参数结构、蒙特卡洛主循环与第一次 BLER 曲线
我自己跑链路级仿真的习惯是先把“能跑通”和“跑得对”分成两个阶段。很多分支版本的主函数入口不一样,有的叫run_sim,有的是直接打开一个脚本文件就能跑,但外层统计逻辑大同小异:对一串 SNR 点,每个 SNR 点跑几百上千个子帧,每个子帧推进一次完整收发,最后统计 CRC 结果。先不要在仿真时长上贪多,用默认参数跑一个最小集,确认曲线长出来,再开始调参数。
3.1 仿真配置怎么组织:字段设置与参数文件路径
我一般不用脚本尾部那一大串全局变量,而是把所有配置收拢进一个 MATLAB 结构体。这样可以避免后面做 SNR 扫描或 MCS 扫描时,参数散落各处改不全。下面这个结构体是典型配置模板,字段名按你拿到的版本对一下即可,不必强求完全一致。
% 典型 10 MHz 下行链路的配置结构 p = struct(); p.bandwidth = '10MHz'; % 带宽决定 FFT 大小和采样率 p.fftLen = 1024; % 10MHz 对应 1024 点 FFT p.samplingRate = 15.36e6; % 采样率必须与带宽配套 p.cellId = 0; % lte小区id,加扰序列和参考信号位置会用到 p.nTx = 2; % 发射天线端口数 p.nRx = 2; % 接收天线数,构成 2x2 两路 p.txMode = 'txd'; % 'txd' 发射分集 / 'olsm' 开环空间复用 / 'clsm' 闭环空间复用 p.mcs = 15; % 调制与码率通过 MCS 查表得到 p.chanModel = 'EVA'; % 信道模型:EPA / EVA / ETU p.dopplerHz = 70; % 多普勒频移,约等于 37km/h 车速在 2GHz 载频下的表现 p.numSubframes = 400; % 每个 SNR 点统计的子帧数 p.snrVec = -5:2:20; % SNR 扫描范围这里最容易犯的错是把fftLen、samplingRate直接改掉,却忘了同步调bandwidth。10 MHz 配置下采样率是 15.36 Msps,FFT 是 1024 点;改成 20 MHz 后采样率要变成 30.72 Msps,FFT 变成 2048 点。如果你只是把带宽字号改成 20MHz,但采样率还是 15.36e6,仿真出来的频带范围就是错的,且不会报错,只会让曲线整体偏移几个 dB。
3.2 SNR 扫描主循环:带随机种子的子帧推进
主循环的结构很直接:外层遍历 SNR 点,内层遍历子帧。每个子帧调用一次平台的收发入口,拿到该子帧的 CRC 结果并且累计正确传输块承载的比特数。下面这个循环就是模板,runLteSubframe是占位入口名,你手里的代码可能叫simulate_subframe,也可能叫lte_sim,只要返回结构里带 CRC 标志和传输块大小,外层逻辑就能复用。
bler = NaN(numel(p.snrVec), 1); throughputMbps = NaN(numel(p.snrVec), 1); for i = 1:numel(p.snrVec) errCnt = 0; ackedBits = 0; % 正确传输块承载的总比特数 for sf = 1:p.numSubframes res = runLteSubframe(p, p.snrVec(i), sf); if res.crcError errCnt = errCnt + 1; else ackedBits = ackedBits + res.tbBits; end end bler(i) = errCnt / p.numSubframes; % 一个子帧时长 1ms,所有子帧加起来是 numSubframes * 1e-3 秒 throughputMbps(i) = ackedBits / (p.numSubframes * 1e-3) / 1e6; end这里有几个细节。res.tbBits是当前子帧承载的传输块比特数,不是手写的常量;固定 MCS 和固定 RB 数时它基本不变,但平台可能会按 AMC 自适应调整,所以要累加而不是乘子帧数。crcError这个字段名只是示例,不同分支里可能叫crcStatus或blockErr,判断条件跟着改就行。关键词只有一个:最终的传输块层面对错,不是比特级误码率。
3.3 计算 BLER 与吞吐量:单位与绘图
统计结果后,把原始数据存成.mat文件,再画 BLER 曲线。BLER 曲线用对数坐标更直观,但注意 SNR 高到一定程度后 BLER 会变成 0,对数坐标显示不了 0,只能画到某个下限,比如max(bler, 1e-4)。这不是真实值,只是为了图形可读。
save('lte_link_result.mat', 'p', 'bler', 'throughputMbps'); figure('Color', 'w'); semilogy(p.snrVec, max(bler, 1e-4), '-o', 'LineWidth', 1.2); grid on; xlabel('SNR (dB)'); ylabel('BLER'); title(sprintf('LTE DL %dTx%dRx %s %s', p.nTx, p.nRx, p.chanModel, p.txMode));吞吐量曲线的单位建议统一到 Mbps。传输块比特数除以总仿真秒数,再除以 1e6 就是 Mbps。不要用正确子帧占比去乘以某个“理想峰值速率”,那会把 AMC 和 CRC 的影响混在一起。第一次跑这条曲线时,重点看两点:曲线是否随 SNR 单边下降,以及高 SNR 端是否接近该 MCS 的理论传输块速率。如果有明显的平台段,多半是子帧数太少或者 SNR 定义不对。
4. 必调的 5 个参数:带宽、循环前缀、信道模型、MIMO 模式与统计量
跑通一次不难,难的是把参数调到能回答你的问题。链路级仿真最怕的不是代码跑不动,而是参数之间互相矛盾还浑然不觉。我把最常动的 5 类参数列出来,每一类都带着选型理由和改错会怎样,这些经验在换平台分支时也适用。
| LTE 带宽 | 采样率 Msps | FFT 点数 | 资源块数 |
|---|---|---|---|
| 5 MHz | 7.68 | 512 | 25 |
| 10 MHz | 15.36 | 1024 | 50 |
| 20 MHz | 30.72 | 2048 | 100 |
4.1 带宽、FFT 大小与采样率:一张表对齐
带宽不是给仿真器看的一个名字,它背后绑定了资源块数、FFT 点数和采样率三样东西。5 MHz 对应 25 个资源块,每个资源块 12 个子载波,共 300 个有用子载波;10 MHz 翻一倍,20 MHz 再翻一倍。FFT 点数决定频域分辨率,采样率决定时域波形的时间分辨率。改带宽时三者必须一起动。
这块最典型的翻车场景,是从网上教程复制了 10 MHz 参数改成 20 MHz,只把bandwidth = '20MHz'写上,fftLen和samplingRate还是 1024 和 15.36e6。仿真器通常不会拦你,它会照常生成 OFDM 符号,但有用子载波分布和滤波器带宽都对不上,BLER 曲线整体右移好几个 dB,你还会以为是信道模型的问题。
注意:改带宽前先看平台内部有没有自动派生函数。如果代码从带宽字段直接计算采样率和 FFT,你只需要改一个字段;如果代码全部写死,就要手动同步所有相关字段。
4.2 信道模型 EVA/EPA/ETU 与多普勒选择
EPA、EVA、ETU 是 3GPP 定义的三种典型信道模型,区别主要在时延扩展和多普勒谱。EPA 的时延扩展最短,频率选择性弱,适合验证基础收发链路;EVA 的时延扩展中等,多普勒谱更明显,是城市道路场景的老熟人;ETU 时延尾巴长,对循环前缀和均衡器压力最大。
多普勒频移和车速的关系是定死的:fd = v * fc / c。在 2GHz 载频下,70Hz 多普勒大约对应 37km/h,300Hz 对应约 160km/h。仿真器里的dopplerHz直接决定信道随时间衰变的快慢,这会影响信道估计的跟踪能力。如果你做的是低速场景却配了 300Hz 多普勒,接收机算法会被平白无故地打击;反过来做高铁场景却用 5Hz,结果会乐观得离谱。
4.3 发射分集还是空间复用:两路 MIMO 的取舍
2×2 天线配置不是只能跑空间复用,发射分集和空间复用在低、高 SNR 区各有胜场。多数经典平台默认跑发射分集,因为不需要反馈链路,算法稳定;空间复用要拿到分集和复用双重增益,得依赖信道条件、天线相关性和 PMI 反馈。我建议用下面这张表做初始选择。
| 场景 | 推荐模式 | 原因 |
|---|---|---|
| 低 SNR、覆盖受限 | 发射分集 SFBC | 不依赖反馈,拿到分集增益 |
| 高 SNR、信道变化快 | 开环空间复用 + CDD | 平均信道下仍有复用增益 |
| 低速、室内信道稳定 | 闭环空间复用 | 靠 PMI 精细适配天线相关性 |
如果你在低速 EPA 信道里强制开闭环空间复用,又不实现 PMI 反馈,2×2 的曲线很容易比 1×2 还差。这不是平台坏了,是空间复用在这种场景下拿不到足够增益。新手的稳妥路线是:先用发射分集把整条链路验证明白,再切olsm与clsm,看曲线差异是否符合理论预期。
4.4 SNR 定义和传输块大小:为什么低 SNR 点会飘
同一套仿真,SNR 定义可以差出好几分贝。有的平台按每符号 SNR,有的按每比特 SNR,两者之间差一个10*log10(调制阶数 * 编码速率)的平移量。如果你拿自己的曲线和论文里参考曲线比较,发现整体横轴差 3 到 6dB,先不要怀疑信道模型,先查 SNR 定义是否一致。这个问题不会让曲线形状变丑,但会让验收变成玄学。
传输块大小也不能拍脑袋写。MCS 和资源块数确定后,传输块大小查 36.213 的映射表是唯一值。平台里通常自带查表函数,出错率低。怕的是有人为了凑吞吐量,把一个 10MHz、64QAM、码率 0.5 的链路手动设置成 8000 比特,然后高 SNR 端吞吐量超过了理论上限,那后面所有结论都没有意义。
4.5 跑多少子帧才够:统计置信与随机种子
子帧数目决定曲线的平滑程度,但“越多越好”是最贵的答案。理想信道估计下,每个 SNR 点跑 200 到 500 个子帧就能看到趋势;如果要在 BLER=10% 附近做验收,错误块的绝对数量至少要到几十个甚至上百个。按 BLER=0.1 反推,你要准备 1000 个传输块才比较稳,也就是 1000 个子帧。
很多平台统计结果抖得厉害,不是代码问题,是独立样本太少。低速信道下相邻子帧之间的信道相关性很高,跑 2000 个子帧等效独立样本数可能只有几百。遇到这种情况,优先让 SNR 点落在 BLER 0.01 到 0.5 这个区间,而不是一味把每个点都拉长到上万帧。仿真时间宝贵,花在 BLER 0.001 以下的点上基本是浪费电。
5. 避坑清单:在 MATLAB 里跑这套链路级仿真的 5 个常见翻车点
下面这 5 条是我自己踩过、也看别人反复踩过的坑,每一条都按“现象、原因、解决”三段写。链路级仿真有一个特点:大多数参数错误不报错,只让曲线悄无声息地变难看。所以排查要有点刑侦思维,从现象反推入口。
5.1 改参数后曲线纹丝不动:查入口实际传入值
现象:你把p.cellId从 0 改成 350,或者把p.chanModel从 EVA 改成 EPA,BLER 曲线几乎一模一样,误差在随机噪声范围内。
原因:这套平台的分支版本很多,有些代码把参数做成了全局变量或默认配置,主脚本开头加载一次默认参数,后续又把你传进来的结构体整个覆盖掉。你改的字段根本没进子帧函数。
解决:先在子帧函数入口打印关键配置字段,比如disp(p.cellId)、disp(p.chanModel),确认实际值。更稳的做法是给配置结构算一个指纹字段,例如把所有关键字段拼成字符串,每次运行打印出来。这样改没改进去一目了然。
5.2 把小区级概念带进链路级:TAC、小区 ID 与加扰的关系
现象:你在参数文件里到处找 tac(LTE 跟踪区)相关字段,找不到就开始怀疑代码不完整,觉得这个平台缺了上一层的模块。
原因:跟踪区 TAC 属于 RRC 和网络规划层面的概念,链路级仿真里不会出现。但 lte 小区ID 是真实参与链路级运算的,它决定扰码序列初始化和参考信号位置,是我们要在乎的那个字段。
解决:看物理层行为,请盯着 cellId;看跟踪区更新和寻呼行为,请去找系统级仿真器。这个边界想清楚,能少折腾一整天。
提示:链路级仿真里没有 TAC 不是缺陷,是分工。
5.3 低速信道里 2×2 反而不如 1×2:MIMO 模式选错
现象:EPA 5Hz、中等 SNR 区域,2×2 空间复用的 BLER 曲线始终在 1×2 发射分集上方,10% BLER 点落后至少 2dB。
原因:低速、低时延扩展信道里,两天线接收到的信号相关性很高,空间复用拿不到足够的独立衰落分支。没有 CDD 或者 PMI 反馈时,复用增益约等于零,还白付了层间干扰的代价。
解决:把txMode从空间复用改成发射分集,重跑一遍。如果曲线明显变好,说明问题出在模式选择而不是代码实现。要验证空间复用,就该选高 SNR、低天线相关性场景,别拿 EPA 低速信道来抬杠。
5.4 中文注释乱码与 GBK/UTF-8 编码问题
现象:从教学站或者课程镜像拿到的分支,在 R2023b 及以上版本打开后中文注释乱码,运行时报“未定义文本”或者在注释位置直接报错。
原因:文件保存时的编码和 MATLAB 编辑器默认编码不一致。老版本导出的脚本常是 GBK 编码,新版 MATLAB 默认 UTF-8,Windows 和 Linux 平台之间再混一轮,会乱得更厉害。有代码报错会说编码器为 'gbk' 怎么改为 utf-8,就是这个场景。
解决:用外部编辑器把整个源码目录里的中文注释文件统一转码成 UTF-8。如果只有几个文件带中文注释,只转这几个就好;代码主体的英文注释不乱码就尽量别动。转完以后重新打开,确认脚本能正常进断点。
5.5 parfor 随机种子复制导致结果对不上
现象:同一套配置,第一次跑 BLER 曲线是平滑的,第二次重跑结果差一截;用parfor并行以后,两个 SNR 点的 BLER 甚至完全一样,或者每次运行不同。
原因:链路级仿真里信道衰落依赖随机数流。如果在进入parfor之前只rng(seed)一次,任务被分配到不同 worker 后,随机流没法保证和串行版本一致,甚至多个 worker 可能从同一个种子开始,生成完全相同的一组信道。
解决:不要依赖任务调度的随机性,给每个子帧显式分配唯一种子。每次调用平台入口前执行rng(seedBase + i*10000 + sf, 'twister'),其中i是 SNR 序号、sf是子帧序号。这样无论串行并行,结果都可复现;如果平台内部强制自己初始化随机流,那就找找有没有关闭内部rng的开关。
6. 进阶用法:把结果做成验收曲线,再看向 4×4 扩展
曲线能画出来只是开始,能不能作为结论被引用,是另一回事。我会在项目里做一次“验收基线”:把默认配置、固定种子、固定参数跑出来的 BLER 和吞吐量曲线存档,之后所有改动都和这条基线对比。下面这个做法帮我挡住过很多次“算法优化了 2dB”的假象。
先用固定种子跑一组完整结果,验证可重复性。如果你在parfor并行环境里,也要保证每次运行得到完全相同的曲线。代码里可以直接在子帧调用前重置随机流,拿到的曲线应该每次一分不差。
baseSeed = 20240512; for i = 1:numel(p.snrVec) for sf = 1:p.numSubframes rng(baseSeed + i * 10000 + sf, 'twister'); res = runLteSubframe(p, p.snrVec(i), sf); end end有了可重复的基线,下一步是选验收点。一般我会挑 BLER=10% 对应的 SNR 作为主指标,再挑吞吐量曲线的饱和点和 1Mbps 门槛点作为辅助指标。把这些点记录在案,之后改预编码矩阵、信道估计算法或解码器实现,只对比这三个点,效率远高于对着整条曲线猜。
出图时也要注意矢量图问题。论文经常要求 EPS 或 PDF 格式,不要在 MATLAB 图形窗口里点导出按钮后直接交。用命令行打印最可控,R2023b 及更新版本依然支持-depsc2矢量输出。
set(0, 'DefaultAxesFontSize', 11); figure('Color', 'w'); semilogy(p.snrVec, max(bler, 1e-4), '-o', 'LineWidth', 1.2); grid on; xlabel('SNR (dB)'); ylabel('BLER'); legend(sprintf('%dTx%dRx %s', p.nTx, p.nRx, p.txMode), 'Location', 'southwest'); print -depsc2 LTE_BLER_2x2_EVA_txd.eps打印 EPS 前先把字体大小固定住,不然 Linux 和 Windows 下生成的图字号不一样。如果你的分支代码里默认字体偏小,负 11 号是多数论文可接受的底线。原始.mat数据也要随图一起保留,别只存一张图,因为评审问你“BLER 10% 点具体是 7.3dB 还是 7.8dB”时,再跑一遍 1000 子帧的代价很高。
做 4×4 扩展时,我习惯不过度自信。先把单天线、2×2、4×2 三条曲线跑出来,确认它们按理论顺序排开,再动 4×4。4×4 不只是改天线数量两个字段,预编码矩阵、信道相关矩阵和空间层数都要跟着扩,很多分支版本在这块代码里藏着半成品的矩阵定义,直接填 4 会得到一个看似正常实则错误的曲线。
有个习惯我保留了很久:无论拿到哪一版代码,都先不多嘴,用默认配置把 BLER 曲线存成一张图,再开始动任何参数。没有这条参考线,后面改代码的时刻就越改越像玄学,曲线偶尔会好,但你说不清是算法进步还是随机种子运气。链路级仿真不是黑匣子,能复现的结果才值得被写进报告。希望帮到你。
本文还有配套的精品资源,点击获取