
简介面向5G与未来移动通信领域的研究者和工程人员这套MATLAB程序面向蜂窝网络下的D2D通信场景聚焦功率控制与资源分配问题可帮助解决复用蜂窝频谱时D2D链路与蜂窝用户之间的同频干扰、覆盖范围和系统容量平衡。资源压缩包共3个文件全部为.m脚本整体仅3KB三个模块分别负责信道生成、最佳响应功率迭代和统一价格更新覆盖从信道模拟到分布式功率优化再到干扰定价的完整链路。通过运行代码可以直观观察发射功率变化对用户速率、系统吞吐量和干扰水平的影响并对比不同功率控制策略的性能差异。同时代码简洁、独立性强适合作为无线通信课程设计、毕业设计或科研仿真中D2D方向的快速原型和扩展基础可进一步加入中继模式、资源调度或更复杂的优化目标。目前已有396人浏览学习文件数少、逻辑清晰便于初学者理解和二次开发。1. 基于蜂窝网络的D2D功率控制先想清楚这行代码为谁在抢频谱蜂窝网络里的D2D通信说白了就是让两个距离很近的用户不走基站转发直接点对点传输。它的诱惑在于复用蜂窝频谱几乎零成本麻烦也在这里D2D发射端一开机就在给被复用频段的蜂窝用户制造干扰。很多人第一次跑这类仿真以为资源分配的核心是把信道分好结果固定功率一跑蜂窝用户吞吐量掉了一半D2D也没好到哪去——功率控制没做。功率控制在基于蜂窝网络的D2D通信资源分配里管两件事让D2D维持在满足自身通信质量的最低发射功率把对蜂窝用户的干扰压到门限内。这篇笔记适合正在搭MATLAB仿真、被SINR算不对或功率不收敛卡住的人我把干扰建模、迭代功率更新的代码和五个高频翻车点一次讲清。2. D2D资源分配的问题建模复用哪段频谱以及谁在干扰谁2.1 蜂窝网络下D2D链路的干扰结构不做功率控制时会发生什么D2D通信要复用蜂窝用户CUE的上行频谱。为什么要选上行而不是下行上行频谱离基站接收机近D2D发射端干扰的是基站接收而基站接收天线的位置固定、信道相对好估计功率控制的反馈也更稳。这是多数仿真默认的设定你也沿用这个设定就好。D2D功率控制的收益要从干扰结构说起。假设一对D2D用户复用了某个蜂窝用户的上行频段这个频段上同时存在两条链路蜂窝用户到基站的链路D2D发射端到D2D接收端的链路。干扰是双向的D2D发射端会干扰基站的接收信号蜂窝用户也会干扰D2D接收端的信号。这意味着你调D2D功率时不只是把自己的SINR调高还会改变基站侧收到的干扰电平。功率加得越大自身SINR提升越快但也把蜂窝链路压得越低。这里就出现了一个所有资源分配算法都要面对的核心矛盾D2D功率既是自己的信号也是别人的干扰。所以功率控制的本质是在二维平面上找一组发射功率同时满足“自己的SINR不低于门限”和“蜂窝用户的SINR不低于门限”。这个约束如果只靠固定功率去碰运气绝大多数候选解都会落在某一边的禁区里。我见过不少人直接把蜂窝用户发射功率设成常数、D2D功率设成常数然后跑调度最后画出来的吞吐量曲线是一条对参数极不敏感的水平线。这说明算法根本没有在“调”功率只是在“撞”功率。你要判断自己的仿真有没有在做资源分配就看一个指标D2D功率是否随信道变化而变化。如果整个循环里P_d没被更新过那你跑的不是资源分配是信道遍历。2.2 用MATLAB表达系统模型小区布局、信道增益与SINR公式先把系统模型落到MATLAB里。常见做法是单小区、圆形覆盖蜂窝用户和D2D用户随机撒点。撒点有个容易被忽略的细节要在圆内均匀分布半径不能用均匀分布直接乘而要用平方根乘。因为面积正比于半径平方直接均匀采样会让点稠密地挤在圆心附近仿真结果会偏向短距离链路。以下代码初始化小区、用户位置与路径损耗% 系统模型初始化单小区、随机用户撒点 rng(2024); % 固定随机种子结果可复现 cellRadius 500; % 小区半径单位m numCUE 4; % 蜂窝用户数 numD2D 4; % D2D对数量 P_c 100; % 蜂窝用户发射功率mW N0 1e-6; % 噪声功率mW线性域 % 蜂窝用户位置sqrt(rand)保证圆内均匀分布 cuePos cellRadius * sqrt(rand(numCUE,1)) ... .* exp(1j * 2 * pi * rand(numCUE,1)); % D2D发射端位置 d2dTxPos cellRadius * sqrt(rand(numD2D,1)) ... .* exp(1j * 2 * pi * rand(numD2D,1)); % D2D接收端在发射端周围随机偏移距离10~50m d2dDist 10 40 * rand(numD2D,1); d2dRxPos d2dTxPos d2dDist .* exp(1j * 2 * pi * rand(numD2D,1));这段代码里有两个必须说明的参数选型。第一rng(2024)不是随便写的D2D功率控制的仿真结果对用户位置极其敏感同一套算法换一组用户坐标可能差出3dB以上固定种子是可复现的最低要求。第二D2D距离限制在10到50m之间如果距离超过100mD2D直连的优势就走远了功率控制做到极限也很难满足高质量通信。距离分布的范围直接影响后续功率迭代能收敛到的SINR上限后面避坑章节会展开说。路径损耗函数单独抽出来避免在主循环里把公式写散function pl pathLoss(distM) % distM: 距离米输出路径损耗dB dKm max(distM, 1) / 1000; % 防止距离为0导致log10(0) pl 128.1 37.6 * log10(dKm); end注释里已经说明这是常见的城市宏小区路径损耗模型系数128.1对应1km参考距离处的损耗37.6是路径损耗指数。这两个数决定了功率控制的“难度”指数越大功率提升对SINR的边际收益越低功率控制越倾向于在低功率区达到平衡。你在自己的仿真里换成室内部署时指数一般降到2左右结论会明显偏向高功率。注意在MATLAB脚本中函数要放在脚本末尾或单独存成m文件否则会报“Function definitions are not permitted in this context”的错误。有了路径损耗接下来算三类信道增益D2D对自身的链路增益G_dd、蜂窝用户到D2D接收端的干扰增益G_cd、D2D发射端到基站的干扰增益G_db。计算统一用线性域% 信道增益计算线性域假设一对D2D复用一个蜂窝用户 G_dd zeros(numD2D, 1); % D2D发射端到接收端 G_cd zeros(numD2D, 1); % 蜂窝用户到D2D接收端干扰 G_db zeros(numD2D, 1); % D2D发射端到基站干扰 basePos 0; % 基站位于坐标原点 for i 1:numD2D distDD abs(d2dRxPos(i) - d2dTxPos(i)); % D2D链路距离 distCD abs(d2dRxPos(i) - cuePos(i)); % 被复用蜂窝用户到D2D接收端 distDB abs(d2dTxPos(i) - basePos); % D2D发射端到基站 G_dd(i) 10^(-pathLoss(distDD)/10); G_cd(i) 10^(-pathLoss(distCD)/10); G_db(i) 10^(-pathLoss(distDB)/10); end这里默认了D2D对i复用蜂窝用户i的频谱是一对一的配对关系。真实场景里配对策略可以更复杂比如让每个D2D对选择使G_cd最小的蜂窝用户来复用这能让功率控制更快收敛也能让更多的D2D对同时接入。初版仿真建议先用一对一配对把功率控制调通再换配对策略否则两个变量一起改出了问题很难定位。2.3 分配策略的两条路线先信道再功率还是功率信道联合D2D资源分配在方案层面分两种流派。第一种是“先信道再功率”先把D2D对分配到蜂窝用户频谱上再在给定配对关系下用迭代算法调功率。优点是简单、可解释性强每一步都有明确的物理含义适合验证基本想法。缺点是信道分配错了功率控制也救不回来比如一个D2D对不幸复用了离接收端极近的高功率蜂窝用户干扰项G_cd非常大功率控制收敛后该D2D对的SINR可能连门限都摸不到。第二种是“功率信道联合优化”把配对变量和功率变量放在同一个优化问题里求解常见做法是引入信道分配指示变量配以SINR线性化约束把问题转成混合整数规划或交替优化。这条路上限更高但在MATLAB里复杂度陡增而且对初版验证很不友好——一旦结果不对你分不清是求解器的问题还是建模的问题。我的习惯是先用第一种路线搭出基线把功率控制的收敛和单位问题全部调干净再往第二种路线演进。大部分仿真验证场景下第一种路线的实现已经足够说明功率控制的收益。3. 功率控制算法设计从固定功率到迭代调整收敛到哪个门限才算数3.1 三类常用功率控制方案对比该选哪条路入手常见做法里有三条路线固定功率、分布式迭代功率控制、集中式优化功率分配。固定功率不叫算法但它是所有对比实验的基准。分布式迭代的思路是每个D2D链路拿本地的SINR测量值按目标SINR做比例调整复杂度低且不需要全局信道信息。集中式优化则要求基站知道全部信道增益对蜂窝用户SINR约束做显式建模求解精度高但对信道估计要求苛刻。三类方案的差别可以这样记方案需要的信息复杂度适合的阶段固定功率无最低基线对比、信道遍历迭代功率控制本地SINR、目标门限低初版算法验证集中式优化全局信道信息高方案上限评估迭代功率控制在MATLAB里最容易落地它对初始值不挑剔收敛速度也够快而且逻辑和论文里常写的“分布式资源分配”能对上。下面以它为主线展开。3.2 迭代功率控制的更新公式与收敛条件为什么是乘性更新而不是加性迭代功率控制的核心是乘性更新公式写出来是这样P_d(t1) min(P_max, P_d(t) * (SINR_target / SINR_current(t)))为什么用乘性而不是加性加性更新P_d加一个固定步长在功率值跨越多个数量级从微瓦到百毫瓦时步长要么太小导致收敛极慢要么太大导致在目标点附近震荡。乘性更新本质是在对数域做梯度下降步长正比于当前功率值动态范围适应能力强这是它在功率控制里被广泛采用的原因。收敛条件有两个层面。第一功率序列本身收敛连续两次迭代的相对变化低于阈值比如1e-3。第二收敛点有意义收敛后的SINR不能低于目标门限。如果迭代跑满50次还没收住多半是目标SINR设得太高超出这条链路的容量上限。很多人在这一步翻车以为是代码问题其实是门限设得超过了短距离信道能支撑的物理上限。3.3 在MATLAB里写D2D迭代功率控制核心循环与两类保护机制下面这段代码是功率控制的主体循环实现了乘性更新和蜂窝用户保护两种机制% 参数区 P_d ones(numD2D,1) * 20; % D2D初始功率mW P_max 200; % D2D最大发射功率mW P_min 0.1; % D2D最小发射功率mW SINR_target 10; % D2D目标SINR线性域约10dB SINR_cue_min 3.16; % 蜂窝用户保护门限线性域约5dB maxIter 50; % 最大迭代次数 for iter 1:maxIter P_d_prev P_d; % 计算D2D当前SINR线性域 SINR_d P_d .* G_dd ./ (P_c .* G_cd N0); % 计算蜂窝用户在当前干扰下的SINR SINR_cue P_c .* G_cb ./ (P_d .* G_db N0); % 第一层乘性更新向目标SINR逼近 P_d_new P_d .* (SINR_target ./ SINR_d); % 第二层蜂窝用户保护SINR跌破门限则回退功率 overLimit (SINR_cue SINR_cue_min); P_d_new(overLimit) P_d(overLimit) .* ... (SINR_cue(overLimit) ./ SINR_cue_min); % 功率边界裁剪 P_d min(P_max, max(P_min, P_d_new)); % 收敛判断 if max(abs(P_d - P_d_prev) ./ P_d_prev) 1e-3 break; end end这里有两层更新机制说明一下各自的逻辑。第一层是标准的目标SINR追踪当前SINR比目标低就在保证不超过P_max的前提下加大功率比目标高就降功率——这正好体现D2D“够用就好”的原则不盲目发射。第二层是蜂窝保护算出每个被复用蜂窝用户当前的SINR如果被D2D干扰压到了门限以下就把对应D2D的功率按比例回退回退比例是当前SINR与门限的比值。这是一个保守估计因为降低功率会同时降低对基站的干扰实际蜂窝SINR一定会高于回退比例对应的水平下一轮迭代会继续修正。关于这部分注意代码里G_cb是蜂窝用户到基站的信道增益初版模型里它和CUE的位置相关计算方式和前面的G_cd、G_db一致。我没有把G_cb的求解写进循环内部而是单独算好传入避免每次迭代重复做路径损耗计算这个习惯在仿真帧数高的时候能显著省时间。这段迭代还有一个实用技巧尽量让初始P_d偏保守比如取P_max的十分之一。如果初始功率过大乘性更新虽然不会发散但会在头几轮频繁触发P_max裁剪收敛要多花好几轮迭代。初始功率设小迭代方向是单调向上逼近目标的收敛路径更干净。4. 完整仿真主程序参数初始化、用户配对与调度统计4.1 仿真参数表一套能直接用的参数组合把前面分散的参数汇总成一个可复制的参数表。这套参数模拟的是一个500m半径的热点区域蜂窝用户功率较高D2D对距离近、功率受限属于比较典型的研究场景。噪声功率按10MHz带宽和5dB噪声系数折算到mW域如果你改带宽这个数要一起改。参数取值说明cellRadius500 m小区半径numCUE / numD2D4 / 4用户规模一对一复用P_c100 mW蜂窝用户发射功率P_max / P_min200 / 0.1 mWD2D功率边界SINR_target10线性域D2D目标SINR约10 dBSINR_cue_min3.16线性域蜂窝保护门限约5 dBN01e-6 mW噪声功率这套参数下D2D的SINR目标设成10dB是一个上限参考。D2D链路距离十几米时路径损耗只有70dB上下10dB的SINR目标在功率上完全够得着但一旦蜂窝用户离D2D接收端很近干扰项P_c*G_cd会把这个目标的达成难度拉高一截这时要么换配对要么让蜂窝用户SINR保护门限先牺牲一点。参数调节方向在最后一章详细说这里先把程序跑通。4.2 用户配对与复用调度在功率控制前先决定谁能接入配对策略直接决定功率控制的效果。最简单的策略是随机配对代码好写但结果方差大。更常见的是最小干扰配对对每个D2D对在所有蜂窝用户里选G_cd最小的那个作为复用对象。G_cd是蜂窝用户到D2D接收端的干扰信道增益选它最小意味着蜂窝用户对D2D的干扰最小功率控制的收敛压力最小。% 最小干扰配对每个D2D对选择对其接收端干扰最小的蜂窝用户 pairIdx zeros(numD2D, 1); G_cd_all zeros(numD2D, numCUE); for i 1:numD2D for j 1:numCUE distCD abs(d2dRxPos(i) - cuePos(j)); G_cd_all(i,j) 10^(-pathLoss(distCD)/10); end [~, pairIdx(i)] min(G_cd_all(i, :)); end配对完成后需要做接入判定如果某个D2D对在P_max下都无法达到SINR_target说明这条链路在这个位置上根本不适合D2D通信直接判定该对不可接入。这个判定要在功率控制之前做否则迭代控制会把P_d顶到P_max还收敛不了干扰其他链路的调度。判定的代码逻辑很简单就是拿P_max代入SINR公式和门限比较省去一整轮无效迭代。4.3 把功率控制装进蒙特卡洛框架多帧统计比单帧结果可靠得多单帧仿真只能说明一组用户坐标下算法表现不能支撑结论。标准做法是蒙特卡洛跑很多次用户撒点每帧执行一次配对和功率控制最后统计吞吐量的累计分布。这样写出的主程序结构如下numFrames 1000; % 仿真帧数 thrD2D zeros(numD2D, numFrames); thrCUE zeros(numCUE, numFrames); for frame 1:numFrames % 每帧重新撒点外层控制rng保证整体可复现 [cuePos, d2dTxPos, d2dRxPos] genUsers(cellRadius, numCUE, numD2D); [G_dd, G_cd, G_db, G_cb] calcGains(cuePos, d2dTxPos, d2dRxPos, basePos); pairIdx minInterfPair(cuePos, d2dRxPos, numCUE, numD2D); [P_d, SINR_d, SINR_cue] powerControl(... P_c, P_max, P_min, SINR_target, SINR_cue_min, ... G_dd, G_cd, G_db, G_cb, pairIdx); % 用香农公式算吞吐量单位bit/s/Hz thrD2D(:, frame) log2(1 SINR_d); thrCUE(:, frame) log2(1 SINR_cue); end我倾向于把撒点、信道增益、配对、功率控制拆成独立函数这样批量扫描参数时不用改主循环。代码里genUsers、calcGains、minInterfPair、powerControl都是从前面小节提炼的函数内部逻辑不变。蒙特卡洛帧数从1000起调帧数太少的后果是吞吐量CDF曲线毛刺重两个算法对比时看不出差距。吞吐量统计用香农容量log2(1SINR)是标准做法注意单位是bit/s/Hz不乘带宽。如果你想报告实际速率Mbps再乘以系统带宽即可。这里SINR是线性域数值直接带入log2计算不需要转dB。5. 功率控制仿真的玄学与排查5个让结果翻车的细节跑D2D功率控制仿真的经验里有一半是调通算法另一半是处理那些看起来毫无规律、实则原因单一的问题。我把自己踩过的坑按“现象→原因→解决”整理成五条每条都对应我会优先检查的代码位置。5.1 现象SINR算出来高达几百甚至上千很多人上去跑第一轮就发现SINR_d大得离谱比如1400第一反应是信道增益算错了其实九成是单位混用。路径损耗函数返回的是dB信道增益必须用10^(-dB/10)转成线性域但功率P_c如果用了W、噪声N0用了mW或者把dB当线性值直接做了乘法SINR就会变成奇怪的大数。原因和解决路径损耗、功率、噪声三者必须统一到同一个单位域。我一般统一用mW和线性增益路径损耗只出现在中间转换步骤里。排查时把三个量分别打印出来G_dd应该在1e-7到1e-4量级P_c是100N0是1e-6SINR就该在几十以内。看量级就能快速定位是哪一项的单位出了问题。5.2 现象路径损耗出现NaN或Inf功率控制循环直接挂掉如果算法跑到一半P_d更新成NaN再往下所有结果都是NaN。最常见的触发点是距离为0D2D发射端和接收端坐标重合或者落在同一个栅格里pathLoss里的log10(0)产生-Inf10^(-Inf/10)变成0然后除零。原因和解决随机撒点逻辑里接收端相对发射端的距离从10m起本来不该为0但如果你改成多元分布采样或者对坐标做了取整操作就可能出现碰撞。解决很简单距离带入路径损耗前用max(distM, 1)兜底1m以下的距离按1m算这在物理上也说得通。再加一层保护每次撒点后检查distDD是否小于0.1m若是则重新撒这一对。5.3 现象迭代功率控制不收敛功率一直顶在P_max上震荡SINR_target设成30dB迭代了50次P_d还是满的这不是代码bug是目标超出了链路能力。乘性更新的收敛性依赖目标SINR在系统可达范围内。D2D链路的可达SINR上限由路径损耗、干扰和噪声共同决定在干扰受限场景里尤其如此。原因和解决先把目标门限降到10dB左右跑一遍如果收敛了说明算法没问题是这个位置或参数下够不着更高目标。也可以在乘性更新里加入阻尼系数P_d(t1) P_d(t) * (SINR_target/SINR_curr)^alphaalpha取0.5~0.8收敛更稳定但会变慢。用alpha小于1本质上是降低每步修正力度能避开临界点附近的震荡。5.4 现象蜂窝用户SINR被D2D拉爆但功率控制根本没反应只做第一层目标SINR追踪时算法眼里只有D2D自己的通信质量蜂窝用户死活不管。如果蜂窝用户SINR掉到1dB以下D2D功率还是照常加大因为算法觉得“D2D还不够好”。原因和解决这就是前面代码里第二层保护机制存在的意义。蜂窝用户SINR跌破门限时对应D2D的功率必须按比例回退而不是继续追自己的目标。推进时把SINR_cue_min设成5dB左右比较合理太低等于没有保护太高又会让D2D功率被频繁压制、吞吐量上不去。这是一个纯调参问题建议画一条蜂窝用户SINR随D2D功率变化的曲线门限选在曲线斜率最大的区域附近能最大程度体现“保护”的价值。5.5 现象仿真结果和论文里的曲线差得很远甚至趋势相反这是最让人头疼的一类问题。D2D功率控制类论文里常见结论是“所提算法吞吐量高于固定功率方案20%到40%”但你复现出来可能只有5%或者干脆是负的。原因通常在仿真环境和对比基准的差异论文可能用了室内部署、不同路径损耗指数或者对比对象根本不是同一套固定功率取值。原因和解决第一把你的路径损耗模型、用户距离分布、噪声功率列表写清楚先把这些变量固定成和论文尽量一致再谈对比。第二检查对比基准固定功率方案里的“固定”是固定成P_max还是P_max/2这个选择决定了对比的幅度固定得越低迭代算法的相对收益越大。第三用CDF对比而不是用单帧平均值。平均吞吐量对异常值极其敏感一对用户位置极端就能把均值拉高15%CDF中位数对比稳健得多。如果CDF曲线整体包络一致只是偏移几个dB大概率是干扰系数或距离分布的正常差异不必强行对齐。6. 用吞吐量CDF和参数敏感性验收算法功率控制到底值多少6.1 三组CDF对比固定功率与迭代功率控制的差距在哪算法写完不能说“能跑”要有验收手段。我习惯做三件事第一件就是对三组方案的吞吐量CDF固定功率、带完整保护的迭代功率控制、只做第一层更新的无保护迭代。固定功率取P_max的一半作为典型值画图用排序后自己画阶梯曲线。预期看到的结果是迭代功率控制在D2D中位数吞吐量上和固定功率相当但蜂窝吞吐量的尾部最差10%用户位置显著抬升。这就是功率控制的收益所在——它不是让所有用户变快而是把最差场景拉到可接受范围。6.2 参数敏感性扫描P_max与SINR_target怎么定第二件事是参数敏感性分析。把P_max从50mW扫到500mWSINR_target从5扫到20每档跑1000帧记录D2D平均吞吐量和蜂窝平均吞吐量画两条曲线。你会发现D2D吞吐量随P_max上升先快后慢最终趋于平台同时蜂窝吞吐量单调下降。平台出现的地方就是这条信道下D2D功率的“物理上限”超过它继续加功率只会恶化蜂窝侧收益。第三个实用技巧是把功率控制封装成函数传入参数结构体这样批量扫描的代码就是几行循环加一个函数调用。我早期直接把参数写死在主脚本里每次扫参数都要复制一大段脚本改数值而且很容易漏改一处导致整批结果作废。现在我把参数都收进params结构体控制函数只接受params和信道变量扫P_max、扫门限都是同一套代码改一个字段跑一遍。最后再做一次收敛性自检打印迭代过程中P_d的变化轨迹确认不是每次都顶到边界如果大量D2D对最终功率落在P_max上说明SINR_target定得太高或者P_max太低系统里大部分链路工作在硬约束上这时算法的“调功率”能力是虚的结果就没有说服力。这个习惯和上面的避坑清单希望帮你在D2D功率控制的仿真路上少走几段弯路。本文还有配套的精品资源点击获取