拓冰建站拓冰建站
首页 / 资讯中心 / 正文

MATLAB通信中继仿真:AF与DF误码率对比从零实现

通信仿真圈子里被问得最多的问题之一就是“我做中继仿真AF和DF到底哪个好为什么我的两条曲线差别不大”我自己当年在毕设阶段也卡在这上面好几天。后来把系统模型、功率分配、信道实现全部捋清楚才发现很多看似“结论不对”的结果其实是仿真里的一个共轭符号、一个归一化因子写错了。这篇文章就从零到一带大家完整跑通一个基于MATLAB的三节点通信中继仿真重点做AF放大转发与DF解码转发两种策略的误码率性能对比。做完你能得到什么一条BER-SNR曲线、一组直传基线对照、一套能反复改参数和扩展的仿真框架。适合正在做中继/协作通信课题的学生也适合想把理论框图快速变成可运行代码的工程师。先说清楚一个概念AF就是中继把收到的模拟信号直接放大后转发简单粗暴但会连噪声一起放大DF则是中继先把信号解出来再重新编码发送理论性能更好可一旦第一跳判错就会把错符号传给目的地这就是大家都听说过的“错误传播”。两种策略谁更优跟信道质量、中继位置、功率分配都有关系光靠手算公式很难直观感受到所以用MATLAB做蒙特卡洛仿真是最稳妥也最省事的验证方式。1. 项目立项AF/DF策略对比要解决什么问题1.1 为什么是中继为什么是AF/DF中继通信并不是什么新概念它解决的核心问题是覆盖盲区和阴影衰落。比如一个基站信号到不了的小区角落中间放一个中继节点做转发用户就能正常通信了。在现代的蜂窝网络、物联网和卫星通信里中继节点到处都在用只是换了各种名字中继站、转发器、协作节点、智能表面。AF和DF是两类最基础的协议。AF的全称是Amplify-and-Forward中继节点不判断信号内容只做幅度缩放后转发。它的优势是时延小、复杂度极低。劣势也很明显第一跳噪声会被第二跳再次放大导致目的节点的等效噪声变高。DF的全称是Decode-and-Forward中继节点先把收到的信号解调解码硬判决出比特再重新调制发送。优势是第二跳发射的是“干净”的信号不带第一跳噪声。劣势是中继需要一定的计算能力而且一旦中继的判决出错它转发出去的错误信号会让目的节点在第二跳上收回一个完全反向的信号最终BER可能比AF还差。所以AF和DF的对比并不是“谁绝对碾压谁”的问题而是“在什么信道条件下各自表现如何”的问题。仿真就是解决这类问题最直观的手段。1.2 仿真目标与验收标准动手写代码之前先把目标定清。一个合格的AF/DF对比仿真至少要能回答下面几个问题在对称信道S-R链路和R-D链路统计特性相同下AF和DF的误码率曲线差别有多大当S-R链路质量变差时DF的错误传播会不会导致它的性能曲线出现“地板效应”相比直传链路两种中继策略在中高信噪比下能带来多大的分集增益在中继节点发射功率受限时AF的放大因子该如何取值才能保证仿真结果可信明确这些之后验收标准就非常清晰了三条BER-SNR曲线AF、DF、直传加一段对曲线形状和交叉点的解读。直传曲线在这里是天然的坐标标尺如果中继方案的BER曲线连直传都不如那说明方案选型或仿真实现有问题。2. 系统模型与参数选型先画好链路再写代码2.1 三节点单中继模型的选择理由很多人一上来就想做多中继、多天线、多跳的复杂模型我觉得这是一个典型的“先跑再走”的错误。AF/DF的对比研究用一个最基础的三节点模型就够了。模型里只有三个角色源节点S、中继节点R、目的节点D。三节点模型的正确性非常好验证。AF的端到端等效信噪比有一个教科书写好的闭合表达式[ \gamma_{eq} \frac{\gamma_1 \gamma_2}{\gamma_1 \gamma_2 1} ]其中(\gamma_1 |h_{SR}|^2 P_S / N_0) 是第一跳信噪比(\gamma_2 |h_{RD}|^2 P_R / N_0) 是第二跳信噪比。仿真结果可以直接跟这个公式对拍如果趋势对不上说明代码某处一定写错了。多中继的模型虽然真实但干扰变量太多出了问题很难定位。另外三节点模型的每个模块都可以独立复用。后面你想加直传链路合并、加信道估计误差、加功率分配优化只需要改一小块代码不用把整体推倒重来。2.2 瑞利信道与复高斯噪声建模要点无线传播环境里小尺度衰落最常见的模型就是瑞利衰落。所谓瑞利衰落本质上是复信道系数 (h) 满足实部、虚部都是零均值独立同分布的高斯分布且 (|h|) 服从瑞利分布(|h|^2) 服从指数分布。在MATLAB里生成一个归一化的复高斯信道系数标准写法是这样的h (randn(1, N) 1i * randn(1, N)) / sqrt(2);注意这里一定要除以 sqrt(2)。很多人初学时容易漏掉导致信道的平均功率变成2而不是1。后面所有功率计算、SNR换算全都跟着错最后曲线整体偏移好几个dB。除完 sqrt(2) 后(E[|h|^2] 1)也就是信道既不放大信号也不吸收信号只负责叠加随机相位和幅度波动这样最方便作为标准衰落信道来评估系统性能。噪声建模同理。复基带高斯白噪声的方差是 (N_0)即每个实部维度方差是 (N_0/2)。代码写成n sqrt(N0/2) * (randn(1, N) 1i * randn(1, N));这里 (N_0) 是复噪声在单个符号周期上的总功率。因为在复基带模型里信号本身也是复信号噪声是复变量所以必须这么生成不能用randn(1,N)一个维度就直接当复噪声用。如果用了单维度实噪声仿真结果会把噪声功率低估一半BER曲线整体左移约3dB这种情况我在帮别人看代码时遇到过无数回。2.3 调制方式、功率分配与合并策略调制方式建议第一步从BPSK做起。理由很实在BPSK是实信号调制判决只需要看实部符号代码简单、逻辑清晰便于把注意力集中在中继策略本身。等BPSK跑通了再换成QPSK、16QAM都只是扩展。在功率分配上我习惯把源节点功率 (P_S) 和中继节点功率 (P_R) 都设为1然后通过改变噪声功率 (N_0) 来扫描信噪比。这样最简单也最容易对照公式。目的节点的合并策略需要重点说一下。如果仿真模型里只存在中继一条路径那么目的节点的处理其实是一个单支路检测谈不上“合并”。但如果加上了直传链路就需要考虑是否把直传和中继两路信号做最大比合并MRC。最大比合并的本质是每一路信号在判决前乘上自己等效信道系数的共轭再按噪声功率做归一化让信噪比高的支路权重更大。在AF转发场景下目的节点收到的信号经过等效信道 (h_{eff}) 后判决统计量可以用下面这个伪代码表示z conj(h_eff) .* y_d; % 等效匹配滤波 bits_hat real(z) 0; % BPSK硬判决这就是单路情况下的最大比合并本质上是一个匹配滤波。如果后面要扩展成直传中继的两路合并也是同样的思路只要把两路信号分别乘共轭再相加即可。建议第一版先不做复杂合并先把三条独立曲线画出来。2.4 为什么用蒙特卡洛而不是直接推导BER看文章前面写了等效信噪比公式你可能会有疑问既然有公式直接代入概率密度函数求积分不就能得到理论BER了吗为什么还要蒙特卡洛仿真理论上确实可以积分但实际做起来很痛苦。两跳瑞利信道的端到端信噪比是一个连乘和连加混合的统计量想求它的概率密度函数在绝大多数情况下找不到初等函数形式的闭式解。即使AF能用调和级数近似DF还要考虑中继判决错误和重传符号的相关性复杂度直接指数上升。蒙特卡洛方法的思路要简单得多随机生成海量的信道系数和噪声跑一遍完整的发送-中继-接收流程数一数错了多少个比特。当随机实验次数足够多时错误率就会收敛到真实的误码率。这是工程界最常用、最不会出错的验证方式。代价是仿真时间长高信噪比下为了累积足够的错误比特往往要跑几千万个符号。但好处是模型越复杂越自由任何诡异的链路模型都能直接仿真。3. MATLAB核心实现从第一跳到最终判决的完整拆解3.1 仿真框架与蒙特卡洛循环结构我的MATLAB仿真框架是外层SNR循环、内层蒙特卡洛循环。外层扫信噪比从0dB到30dB步进2dB内层按照固定的块长批量发送符号循环很多次统计并累积误比特数。每一步的框架如下clear; clc; rng(2024); P_s 1; % 源发送功率 P_r 1; % 中继发送功率 block_len 1000; % 每块符号数 N_block 2000; % 块数等效总符号数 block_len * N_block snr_dB_vec 0:2:30; BER_AF zeros(size(snr_dB_vec)); BER_DF zeros(size(snr_dB_vec)); BER_direct zeros(size(snr_dB_vec)); for k 1:length(snr_dB_vec) snr_dB snr_dB_vec(k); N0 P_s / 10^(snr_dB / 10); err_af 0; err_df 0; err_d 0; total_bits 0; for blk 1:N_block % 发送比特BPSK调制 bits randi([0 1], 1, block_len); x 2 * bits - 1; % 0 - -1, 1 - 1 % 复高斯信道系数每块重新生成块与块之间独立 h_sr (randn 1i * randn) / sqrt(2); h_rd (randn 1i * randn) / sqrt(2); h_sd (randn 1i * randn) / sqrt(2); % ---- AF 和 DF 的处理代码在这里 ---- total_bits total_bits block_len; end end为什么要用块结构而不是逐符号循环因为生成一个块的数据明显比生成一个符号的数据更高效而且无线信道通常是块衰落的也就是在一个短时间块内信道系数基本不变这样仿真更接近真实场景。rng(2024)的设置也很有必要固定随机数种子可以确保每次跑出来的曲线完全一致调Bug和复现结果都方便。3.2 AF放大转发实现细节AF的实现关键在于如何确定放大因子。中继收到第一跳信号[ y_R \sqrt{P_S} h_{SR} x n_R ]中继需要把这个信号放大到自己的发射功率水平然后转发。最常用的变增益放大因子是[ \beta \sqrt{\frac{P_R}{P_S |h_{SR}|^2 N_0}} ]这个分母代表中继接收信号的平均总功率。为什么要加 (N_0)因为你处理的信号不只是有用的那部分还混着噪声。如果忽略噪声功率中继的实际输出功率就会比设定值偏大仿真结果也会随之偏移。MATLAB代码如下% 第一跳 n_r sqrt(N0/2) * (randn(1, block_len) 1i * randn(1, block_len)); y_r sqrt(P_s) * h_sr * x n_r; % 放大因子变增益方案 beta sqrt(P_r / (P_s * abs(h_sr)^2 N0)); % 转发 x_af beta * y_r; % 第二跳 n_d_af sqrt(N0/2) * (randn(1, block_len) 1i * randn(1, block_len)); y_d_af h_rd * x_af n_d_af; % 等效信道系数 h_eff_af sqrt(P_s) * h_sr * h_rd * beta; % 匹配滤波判决 bits_hat_af real(conj(h_eff_af) .* y_d_af) 0; err_af err_af sum(bits_hat_af ~ bits);代码里有个细节容易写错h_eff_af必须用sqrt(P_s) * h_sr * h_rd * beta这里面的sqrt(P_s)属于第一跳接收信号的一部分不能漏掉。漏掉它的结果是等效信道幅度差了一个倍率判决时相位关系错乱最终误码率会变得很奇怪。3.3 DF解码转发实现细节DF的逻辑比AF简单但更考验对错误传播的理解。中继先对第一跳信号做一次独立的解调判决得到 ( \hat{x} )然后把 ( \hat{x} ) 当作“真实符号”重新发送给目的节点。完整代码% 第一跳 y_r sqrt(P_s) * h_sr * x n_r; % 中继判决BPSK x_hat_r 2 * (real(conj(h_sr) .* y_r) 0) - 1; % 第二跳重新发送 x_hat_r n_d_df sqrt(N0/2) * (randn(1, block_len) 1i * randn(1, block_len)); y_d_df sqrt(P_r) * h_rd * x_hat_r n_d_df; % 目的节点判决 bits_hat_df real(conj(h_rd) .* y_d_df) 0; err_df err_df sum(bits_hat_df ~ bits);这里最核心的一点是第二跳发射的并不是原来的 (x)而是中继判决后的 ( \hat{x}_R )。如果第一跳SNR足够高中继判对的概率接近1第二跳信号就跟直接发送一样如果第一跳SNR低中继判决频频出错第二跳就会朝着错误的方向叠加。为什么DF的错误传播会让BER曲线出现“地板”你可以把中继看成一个带误码率 (P_{e1}) 的转发节点。当 (P_{e1}) 较高时即便第二跳信道再好目的节点收到的也是一半以上的错符号。这条链路的BER会被第一跳质量“卡住”。在仿真曲线上DF的性能会明显变差尤其在S-R链路比R-D链路更差的场景下。3.4 直传基线曲线怎么加进来没有参照物的性能曲线是没有说服力的。直传direct链路作为最简单的零中继方案可以直接作为性能下界或参照。直传的处理极其简单——源节点直接把信号发送给目的节点没有中继参与n_d sqrt(N0/2) * (randn(1, block_len) 1i * randn(1, block_len)); y_d_direct sqrt(P_s) * h_sd * x n_d; bits_hat_direct real(conj(h_sd) .* y_d_direct) 0; err_d err_d sum(bits_hat_direct ~ bits);这里我特意用了跟中继链路同一批随机数种子和同一个SNR定义这样三者的比较是公平的。在实际项目中我见过有人忘记给直传链路乘sqrt(P_s)导致直传BER曲线比真实情况低很多误以为是中继带来巨大增益——这个错误很隐蔽自查时一定要留意。如果你想更进一步把直传信号和中继信号在目的节点做最大比合并形成一个真正的协作分集系统可以这样构造合并后的信号% AF 场景下的两路MRC合并示例 z_mrc conj(h_eff_af) .* y_d_af / N0 conj(h_sd) .* y_d_direct / N0; bits_hat_mrc real(z_mrc) 0;不过这个扩展涉及噪声功率归一化和信道表达式的调整建议先把三曲线独立版本跑通再考虑要不要加。4. 结果解读两条曲线背后的物理与工程权衡4.1 典型BER曲线长什么样我在对称信道(S-R) 和 (R-D) 都是单位功率瑞利信道下跑完仿真绘制出的BER-SNR曲线大致符合下面的规律方案低SNR0-8dB表现高SNR20dB以上表现渐近分集阶数直传BER下降平缓曲线斜率约为 -11AFBER较高噪声放大明显曲线斜率约为 -22DF低SNR时受第一跳判决限制如果第一跳足够可靠曲线斜率约为 -22也就是说在独立同分布对称信道下AF和DF最终都能获得2阶分集增益也就是BER下降速度是直传的两倍。但DF要达到这个渐近性能需要第一跳的SNR足够高否则错误传播会拖后腿。实际画图时AF曲线通常会落在DF曲线上方尤其是在中高信噪比区域。这在理论上是预期的AF把噪声也放大了等效SNR的闭式表达式里分母多了一个1天然就有一个固定损耗。但两条线不应差得离谱如果AF比DF差了好几个数量级大概率是放大因子算错了。4.2 错误传播什么时候会毁掉DF当我把S-R链路的平均功率人为调低例如乘以0.1后DF的性能立刻会变脸。随着SNR增加DF的BER曲线会先下降然后逐渐出现平缓趋势也就是“地板效应”。而AF由于中继不做硬判决没有错误传播所以它的曲线在高SNR段反而能保持稳定下降最终超越DF。这个现象的物理直觉是DF把第一跳的可靠性直接用硬判决暴露出来了第一跳错一点第二跳全部白费AF则没有这样一个“开关键”但代价是把噪声也传到第二跳。所以在仿真里一定要设置两组对照实验一组是对称信道另一组是S-R弱、R-D强的非对称信道。只有同时看到这两种结果才能完整理解“DF的误差传播”这个关键概念也才能在论文或报告里把两种策略的优劣讲透。4.3 工程上AF/DF如何选型做完仿真后你会发现选AF还是DF不是一个单纯的数学问题而是一个工程取舍问题。我总结了三板斧判断准则中继节点离源节点近、第一跳质量好优先DF。因为解码几乎不会错第二跳是干净的性能上限更高。中继节点位于两跳正中间且两边都一般可以选AF简单可靠没有误码传播风险。中继节点计算能力受限、电池有限必定选AF。DF需要完整的基带解调、判决和再调制功耗和时延都上去了AF只是一个AGC加功率放大器的量级。“0.1的S-R信道幅度差能对DF造成多大影响”这类数值实验代码跑一遍远比口头解释有说服力。在我看来做AF/DF对比的完整结论永远是一张二维表格横轴是第一跳信道质量纵轴是第二跳信道质量中间填“推荐AF还是DF”。5. 踩坑实录与调试技巧5.1 复数信号处理中隐藏的错误在MATLAB里做复基带仿真最常见的错误基本集中在复数运算上。第一个坑是忘记共轭。BPSK解调需要一个匹配滤波也就是乘上信道系数 (h) 的共轭 (h^*)。如果直接乘 (h)相位没有对齐判决结果会变成随机。这个错误隐蔽因为低SNR时差别可能不大但高SNR下曲线会奇怪地逼近某个不为零的错误平层。第二个坑是abs和abs().^2用错。比如计算信道功率用abs(h_sr)^2是模平方用abs(h_sr).^2也是模平方——两个写法都有效但要小心abs(h_sr)如果用在功率公式里其实是模值。在放大因子那里必须用模平方即功率千万不要用模值。第三个坑是实部虚部维度。MATLAB里randn(1, N) 1i * randn(1, N)是生成复噪声的标准方式。有人图省事只写了randn(1, N)然后后面real(...) 0又刚好能跑导致噪声功率偏低的假象。这个小问题会让所有仿真结果左移几个dB需要特别注意。5.2 仿真结果抖动怎么压低蒙特卡洛仿真天然有随机波动但你可以控制波动幅度。核心原则是统计结果可靠程度取决于累积的错误比特数而不是总符号数。比如某SNR点真实BER是1e-4如果只发1e5个比特平均只错误10次这个10次的高斯波动会非常大算出来的BER可能在0到2e-4之间疯狂跳动。正确做法是让每个SNR点至少累积100个错误比特再停这样估计的相对误差能降到20%以内。想更省时间可以给每个SNR点设定一个最大符号数上限防止低SNR时为了凑错误数而跑太久。在统一时间的SNR循环里先跑低SNR的小批量确认整体趋势对再放量跑高SNR点效率会高很多。另外用semilogy画BER曲线时如果发现个别点掉到0一定是因为错误比特数是0这在低SNR下正常在高SNR下则说明仿真量不够。建议在算BER之前加上一句保护避免log10(0)报警BER_AF(k) max(err_af / total_bits, 0.5 / total_bits);5.3 MATLAB性能优化与脚本设计通信系统的蒙特卡洛仿真动不动就要跑几千万次循环性能优化特别重要。我通常从三个方向下手。第一去内层循环。不要逐比特或逐符号循环改成向量化块操作一次处理一整块数据MATLAB的向量化运算比for循环快一个数量级以上。第二parfor可以拿来加速SNR循环。如果你装了并行计算工具箱把外层for k...改成parfor k...然后在循环内自己汇总错误比特个数就能同时占用多个内核跑不同SNR点。要注意每个worker里都要有独立的随机数流方便复现时统一设置rng。第三合理设置数据量和数据类型。做通信仿真默认用double除非内存极度不够否则别碰single。另外把randi生成的大矩阵用完后及时清空别一直攒着占内存。还有一个小习惯把参数写进脚本顶部的配置区方便批量跑不同场景。我用一个结构体存所有参数cfg.P_s 1; cfg.P_r 1; cfg.block_len 1e3; cfg.N_block 2e3; cfg.snr_dB_vec 0:2:30;这样跑对比实验时只需要改结构体里的一个字段不会误改核心算法代码也不容易弄乱变量空间。写在最后的调试经验这套仿真框架我反复用过很多次也帮别人审过不少类似的代码。到最后只剩一句话想说如果BER曲线跑出来跟理论预期对不上先检查信道系数有没有除以sqrt(2)再检查共轭有没有漏乘最后检查放大因子分母的噪声功率。这三个点解决了AF/DF对比仿真基本能一次跑通。搞通信仿真是一个“堆细节”的活儿任何一个看似不起眼的常数错误最终都会在曲线图上变成莫名其妙的偏移或平层。希望这篇文章能帮你省下几个晚上的排查时间。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门