你现在是无线通信方向的专家。正在写一个OTFS和AFDM的ISAC感知的代码。现在我们已经写好了OT...
تم الإنشاء في: ٩ يناير ٢٠٢٦
تمت الإجابة باستخدام GPT-5.2 Thinking بواسطة Chat01
تم الإنشاء في: ٩ يناير ٢٠٢٦
تمت الإجابة باستخدام GPT-5.2 Thinking بواسطة Chat01
你现在是无线通信方向的专家。正在写一个OTFS和AFDM的ISAC感知的代码。现在我们已经写好了OTFS sensing的程序,相关需要调用的参数都已经给到了。现在需要你在这个基础上,我们增加一个可以仿真AFDM的代码。现在这个OTFS也是在OFDM的框架上来写的。我们现在一步一步来,先来理解一下下面的代码:function [Range,Velocity,Angle]=offsSensing(Hest,sensing)
Angle=0;
targetNum=sensing.targetNum;
fc=sensing.fc;
SCS=sensing.SCS;
slotT=sensing.frameLen*1e-3/sensing.sensingSymbolNum;
sensingSymbolNum=sensing.sensingSymbolNum;
fdmax=1/slotT;
Nc=sensing.Nc;
Nzc=sensing.Nzc;
TxSignalDD=sensing.TxSignalDD;
overSamplNumf=sensing.overSamplNumf;
overSamplNumt=sensing.overSamplNumt;
overSamplFlag=sensing.overSamplFlag;
freqSpace=sensing.freqSpace;
ZcSeq=sensing.ZcSeq;
if sensing.otfsSensingFlag==1
% SFFT
RxSignalTF=Hest;
SensingNsymbol=size(Hest,2);
RxSignalDD=ifft(RxSignalTF).'.*sqrt(Nc/SensingNsymbol);
RxRsSignal=RxSignalDD(1:Nzc,:);
text% Receive reference signal oersampling RxRsSignalTF=ifft(fft(RxRsSignal).'.*sqrt(SensingNsymbol/Nzc)); RxOverSamplSignalDD=ifft(fft(RxRsSignalTF,overSamplNumf).',overSamplNumt).*sqrt(Nzc/SensingNsymbol); % Transmit reference signal oversampling TxRsSignal=repmat(ZcSeq,1,SensingNsymbol); TxRsSignalTF=ifft(fft(TxRsSignal).'.*sqrt(SensingNsymbol/Nzc)); TxOverSamplSignalDD=ifft(fft(TxRsSignalTF,overSamplNumf).',overSamplNumt).*sqrt(Nzc/SensingNsymbol); % reconstruct local oversampling signal OverSampZcSeq=ifft(fft(ZcSeq,overSamplNumf)); OverSampZcSeq=repmat(OverSampZcSeq,1,overSamplNumt); % reconstruct receive oversampling signal OverSampTF=ifft(fft(TxSignalDD(1:Nzc,:)).'.'); OverSampDD=fft(ifft(OverSampTF,overSamplNumf).',overSamplNumt).'; % channel estimate if overSamplFlag == 0 EstHDD=ifft(conj(fft(TxRsSignal)).*fft(RxRsSignal))/(sqrt(SensingNsymbol)*Nzc); else EstHDD=ifft(conj(fft(TxOverSamplSignalDD)).*fft(RxOverSamplSignalDD))/(sqrt(SensingNsymbol)*Nzc); end peakValue=max(EstHDD(:)); EstHDD=EstHDD/peakValue; if overSampFlag == 0 deltaD = 3e8/(SCS*Nzc); len=sensingNsymbol; deltafd = fdmax/len; else deltaD = 3e8/(SCS*overSampNumf); len=overSamplNumt; deltafd = fdmax/len; end initH=fftshift(abs(EstHDD),2); [lenf,lent]=size(initH); % mesh(-lent/2:lent/2-1]*deltafd,[0:lenf-1]*deltaD,(initH)); % xlabel('Doppler [Hz]'); % ylabel('Range [m]') % zlabel('Normalized Power') % ylim([0,2000]) % xlim([-15000,15000]) % set(gcf,'Color','white'); % colorbar residualH=initH; distanceEst=zeros(targetNum,1); dopplerEst=zeros(targetNum,1); velocityEst=zeros(targetNum,1); for targetInd=1:targetNum [~,peakInd]=max(residualH(:)); [row,col]=ind2sub(size(residualH),peakInd); range=(row-1)*deltaD; doppler=(col-1-lenf/2)*deltafd; rowSet=[row]; colSet=[col]; rowSet(rowSet==0)=[]; rowSet(rowSet>size(initH,1))=[]; colSet(colSet==0)=[]; colSet(colSet>size(initH,2))=[]; for row=rowSet for col=colSet if overSamplFlag==0 reconSignal=circshift(TxSignalDD(1:Nzc,:),row-1,1); reconSignal=circshift(reconSignal,col-1,2); reconH=ifft(conj(fft(TxRsSignal)).*fft(reconSignal))/(sqrt(SensingNsymbol)*Nzc); else reconSignal=circshift(OverSampDD,row-1,1); reconSignal=circshift(reconSignal,col-1,2); reconH=ifft(conj(fft(OverSampZcSeq)).*fft(reconSignal))/(sqrt(SensingNsymbol)*Nzc); end reconH=abs(reconH/max(reconH(:))); factor=initH(row,col); residualH=residualH-factor*reconH; residualH(residualH<0)=0; end end distanceEst(targetInd,1)=range; dopplerEst(targetInd,1)=doppler; velocityEst(targetInd,1)=dopplerEst(targetInd,1)*3e8/fc/2; Range=distanceEst; Velocity=velocityEst; end
else
channelHEst=Hest;
if overSamplFlag == 0
[lenf,lent]=size(channelHEst);
else
lenf = overSamplNumf;
lent = overSamplNumt;
end
deltaD = 3e8/(SCSfreqSpacelenf);
deltafd = fdmax/lent;
textEstHTF=abs(fft(ifft(channelHEst,lenf).',lent).')*sqrt(lenf/lent); peakValue=max(EstHTF(:)); EstHTF=EstHTF/peakValue; initH=fftshift(abs(EstHTF),2); residualH=initH; distanceEst=zeros(targetNum,1); dopplerEst=zeros(targetNum,1); velocityEst=zeros(targetNum,1); [lenf,lent]=size(initH); % mesh(-lent/2:lent/2-1]*deltafd,[0:lenf-1]*deltaD,(initH)); % xlabel('Doppler [Hz]'); % ylabel('Range [m]') % zlabel('Normalized amplitude') % ylim([0,800]) % xlim([-10000,10000]) % set(gcf,'Color','white'); % colorbar for targetInd=1:targetNum [~,peakInd]=max(residualH(:)); [row,col]=ind2sub(size(residualH),peakInd); range=(row-1)*deltaD; doppler=(col-1-lent/2)*deltafd; c=3e8; fR=exp(-1j*2*pi*[0:Nzc-1]*SCS*range/c); fV=exp(1j*2*pi*[0:sensingSymbolNum-1]*(col-1)*deltafd*slotT); recon=fR.'*fV; reconH=fft(ifft(recon,lenf).',lent).'*sqrt(lenf/lent); reconH=abs(reconH/max(reconH(:))); factor=initH(row,col); residualH=residualH-factor*reconH; distanceEst(targetInd,1)=range; dopplerEst(targetInd,1)=doppler; velocityEst(targetInd,1)=dopplerEst(targetInd,1)*3e8/fc/2; Range=distanceEst; Velocity=velocityEst; end
end
end
下面我按“这段代码到底在做什么—每一步为什么这么做—输出怎么来的”的顺序,把 offsSensing(Hest,sensing) 讲清楚。整体上它是在做 Range–Doppler(距离–多普勒)图 的生成 + 多目标峰值检测/消除(类似 CLEAN / Matching Pursuit),最后把 Doppler 换算成速度。Angle 目前恒置 0,相当于“角度维未实现”。
Hest:你们前端已经估计出来的“接收二维信号/信道”的矩阵
sensing.otfsSensingFlag==1:代码把它当作 OTFS sensing 的 Rx TF-domain 矩阵(变量名 RxSignalTF=Hest)channelHEst=Hest)sensing:结构体,包含载频、子载波间隔、帧结构、ZC 序列、过采样参数等Range:长度 targetNum 的距离估计(单位 m)Velocity:长度 targetNum 的速度估计(单位 m/s)Angle:恒为 0(占位)initH(二维幅度图)targetNum 次:
(row,col) → 转成 range,dopplerreconHresidualH = residualH - factor * reconH 做消除,继续找下一个目标fc:载频,用于 Doppler→速度:
SCS:子载波间隔(Hz)slotT = frameLen*1e-3/sensingSymbolNum:一个 sensing symbol 对应的“符号时长”(这里你们把一帧毫秒长度平均到 sensingSymbolNum)fdmax = 1/slotT:可分辨/可观测的 Doppler 周期范围相关(近似最大无模糊 Doppler 间隔的倒数尺度)Nc:系统子载波数(或 OTFS 网格的频域点数)Nzc:用于 sensing 的参考序列长度(ZC 序列长度,对应参与距离处理的有效频点数)overSamplNumf / overSamplNumt:频向/时向过采样长度(本质是 零填充 FFT 长度,提高距离/多普勒栅格分辨率)freqSpace:在非 OTFS 分支里用于距离分辨率的“频域抽取间隔”(例如导频每隔多少子载波放一个)ZcSeq:发射参考序列otfsSensingFlag==1(OTFS sensing 路径)matlabRxSignalTF=Hest; SensingNsymbol=size(Hest,2); RxSignalDD=ifft(RxSignalTF).'.*sqrt(Nc/SensingNsymbol); RxRsSignal=RxSignalDD(1:Nzc,:);
SensingNsymbol 是 sensing 使用的符号数(列数)ifft(RxSignalTF):沿第 1 维做 IFFT(MATLAB 默认对每列做),然后转置。你们这里的“DD”更像是把频维做 IFFT得到“delay-like”维度,再把时间维放到第二维。RxRsSignal=... (1:Nzc,:):只取前 Nzc 个 delay bin(对应 ZC 长度)参与后续相关,等于“只用参考资源块”。这一段的形式很像:先在某一维做 FFT 转域,再用指定 FFT 长度 overSamplNumf/overSamplNumt 做零填充,从而得到更细的 delay/Doppler 网格。
matlabRxRsSignalTF=ifft(fft(RxRsSignal).'.*sqrt(SensingNsymbol/Nzc)); RxOverSamplSignalDD=ifft(fft(RxRsSignalTF,overSamplNumf).',overSamplNumt).*sqrt(Nzc/SensingNsymbol); ... TxRsSignal=repmat(ZcSeq,1,SensingNsymbol); TxRsSignalTF=ifft(fft(TxRsSignal).'.*sqrt(SensingNsymbol/Nzc)); TxOverSamplSignalDD=ifft(fft(TxRsSignalTF,overSamplNumf).',overSamplNumt).*sqrt(Nzc/SensingNsymbol);
TxRsSignal = repmat(ZcSeq,1,SensingNsymbol):每个 symbol 发相同 ZC...overSamplNumf..., overSamplNumt...:分别把两个维度的 FFT 长度拉长 → 过采样后的 DD 信号sqrt(...) 是能量归一化,避免长度变化导致幅度缩放。matlabOverSampZcSeq=ifft(fft(ZcSeq,overSamplNumf)); OverSampZcSeq=repmat(OverSampZcSeq,1,overSamplNumt); OverSampTF=ifft(fft(TxSignalDD(1:Nzc,:)).'.'); OverSampDD=fft(ifft(OverSampTF,overSamplNumf).',overSamplNumt).';
OverSampZcSeq:过采样版的 ZC(频向零填充)OverSampDD:过采样版的发射 DD(你们用 TxSignalDD 的前 Nzc 行)这两者用于后面 CLEAN:当检测到某个 (delay,doppler) 峰值时,构造该峰对应的“模板响应”,从 residualH 里减掉。
matlabif overSamplFlag == 0 EstHDD=ifft(conj(fft(TxRsSignal)).*fft(RxRsSignal))/(sqrt(SensingNsymbol)*Nzc); else EstHDD=ifft(conj(fft(TxOverSamplSignalDD)).*fft(RxOverSamplSignalDD))/(sqrt(SensingNsymbol)*Nzc); end peakValue=max(EstHDD(:)); EstHDD=EstHDD/peakValue; initH=fftshift(abs(EstHDD),2);
ifft(conj(FFT(tx)).*FFT(rx)):经典 FFT 快速实现的 循环相关/匹配滤波fftshift 把 Doppler 零频移到中间。matlabif overSampFlag == 0 deltaD = 3e8/(SCS*Nzc); len=sensingNsymbol; deltafd = fdmax/len; else deltaD = 3e8/(SCS*overSampNumf); len=overSamplNumt; deltafd = fdmax/len; end
Δf = SCS,N 用 Nzc 或过采样长度fdmax / (#doppler_bins) 这样的离散化(你们用 len = sensingNsymbol 或 overSamplNumt)核心循环:
row→range,col→dopplerreconH 并消除matlab[~,peakInd]=max(residualH(:)); [row,col]=ind2sub(size(residualH),peakInd); range=(row-1)*deltaD; doppler=(col-1-lenf/2)*deltafd; % (这里有个明显的维度问题,下面我会指出) reconSignal=circshift(TxSignalDD(1:Nzc,:),row-1,1); reconSignal=circshift(reconSignal,col-1,2); reconH=ifft(conj(fft(TxRsSignal)).*fft(reconSignal))/(sqrt(SensingNsymbol)*Nzc); ... residualH=residualH-factor*reconH;
circshift:把发射信号在 delay 维、Doppler 维做循环移位,模拟一个目标带来的时延和多普勒reconH:对该移位信号再做一次匹配滤波,得到这个目标在 RD 图上的“点扩散/旁瓣形状”residualH -= factor * reconH:把该目标贡献减掉,从而更容易找到下一个目标(典型 CLEAN)otfsSensingFlag!=1(更“OFDM/TF 信道估计→RD 图”的路径)matlabif overSamplFlag == 0 [lenf,lent]=size(channelHEst); else lenf = overSamplNumf; lent = overSamplNumt; end deltaD = 3e8/(SCS*freqSpace*lenf); deltafd = fdmax/lent;
freqSpace:表示你在频域不是每个子载波都用(例如导频每隔 freqSpace 放一个),有效带宽缩小 → 距离分辨率变差matlabEstHTF=abs(fft(ifft(channelHEst,lenf).',lent).')*sqrt(lenf/lent); EstHTF=EstHTF/max(EstHTF(:)); initH=fftshift(abs(EstHTF),2);
ifft(...,lenf):频域 → delay(距离维)fft(...,lent):慢时间 → Dopplerfftshift(...,2):把 Doppler 轴搬到中心matlabrange=(row-1)*deltaD; doppler=(col-1-lent/2)*deltafd; fR=exp(-1j*2*pi*[0:Nzc-1]*SCS*range/c); fV=exp(1j*2*pi*[0:sensingSymbolNum-1]*(col-1)*deltafd*slotT); recon=fR.'*fV; reconH=fft(ifft(recon,lenf).',lent).'*sqrt(lenf/lent); residualH=residualH-factor*abs(reconH/max(reconH(:)));
fR:距离引起的频域相位斜率(delay → 频域线性相位)fV:Doppler 引起的慢时间相位旋转recon = fR.' * fV:生成 TF 域的“单目标响应”reconH,然后 CLEANoverSamplFlag vs overSampFlag 拼写不一致你开头定义的是:
matlaboverSamplFlag=sensing.overSamplFlag;
但后面多处写成了 overSampFlag(少了 l)以及 overSampNumf(少了 l)。
MATLAB 这会直接报未定义变量,或者你们工作区恰好有同名变量导致“隐形错误”。
✅ 建议:全局统一为 overSamplFlag / overSamplNumf / overSamplNumt。
这里:
matlab[lenf,lent]=size(initH); doppler=(col-1-lenf/2)*deltafd;
col 是第 2 维索引(Doppler 维),应该用 lent/2,不是 lenf/2。✅ 应为:
matlabdoppler = (col-1 - lent/2) * deltafd;
rowSet/colSet 现在是“空壳”matlabrowSet=[row]; colSet=[col]; ... for row=rowSet for col=colSet ... end end
这其实等效于只跑一次。看起来你们原本想把邻域一起消(比如 3x3 或主瓣范围),但现在没实现。
✅ 若你们想增强消除效果,可以把 rowSet/colSet 扩成邻域(注意边界裁剪你们已经写了)。
fR 用 Nzc 可能与 lenf 不一致你们 RD 图的距离维长度是 lenf,但 fR 的频点用了 0:Nzc-1。如果 lenf ≠ Nzc(例如 overSampling 或 freqSpace 情况),模板长度可能不匹配或物理含义不一致。
✅ 更稳妥:fR 的索引长度与 lenf 或“实际参与的频点数”对齐。
如果你同意,下一步我们就可以在这个“RD 图 + CLEAN 检测框架”上加 AFDM 的发射、接收与等效 RD 映射:关键是把 AFDM 的“chirp/DAFT 结构”接到你们现在的 OFDM 框架里(Tx/Rx 变换、参考信号插入方式、以及 Hest 的定义域保持一致)。你把你们 AFDM 目前计划的参数(比如 chirp rate、DAFT/IDFT 尺寸、循环前缀/guard、以及你们希望 Hest 处于哪个域)发我,我就按你们现有 OTFS 的接口给出一份可直接跑的 AFDM sensing 分支代码。
我们现在认为这个框架没问题。现在在这个框架上我们想要改成AFDM,应该需要怎么调整?已知现在这个otfs,输入进来的量就是OFDM的。我可以直接用这个修改么?
可以“复用这个框架”,但不能只在这一个 offsSensing() 里把 OTFS 分支改几行就算 AFDM——关键取决于你现在喂进来的 Hest 到底处在哪个“域”。
你现在这段 sensing 的本质是:
initHtargetNum 次峰值检测 +(可选)CLEAN 消除所以要改成 AFDM,框架(RD 图 + peak/CLEAN)基本不动,要动的是“把接收观测变成 RD 图”的那一段,以及“重构模板 reconH”的那一段。
如果你输入的 Hest 还是 OFDM 的 TF 信道估计(子载波×符号):
你在 offsSensing() 里不管怎么改,最后做出来的 RD 图本质仍然是“OFDM sensing”(2D FFT/相关意义下),不是 AFDM waveform 的 sensing。
要做“AFDM 的 sensing 仿真”:你必须让输入进 sensing 的观测是“AFDM 解调域(DAFT 域 / affine-frequency 域)”下的观测或等效信道,也就是:** 发的是 AFDM,收端按 AFDM 解调,再把得到的矩阵送进 sensing**。这样你才能说你仿真的确实是 AFDM。
AFDM 常见实现(不同论文符号略有差别,但结构一样)是:
定义两个对角 chirp 矩阵(或等效逐点相乘向量)
,
**调制(每个符号)**可以写成(示意):
这里的 IFFT 就是你 OFDM 的 IFFT,所以你们“OFDM 框架”可以直接复用:只是在 IFFT 前后各乘一次 chirp。
✅ 你要新增 sensing.c1, sensing.c2(或 alpha/beta)这类 AFDM 参数。
去 CP 后,对每个符号(示意):
得到的 (按符号堆起来形成 N×M 矩阵)才是你后面做 AFDM sensing 最合适的输入。
Hest 的定义要换:从 “OFDM TF 信道”换成 “AFDM 解调域等效观测/等效信道”你现在 offsSensing() 里默认 Hest 是某种“TF 估计/观测”。
做 AFDM 时,建议你把 Hest 定义为:
Y_A:AFDM 解调后的导频观测矩阵(N×M),或H_A:用导频估出来的 AFDM 域等效信道(N×M)最简单的导频估计(如果导频逐点非零):
然后把这个 H_A 当作你 sensing 的输入。
offsSensing() 里具体该怎么改(保持你现有框架最大复用)我建议你们加一个分支,比如 sensing.afdmSensingFlag==1,并把它放到“非 OTFS 分支”的同一层,因为 AFDM 也更像“每个符号一维 N 点 + 跨符号慢时间”的结构。
如果你把输入改成 H_A(N×M):
也就是你现在非 OTFS 分支的这一句逻辑基本能复用:
matlabEstRD = abs( fft( ifft(H_A, lenf).', lent ).' ) * sqrt(lenf/lent); initH = fftshift(EstRD, 2);
注意:这时你做出来的是“AFDM 解调域等效信道的 RD 图”。只有当你的发射/接收确实按 AFDM chirp 做过,上述 RD 图才对应 AFDM 体制下的性能。
reconH:不能再只用“纯 circshift”你 OTFS 分支里用的是:
而 AFDM 下,延迟/多普勒映射到解调域通常是“移位 + 额外相位项(chirp 相关的二次相位)”。所以:
reconSignal = circshift(...) 这一行要改成:
circshift 之后再乘一个与 (range,doppler) 相关的相位补偿向量(由你选的 c1,c2 推出来)reconH(模板生成更通用,但算量大)✅ 实操建议:第一版 AFDM sensing 先不做 CLEAN,只做 initH 出峰 + targetNum 个最大峰抓取,等 RD 图逻辑验证正确后,再把 CLEAN 模板精确化。
offsSensing() 的输出/接口不变H_A(N×M)H_A 作为 Hest 喂给 sensing(新增 afdmSensingFlag 分支)这样你基本不用推翻现有框架,也不会在“输入还是 OFDM,却说自己在仿 AFDM”这件事上踩坑。
不用你再解释很多,只要明确:
c1,c2(或论文里的 alpha,beta)以及 N(=Nc?)offsSensing() 的输入 Hest 在 AFDM 情况下是:Y_A 还是 H_A?d(AFDM 域)里整段占满,还是只占一部分子载波?(影响 deltaD 和模板)你给出这几点后,我可以直接给你一份 afdmSensingFlag 的 MATLAB 代码:
那我们不用这个算法也行。因为我们的AFDM实际上解调用的也是2DDFT对吧?下面这个是我们OFDM在解调时候用的程序,看一下在这个程序里面,想实现AFDM,有没有需要调整的地方?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
你这段 dft_2d() 本质上不是“解调”,而是把 输入矩阵 Hest(子载波×慢时间×天线) 做
ifft(·, lenf)fft(·, lent)从而得到 Range–Doppler 图(你叫 Hdft / dataSet / initH)。
典型 AFDM(DAFT)在每个符号内是“chirp × FFT/IFFT × chirp”的结构(离散仿射傅里叶变换),它确实用到了 DFT,但多了两侧的 chirp(二次相位)。
你做 Doppler(跨符号)那一维用 DFT 是合理的,但“频→延迟”的那一维如果输入在 AFDM 域,就不能直接当 OFDM 的子载波频响来 ifft。
dft_2d() 里要不要改,取决于你喂进来的 Hest 是什么域Hest 仍然是 “OFDM-like 的 TF 频响/信道估计”也就是:即便你发的是 AFDM,你在接收端把 chirp 去掉/补偿,再做 FFT 得到“等效子载波频响”,然后估 Hest。
✅ 那么 dft_2d() 里 几乎不用改:
Hdft=fft(ifft(Htemp,lenf).',lent).'; 这条 2D 变换继续成立,你得到的 RD 图就是对的。
你需要改的是 AFDM 的调制/解调与信道估计模块,保证输出的 Hest 还是“每个子载波的频响随慢时间变化”。
也就是 Hest 的第一维不是“子载波频率采样”,而是 DAFT 的系数。
✅ 那么 dft_2d() 里 必须加一步“从 AFDM 域转回 OFDM-like 频域”,否则 ifft 的“距离意义”不成立。
最常见做法:在做 ifft(·) 之前,先对每个慢时间列做一次 inverse-DAFT(或等效去 chirp),把 AFDM 域矩阵变成等效频域矩阵,再按你原来的 2D DFT 做 RD。
你现在每根天线做:
matlabHtemp=Hest(:,:,antId); Hdft=fft(ifft(Htemp,lenf).',lent).';
如果你是情况 B(输入在 AFDM 域),你把它改成:
matlabHtempA = Hest(:,:,antId); % AFDM/DAFT 域 HtempF = idaft_cols(HtempA, sensing); % 先变回“等效频域”(每列做一次 inverse-DAFT) Hdft = fft(ifft(HtempF,lenf).',lent).';
下面给你一个列处理版本的模板(你只需要把 chirp 参数按你们 AFDM 定义填对;共轭方向可能与你们论文符号相反,但结构就是这样):
matlabfunction Xf = idaft_cols(Xa, sensing) % Xa: N x M (AFDM/DAFT域), 每列一个symbol % Xf: N x M (等效“频域/子载波域”) N = size(Xa,1); M = size(Xa,2); n = (0:N-1).'; alpha = sensing.afdmAlpha; % 你们AFDM参数名自定 beta = sensing.afdmBeta; Da = exp( 1j*pi*alpha*(n.^2)/N ); % 可能需要共轭,取决于你们DAFT定义 Db = exp( 1j*pi*beta *(n.^2)/N ); Xf = zeros(N,M); for m = 1:M % 一种常见inverse-DAFT形态: x = Da .* ifft( conj(Db).*X ) Xf(:,m) = Da .* ifft( conj(Db).*Xa(:,m), N ); end end
你们到底用
Da/Db还是它们的共轭,取决于你们 AFDM 调制公式是 “DbFDa” 还是 “Db^HFDa^H”。但无论如何:在进入ifft(·)做距离之前,要先把 AFDM 域恢复成等效“频域采样”,这是硬要求。
matlabdeltafd=1/(lenf*slotT*2); % 这里用lenf不对
Doppler 轴长度是 lent,一般应跟 lent 相关(是否要 *2 取决于你把速度/单程双程怎么定义):
matlabdeltafd = 1/(lent*slotT); % 常见写法(再根据双程/符号定义调整)
deltaV 你写成:matlabdeltaV=c/(fc*slotT*lent*2);
这相当于默认双程雷达(有 /2)。如果你 ISAC 里确实按双程换算就留着;否则会差一倍。
你里面用 phiR/phiV 生成模板,再走同样的 2DDFT:
matlabreSignal=abs(fftshift(fft(ifft(phi,lenf).',lent).',2));
如果你的 residualH 来自 AFDM 域输入(情况B),那模板也必须经过同样的“AFDM域↔等效频域”的处理,否则模板和观测不在同一个域,CLEAN 会乱减。
所以:
如果你想“AFDM 仿真 + 继续用这套 2DDFT sensing”:
Hestdft_2d() 基本不用动(最多修 deltafd 那个 bug),你就能公平对比 OFDM/OTFS/AFDM 的 sensing 表现如果你告诉我一句话就行:你们现在的 Hest(:,:,ant) 在 AFDM 情况下到底是“DAFT域”还是“等效子载波频域”(也就是你们 AFDM 解调后输出在哪一层),我就能把上面 idaft_cols() 里的共轭方向和参数位置按你们的 AFDM 定义一次性对齐,并把 dft_2d() 给你改成可直接跑的版本。
我们在调制解调里面现在是按下面写的:
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
然后参数定义上,c1=1/2N;c2=1/128N。这种配置可以么?
你们现在这套改法(把 OFDM 的 ifft/fft 换成 idaft/daft,CP 结构不变)总体思路是对的:AFDM 本质上就是在每个 OFDM 符号的 IFFT/FFT 两侧加“chirp(二次相位)”的 DAFT/IDAFT,论文里的框图就是 IDAFT → 加前缀 → 信道 → 去前缀 → DAFT。 (arXiv)
但要注意两点:前缀类型和c1/c2 的取值条件。下面按你问的“这个程序里要不要调整、c1/c2 这样配行不行”来回答。
AFDM 推导里用的是 chirp-periodic prefix(CPP),并给了 CPP 的具体形式;只有在某些条件下 CPP 才会退化成普通 CP。 (arXiv)
论文明确写到:当 是整数且 为偶数时,CPP 就“等同于”普通 CP。 (arXiv)
所以:
c1 选成满足 2*N*c1 为整数(并确保 fftSize=N 是偶数,通常都是偶数)。c1 选成不满足这个条件,你的 CP 就不再等价于 CPP,严格模型下会出现额外失配(尤其在推导“循环卷积/简化输入输出关系”时)。✅ 你现在给的 c1=1/(2N):
2*N*c1 = 1(整数),只要 N 偶数,则 你现在的 CP 写法是 OK 的(等价 CPP)。
idaft/daft 是否是“单位酉(unitary)”AFDM/DAFT 在文中是用带 1/√N 的 DFT 矩阵定义的,并且提到相应变换是 unitary(噪声协方差不变)。 (arXiv)
你 OFDM 分支用的是 unitary 的实现方式:
ifft()*sqrt(N) 和 fft()/sqrt(N)而 AFDM 分支你直接:
idaft(...) / daft(...)(没额外乘除)所以关键是:你们的 idaft/daft 函数内部是不是也按 unitary 来写的(比如 DFT 矩阵用 /sqrt(N),或在 fft/ifft 外做了对应尺度补偿)。
✅ 建议你做个快速一致性检查(不改算法也能发现问题):
mean(abs(ifft(X)*sqrt(N)).^2) 和 mean(abs(idaft(X,c1,c2)).^2) 是否同量级daft(idaft(X)) 是否≈X(数值误差内)如果能量不一致,你后面所有 SNR/检测阈值/峰值高度都会偏。
sensing.cfr==1 这个旁路要小心你解调里:
matlabelse RxFreqSignal=channelOut; % if pass frequency domain channel,skip this step end
如果你在 AFDM 模式下走这个分支,那么 channelOut 必须已经是 DAFT 域输出(等价你做过 daft 了),否则域不对。
c1=1/(2N),c2=1/(128N) 这样配可以吗?c2=1/(128N):一般是“安全的/推荐方向”的经典 AFDM 结论之一是:为了保证某些满秩/全分集性质,c2 往往选 无理数,或者选 远小于 的有理数。 (arXiv)
你这个 c2=1/(128N) 明显满足 “”(小了 64 倍),所以从这个角度看是合理的。 (arXiv)
另外也有文献提到 c2 具有周期性、有效范围在 [0,1]。 (arXiv)
✅ 结论:c2=1/(128N) 作为仿真起点没问题。
c1=1/(2N):能用,但它隐含“多普勒不大/不追求最强分离”的假设在 AFDM 参数设计里,c1 的作用是让不同 delay/Doppler 的路径在 DAFT 域不重叠。论文给了避免重叠的约束,并在“最坏情况下(delay 不稀疏,相邻 delay 差 1)”给出一个直接的选法:
其中 是“归一化多普勒(相对 SCS)”的整数部分上界。 (arXiv)
同时论文也指出:OCDM 取 c1=c2=1/(2N),它比 OFDM 好但不保证在一般 LTV 信道下达到 AFDM 的“完全分离/全分集”效果。 (arXiv)
而你现在 c1=1/(2N) 等价于把上面那个公式里的 。
如果你系统里最大归一化多普勒 明显不为 0(高速/高载频 ISAC 很常见),那 c1 选太小就可能导致 DAFT 域路径“挤在一起”。
论文对归一化多普勒的定义也写得很清楚:, 是相对 SCS 的归一化多普勒, 是整数部分。 (arXiv)
✅ 结论:
c1 应该按你们的 (或 )去调,常用就是把 c1 提到 这个量级。 (arXiv)额外好消息:如果你把 c1 选成 ,那么 2Nc1=2\alpha_{\max}+1 仍然是整数;只要 N 偶数,你仍可继续用“普通 CP”,不必改成 CPP(满足“CP=CPP”的条件)。 (arXiv)
就调制/解调这块来说,你们缺的不是大改,而是三件事的“确认/约束”:
idaft/daft 的归一化(是否 unitary),否则 SNR 和峰值会不公平。 (arXiv)2Nc1 为整数且 N 偶数,你才能一直用普通 CP,不用实现 CPP。 (arXiv)c1 要跟你们的最大归一化多普勒匹配,否则 AFDM 的“分离”效果出不来。 (arXiv)如果你愿意,我下一步可以直接给你一个“按你们 fc/SCS/frameLen 计算 、进而给出推荐 c1”的计算方式(并说明双程 ISAC 的 Doppler 归一化该怎么取),这样你们可以把 c1 自动化配置起来,不靠拍脑袋。
现在我们已经确认了 idaft/daft 的归一化,但是现在距离误差很大,速度误差和OFDM是一样的。我们现在需要分析一下原因并一起来找一找问题在哪。现在速度检测是没问题的,但是距离检测误差较大
现在我们已经确认了 idaft/daft 的归一化,但是现在距离误差很大,速度误差和OFDM是一样的。我们现在需要分析一下原因并一起来找一找问题在哪。现在速度检测是没问题的,但是距离检测误差较大。我们现在收发都是用的DAFT来做的,但是在做匹配滤波的时候我们做了如下处理。所以速度检测问题不大。但是delay感觉是不是得补偿:
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
你们这套 daft/idaft 写法(截图里那种“chirp→FFT/ifft→chirp” + 1/sqrt(N))本身没问题;现在速度(Doppler)正常但距离(delay/range)误差大,从你给的 channelEst() 逻辑来看,最常见的根因基本都落在 “距离维用到的频域样本/权重不对或病态” 上,而 Doppler 维(跨符号 FFT)天然更稳,所以会出现“速度像 OFDM 一样,距离却崩”的现象。
下面我按“最可能原因 → 怎么验证 → 怎么改”给你一个可落地的排查/修改路径。
idaft 后,其 FFT 频谱幅度不平坦甚至有深陷,你们在 Hest = Y/X 时在某些子载波上被 小 |X| 放大噪声/ICI,导致 delay 峰位跑偏你在 AFDM 分支里做了:
matlabtxTime = idaft(TxFreqSignal,c1,c2); rxTime = idaft(RxFreqSignal,c1,c2); % 其实就是把DAFT解调输出变回时域 TxFreqSignal = fft(txTime,Nc,1)/sqrt(Nc); RxFreqSignal = fft(rxTime,Nc,1)/sqrt(Nc); ... Hest = RxSignalTF ./ TxSignalTF;
这一步 数学上在“严格循环卷积且无 ICI”时是成立的(任意波形都可做 Y/X 得 CFR),但在 ISAC 的 LTV(存在 Doppler)场景里,Y[k] 不是单纯 H[k]X[k],而是带 ICI 的混合;此时 |X[k]| 很小的频点会把 ICI/噪声的影响放大很多,delay(靠跨频相位斜率/一致性)会最先坏,而 Doppler(靠跨符号相位变化)看起来还行。
OFDM pilot 常常是“选出来的那些频点幅度恒定”,所以不会触发这个病态;AFDM 经过 chirp 后的 FFT 幅度一般不再恒定,这是差异来源。
fpattern 这些稀疏频点上估计 Hest,对 AFDM 来说等效带宽/有效频点更“不可控”OFDM 时 fpattern 上就是你真正发的 pilot,能量集中、SNR 高;
AFDM 时你虽然也能算出这些频点的 X[k],但它们可能刚好落在谱陷/低能量区域,导致距离误差大。
fpattern/freqSpace 与 deltaR 的标定不一致这个一般会让 OFDM 也不对;你说 OFDM 的距离没问题,所以概率较低,但还是建议顺手确认一下:maxDelayTap <= min(CplenSet)。
在 channelEst() 的 AFDM 分支里加几行(随便取一个符号):
matlabX = fft(txTime(:,1),Nc)/sqrt(Nc); fprintf("Xmag min/max = %.3e / %.3e\n", min(abs(X)), max(abs(X))); fprintf("Xmag@fpattern min/max = %.3e / %.3e\n", ... min(abs(X(fpattern))), max(abs(X(fpattern))));
min(abs(X(fpattern))) 很小(比如比 max 小 30~60 dB),那基本就坐实“距离坏是因为 Y/X 病态”。Hest(:,m) 相位是否接近线性(delay 应该对应线性相位)取一个 symbol m,画 unwrap(angle(Hest(:,m))),应该基本是直线(加少量噪声)。
如果不是线性而是乱飘/二次弯曲,delay 峰必然会漂。
Hest = Y/X 改成“正则化 LS”,并避免用低能量频点把这一句:
matlabHest(:,:,RxIdx)=RxSignalTF ./ TxSignalTF;
改成(核心是加 eps,并且推荐只选 |X| 足够大的频点):
matlabeps0 = 1e-8; Hest(:,:,RxIdx) = (RxSignalTF .* conj(TxSignalTF)) ./ (abs(TxSignalTF).^2 + eps0);
再进一步(很有效):AFDM 时别用原来的 fpattern,改成:
fpattern = 1:Nc; freqSpace = 1;(用于 sensing 仿真对比最公平)abs(TxFreqSignal(:,m)) 大于某阈值的频点(比如 > -15 dB of max)这样通常能显著改善距离误差,且不改你后面的 dft_2d()。
Hest,直接用时域做匹配滤波得到 delay,再做 Doppler FFT既然你在 AFDM 分支里已经得到了 txTime 和 rxTime(其实就是每个 symbol 的时域波形),那距离最稳的方法是标准雷达脉压:
对每个 symbol:
也就是“时域循环相关”。这样你不会被 1/X 放大,也不依赖频点选择。
MATLAB 形态就是:
matlabXf = fft(txTime, Nc, 1); Yf = fft(rxTime(:,:,RxIdx), Nc, 1); corrDelay = ifft(conj(Xf).*Yf, Nc, 1); % Nc × Ns (delay × slow-time) RD = fftshift(fft(corrDelay, lent, 2), 2); % delay × doppler
然后你沿用你现在的 peak picking(maxRow/maxCol→range/velocity)即可。
这套方法一般会出现你描述的现象:速度继续正确,距离从“漂”变成“稳”。
这其实也解释了你现在的现象:你们“速度检测没问题”是因为 Doppler FFT 本来就对这些病态不敏感;距离靠相位一致性/相关峰,最容易被
Y/X的病态搞坏。
channelEst() 里把 DAFT 解调结果 idaft 回去再 FFT你现在 AFDM 分支把 RxFreqSignal(DAFT 域)又 idaft 回时域再 FFT,本质上是在用 OFDM 的 CFR 视角做 sensing;这会让 AFDM 的优势(在 DAFT 域下对 delay/Doppler 的结构化表征)发挥不出来。
如果你们想做“真正 AFDM 域 sensing”,应该直接在 DAFT 域构造 RD(这需要把你们后面的 range 轴变换从 ifft 换成“与 DAFT 定义匹配的逆变换/模板”,工作量更大,但更“正宗”)。
channelEst() 里一个我建议立刻改的小点你现在 AFDM 分支里估计 Hest 用的是 RxSignalTF./TxSignalTF,如果 TxSignalTF 有很小的点,会直接炸。
所以至少先做方案 1 的正则化 LS,这是低成本高收益的第一步。
不用发全工程,只要你运行一次把下面打印出来:
min(abs(X(fpattern))) / max(abs(X(fpattern)))(AFDM 下)如果 AFDM 这个比值小很多(深陷明显),那距离误差几乎肯定就是“Y/X 病态 + ICI/噪声放大”导致的;我们就按上面方案 1/2 改,基本就能收敛。
你要是愿意,我也可以按你们当前接口把“方案 2(时域匹配滤波 + Doppler FFT)”封装成一个 afdmSensing(),输出 Range/Velocity 与 dft_2d() 一致,这样你们主流程改动最小。
我们现在想参考下面这个afdm是对的的demo,看看基于这个思路,对于我们的channelEst以及DFT应该怎么改?或者我们可以不用原来的DFT2d,而是重新写一个代码:
clc; clear; close all;rng(2025);
%% ===================== 系统参数 =====================
N = 1024; % AFDM 子载波数
Nsym = 64; % 慢时脉冲数
M = 16; % QAM 阶数
SNRdB = 15; % AWGN
fc = 24e9; % 载频 [Hz]
DeltaF = 22.729e3; % 子载波间隔 [Hz]
Fs = N*DeltaF; % 采样率 [Hz]
Ts = 1/Fs; % 采样间隔 [s]
alphamax = 2; kv = 4;
c1 = (2*(alphamax+kv)+1)/(2N);
c2 = 1/(128N);
Ncp = 128; % CPP(需 > 最大时延采样)
TAFDM = (N+Ncp)/Fs; % 单符号时长(含CPP)
c_light = 3e8;
%% ===================== 发射端:QAM & AFDM 调制 =====================
symIdx = randi([0 M-1], N*Nsym, 1);
xTx = qammod(symIdx, M, 'UnitAveragePower', true);
X = reshape(xTx, [N, Nsym]); % DAFT 域符号
s_blocks = afdm_mod(X, c1, c2); % N x Nsym(时域)
s_tx = cpp_add(s_blocks, Ncp); % (N+Ncp)*Nsym x 1
%% ===================== 目标真值(请确保 max(li)<Ncp) =====================
R_true = [400, 20, 180]; % m
v_true = 2*[ 31, 31, -10]; % m/s
P = numel(R_true);
tau = 2R_true / c_light; % s
li = round(tau / Ts); % 样点
fd = 2v_true*fc / c_light; % Hz
fi = fd * Ts; % 归一化(乘以 n 的相位增量)
h_i = (randn(1,P)+1jrandn(1,P))/sqrt(2P);
%% ===================== 接收:叠加回波 + 噪声 =====================
r_rx = apply_radar_channel(s_tx, N, Ncp, Nsym, li, fi, h_i);
r_rx = awgn(r_rx, SNRdB, 'measured');
%% ===================== 通信侧接收:DAFT & 简易等化 =====================
Y_blocks = cpp_remove(r_rx, N, Ncp, Nsym); % N x Nsym(时域)
Y_daft = daft_colwise(Y_blocks, c1, c2); % N x Nsym(DAFT)
Hhat = Y_daft ./ (X + 1e-12); % 粗糙 LS,默认通信信息完全已知
Xhat = Y_daft ./ (Hhat + 1e-12);
plot_constellation(X(:), Xhat(:), M);
%% ===================== 公共坐标换算(物理轴) =====================
% 距离分辨率:ΔR = c/(2B) = c/(2NΔf) = c/(2Fs)
range_bin_m = c_light/(2Fs);
% Doppler 轴:K 点慢时 FFT,Δfd = 1/(KTAFDM),范围 ±1/(2TAFDM)
fd_axis = (-floor(Nsym/2):ceil(Nsym/2)-1) * (1/(NsymTAFDM)); % Hz
vel_axis = fd_axis * (c_light/(2fc)); % m/s
%% ===================== 感知 :时域 FCCR =====================
RD_fccr = abs( rdm_fccr(Y_blocks, s_blocks) ); % N x Nsym
RD_show = fftshift(RD_fccr(1:Ncp, :), 2); % 仅对 Doppler 维中心化
ranges_plot = (0:Ncp-1) * range_bin_m;
% 2D RDM
figure('Name','RDM (Time-Domain FCCR)','Color','w');
imagesc(vel_axis, ranges_plot, mag2db_norm(RD_show)); axis xy;
colormap turbo; colorbar; xlabel('Velocity (m/s)'); ylabel('Range (m)');
title('Time-Domain FCCR (Range–Doppler)'); hold on;
hScat = scatter(v_true, R_true, 80, 'r', 'filled', 'MarkerEdgeColor','k', 'LineWidth',1.2);
legend(hScat, {'True targets'}, 'Location','northoutside'); % 仅真值图例
grid on; box on;
% 3D RDM
figure('Name','RDM 3D (FCCR)','Color','w');
[VV,RR] = meshgrid(vel_axis, ranges_plot);
Zfccr = mag2db_norm(RD_show);
surf(VV, RR, Zfccr, 'EdgeColor','none'); view(40,30);
xlabel('Velocity (m/s)'); ylabel('Range (m)'); zlabel('Magnitude (dB)');
title('3D Range–Doppler (FCCR)'); colormap turbo; colorbar; grid on; box on;
% 3D 叠加真值(Z 取最近网格值)
[~,kidxT] = arrayfun(@(v) min(abs(vel_axis-v)), v_true);
ridxT = min(max(round(R_true/range_bin_m)+1,1), Ncp);
zT = arrayfun(@(ii) Zfccr(ridxT(ii),kidxT(ii)), 1:P);
hold on; scatter3(v_true, R_true, zT, 60, 'r', 'filled', 'MarkerEdgeColor','k', 'LineWidth',1.2);
disp('仿真完成。');
%% ======================================================================
%% ============================ 本地函数区 ===============================
%% ======================================================================
function s_blocks = afdm_mod(X, c1, c2)
% 逐列 IDAFT(论文式(1) 的快速实现)
[N, K] = size(X);
s_blocks = zeros(N, K);
for k = 1:K
s_blocks(:,k) = idaft_col(X(:,k), c1, c2);
end
end
function s = idaft_col(x, c1, c2)
% 单列 IDAFT
[N, K] = size(x); assert(K==1, 'idaft_col 需要 N×1 列向量');
n = (0:N-1).'; m = (0:N-1).';
E1 = exp(1j2pic1(n.^2)); % 时间域 chirp
E2 = exp(1j2pic2(m.^2)); % DAFT 域 chirp
s = E1 .* (ifft(E2 .* x, [], 1) * sqrt(N));
end
function Y = daft_colwise(S, c1, c2)
% 逐列 DAFT(IDAFT 的逆)
[N, K] = size(S);
n = (0:N-1).'; m = (0:N-1).';
E1 = exp(-1j2pic1(n.^2));
E2 = exp(-1j2pic2(m.^2));
Y = (fft(E1 .* S, [], 1) / sqrt(N)) .* E2;
end
function s_tx = cpp_add(s_blocks, Ncp)
% CPP 添加:每块头部拼接 Ncp 个尾样点
[N, K] = size(s_blocks);
out = zeros(N+Ncp, K);
for k = 1:K
blk = s_blocks(:,k);
out(:,k) = [blk(end-Ncp+1:end); blk];
end
s_tx = out(:);
end
function S_blocks = cpp_remove(rx, N, Ncp, K)
% 去 CPP:按块切分并去掉前 Ncp
rx_mat = reshape(rx, N+Ncp, K);
S_blocks = rx_mat(Ncp+1:end, :);
end
function r = apply_radar_channel(s_tx, N, Ncp, K, li, fi, h_i)
% 论文式(3):r[n] = Σ_i h_i · s[n-li] · e^{j2π f_i n}
P = numel(li);
L = (N+Ncp)K;
n = (0:L-1).';
r = zeros(L,1);
s_mat = reshape(s_tx, N+Ncp, K);
for p = 1:P
s_shift = zeros(size(s_mat));
for k = 1:K
s_shift(:,k) = circshift(s_mat(:,k), li(p));
end
r = r + h_i(p) * ( s_shift(:) . exp(1j2pi*fi(p)*n) );
end
end
function RD = rdm_fccr(R, S)
% 时域 FCCR:fast-time FFT → 乘积 → IFFT → slow-time FFT
Rf = fft(R, [], 1);
Sf = fft(S, [], 1);
Z = ifft(Rf .* conj(Sf), [], 1);
RD = fft(Z, [], 2);
end
function ydb = mag2db_norm(Y)
% 归一幅度 → dB(避免 -Inf)
Yn = Y ./ (max(Y(:)) + eps);
ydb = 20*log10(Yn + eps);
end
function plot_constellation(X, Xhat, M)
% 星座图(仅演示)
Nplot = min(4000, numel(X));
idx = randperm(numel(X), Nplot);
figure('Name','Constellation','Color','w');
subplot(1,2,1);
plot(real(X(idx)), imag(X(idx)), 'o'); axis equal; grid on;
title(sprintf('TX %d-QAM', M)); xlabel('I'); ylabel('Q');
subplot(1,2,2);
plot(real(Xhat(idx)), imag(Xhat(idx)), 'o'); axis equal; grid on;
title('RX Equalized'); xlabel('I'); ylabel('Q');
end
你这个现象(速度跟 OFDM 一样正常,但距离误差很大)和你现在 channelEst() 的 AFDM 处理方式高度一致:你把 DAFT 域的收发符号又 idaft→(时域)→fft→(OFDM 频域),然后再做 Hest = Y./X,这会让距离维(delay)特别容易崩,而速度(慢时 FFT)看起来还能对。
下面我按你给的 demo 思路,把“问题在哪 + 怎么改”说清楚,并给你一套可以直接替换的实现方案(你们也可以完全不用原来的 dft_2d)。
Y./X 做“频域信道估计”,但在雷达/ISAC 的 LTV(含 Doppler)情况下对 OFDM 来说,Y[k] ≈ H[k]X[k] 只有在多普勒很小/ICI 可忽略时才近似成立;
对 AFDM 来说,你又额外做了基变换(DAFT↔时域↔FFT),等效会让
Y[k] 里 ICI/混叠更明显;|X[k]| 可能很小(或谱形不平坦),Y./X 会把误差放大;你自己也观察到:你现在“匹配滤波”那套处理对速度影响不大,但 delay 需要补偿——是的,本质就是“你现在 delay 维的处理域不对/估计方式不稳”。
Hest = Y./X,直接用时域 FCCR 做 Range–Dopplerdemo 的 sensing 用的是 Time-domain FCCR:
Y_blocks (N×K)s_blocks (N×K)1/X,delay 会稳很多。你的 ofdmDemodulate() 在 useAFDM 时输出的 RxFreqSignal 实际上就是 DAFT 域的 Y;TxFreqSignal 也是你发的 DAFT 域 X。那通信侧最一致的 LS 就是:
matlabtxTime = idaft(TxFreqSignal,...); rxTime = idaft(RxFreqSignal,...); TxFreqSignal = fft(txTime,...); RxFreqSignal = fft(rxTime,...);
matlabif sensing.useAFDM % TxFreqSignal: X (DAFT域), RxFreqSignal: Y (DAFT域) eps0 = 1e-8; TxSignalTF = TxFreqSignal(fpattern,tpattern); % 如果你坚持 pattern for RxIdx = 1:rxAntNum RxSignalTF = RxFreqSignal(fpattern,tpattern,RxIdx); Hest(:,:,RxIdx) = (RxSignalTF .* conj(TxSignalTF)) ./ (abs(TxSignalTF).^2 + eps0); end return; end
重要:AFDM 下 不要做
circshift(Nc/2)再除 —— 那是 OFDM 子载波重排的习惯操作,但你现在已经不在 OFDM 子载波域了,会引入额外相位/索引错配,最先影响的就是 delay。
afdmSensing_fccr() 替换 dft_2d()你不需要 Hest,直接用 去 CP 后的时域块做 RDM:
ofdmModulate() 里已经算出了 TxSignalT(每符号 N 点、无 CP 的时域块),AFDM 时它就是 demo 里的 s_blocksofdmDemodulate() 里你已经有 RxSignal(:,sym,ant)(去 CP 后的时域块),这就是 demo 里的 Y_blocksofdmDemodulate 改成同时输出时域块(最小侵入)matlabfunction [RxFreqSignal, RxTimeBlocks] = ofdmDemodulate(channelOut,sensing) ... RxTimeBlocks = RxSignal; % N×Nsymbol×rxAnt end
ofdmModulate() 里把 TxSignalT 存到 sensing 里返回:
matlabif sensing.useAFDM TxSignalT = idaft(...); else TxSignalT = ifft(...); end sensing.TxTimeBlocks = TxSignalT; % N×Nsymbol(或按天线存)
下面这版先实现 R/V(Angle 你们可以沿用现在 MUSIC:在检测到的 RD bin 上取各天线复值快照即可)。
matlabfunction [Range,Velocity,Angle, RDsum, RDant] = afdmSensing_fccr(RxTimeBlocks, TxTimeBlocks, sensing) % RxTimeBlocks: N×K×Nr (去CP后的时域块) % TxTimeBlocks: N×K (发射无CP时域块,AFDM: idaft输出;OFDM: ifft输出) % RDsum: 距离×多普勒幅度图(用于debug) % RDant: 距离×多普勒×天线 复值(用于角度估计) c = 3e8; fc = sensing.fc; N = size(RxTimeBlocks,1); K = size(RxTimeBlocks,2); Nr = size(RxTimeBlocks,3); targetNum = sensing.targetNum; % ---------- 采样率/符号时长(建议用真实 N*Δf) ---------- Deltaf = sensing.SCS; Fs = N * Deltaf; % 你们CP若每符号不同,严格说慢时FFT会受影响;建议AFDM用固定CP/CPP if isfield(sensing,'CplenSet') Ncp = min(sensing.CplenSet(1:K)); % 保守取最短CP对应的无歧义距离 else Ncp = sensing.afdm.Ncp; % 或你们自己存 end Tsym = (N + Ncp)/Fs; % 慢时采样周期(含前缀) % ---------- 物理坐标:注意你们仿真里 delay 是 R/c 还是 2R/c ---------- twoWay = true; % <<< 如果你们信道里用 tau=2R/c,就设 true;若 tau=R/c,就设 false if twoWay range_bin = c/(2*Fs); else range_bin = c/(Fs); end fd_bin = 1/(K*Tsym); % 多普勒分辨率(Hz) vel_bin = fd_bin * c/(2*fc); % 速度分辨率(m/s,双程) % ---------- FCCR:快时相关 + 慢时FFT ---------- Sf = fft(TxTimeBlocks, [], 1); % N×K RDsum = zeros(N, K); RDant = zeros(N, K, Nr); for ant = 1:Nr Rf = fft(RxTimeBlocks(:,:,ant), [], 1); % N×K Z = ifft(Rf .* conj(Sf), [], 1); % N×K (delay相关输出) RD = fft(Z, [], 2); % N×K (doppler) RD = fftshift(RD, 2); RDant(:,:,ant) = RD; RDsum = RDsum + abs(RD); end % 只取无歧义距离(<= CP)范围 RDcut = RDsum(1:Ncp, :); % ---------- 峰值检测(先做最简单版本:逐次取最大点并做小窗口抑制) ---------- Range = zeros(targetNum,1); Velocity = zeros(targetNum,1); Angle = zeros(targetNum,1); res = RDcut; guardR = 2; guardV = 2; % 抑制窗口(可按需要改) for p = 1:targetNum [~,idx] = max(res(:)); [rIdx, vIdx] = ind2sub(size(res), idx); % 距离与速度 Range(p) = (rIdx-1) * range_bin; Velocity(p) = (vIdx - (K/2) - 1) * vel_bin; % 窗口抑制(避免重复挑到同一个主瓣) r1 = max(1, rIdx-guardR); r2 = min(size(res,1), rIdx+guardR); v1 = max(1, vIdx-guardV); v2 = min(size(res,2), vIdx+guardV); res(r1:r2, v1:v2) = 0; % 角度:你们如果要MUSIC,就在这里取复值快照 % snapshot = squeeze(RDant(rIdx, vIdx, :)); % Nr×1 % Angle(p) = musicAoA(snapshot, sensing); % 你们现有的那套MUSIC可复用 end end
Hest = Y./X 走距离维,先用上面 FCCR(基本能立刻把距离拉回正常)idaft→fft 再除,且别 circshift(N/2)tau=2R/c,range_bin 必须有 /2;如果你们是 tau=R/c,就不要 /2。这个一旦搞反,距离误差会“很大而且呈比例”。RDcut 看峰是不是在 li 对应的 bintwoWay=true/false 切一下:
如果你把你们信道里“目标时延”到底是 tau=R/c 还是 tau=2R/c(以及 AFDM 时 CP 长度是否固定、CplenSet 是否每符号不同)告诉我一句,我就能把上面函数里的 Tsym/range_bin 精确对齐到你们工程的定义,并把 Angle 的 MUSIC 部分也按你们现有 dft_2d 的写法直接接上去。
Nc = 3276 目标双程最大距离1000 速度最大0,SCS = 30000 CP长度是288
Nc = 3276 目标双程最大距离1000 速度最大0,SCS = 30000 CP长度是288。我也觉得我们可以直接写一个demo,而不是在channelest中进行修改。我们就像下面的这个OTFSsensing一样,单独写一个AFDM的函数吧。原来的channelEst我们就不调整了。现在OFDM的调制解调都用的DAFT,其他没有做处理。请写一个函数,用上面发你的参考代码的思路,对照OTFS和OFDM的框架,写一个AFDM,用类似2DDFT的·方式进行感知: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 [Range,Velocity,Angle]=offsSensing(Hest,sensing)
Angle=0;
targetNum=sensing.targetNum;
fc=sensing.fc;
SCS=sensing.SCS;
slotT=sensing.frameLen*1e-3/sensing.sensingSymbolNum;
sensingSymbolNum=sensing.sensingSymbolNum;
fdmax=1/slotT;
Nc=sensing.Nc;
Nzc=sensing.Nzc;
TxSignalDD=sensing.TxSignalDD;
overSamplNumf=sensing.overSamplNumf;
overSamplNumt=sensing.overSamplNumt;
overSamplFlag=sensing.overSamplFlag;
freqSpace=sensing.freqSpace;
ZcSeq=sensing.ZcSeq;
if sensing.otfsSensingFlag==1
% SFFT
RxSignalTF=Hest;
SensingNsymbol=size(Hest,2);
RxSignalDD=ifft(RxSignalTF).'.*sqrt(Nc/SensingNsymbol);
RxRsSignal=RxSignalDD(1:Nzc,:);
text% Receive reference signal oersampling RxRsSignalTF=ifft(fft(RxRsSignal).'.*sqrt(SensingNsymbol/Nzc)); RxOverSamplSignalDD=ifft(fft(RxRsSignalTF,overSamplNumf).',overSamplNumt).*sqrt(Nzc/SensingNsymbol); % Transmit reference signal oversampling TxRsSignal=repmat(ZcSeq,1,SensingNsymbol); TxRsSignalTF=ifft(fft(TxRsSignal).'.*sqrt(SensingNsymbol/Nzc)); TxOverSamplSignalDD=ifft(fft(TxRsSignalTF,overSamplNumf).',overSamplNumt).*sqrt(Nzc/SensingNsymbol); % reconstruct local oversampling signal OverSampZcSeq=ifft(fft(ZcSeq,overSamplNumf)); OverSampZcSeq=repmat(OverSampZcSeq,1,overSamplNumt); % reconstruct receive oversampling signal OverSampTF=ifft(fft(TxSignalDD(1:Nzc,:)).'.'); OverSampDD=fft(ifft(OverSampTF,overSamplNumf).',overSamplNumt).'; % channel estimate if overSamplFlag == 0 EstHDD=ifft(conj(fft(TxRsSignal)).*fft(RxRsSignal))/(sqrt(SensingNsymbol)*Nzc); else EstHDD=ifft(conj(fft(TxOverSamplSignalDD)).*fft(RxOverSamplSignalDD))/(sqrt(SensingNsymbol)*Nzc); end peakValue=max(EstHDD(:)); EstHDD=EstHDD/peakValue; if overSampFlag == 0 deltaD = 3e8/(SCS*Nzc); len=sensingNsymbol; deltafd = fdmax/len; else deltaD = 3e8/(SCS*overSampNumf); len=overSamplNumt; deltafd = fdmax/len; end initH=fftshift(abs(EstHDD),2); [lenf,lent]=size(initH); % mesh(-lent/2:lent/2-1]*deltafd,[0:lenf-1]*deltaD,(initH)); % xlabel('Doppler [Hz]'); % ylabel('Range [m]') % zlabel('Normalized Power') % ylim([0,2000]) % xlim([-15000,15000]) % set(gcf,'Color','white'); % colorbar residualH=initH; distanceEst=zeros(targetNum,1); dopplerEst=zeros(targetNum,1); velocityEst=zeros(targetNum,1); for targetInd=1:targetNum [~,peakInd]=max(residualH(:)); [row,col]=ind2sub(size(residualH),peakInd); range=(row-1)*deltaD; doppler=(col-1-lenf/2)*deltafd; rowSet=[row]; colSet=[col]; rowSet(rowSet==0)=[]; rowSet(rowSet>size(initH,1))=[]; colSet(colSet==0)=[]; colSet(colSet>size(initH,2))=[]; for row=rowSet for col=colSet if overSamplFlag==0 reconSignal=circshift(TxSignalDD(1:Nzc,:),row-1,1); reconSignal=circshift(reconSignal,col-1,2); reconH=ifft(conj(fft(TxRsSignal)).*fft(reconSignal))/(sqrt(SensingNsymbol)*Nzc); else reconSignal=circshift(OverSampDD,row-1,1); reconSignal=circshift(reconSignal,col-1,2); reconH=ifft(conj(fft(OverSampZcSeq)).*fft(reconSignal))/(sqrt(SensingNsymbol)*Nzc); end reconH=abs(reconH/max(reconH(:))); factor=initH(row,col); residualH=residualH-factor*reconH; residualH(residualH<0)=0; end end distanceEst(targetInd,1)=range; dopplerEst(targetInd,1)=doppler; velocityEst(targetInd,1)=dopplerEst(targetInd,1)*3e8/fc/2; Range=distanceEst; Velocity=velocityEst; end
else
channelHEst=Hest;
if overSamplFlag == 0
[lenf,lent]=size(channelHEst);
else
lenf = overSamplNumf;
lent = overSamplNumt;
end
deltaD = 3e8/(SCSfreqSpacelenf);
deltafd = fdmax/lent;
textEstHTF=abs(fft(ifft(channelHEst,lenf).',lent).')*sqrt(lenf/lent); peakValue=max(EstHTF(:)); EstHTF=EstHTF/peakValue; initH=fftshift(abs(EstHTF),2); residualH=initH; distanceEst=zeros(targetNum,1); dopplerEst=zeros(targetNum,1); velocityEst=zeros(targetNum,1); [lenf,lent]=size(initH); % mesh(-lent/2:lent/2-1]*deltafd,[0:lenf-1]*deltaD,(initH)); % xlabel('Doppler [Hz]'); % ylabel('Range [m]') % zlabel('Normalized amplitude') % ylim([0,800]) % xlim([-10000,10000]) % set(gcf,'Color','white'); % colorbar for targetInd=1:targetNum [~,peakInd]=max(residualH(:)); [row,col]=ind2sub(size(residualH),peakInd); range=(row-1)*deltaD; doppler=(col-1-lent/2)*deltafd; c=3e8; fR=exp(-1j*2*pi*[0:Nzc-1]*SCS*range/c); fV=exp(1j*2*pi*[0:sensingSymbolNum-1]*(col-1)*deltafd*slotT); recon=fR.'*fV; reconH=fft(ifft(recon,lenf).',lent).'*sqrt(lenf/lent); reconH=abs(reconH/max(reconH(:))); factor=initH(row,col); residualH=residualH-factor*reconH; distanceEst(targetInd,1)=range; dopplerEst(targetInd,1)=doppler; velocityEst(targetInd,1)=dopplerEst(targetInd,1)*3e8/fc/2; Range=distanceEst; Velocity=velocityEst; end
end
end
下面给你一个独立的 AFDM 感知函数(不改你们现有 channelEst),思路严格对齐你贴的 demo:** 去 CP 后的时域块做 FCCR(快时匹配滤波)+ 慢时 FFT(多普勒)**,本质就是“类似 2DDFT”的实现方式:
FFT(R) * conj(FFT(S)) → IFFT 得到 delay 相关(脉压)FFT 得到 Doppler同时我先把你们当前参数里一个决定性问题讲清楚(这就是“距离误差很大”的最常见根因):
你给的:Nc=3276, SCS=30000
采样率
双程距离的每个 delay bin 距离分辨率:
CP=288 的无歧义最大双程距离(近似):
而你们目标双程最大 1000 m,需要的 CP 至少:
所以如果你仿真里真的放了 1000 m 的目标,delay 一定会循环混叠(wrap/alias),距离就会“很大误差/跳变”,而速度(你这里 vmax=0)当然看起来没问题。
afdmSensing_fccr.m输入建议直接用你们解调输出
RxFreqSignal(AFDM 模式下是 DAFT 域的 Y)以及发射的TxFreqSignal(DAFT 域的 X)。函数内部会idaft回到去 CP 后的时域块,然后按 demo 做 FCCR + Doppler FFT。
matlabfunction [Range,Velocity,Angle, RDsum] = afdmSensing_fccr(RxFreqSignal, TxFreqSignal, sensing) % AFDM sensing via time-domain FCCR + slow-time FFT (2D-DFT style) % % RxFreqSignal: Nc x Nsym x Nr (AFDM mode: DAFT-domain output of daft()) % TxFreqSignal: Nc x Nsym (or Nc x Nsym x Nt) (AFDM mode: DAFT-domain symbols) % sensing: struct with fields: % Nc, SCS, fc, CplenSet or CP length, targetNum, rxAntNum % afdm.c1, afdm.c2 % pseudoPeakFlag (optional), overSamplFlag/overSamplNumf/overSamplNumt (optional) % AsetH/AsetV, UEdH/UEdV, verticalFlag (optional for Angle) % % Outputs: % Range [m], Velocity [m/s], Angle [deg], RDsum magnitude map (for debug) c = 3e8; Nc = sensing.Nc; Deltaf = sensing.SCS; fc = sensing.fc; Nr = sensing.rxAntNum; targetNum = sensing.targetNum; % --------- symbol count ---------- if ndims(RxFreqSignal) == 2 K = size(RxFreqSignal,2); Nr = 1; else K = size(RxFreqSignal,2); end % --------- CP length ---------- if isfield(sensing,'CplenSet') && ~isempty(sensing.CplenSet) Ncp = min(sensing.CplenSet(1:K)); % conservative elseif isfield(sensing,'CP') && ~isempty(sensing.CP) Ncp = sensing.CP; else % fallback Ncp = 0; end % --------- sampling / slow-time period ---------- Fs = Nc * Deltaf; Tsym = (Nc + Ncp) / Fs; % include CP/CPP for Doppler grid % --------- bins (two-way radar) ---------- range_bin = c/(2*Fs); % meters per delay sample (two-way) fd_bin = 1/(K*Tsym); % Hz per Doppler bin vel_bin = fd_bin * c/(2*fc); % m/s per Doppler bin (two-way) % warn if max range exceeds CP unambiguous Rmax_unamb = (max(Ncp,1)-1) * range_bin; if isfield(sensing,'targetRange') && ~isempty(sensing.targetRange) if max(sensing.targetRange) > Rmax_unamb && Ncp > 0 warning('AFDM sensing: CP=%d gives Rmax≈%.1f m (two-way). Targets beyond this will alias. Need CP>=~%d for 1000m at Nc=%d,SCS=%.0f.', ... Ncp, Rmax_unamb, ceil(1000/range_bin)+1, Nc, Deltaf); end end % --------- optional oversampling (zero-padding) ---------- if isfield(sensing,'overSamplFlag') && sensing.overSamplFlag == 1 Nfft = sensing.overSamplNumf; Kfft = sensing.overSamplNumt; else Nfft = Nc; Kfft = K; end % --------- pick Tx (assume single TX; if multiple, use the first) ---------- if ndims(TxFreqSignal) == 3 TxX = TxFreqSignal(:,:,1); else TxX = TxFreqSignal; end % --------- reconstruct no-CP time blocks via IDAFT ---------- % Tx blocks: Nc x K TxTimeBlocks = idaft(TxX, sensing.afdm.c1, sensing.afdm.c2); % Rx blocks per antenna: Nc x K x Nr RxTimeBlocks = zeros(Nc, K, Nr); if Nr == 1 RxTimeBlocks(:,:,1) = idaft(RxFreqSignal, sensing.afdm.c1, sensing.afdm.c2); else for ant = 1:Nr RxTimeBlocks(:,:,ant) = idaft(RxFreqSignal(:,:,ant), sensing.afdm.c1, sensing.afdm.c2); end end % --------- FCCR + Doppler FFT ---------- Sf = fft(TxTimeBlocks, Nfft, 1); % Nfft x K RDsum = zeros(Nfft, Kfft); RDant = zeros(Nfft, Kfft, Nr); % complex map for AoA for ant = 1:Nr Rf = fft(RxTimeBlocks(:,:,ant), Nfft, 1); % Nfft x K Z = ifft(Rf .* conj(Sf), Nfft, 1); % delay correlation RD = fft(Z, Kfft, 2); % Doppler FFT RD = fftshift(RD, 2); RDant(:,:,ant) = RD; RDsum = RDsum + abs(RD); end % use only unambiguous delay window (<= CP). If Ncp==0, use full if Ncp > 0 Nwin = min(Ncp, Nfft); else Nwin = Nfft; end RDwin = RDsum(1:Nwin, :); % --------- peak picking (simple + optional pseudo peak neighborhood) ---------- Range = zeros(targetNum,1); Velocity = zeros(targetNum,1); Angle = zeros(targetNum,1); pseudoPeakFlag = isfield(sensing,'pseudoPeakFlag') && sensing.pseudoPeakFlag == 1; res = RDwin; guardR = 2; guardV = 2; % suppression window to avoid picking same mainlobe for p = 1:targetNum [~,idx] = max(res(:)); [rIdx, vIdx] = ind2sub(size(res), idx); Range(p) = (rIdx-1) * range_bin; Velocity(p) = (vIdx - floor(Kfft/2) - 1) * vel_bin; % Angle estimate (single-snapshot beam scan; more stable than "MUSIC with 1 snapshot") ang = 0; if Nr > 1 && isfield(sensing,'AsetH') && isfield(sensing,'AsetV') x = squeeze(RDant(rIdx, vIdx, :)); % Nr x 1 complex snapshot if isfield(sensing,'verticalFlag') && sensing.verticalFlag == 1 Aset = sensing.AsetV; d = sensing.UEdV; score = zeros(size(Aset)); for ii=1:numel(Aset) A = Aset(ii); a = exp(1j*2*pi*(0:Nr-1).'*d*cos(A*pi/180)); score(ii) = abs(a' * x).^2; end else Aset = sensing.AsetH; d = sensing.UEdH; score = zeros(size(Aset)); for ii=1:numel(Aset) A = Aset(ii); a = exp(1j*2*pi*(0:Nr-1).'*d*sin(A*pi/180)); score(ii) = abs(a' * x).^2; end end [~,im] = max(score); ang = Aset(im); end Angle(p) = ang; % suppression / pseudo neighborhood if pseudoPeakFlag r1 = max(1, rIdx-1); r2 = min(size(res,1), rIdx+1); v1 = max(1, vIdx-1); v2 = min(size(res,2), vIdx+1); else r1 = max(1, rIdx-guardR); r2 = min(size(res,1), rIdx+guardR); v1 = max(1, vIdx-guardV); v2 = min(size(res,2), vIdx+guardV); end res(r1:r2, v1:v2) = 0; end end
你们现在 AFDM 模式下:
ofdmModulate() 的输入 TxFreqSignal 是 DAFT 域 XofdmDemodulate() 的输出 RxFreqSignal 是 DAFT 域 Y所以直接在接收端(得到 RxFreqSignal 后)调用:
matlab[Range,Velocity,Angle,RDsum] = afdmSensing_fccr(RxFreqSignal, TxFreqSignal, sensing);
不需要走
channelEst(),也不需要用dft_2d(Hest)。
如果你愿意,我也可以把这个函数按你们现有 dft_2d() 的输出风格补齐:
mesh/imagesc 的 RD 图可视化(带物理轴)slotT 的定义替换成你们框架的一致版本(如果你们符号时长不是 (Nc+CP)/Fs 而是由 frameLen/sensingSymbolNum 决定)但即使不改,上面这版对你给的 Nc/SCS/CP/fc 是自洽的,并且会直接暴露“CP 不够导致距离混叠”的问题。
pythonNc=3276; SCS=30000; c=3e8 Fs=Nc*SCS range_bin=c/(2*Fs) Rmax=(288-1)*range_bin Fs, range_bin, Rmax (98280000, 1.5262515262515262, 438.034188034188)
下面我给你一份独立 AFDM sensing 函数,满足你提出的几点:
slotT = sensing.frameLen*1e-3/sensing.sensingSymbolNum;Hest(而不是 Y_blocks / s_blocks),我用一个等价的“加权相关”版本来避免 Y./X 带来的距离偏差(AFDM 最常见问题点)。你给的:Nc=3276, SCS=30000, CP=288
采样率 ,双程距离 bin:
CP=288 的无歧义最大双程距离:
所以只要你真的放了 >438 m 的目标(比如 1000 m),不管 OFDM/AFDM/OTFS 你怎么做,距离都会 wrap/alias,表现就是“距离误差很大、而速度可能还正常”。
如果你要双程 1000 m,CP 至少要到大约 656(按 full-rate 采样算)。
你现在 vmax=0,所以速度“看着没问题”非常正常;距离这边如果超过 CP 覆盖范围,必错。
afdmSensing_2d.m特点:
Hest(你现有 channelEst 输出)TxTF,就做:ifft( |TxTF|^2 .* Hest )ifft( Y .* conj(X) )(避免 1/X 放大导致距离漂)你需要在 sensing 里提供一个 TxTF(与 Hest 同维度的 Tx reference 网格,通常就是 channelEst 里用的
TxSignalTF)。不改 channelEst 的话,你在主程序里自己算一次并塞进sensing.afdmTxTF就行;如果不给,我会退化成TxTF=ones(效果可能仍然差)。
matlabfunction [Range,Velocity] = afdmSensing_2d(Hest, sensing) % AFDM sensing (2D style): weighted fast-time correlation + slow-time FFT % Input: % Hest: Nf x Nt x Nr (output of channelEst) % sensing: includes fc, SCS, frameLen, sensingSymbolNum, freqSpace, targetNum, % overSamplFlag/overSamplNumf/overSamplNumt (optional), % clutterCancelFlag (optional), % afdmTxTF (recommended): Nf x Nt complex reference grid aligned with Hest % % Output: % Range [m], Velocity [m/s] c = 3e8; targetNum = sensing.targetNum; fc = sensing.fc; Deltaf = sensing.SCS; freqSpace = sensing.freqSpace; % slotT aligned with your framework slotT = sensing.frameLen*1e-3 / sensing.sensingSymbolNum; % --- clutter cancel (as you required) --- % assumes you have: function Hest=clutterCancel(Hest,sensing) if isfield(sensing,'clutterCancelFlag') && sensing.clutterCancelFlag==1 Hest = clutterCancel(Hest, sensing); end % --- sizes / oversampling --- if isfield(sensing,'overSamplFlag') && sensing.overSamplFlag==1 lenf = sensing.overSamplNumf; lent = sensing.overSamplNumt; else lenf = size(Hest,1); lent = size(Hest,2); end rxAntNum = size(Hest,3); % --- Range/Velocity bin spacing --- % Range: use two-way radar scaling (recommended). If you want to keep legacy behavior, % remove the "/2". deltaR = c / (2 * freqSpace * Deltaf * lenf); deltaV = c / (2 * fc * slotT * lent); % two-way % --- get Tx reference aligned with Hest --- % strongly recommended: sensing.afdmTxTF is same size as Hest(:,:,1) if isfield(sensing,'afdmTxTF') && ~isempty(sensing.afdmTxTF) TxTF = sensing.afdmTxTF; % allow TxTF to be Nf x Nt; if oversampled you can zero-pad via fft-size below else % fallback: no weighting (will behave closer to plain IFFT(Hest)) TxTF = ones(size(Hest,1), size(Hest,2)); warning('afdmSensing_2d: sensing.afdmTxTF not provided. Falling back to unweighted processing; range may be inaccurate for AFDM.'); end % use |Tx|^2 weighting to emulate matched filtering: conj(X).*Y = |X|^2 .* (Y./X) = |X|^2 .* Hest W = abs(TxTF).^2; % --- build Range-Doppler map --- initH = zeros(lenf, lent); for antId = 1:rxAntNum Htemp = Hest(:,:,antId); % weighted fast-time correlation (delay compression) % (zero-padding via ifft length = lenf) Zdelay = ifft(W .* Htemp, lenf, 1); % lenf x Nt % slow-time FFT (Doppler) Zrd = fft(Zdelay.', lent, 1).'; % lenf x lent initH = initH + fftshift(abs(Zrd), 2); end % --- peak picking (simple iterative, no angle, no CLEAN) --- Range = zeros(targetNum,1); Velocity = zeros(targetNum,1); residualH = initH; % small suppression window to avoid picking same mainlobe repeatedly guardR = 2; guardV = 2; for k = 1:targetNum [~,idx] = max(residualH(:)); [rIdx, vIdx] = ind2sub(size(residualH), idx); Range(k) = (rIdx-1) * deltaR; Velocity(k) = (vIdx - floor(lent/2) - 1) * deltaV; r1 = max(1, rIdx-guardR); r2 = min(size(residualH,1), rIdx+guardR); v1 = max(1, vIdx-guardV); v2 = min(size(residualH,2), vIdx+guardV); residualH(r1:r2, v1:v2) = 0; end end
sensing.afdmTxTF因为你坚持“不改 channelEst”,但 AFDM 下距离想稳,必须知道 Tx 在同一网格上的幅度权重(至少 |X|^2)。
最简单做法:你在调用 channelEst(TxFreqSignal,RxFreqSignal,...) 之前,按 channelEst 里 AFDM 分支同样的方式算一遍 TxTF,然后存进 sensing。
如果你现在传进 channelEst 的 TxFreqSignal 是 DAFT 域的 X(Nc×Nt),那 TxTF 可以这样算(与你们 channelEst 的 AFDM 分支保持一致):
matlab% X_daft = TxFreqSignal(:,:,txAntId) or TxFreqSignal (Nc x Nt) txTime = idaft(X_daft, sensing.afdm.c1, sensing.afdm.c2); TxF_ofdm = fft(txTime, sensing.Nc, 1)/sqrt(sensing.Nc); TxF_ofdm = circshift(TxF_ofdm, sensing.Nc/2, 1); % 若你们channelEst里做了这个,就保持一致 TxTF = TxF_ofdm(sensing.fpattern, sensing.tpattern); sensing.afdmTxTF = TxTF; % 维度应与 Hest(:,:,1) 一致
如果你们 Hest 只在
fpattern/tpattern上估计,那afdmTxTF也必须是同样的 pattern,否则维度对不上。
你们现有 Hest 在 AFDM 模式下本质上是 Hest = Y./X。当 |X| 有起伏/存在很小点时,Y./X 会把噪声和 ICI 放大,最先破坏 delay 的“相位一致性/相关峰”。
我这里用 |X|^2 .* Hest 等价于 conj(X).*Y(匹配滤波),避免 1/X 放大,这就是 demo 的核心思路迁移到你“必须经过 channelEst”的限制下的折中实现。
sensing.afdmTxTF 后再看距离误差——通常会明显改善你要是把你们 fpattern 的长度(等效 Nf)和 tpattern 的长度(Nt)发一句,我也可以顺手帮你把 deltaR/deltaV 的标定再核一遍,确保跟你们现有 OFDM/OTFS 输出完全一致(尤其是是否需要 /2 这一点)。