ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

SIMO/MIMO/MISO/STBC仿真资源:MATLAB BER曲线对比实践

SIMO/MIMO/MISO/STBC仿真资源:MATLAB BER曲线对比实践 简介针对无线通信系统性能评估需求这份压缩包聚焦单输入多输出、多输入单输出与多输入多输出系统在误比特率方面的仿真实现适合通信专业学生、研究人员以及需要对比不同接收机方案的工程师。包内共有九个MATLAB脚本覆盖最大似然、最小均方误差、迫零、空时分组编码、最大比合并以及多径正交频分复用等典型场景还包含多径信道冲激响应生成模块可直接运行并观察二发二收、二发一收等天线配置下的误比特率变化曲线。压缩包整体只有十三KB左右轻量易用目前已有192人学习。通过对照不同解码策略与接收天线数量的仿真结果读者能快速理解各算法在信噪比变化时的性能差异为后续系统设计与课程实验提供可复用的参考。1. SIMO/MIMO/MISO/STBC 仿真资源这套 BER 代码包到底能帮你算什么做无线通信物理层的人都有过这种经历论文里那几条 BER 曲线看着挺干净自己动手复现时却怎么都对不上。差一个天线配置、差一个信道矩阵维度误码率能差出一个数量级。这套 SIMO_MIMO_MISO_STBC 仿真资源解决的正是这个问题——它把四种天线配置单输入多输出、多输入多输出、多输入单输出和 STBC 空时分组编码放在一套 MATLAB 代码里直接给出在不同信噪比下的 BER 曲线对比。你不需要从零搭链路只需要改参数、跑脚本、看曲线。适合正在做 MIMO 课程设计的学生、需要快速验证算法增益的基带工程师以及想把理论曲线和仿真结果对照着看的通信算法从业者。2. 系统模型与这套代码的架构先搞清楚四种链路在仿真里是怎么建模的2.1 SIMO/MISO/MIMO 的收发模型差异天线数如何影响信号表达在把这套代码跑起来之前我建议先把 SIMO、MISO、MIMO 的数学模型在脑子里过一遍因为代码里的每个矩阵维度都和模型严格对应。SIMO单输入多输出是发射端 1 根天线、接收端 N 根天线。接收信号可以用y h * x n来描述其中h是 N×1 的信道向量x是发射符号n是 N×1 的高斯白噪声向量。在代码里你会发现SIMO 的接收端做了最大比合并也就是把各接收天线上的信号按信道增益加权求和信噪比能获得约 10*log10(N) 的增益因为多根接收天线提供了分集。这里注意分集增益来自接收端发射端依然只有一根天线所以 SIMO 不需要 STBC。MISO多输入单输出恰好相反是发射端 N 根天线、接收端 1 根天线。它的接收模型是y h * x nh是 N×1 的列向量x是 N 个发射符号组成的向量。没有 STBC 的话MISO 在平坦衰落信道里其实拿不到分集增益因为多根发射天线产生的信号在接收端是叠加的符号间还会互相干扰。要拿到发射分集就必须用 STBC。这也是为什么这套代码里 MISO 部分一定配有 Alamouti 编码或类似的正交空时分组码。MIMO 则是两端都多天线模型变成Y H * X NH是 Nr×Nt 的矩阵。MIMO 既可以做分集用 STBC 或 MRC也可以做空间复用每根天线发独立数据流。这套仿真资源的 MIMO 部分用的是 STBC 方案也就是说它的目标是通过空时编码换取分集增益而不是提升峰值速率。这三种模型在代码里分别对应不同的子函数文件名也能看得出来。你需要记住的关键点是SIMO 的增益来自接收合并MISO 的增益来自发射编码MIMO 的增益来自收发两端共同贡献。搞混了这些后面调参时你连 BER 曲线为什么长这样都解释不了。2.2 STBC 编码与信道建模Alamouti 编码的分集增益从哪里来STBC 的核心思想是把多个符号在时间和空间两个维度上展开让它们在经过不同的衰落路径之后在接收端能够被独立地、互不干扰地分离出来。这套代码里最核心的是 Alamouti 编码也就是 2×1 或 2×2 的 STBC 方案。Alamouti 编码对两个连续符号 s1、s2 做如下映射第一个时隙里天线 1 发 s1、天线 2 发 s2第二个时隙里天线 1 发 -conj(s2)、天线 2 发 conj(s1)。在接收端如果能完美估计信道 h1、h2那么接收信号经过共轭和线性合并之后s1 和 s2 可以被完全分离而且每个符号都等效经过了 |h1|^2 |h2|^2 的合成增益。这个合成增益意味着只要有一条路径没深衰落符号就能被正确解出来这就是 2 阶发射分集的作用原理。这套代码里的信道建模采用的是频率平坦瑞利衰落信道矩阵的每一项是均值为 0、方差为 1 的复高斯随机变量并且假定在每个 STBC 编码块内信道保持不变。这是一个很标准的慢衰落假设对应实际场景中的室内低速移动环境。要注意的是代码里生成信道用的是(randn 1j*randn) / sqrt(2)这种写法这样保证 E[|h|^2] 1归一化的好处是 SNR 的定义可以直接用发射符号能量比噪声功率来表示不需要额外的路径损耗修正。从实现角度说STBC 编码器在这套代码里是一个独立函数输入是调制后的符号向量输出是按 Alamouti 矩阵重新排列的发射矩阵。矩阵的行对应发射天线列对应时间时隙。如果你要换成 4×4 的 STBC如正交空时分组码 OSTBC需要修改编码矩阵的结构并且要注意码率会从 Alamouti 的 1 降到 3/4BER 曲线会相应地偏移。2.3 这套代码的文件结构和仿真主流程从参数初始化到 BER 统计拿到这套资源解压之后你应该会看到一个主脚本和若干个子函数。常见做法是主脚本负责参数定义、循环和绘图子函数分别负责调制、STBC 编码、信道生成、接收检测和 BER 统计。典型的主流程是这样的% 主脚本 main_simo_mimo_ber.m % 参数初始化 snr_dB 0:2:20; % 信噪比扫描范围单位 dB numBits 1e6; % 每个 SNR 点仿真的比特数 modOrder 4; % QPSK 调制4 对应每符号 2 比特 numTx 2; % 发射天线数 numRx 2; % 接收天线数 numTrials 100; % 蒙特卡洛循环次数 % 预分配误码率存储向量 berResults zeros(length(snr_dB), 1); % 主循环遍历每个 SNR 点 for snrIdx 1:length(snr_dB) % 计算当前 SNR 对应的噪声方差 Es 1; % 符号能量归一化 N0 Es / (10^(snr_dB(snrIdx)/10)); % 噪声功率谱密度 noiseVar N0 / 2; % 每维噪声方差 numErr 0; % 错误比特计数 totalBits 0; % 总发送比特计数 % 蒙特卡洛循环 for trial 1:numTrials % 1. 生成随机比特流 dataBits randi([0 1], numBits, 1); % 2. 调制映射QPSK modSymbols qpsk_modulate(dataBits, modOrder); % 3. 重塑为 STBC 编码块 % 每两个符号一组送入 Alamouti 编码器 numBlocks length(modSymbols) / 2; txMatrix zeros(numTx, 2, numBlocks); % [发送天线, 时隙, 块序号] for blk 1:numBlocks txMatrix(:,:,blk) alamouti_encode(modSymbols(2*blk-1), modSymbols(2*blk)); end % 4. 生成衰落信道并过信道 % 每个块的信道独立生成 rxSignal zeros(2, numBlocks); % 接收信号 noise sqrt(noiseVar) * (randn(2, numBlocks) 1j*randn(2, numBlocks)); for blk 1:numBlocks H (randn(numRx, numTx) 1j*randn(numRx, numTx)) / sqrt(2); rxSignal(:,blk) H * txMatrix(:,:,blk) noise(:,blk); end % 5. 检测与解调最大似然或 ZF/MMSE detectedSymbols alamouti_detect(rxSignal, numBlocks, numRx, numTx); % 6. 解调回比特并统计误码 detectedBits qpsk_demodulate(detectedSymbols, modOrder); numErr numErr sum(dataBits ~ detectedBits); totalBits totalBits numBits; end berResults(snrIdx) numErr / totalBits; end % 绘制 BER 曲线 semilogy(snr_dB, berResults, o-); grid on; xlabel(SNR (dB)); ylabel(BER); title(MIMO 2x2 Alamouti STBC BER Performance);这段流程的逻辑说明主循环按 SNR 从低到高扫描每个 SNR 点内部做多次独立蒙特卡洛实验信道每次重新生成模拟不同的衰落实现噪声功率由 SNR 定义反推得到BER 是所有发射比特中出错比特的占比。参数方面snr_dB决定曲线的横坐标范围numBits要足够大才能保证高 SNR 区域也统计到足够的错误比特modOrder 4表示 QPSK改成 16 就是 16QAM但要注意 STBC 编码的分集增益不变曲线的绝对位置会整体上移。3. 动手复现这套资源从运行主脚本到改参数、改配置3.1 首次运行与结果判读怎么确认代码功能正常第一次拿到这套代码不要急着改参数。我一般的做法是先按默认配置原样跑一遍确认结果符合预期后再动手。理由很简单你不知道原始作者的参数习惯比如信噪比范围、每点比特数、调制阶数都可能是特定场景下才合理的配置直接乱改容易把功能性的 bug 和参数设置问题混淆。默认配置跑完之后你至少应该验证三件事。第一BER 曲线是否随 SNR 单调下降第二高 SNR 区域的曲线斜率是否符合分集阶数预期2 阶分集对应曲线在 log-log 坐标下的渐近斜率是 -24 阶分集是 -4第三低 SNR 区域的 BER 不应高于 0.5因为 QPSK 的随机猜测误码率就是 0.5如果仿真结果是 0.6 说明符号映射或者判决方向搞反了。# 在 MATLAB 命令行窗口运行 cd /path/to/SIMO_MIMO_MISO_STBC main_simo_mimo_ber如果运行报错最常见的两个原因一是 MATLAB 当前工作路径不在代码根目录导致子函数找不到用which命令排查二是旧版本 MATLAB 不支持某些新语法比如1j虚数单位在老版本里也能用但string类型或tiledlayout等函数需要 R2019b 以上。这套代码如果用了较新版本的绘图函数建议先在 R2020a 及以上环境跑。结果判读时还要注意曲线之间的相对关系。同一套参数下SIMO 的 BER 曲线应该比 SISO 好一个接收分集增益MIMO 2×2 应该比 MISO 2×1 更好因为 MIMO 同时获得了发射和接收分集。如果曲线关系相反大概率是天线配置映射错了位置比如把发射天线数当成了接收天线数。3.2 核心参数设置与遍历策略SNR、天线数、调制阶数怎么改这套资源的灵活性主要体现在参数可配置上。从搜索到的关键词看大部分人的关注点是 SIMO、MIMO 和 MISO 之间的 BER 对比所以参数遍历的核心目标应该是画出不同配置的对比曲线。先说 SNR 范围。默认的 0:2:20 dB 适合 QPSK 和 2 阶分集但如果换成 16QAM 或者 4 阶分集同样的 SNR 范围内 BER 可能低到 1e-6 以下也可能高到 0.1 以上曲线形态会很不好看。常见做法是先粗扫一个 0:5:40 dB 的大范围找到 BER 从 1e-1 跌到 1e-5 的区域再细化为 1 dB 或 0.5 dB 步进。注意高 SNR 区域需要的蒙特卡洛次数更多否则统计不到足够的错误比特。天线数的修改会直接影响仿真时间和曲线形状。SIMO 的接收天线数从 2 改成 4接收分集阶数从 2 变 4高 SNR 区域斜率会明显变陡。MIMO 的发射/接收天线数同时加 1分集阶数变为 (Nt × Nr)仿真时间会显著增长因为信道矩阵变大了检测算法的矩阵运算量随之上升。% 参数遍历模版对比 SISO / SIMO 2x1 / MIMO 2x2 的 BER configs { struct(numTx, 1, numRx, 1, label, SISO), struct(numTx, 1, numRx, 2, label, SIMO 1x2), struct(numTx, 2, numRx, 2, label, MIMO 2x2 Alamouti) }; figure; hold on; for cIdx 1:length(configs) cfg configs{cIdx}; [ber, snr] run_ber_simulation(cfg.numTx, cfg.numRx, QPSK, 0:2:20); semilogy(snr, ber, o-, DisplayName, cfg.label); end legend; grid on; xlabel(SNR (dB)); ylabel(BER);这段配置说明run_ber_simulation是封装后的主仿真函数入参是发射天线数、接收天线数、调制方式和 SNR 范围返回的ber向量长度等于snr向量长度。建议把这段封装成函数的原因是想对比不同配置时避免代码重复粘贴造成的参数错乱。调制阶数这块要格外留意。STBC 编码器的输入是调制符号而不是比特所以 QPSK 和 16QAM 的发射符号数量相同但每符号承载的比特数不同。numBits 1e6在 QPSK 下是 50 万个符号在 16QAM 下是 25 万个符号。如果你的仿真时间预算固定改高阶调制之前一定要先减少比特数否则单次仿真时间会成倍增长。3.3 接收端检测算法的影响匹配滤波、ZF 和 ML 的取舍这套代码的接收端实现方式决定了 BER 曲线的绝对高度。我需要提醒的是很多初学者把 STBC 的接收检测和普通 MIMO 检测混为一谈这是最容易翻车的点。Alamouti 编码的接收检测在 2×1 的情况下有一个独特性质通过共轭变换和线性合并接收信号可以被改写成两个独立的标量方程不需要矩阵求逆也不需要复杂的 ML 搜索。具体形式是s1_hat h1* r1 h2 * conj(r2)其中r1、r2是两个时隙的接收信号。这个检测器在理论上是 ML 最优的因为 Alamouti 编码的正交性把矩阵信道分解成了等效对角形式。在 2×2 的 MIMO 场景下接收端有两天线合并等式扩展为四个项相加。如果你的代码借用了 ZF迫零或者 MMSE 检测器PDF 里会看到inv(H*H)*H之类的矩阵运算。ZF 在高 SNR 下和 ML 的差距很小但低 SNR 时噪声放大效应明显。所以如果这套资源里 MIMO 的曲线在高 SNR 区域斜率是对的、低 SNR 区域比理论值差那很可能就是 ZF 检测带来的代价不算 bug而是算法取舍。% 接收端合并的核心片段2x1 Alamouti 检测器 function s_est alamouti_detect_2x1(r1, r2, h1, h2) % 利用 Alamouti 正交性进行最大比合并 % r1, r2: 两个连续时隙的接收信号标量 % h1, h2: 两条路径的信道系数 % 返回估计符号 s1_hat 和 s2_hat s1_hat conj(h1) * r1 h2 * conj(r2); s2_hat conj(h2) * r1 - h1 * conj(r2); % 归一化到单位能量再判决 energy abs(h1)^2 abs(h2)^2; s_est [s1_hat/energy; s2_hat/energy]; end这段代码的逻辑说明conj(h1)*r1对第一时隙信号做匹配滤波h2*conj(r2)处理第二时隙信号的共轭项两者相加后 s1 的等效信噪比为(|h1|^2 |h2|^2) * Es / N0。参数说明energy是两条路径的功率和用它做除法是为了把估计符号归一化到和调制星座相同能量尺度后续的 QPSK 判决才能正常工作。如果你要改成 2×2需要把 r1、r2 扩展成二维列向量h1、h2 扩展成二维列向量合并项从两项变成四项。4. 避坑专场SIMO/MIMO/MISO/STBC 仿真里最常见的五个坑4.1 现象BER 曲线在低 SNR 区域高于 0.5甚至接近 1原因符号映射和判决边界方向不一致。比如发射端用 QPSK 的 Gray 映射00 - 11j, 01 - -11j, 11 - -1-1j, 10 - 1-1j但接收端的判决区域是按0 - real 0、1 - real 0写死死的。如果发射端旋转了星座图的相位相当于把符号星座整体旋转了 45 度或 90 度接收端按原方向判决就会出现系统性翻转。解决把调制映射和解调判决统一到同一份星座表里。很多成熟的代码库会用qpsk_modulate和qpsk_demodulate共用同一个constellation变量我建议你也这么做。调试时先在无噪声的 AWGN 信道下跑一遍如果 BER 不是 0说明映射/解调方向肯定有一个错了。4.2 现象高 SNR 区域 BER 曲线出现地板效应不再下降原因信道估计误差或训练序列不足。这套代码如果理想地假设接收端知道完美信道状态信息那么高 SNR 下 BER 会持续下降只是斜率越来越陡。如果看到曲线在高 SNR 区域平掉大概率是仿真中加入了导频估计误差或者固定了信道矩阵在一个大循环里没有更新导致每一轮的错误比特率被固定信道的深衰落主导。解决检查信道是否在每个编码块后重新生成检查导频插入的功率占比是否符合设定。如果确实是导频开销导致的误差地板把导频符号的功率加到正常数据符号的功率水平错误地板会下移。注意不要为了消除地板而把导频功率设得过高那相当于人为降低有效 SNR曲线会整体右移。4.3 现象MIMO 的分集增益明显低于理论值曲线斜率偏缓原因发射端天线功率归一化没有做好。一个常见的错误是SISO 发射每个符号能量为 EsMISO 或 MIMO 的每根天线也发射能量 Es导致总发射功率变成 Nt 倍。信噪比定义不一致后MIMO 的 BER 曲线会显得虚高但分集斜率看起来还是对的如果反过来总功率固定 Es 而每根天线的能量是 Es/Nt那高 SNR 区域的等效 SNR 会下降曲线右移斜率容易被误判为变缓。解决统一 SNR 定义。我常用的做法是定义 Es_total 为所有发射天线上的总能量每根天线的符号能量为 Es_total / Nt。在代码里检查调制符号的功率QPSK 星座点幅度为 1/sqrt(2) 时每符号功率为 1此时总功率就是 Nt需要除以 Nt 再进信道。4.4 现象仿真时间过长一个 SNR 点要跑几分钟原因蒙特卡洛次数和比特数配置过大或者检测算法复杂度太高。如果用了 ML 检测穷举所有可能的发射向量在 2×2 MIMO 加 QPSK 的情况下搜索空间是 4^216 个候选还能接受但 4×4 MIMO 加 16QAM 就是 65536 个候选每次都遍历的话仿真时间呈指数增长。解决先用理论曲线估计一下目标 BER 对应的 SNR 点然后在确保至少统计到 100 个错误比特的前提下减少比特数。对 ZF/MMSE 检测器可以用向量化写法替代 for 循环H\Y的操作在 MATLAB 里是矩阵左除比手动写逆矩阵快很多。如果资源里已经提供了 ZF 和 ML 两种检测模式高 SNR 区域用 ZF、低 SNR 区域用 ML曲线精度和仿真时间的折中会比较好。4.5 现象SIMO 和 MISO 的 BER 曲线几乎重合看不出发射分集和接收分集的差异原因MISO 没有启用 STBC直接发送了相同的符号或独立数据流。没有编码的 MISO 在平坦衰落信道下没有任何分集增益因为多天线信号在接收端叠加后等效于一个随机相位旋转的标量信道BER 曲线和 SISO 基本一致。如果你在 MISO 配置下发射的是两个独立数据流那接收端还会面临互干扰BER 反而比 SISO 更差。解决确认 MISO 的发射信号是否经过 Alamouti 编码。正确的做法是每两个比特映射成两个 QPSK 符号然后按 STBC 矩阵映射到两根发射天线、两个时隙。接收端只有一个天线但可以通过两个时隙的接收信号联合检测恢复出两个符号。如果没有 STBC 编码器MISO 链路是拿不到分集增益的。5. 进阶用法用这套资源验证分集阶数和扩展信道容量对比当你把这套代码的 BER 曲线跑通之后我建议做一件事用它来验证分集阶数理论值顺便扩展出信道容量的对比仿真。这比单纯调参更有价值也是面试或论文中能体现你理解深度的部分。分集阶数的验证方法很简单在高 SNR 区域BER 曲线的斜率在 log-log 坐标下等于负的分集阶数。你可以在 MATLAB 里对 SNR 和 BER 取对数然后做线性拟合% 验证分集阶数对高 SNR 区域做线性拟合 snr_linear 10.^(snr_dB_high / 10); % 把 dB 转回线性值 log_snr log10(snr_linear); log_ber log10(ber_high); p polyfit(log_snr, log_ber, 1); diversity_order -p(1); % 拟合斜率即为分集阶数 fprintf(Estimated diversity order: %.2f\n, diversity_order);这段代码的逻辑说明polyfit对高 SNR 区域的数据做一阶多项式拟合返回的第一个系数就是 log(BER) 对 log(SNR) 的斜率。对于 2×1 Alamouti你拟合出来的值应该在 1.82.1 之间因为有限比特数的蒙特卡洛仿真会有统计波动。参数说明snr_dB_high选择 BER 低于 1e-3 的区间太低的 SNR 区域受噪声主导拟合斜率不符合渐近规律。信道容量对比仿真的思路也可以直接借用这套代码的链路结构。SISO 的容量是log2(1 |h|^2 * SNR)而 MIMO 的遍历容量是E[log2(det(I (SNR/Nt) * H*H))]。你只需要把原来跑 BER 的信道生成部分抽出来改成容量累积% 用蒙特卡洛均值计算 MIMO 遍历容量 capacitySum 0; numChannels 10000; for ch 1:numChannels H (randn(numRx, numTx) 1j*randn(numRx, numTx)) / sqrt(2); capacity log2(det(eye(numRx) (snr_linear / numTx) * (H * H))); capacitySum capacitySum capacity; end capacityAvg capacitySum / numChannels;这段代码的逻辑说明H*H是 Gram 矩阵它的特征值分布决定了 MIMO 信道的并行子信道个数和增益det操作把多个子信道的容量合成总容量。参数说明numChannels 10000是信道实现的样本数越大容量估计越平滑snr_linear是线性信噪比注意从 dB 转换时需要10^(dB/10)不是乘以 10。从那以后我每次拿到一份仿真代码都会强制走一遍这套验证流程先按默认参数跑通、确认曲线趋势合理再单独修改一个参数观察影响最后用理论值做交叉验证。这套 BER 仿真资源的价值不只是那几条曲线而是帮你把分集增益、检测算法、SNR 定义这些容易忽略的细节串起来。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进