下面的框架中是一个OFDM ISAC的框架,主要关注delay和doppler的感知。下面写的是主函...
생성일: 2026년 1월 9일
답변에 사용된 모델 GPT-5.2 Thinking by Chat01
생성일: 2026년 1월 9일
답변에 사용된 모델 GPT-5.2 Thinking by Chat01
下面的框架中是一个OFDM ISAC的框架,主要关注delay和doppler的感知。下面写的是主函数。现在我们想改成afdm,并在ofdmmodulate和demodulate里面把fft换成daft,同时在接收端信道估计的时候也做了如下处理,利用2d-dft来估计我们的位置和速度。先看一下下面的代码并学习一下。
主函数:clc;
clear;
tic
addpath(genpath(pwd)) % include all the subfolders under the current folder into the calling function directory
snrdB = 10; % SNR configuration
simulationNum = 50;
for SNR = snrdB
rng('default');
[sensing] = sensingInit(); % sensing parameter initialization
text[TxFreqSignal,sensing]=RsMapping(sensing); % sensing reference signal frequency domain mapping %% whether comm rangeErrSet=zeros(sensing.targetNum,simulationNum); veloErrSet=zeros(sensing.targetNum,simulationNum); angleErrSet=zeros(sensing.targetNum,simulationNum); for num=1:simulationNum [sensing,channel]=channelParaConfig(sensing,num); % channel parameter configuration %%target set [sensing,channel] = channelInit(sensing,channel); % initialization of channel parameters %% target add H [TxFreqSignalprecode] = TxAnalogPrecoding(sensing, TxFreqSignal, channel); % multi anteana care [TxTimeSignal,sensing]=ofdmModulate(TxFreqSignalprecode,sensing,num); % ofdm modulation channelOut=channelPass(TxTimeSignal,TxFreqSignalprecode,channel,sensing); % pass channel %%(time/frec) channelOut=awgn(channelOut,SNR); % add Gaussian noise [RxFreqSignal]=ofdmDemodulate(channelOut,sensing); % ofdm demodulation Hest=channelEst(TxFreqSignal,RxFreqSignal,sensing); % frequency domain channel estimation %% (correlation) Hest=clutterCancel(Hest,sensing); % Clutter cancellation [range,velocity,angle]=seningProcess(Hest,sensing); % sensing algorithm processing [rangeErr,veloErr,angleErr]=errStatistics(range,angle,velocity,sensing,channel); % result statistics rangeErrSet(:,num)=rangeErr; veloErrSet(:,num)=veloErr; angleErrSet(:,num)=angleErr; % [Xposi,Yposi]=positionCalcu(range,angle,sensing,channel); end plotfig(sensing,rangeErrSet,veloErrSet,angleErrSet,channel,SNR); % draw the result figure
end
toc
function [Range,Velocity,Angle]=dft_2d(Hest,sensing)
targetNum=sensing.targetNum;
fc=sensing.fc;
Deltaf=sensing.SCS;
slotT=sensing.frameLen*1e-3/sensing.sensingSymbolNum;
c=3e8;
UEdV=sensing.UEdV;
UEdH=sensing.UEdH;
freqSpace=sensing.freqSpace;
pseudoPeakFlag=sensing.pseudoPeakFlag;
Range=zeros(targetNum,1);
Velocity=zeros(targetNum,1);
Angle=zeros(targetNum,1);
ang=0;
if sensing.overSamplFlag==1
lenf=sensing.overSamplNumf;
lent=sensing.overSamplNumt;
else
[lenf,lent]=size(Hest(:,:,1));
end
rxAntNum=sensing.rxAntNum;
initH=zeros(lenf,lent);
dataSet=cell(1,rxAntNum);
for antId=1:rxAntNum
Htemp=Hest(:,:,antId);
Hdft=fft(ifft(Htemp,lenf).',lent).';
dataSet{antId}=fftshift(Hdft,2);
initH=initH+fftshift(abs(Hdft),2);
end
residualH=initH;
for Idx=1:targetNum
loop=1;
flag=0;
while loop
if Idx>1 || flag==1
[Nc,Nt,Np]=size(Hest);
textfor antId=1:rxAntNum for delayIdx=delayTapSet-lent/2-1 for dolpIdx=dolpTapSet-lent/2-1 distance=delayIdx*deltaR; phiA = exp(-1j*2*pi*[0:Np-1]'*0.5*cos(ang*pi/180)); phiR = exp(-1j*2*pi*Deltaf*freqSpace*distance*[0:Nc-1]/c); phiV=exp(1j*2*pi*dolpIdx*2*deltafd*slotT*[0:Nt-1]); phi=kron(phiR,phiV); phiphi=kron(phiA',phi); phi=phiphi; reSignal=abs(fftshift(fft(ifft(phi,lenf).',lent).',2)); reSignal=reSignal/max(reSignal(:)); factor=residualH(delayIdx+1,dolpIdx+1+lent/2+1); DDRxH = factor*reSignal; residualH=residualH-abs(DDRxH); residualH(residualH<0)=0; end end end end end % [Nc,Nt]=size(residualH); % Vset= c*[-Nt/2:Nt/2-1]./(fc*slotT*Nt); % Rset=[0:Nc-1]./(Deltaf*Nc)*c; deltaV=c/(fc*slotT*lent*2); deltafd=1/(lenf*slotT*2); deltaR=c/(freqSpace*Deltaf*lenf); % residualH=residualH(1:floor(lenf/2),:); residualH=residualH(1:floor(lenf/1),:); [~,index]=max(residualH(:)); [maxRow,maxCol]=ind2sub(size(residualH),index); surf(residualH); figure plot(residualH(1:maxCol)); distance=(maxRow-1)*deltaR; rangeReal=sensing.targetRange; realRow = rangeReal/deltaR+1; speed=(maxCol-lent/2-1)*deltaV; if pseudoPeakFlag==1 dolpTapSet=[maxCol-1,maxCol,maxCol+1]; dolpTapSet(dolpTapSet>lent)=[]; dolpTapSet(dolpTapSet<=0)=[]; delayTapSet=[maxRow-1,maxRow,maxRow+1]; delayTapSet(delayTapSet<=0)=[]; else dolpTapSet=maxCol; delayTapSet=maxRow; end % INN=120; % mesh(deltafd*[-lent/2:lent/2-1],deltaR*[0:Nc-1],residualH(1:INN,:)); % xlim([-1000,1000]) % xlabel('Doppler[Hz]'); % ylabel('Range[m]'); % zlabel('Amplitude'); % angle estimate with MUSIC algorithm if rxAntNum>1 && abs(speed)>=min(deltaV,5) Xset=zeros(1,rxAntNum); for rxAntId=1:rxAntNum Xset(1,rxAntId)=dataSet{rxAntId}(maxRow,maxCol); end RxT=Xset; % 补充Xset到RxT的赋值(原代码隐含此步骤) J=flip(eye(rxAntNum)); clear Xset Rv=(RxT*conj(RxT)')/2; [U,Value]=eig(Rv); % Value=diag(Value); sigNum=targetNum; Un=U(:,1:end-sigNum); Pn=Un*Un'; n=0; if sensing.verticalFlag==1 Aset=sensing.AsetV; Psang=zeros(1,length(Aset)); for A=Aset n=n+1; phiA = exp(1j*2*pi*[0:rxAntNum-1]*UEdV*cos(A*pi/180)); % ZOA estimation Psang(n)=1/(abs(phiA'*Pn*phiA)); end else Aset=sensing.AsetH; Psang=zeros(1,length(Aset)); for A=Aset n=n+1; phiA = exp(1j*2*pi*[0:rxAntNum-1]*UEdH*sin(A*pi/180)); % AOA estimation Psang(n)=1/(abs(phiA'*Pn*phiA)); end end [peakPower,peakInd]=findpeaks(Psang); [~,index]=sort(peakPower,'descend'); ang=Aset(peakInd(index(1:1))); end % if abs(speed)<min(deltaV,5) loop=loop+1; flag=1; % else Range(Idx)=distance; Velocity(Idx)=speed; Angle(Idx)=ang; loop=0; flag=0; end if loop>10 break; end
end
end
function [TxTimeSignal,sensing]=ofdmModulate(TxFreqSignal,sensing,frameId)
Nsybmol=sensing.Nsymbol;
txAntNum=sensing.txAntNum;
fftSize=sensing.fftSize;
if sensing.blockCP==1
CplenSet=zeros(1,Nsybmol);
CplenSet(1:14:end)=fftSize;
sensing.CplenSet=CplenSet;
else
CplenSet=sensing.CplenSet;
end
removeCpInd=zeros(1,Nsybmol); % CP position index
TxTimeSignal=zeros(txAntNum,Nsybmol*fftSize+sum(CplenSet(1:Nsybmol)));
for antId=1:txAntNum
TxTimeSignalTemp=[];
% changed:choose AFD
% sensing.useAFDM
if sensing.useAFDM
TxSignalT=idaft(TxFreqSignal(:,:,antId),sensing.afdm.c1,sensing.afdm.c2); % idaft
else
TxSignalT=ifft(TxFreqSignal(:,:,antId),fftSize)*sqrt(fftSize); % ifft
end
%%%
for symInd=1:Nsybmol
removeCpInd(symInd)=length(TxTimeSignalTemp);
Cpdata=TxSignalT(end-CplenSet(symInd)+1:end,symInd);
TxSignalCp=[Cpdata.',TxSignalT(:,symInd).']; % add CP
TxTimeSignalTemp=cat(2,TxTimeSignalTemp,TxSignalCp);
end
TxTimeSignal(antId,:)=TxTimeSignalTemp;
end
removeCpInd(removeCpInd+1)=length(TxTimeSignalTemp);
sensing.removeCpInd=removeCpInd;
sensing.frameId=frameId;
end
function [RxFreqSignal]=ofdmDemodulate(channelOut,sensing)
if sensing.cfr==0
Nsybmol=sensing.Nsymbol;
fftSize=sensing.fftSize;
CplenSet=sensing.CplenSet;
removeCpInd=sensing.removeCpInd;
rxAntNum=sensing.rxAntNum;
text% remove CP and FFT RxSignal=zeros(fftSize,Nsybmol,rxAntNum); RxFreqSignal=zeros(fftSize,Nsybmol,rxAntNum); for antId=1:rxAntNum for symInd=1:Nsybmol RxSignal(:,symInd,antId)=channelOut(antId, removeCpInd(symInd)+CplenSet(symInd)+1 : removeCpInd(symInd)+CplenSet(symInd)+fftSize); %%% AFD added if sensing.useAFDM RxFreqSignal(:,symInd,antId)=daft(RxSignal(:,symInd,antId),sensing.afdm.c1,sensing.afdm.c2); % daft else RxFreqSignal(:,symInd,antId)=fft(RxSignal(:,symInd,antId))/sqrt(fftSize); % fft end end end
else
RxFreqSignal=channelOut; % if pass frequency domain channel,skip this step
end
end
function Hest=channelEst(TxFreqSignal,RxFreqSignal,sensing)
timeSpace = sensing.timeSpace;
freqSpace = sensing.freqSpace;
Nzc=sensing.Nzc;
Nc=sensing.Nc;
fpattern=sensing.fpattern;
tpattern=sensing.tpattern;
sensingSymbolNum=sensing.sensingSymbolNum;
if sensing.fastchannel==1
tpattern=[1:sensingSymbolNum];
end
rxAntNum=sensing.rxAntNum;
%% AFD
if sensing.useAFDM == 1
txTime = idaft(TxFreqSignal,sensing.afdm.c1,sensing.afdm.c2);
rxTime = zeros(size(RxFreqSignal));
for rx=1:rxAntNum
rxTime(:,:,rx) = idaft(RxFreqSignal(:,:,rx),sensing.afdm.c1,sensing.afdm.c2);
end
TxFreqSignal = fft(txTime,Nc,1)/sqrt(Nc);
RxFreqSignal = fft(rxTime,Nc,1)/sqrt(Nc);
end
TxFreqSignal=circshift(TxFreqSignal,Nc/2,1);
TxSignalTF=TxFreqSignal(fpattern,tpattern);
Hest=zeros(length(sensingSymbolNum),sensingSymbolNum,rxAntNum);
RxFreqSignal=circshift(RxFreqSignal,Nc/2,1); % Frequency domain data passing through the time domain channel
for RxIdx=1:rxAntNum
RxSignalTF=RxFreqSignal(fpattern,tpattern,RxIdx);
Hest(:,:,RxIdx)=RxSignalTF ./ TxSignalTF;
end
if sensing.otfsSensingFlag==1
Hest=RxFreqSignal(1:Nc,tpattern);
end
end
我把你贴的这段代码按“整体链路→AFDM改动点→2D-DFT感知点→我看到的潜在坑/需要对齐的地方”梳理一遍(相当于我先把它“学一遍”,方便你下一步继续改)。
你的主函数整体流程是:
你现在的改动策略非常明确:
“调制解调处把FFT/IFFT换成IDAFT/DAFT,但后面的处理尽量复用原OFDM那套(尤其是信道估计 + 2D-DFT感知)。”
ofdmModulate 里替换 IFFT → IDAFT你现在写的是:
TxSignalT = ifft(TxFreqSignal,fftSize)*sqrt(fftSize)TxSignalT = idaft(TxFreqSignal,c1,c2)然后逐符号加CP拼接成连续时域波形。
✅ 这一段逻辑是自洽的:
IDAFT输出的就是每个symbol的时域块,CP照加即可。
关键要对齐的是: IDAFT/DAFT的归一化(是否“能量守恒/单位酉”)要和你原来 *sqrt(N)、/sqrt(N) 的能量标定一致,否则SNR和误差曲线会整体偏移。
ofdmDemodulate 里替换 FFT → DAFT你现在写的是:
RxFreqSignal = fft(RxSignal)/sqrt(fftSize)RxFreqSignal = daft(RxSignal,c1,c2)✅ 也没问题:
去CP后,每个symbol块直接做DAFT,得到AFDM“解调域”的栅格。
channelEst 里你做了一个“回到OFDM域”的桥接你现在的 AFDM 分支是:
idaft → 得到时域 txTimeidaft → 得到时域 rxTimetxTime/rxTime 做普通 FFT → 得到“类似OFDM的频域TF表示”Hest = RxTF ./ TxTF✅ 这一步的含义是:
你不直接在DAFT域里做“除法估计”,而是把双方都“拉回到时域→再转到普通频域”,等价于在传统TF域继续沿用原来的导频估计框架,从而后面的2D-DFT(delay/doppler)也可以继续用。
这是一条很常见的“工程复用路线”,优点是你后面处理不用大改;缺点是如果你未来想发挥AFDM在双选信道下的“近似对角化优势”,就应该考虑在DAFT域做更匹配的估计/均衡(但这是下一步的事)。
dft_2d(Hest,sensing) 里最核心的一句是:
matlabHdft = fft( ifft(Htemp,lenf).', lent ).'; dataSet{antId} = fftshift(Hdft,2); initH = initH + fftshift(abs(Hdft),2);
这对应典型的 “频域→delay:IFFT;时间(symbol)→doppler:FFT”:
ifft(Htemp,lenf):沿着频率/子载波维做IFFT → 得到 delay 维(也就是距离维)fft( ... , lent ):沿着慢时间(symbol)维做FFT → 得到 doppler 维(也就是速度维)fftshift(...,2):把doppler零频移到中间(正负速度对称)然后你用:
deltaR = c/(freqSpace*Deltaf*lenf)deltaV = c/(fc*slotT*lent*2)把二维峰值索引映射成物理 距离/速度。
最后角度用 MUSIC:从 dataSet{antId}(maxRow,maxCol) 取出各天线在该delay-doppler bin上的复值,构造空间谱估计角度。
✅ 所以你的“2D-DFT估计位置速度”这条路是非常清晰的:
Hest(TF) → 2D变换 → Range-Doppler Map → 找峰 → (range, velocity),再用多天线相位差做角度。
removeCpInd 最后那句很可疑你写的是:
matlabremoveCpInd(removeCpInd+1)=length(TxTimeSignalTemp);
这句在MATLAB里很容易越界或写乱索引(因为 removeCpInd 里面存的是“样点位置”,不是 1..Nsymbol 的合法索引)。
通常想要的是:
removeCpInd(symInd) 存每个symbol起点removeCpInd(end+1) 存帧末尾(可选)如果你后续 ofdmDemodulate 用 removeCpInd(symInd) 切片,建议把这里索引逻辑彻底对齐一下(否则CP切片会错一位,后面全崩)。
Hest=zeros(length(sensingSymbolNum),sensingSymbolNum,rxAntNum); 这行维度也不太对sensingSymbolNum 看起来是一个标量(比如 14),那 length(sensingSymbolNum)=1,这会导致 Hest 第一维只有1。
但你后面:
matlabRxSignalTF = RxFreqSignal(fpattern,tpattern,RxIdx); Hest(:,:,RxIdx) = RxSignalTF ./ TxSignalTF;
这里 RxSignalTF 的第一维应该是 length(fpattern)。
所以更合理的形状通常是:
Hest = zeros(length(fpattern), length(tpattern), rxAntNum);你可能原工程里 sensingSymbolNum 其实不是标量(或者你粘贴时有误),但就你这段看,这行非常值得你核对一下。
Nc,要确认 Nc == fftSize你写的是:
matlabTxFreqSignal = fft(txTime,Nc,1)/sqrt(Nc); RxFreqSignal = fft(rxTime,Nc,1)/sqrt(Nc);
如果 Nc 和 fftSize 不一致(比如有保护带、有效子载波Nc<fftSize),那会引入维度/映射不一致。
你后面还 circshift(TxFreqSignal,Nc/2,1),所以这里最好保证:
dft_2d 里有些变量使用顺序要小心你在 while/for 的重构残差部分用到了 deltafd、deltaR 等,但它们在你贴的代码里是在后面才定义的(可能是你截断/复制顺序导致)。
如果这段在你真实工程里也是这个顺序,会直接报错或逻辑不对。
dft_2d 里画 surf/figure/plot 会极大拖慢仿真simulationNum=50、目标多、还多天线的话,这些绘图会让仿真慢很多,也会影响你统计曲线。建议默认关掉,用 flag 控制。
你现在的总体思路是:
如果你下一步要我继续帮你改(比如:把整条链路里变量维度彻底对齐、把 channelEst 改成更“原生AFDM域估计”、或者把 dft_2d 的残差迭代那段补完整并跑通),你直接告诉我你现在工程里这几个量的确切尺寸定义(Nc/fftSize/Nsymbol/fpattern/tpattern 各自含义),我就能按你现有结构把它们一次性改干净,不用你反复试错。
现在很奇怪的是,我们afdm的结果,和实际的结果对应的相应点多了25%。比如原来是164的,afdm到了205.原来241的,现在变成27(一共是272列)。请分析一下是什么问题 并改正
你这个“点位整体多了 25%(164→205≈×1.25),并且高频端发生取模环绕(241×1.25≈301,301 mod 272≈29,接近你看到的 27)”的现象,非常像一个根因:
AFDM链路里用的“变换长度 N”跟 OFDM 的 FFT 长度不一致,导致每个 symbol 的有效时长(或等效采样数)变成了原来的 1.25 倍,于是 Doppler 维的离散频率(bin 索引)整体按 1.25 缩放,超出长度就环绕。
在你贴的代码里,这种不一致最可能发生在两处:
matlabifft(TxFreqSignal, fftSize)
它会把输入 自动补零/截断到 fftSize。
matlabidaft(TxFreqSignal, c1, c2)
而大多数 idaft/daft 实现会默认用 size(TxFreqSignal,1) 作为 N(不会像 ifft 那样按 fftSize 自动补零到指定长度)。
所以如果你系统里存在 Nc(有效子载波数)和 fftSize(IFFT长度)不一致(典型:fftSize = 340, Nc = 272,刚好 340/272=1.25 或反过来),那么:
fftSizeNc
→ 符号时长不一致 → Doppler bin 缩放 → 164→205、241→(取模后变小的数)新增一个小工具函数(放到同文件末尾或单独 .m):
matlabfunction X = pad2fft(Xin, Nfft) % Xin: [N x Ns( x Nant)] % X : [Nfft x Ns( x Nant)] N = size(Xin,1); if N == Nfft X = Xin; return; end X = zeros([Nfft, size(Xin,2), size(Xin,3)], 'like', Xin); % 这里假设你的子载波映射是“居中映射(DC居中)”的思路: % 把 N 点内容放到 Nfft 的中间 st = floor((Nfft - N)/2) + 1; X(st:st+N-1,:,:) = Xin; end
如果你工程里的映射不是“居中映射”,而是通过
fpattern贴到特定位置,那就把上面这段替换成:X(fpattern,:,:) = Xin;(我后面也给你写了一个版本)。
ofdmModulate 里:AFDM 分支也用 fftSize 的长度把你现在的:
matlabTxSignalT=idaft(TxFreqSignal(:,:,antId),sensing.afdm.c1,sensing.afdm.c2);
改成:
matlabX = pad2fft(TxFreqSignal(:,:,antId), fftSize); TxSignalT = idaft(X, sensing.afdm.c1, sensing.afdm.c2); % 现在idaft输入长度=fftSize
channelEst 里:AFDM 分支做“回 OFDM 频域”时也统一用 fftSize你现在写的是(注意你用 Nc):
matlabTxFreqSignal = fft(txTime,Nc,1)/sqrt(Nc); RxFreqSignal = fft(rxTime,Nc,1)/sqrt(Nc);
建议改成:
matlabNfft = sensing.fftSize; % 先把Tx参考也pad到Nfft,保证idaft长度一致 TxRef = pad2fft(TxFreqSignal, Nfft); % TxFreqSignal可能是Nc txTime = idaft(TxRef, sensing.afdm.c1, sensing.afdm.c2); rxTime = zeros(size(RxFreqSignal), 'like', RxFreqSignal); for rx = 1:rxAntNum rxTime(:,:,rx) = idaft(RxFreqSignal(:,:,rx), sensing.afdm.c1, sensing.afdm.c2); % RxFreqSignal来自demod,一般已是Nfft end TxFreqSignal = fft(txTime, Nfft, 1)/sqrt(Nfft); RxFreqSignal = fft(rxTime, Nfft, 1)/sqrt(Nfft);
并且后面的 shift 也用 Nfft/2(不要混用 Nc):
matlabTxFreqSignal = circshift(TxFreqSignal, Nfft/2, 1); RxFreqSignal = circshift(RxFreqSignal, Nfft/2, 1);
Hest 的维度初始化修正(你这行本身也很像 bug)你现在是:
matlabHest=zeros(length(sensingSymbolNum),sensingSymbolNum,rxAntNum);
这很容易变成 1 x sensingSymbolNum x rxAntNum。
建议直接按导频/参考抽取后的大小来:
matlabHest = zeros(length(fpattern), length(tpattern), rxAntNum, 'like', RxFreqSignal);
如果你的 RsMapping 本来就是通过 fpattern 把有效子载波放进大 FFT 栅格,那 pad 推荐写成这样(避免映射错位):
matlabfunction X = map_by_fpattern(Xin, Nfft, fpattern) % Xin: [Nc x Ns], X: [Nfft x Ns] X = zeros(Nfft, size(Xin,2), 'like', Xin); X(fpattern,:) = Xin; end
然后在 ofdmModulate 里用:
matlabX = map_by_fpattern(TxFreqSignal(:,:,antId), fftSize, sensing.fpattern); TxSignalT = idaft(X, sensing.afdm.c1, sensing.afdm.c2);
在 channelEst AFDM 分支也一样先把 TxRef 映射进 Nfft。
因为你观测到的是 ×1.25 的缩放 + 环绕,几乎就是“变换长度/符号时长不一致”的指纹。
把 AFDM 的 DAFT/IDAFT 也强制在 同一个 Nfft 上,就会恢复与 OFDM 相同的符号时长与离散 Doppler 栅格,峰值 bin 就会回到原来的位置(或只剩下一个固定的 shift,那就再检查 fftshift/circshift 的定义是否一致)。
在进入 ofdmModulate 前后打印这几个量(只要一次):
matlabdisp([size(TxFreqSignal,1), sensing.Nc, sensing.fftSize])
如果你看到 size(TxFreqSignal,1) 或 sensing.Nc 和 sensing.fftSize 不是同一个数,并且比值接近 1.25 或 0.8,那基本就坐实了。
如果你按上面改完,峰值位置仍然是整体×1.25,那下一嫌疑就是 你在 AFDM 分支里改变了“符号间隔 slotT 的真实定义”(把 CP 算进去了/算错了) 或者 channelPass 的时间轴定义跟解调抽样点对不上。到那一步你把 sensing.fftSize、CplenSet、采样率Fs、slotT 的定义贴出来,我可以把“真实 slow-time 采样时刻 t_n”那条链路也一并对齐。