%% get the input path and testList
pro_path = getenv('CASCADE_SIGNAL_PROCESSING_CHAIN_MIMO');
input_path = strcat(pro_path,'\main\cascade\input\');
testList = strcat(input_path,'testList.txt');
D:\Desktop\radar\2243\2022-10-12 测试数据\电车 35码左右行驶 70帧 采集时间35s\Cascade_Capture_22xx\
C:\ti\mmwave_studio_03_00_00_14\mmWaveStudio\MatlabExamples\4chip_cascade_MIMO_example\main\cascade\input\calibrateResults_dummy.mat
C:\ti\mmwave_studio_03_00_00_14\mmWaveStudio\MatlabExamples\4chip_cascade_MIMO_example\main\cascade\paramGen\module_param.m
根据上一把得到的一个路径数组,分别获取各种数据的具体文件或者文件夹。
%% get each test vectors within the test list
% test data file name
dataFolder_test = fgetl(fidList); %获取数据目录路径
%calibration file name
dataFolder_calib = fgetl(fidList); %获取校准矩阵,TI提供原始校准矩阵
%module_param_file defines parameters to init each signal processing
%module
module_param_file = fgetl(fidList);
%parameter file name for the test
pathGenParaFile = [input_path,'test',num2str(testID), '_param.m'];
%important to clear the same.m file, since Matlab does not clear cache
%automatically
clear(pathGenParaFile);
%调用该函数,在信号处理前使用Jason文件生成一个完整的参数文件,包括chirp参数和模块参数。
%input:
% test_name: 测试数据文件名,包含chirp配置
%文件为Jason文件格式
% dataFolder_calib: 校准数据文件路径,包含预先校准的.mat文件。这个.mat文件包括校准矩阵,用于adc数据校准模块。
% module_param_file: 包含初始化每个信号处理模块的参数。
% pathGenParaFile: 要生成的完整参数文件名
% dataPlatform:数据采集平台的标志
parameter_file_gen_json(dataFolder_test, dataFolder_calib, module_param_file, pathGenParaFile, dataPlatform);
:::info
parameter_file_gen_json函数的作用就是将数据文件夹的配置文件、校准文件统一的写进test1_param.m文件中,test1_param.m文件是用以下的代码定义
:::
%parameter file name for the test
pathGenParaFile = [input_path,'test',num2str(testID), '_param.m'];
%important to clear the same.m file, since Matlab does not clear cache
%automatically
clear(pathGenParaFile);
将上一步重新写好的test1_param.m文件赋值给各个结构体
simTopObj = simTopCascade('pfile', pathGenParaFile);
calibrationObj = calibrationCascade('pfile', pathGenParaFile, 'calibrationfilePath', dataFolder_calib);
rangeFFTObj = rangeProcCascade('pfile', pathGenParaFile);
DopplerFFTObj = DopplerProcClutterRemove('pfile', pathGenParaFile);
detectionObj = CFAR_CASO('pfile', pathGenParaFile);
DOAObj = DOACascade('pfile', pathGenParaFile);
结构体名 | 描述 |
---|---|
simTopObj | SimTopObj用于顶层参数解析和数据加载保存 |
calibrationObj | 数据校准结构体,包括校准矩阵的路径、话有一些校准参数,用校准来校准ADC数据 |
rangeFFTObj | 进行距离维FFT处理时的参数,包括采样点数、窗口系数等 |
DopplerFFTObj | 进行多普勒维FFT处理时的参数,包括天线数、多普勒点数、窗口系数等 |
detectionObj | CFAR目标检测算法结构体,包括算法参数、算法实现 |
DOAObj | 定义角度检测算法结构体,基于FFT的波束成形方法估计方位角和仰角(如果启用),在方位角方向检测到多个峰值,在仰角方向只检测到最大峰值。 |
% Get Valid Number of Frames
[numValidFrames dataFileSize] = getValidNumFrames(fullfile(dataFolder_test, fileNameStruct.masterIdxFile));
通过读取master_0000_idx.bin文件,获得有效帧数信息。
:::info
fullfile(dataFolder_test, fileNameStruct.masterIdxFile)可以理解为文件文件路径和路径下的文件进行拼接,得到完整的文件位置。
:::
在后处理的matlab代码中,所有模块都有datapath函数,在调用时都是调用同名函数,根据传入的结构体来区分,在调用时将相应的结构体传递给相同名字的函数即可。
:::success
每次数据采集后,都会保存一个二进制数据文件和相应的啁啾配置参数。这两个文件是ADC数据读取和校准模块的输入。二进制数据文件根据基于数据格式、每个啁啾的样本、每帧啁啾、TXRX通道的数量的啁啾参数进行读取/解析。然后,数据被重新格式化为4D矩阵,其维度为每个啁啾点的样本、每帧啁啾点、RX通道的数量和TX通道的数量。每个TX/RX通道根据预先计算好的校准矩阵进行校准,该矩阵可以通过第3节中描述的程序获得。
:::
数据文件夹如下图所示:
读取bin数据代码:
switch obj.dataPlatform
case 'TDA2'
numChirpPerLoop = obj.numChirpsPerFrame/obj.nchirp_loops;
numLoops = obj.nchirp_loops;
numRXPerDevice = 4; % Fixed number
[radar_data_Rxchain] = read_ADC_bin_TDA2_separateFiles(fileName,frameIdx,numSamplePerChirp,numChirpPerLoop,numLoops, numRXPerDevice, 1);
otherwise
error('Not supported data capture platform!');
end
具体的函数解释:
function [radar_data_Rxchain] = read_ADC_bin_TDA2_separateFiles(fileNameCascade,frameIdx,numSamplePerChirp,numChirpPerLoop,numLoops, numRXPerDevice, numDevices)
dataFolder =fileNameCascade.dataFolderName;
fileFullPath_master = fullfile(dataFolder,fileNameCascade.master);
fileFullPath_slave1 = fullfile(dataFolder,fileNameCascade.slave1);
fileFullPath_slave2 = fullfile(dataFolder,fileNameCascade.slave2);
fileFullPath_slave3 = fullfile(dataFolder,fileNameCascade.slave3);
[radar_data_Rxchain_master] = readBinFile(fileFullPath_master, frameIdx,numSamplePerChirp,numChirpPerLoop,numLoops, numRXPerDevice, numDevices);
[radar_data_Rxchain_slave1] = readBinFile(fileFullPath_slave1, frameIdx,numSamplePerChirp,numChirpPerLoop,numLoops, numRXPerDevice, numDevices);
[radar_data_Rxchain_slave2] = readBinFile(fileFullPath_slave2, frameIdx,numSamplePerChirp,numChirpPerLoop,numLoops, numRXPerDevice, numDevices);
[radar_data_Rxchain_slave3] = readBinFile(fileFullPath_slave3, frameIdx,numSamplePerChirp,numChirpPerLoop,numLoops, numRXPerDevice, numDevices);
% Arranged based on Master RxChannels, Slave1 RxChannels, slave2 RxChannels, slave3 RxChannels
% The RX channels are re-ordered according to "TI_Cascade_RX_ID" defined in
% "module_params.m"
radar_data_Rxchain(:,:,1:4,:) = radar_data_Rxchain_master;
radar_data_Rxchain(:,:,5:8,:) = radar_data_Rxchain_slave1;
radar_data_Rxchain(:,:,9:12,:) = radar_data_Rxchain_slave2;
radar_data_Rxchain(:,:,13:16,:) = radar_data_Rxchain_slave3;
end
function [adcData1Complex] = readBinFile(fileFullPath, frameIdx,numSamplePerChirp,numChirpPerLoop,numLoops, numRXPerDevice, numDevices)
Expected_Num_SamplesPerFrame = numSamplePerChirp*numChirpPerLoop*numLoops*numRXPerDevice*2;
fp = fopen(fileFullPath, 'r');
fseek(fp,(frameIdx-1)*Expected_Num_SamplesPerFrame*2, 'bof');
adcData1 = fread(fp,Expected_Num_SamplesPerFrame,'uint16');
neg = logical(bitget(adcData1, 16));
adcData1(neg) = adcData1(neg) - 2^16;
%%
adcData1 = adcData1(1:2:end) + sqrt(-1)*adcData1(2:2:end);
adcData1Complex = reshape(adcData1, numRXPerDevice, numSamplePerChirp, numChirpPerLoop, numLoops);
adcData1Complex = permute(adcData1Complex, [2 4 1 3]);
fclose(fp);
end
readBinFile函数代码解释如下:
最终得到的矩阵是:230 64 16 12
radar_data_Rxchain(:,:,1:4,:) = radar_data_Rxchain_master;
radar_data_Rxchain(:,:,5:8,:) = radar_data_Rxchain_slave1;
radar_data_Rxchain(:,:,9:12,:) = radar_data_Rxchain_slave2;
radar_data_Rxchain(:,:,13:16,:) = radar_data_Rxchain_slave3;
毫米波雷达数据的频率校准和相位校准主要有几种常用方法:
freq_calib = (RangeMat(TXind,:)-RangeMat(TX_ref,1))*fs_calib/Sampling_Rate_sps *chirpSlope/Slope_calib;
freq_calib = 2*pi*(freq_calib)/(numSamplePerChirp * calibrationInterp);
correction_vec = (exp(1i*((0:numSamplePerChirp-1)'*freq_calib))');
freq_correction_mat = repmat(correction_vec, 1, 1, nchirp_loops);
freq_correction_mat = permute(freq_correction_mat, [2 3 1]);
outData1TX = radar_data_Rxchain(:,:,:,iTX).*freq_correction_mat;
校准矩阵可以参考AWRx_TX_Channel_Calibration_Script_User_Guide.pdf
相位校准和频率校准类似,都是选第一个通道作为参考值,公式为:
C p h − a m = C r e f / C t x i / r x j C p h_{-}a m=C_{r e f}/C t x\,i/r\,x j Cph−am=Cref/Ctxi/rxj
Crer是参考通道的复数值,Ctxi,是 其他通道的复数值; Cph_am 是计算的相位和振幅校准值。Cph_am对参考
通道为1,对其他各通道不同。Cph am与频率校准的输出相乘,结果是最终校准的ADC数据。
%construct the phase compensation matrix
phase_calib = PeakValMat(TX_ref,1)./PeakValMat(TXind,:);
phase_correction_mat = repmat(phase_calib.', 1,numSamplePerChirp, nchirp_loops);
phase_correction_mat = permute(phase_correction_mat, [2 3 1]);
outData(:,:,:,iTX) = outData1TX.*phase_correction_mat;
:::info
MIMO模式的虚拟通道:在毫米波雷达的MIMO模式下,虚拟通道数等于发射天线数乘以接收天线数的原因是多个发射天线和多个接收天线之间可以组成多个独立的通道,每个通道可以看作是一个虚拟通道。
在MIMO雷达中,通过使用多个发射天线和多个接收天线可以获得多个接收信号,每个接收信号对应一个虚拟通道。对于每个虚拟通道,都可以通过信号处理算法获得相应的距离、角度和速度信息。因此,在MIMO雷达中,虚拟通道数等于发射天线数乘以接收天线数。
举例来说,如果一个MIMO雷达有12个发射天线和16个接收天线,那么可以组成192个虚拟通道。每个虚拟通道可以得到自己的距离、角度和速度信息,可以提高雷达测量的精度和鲁棒性。
:::
:::success
TXBF模式的虚拟通道:在TXBF(Transmit Beamforming)模式下,虚拟通道数等于每个雷达的接收天线数乘以扫描角度数的原因是,每个接收天线可以接收到来自不同方向的信号,并且每个方向都可以看作是一个独立的通道,每个通道可以看作是一个虚拟通道。
在TXBF模式下,雷达发送的信号经过波束成形后,只会在某个方向上产生较大的功率,而在其他方向上的功率较小。因此,每个接收天线可以接收来自不同方向的信号,每个方向都相当于一个独立的通道,可以看作是一个虚拟通道。而每个接收天线可以接收多个方向的信号,因此每个接收天线可以产生多个虚拟通道。
此外,由于TXBF模式下雷达需要扫描不同的方向来获得目标的位置信息,因此每个方向都需要进行一次扫描,每次扫描可以获得一个虚拟通道的信息。因此,虚拟通道数等于每个雷达的接收天线数乘以扫描角度数。
举例来说,如果一个雷达有4个接收天线,扫描角度数为90度,那么可以产生360个虚拟通道。其中,每个接收天线可以产生90个虚拟通道,每个扫描角度可以产生4个虚拟通道。每个虚拟通道都可以得到自己的距离、角度和速度信息,可以提高雷达测量的精度和鲁棒性。
:::
对每个虚拟通道进行距离FFT和多普勒FFT,12T16R的配置总共有12x16=192个虚拟通道,在代码中并不是直接循环192次,而是先循环发射天线数(12),再循环接收天线数(16),代码如下:
%perform 2D FFT
rangeFFTOut = [];
DopplerFFTOut = [];
%size(adcData) = 230 64 16 12
for i_tx = 1: size(adcData,4) %12
% range FFT
rangeFFTOut(:,:,:,i_tx) = datapath(rangeFFTObj, adcData(:,:,:,i_tx));
% Doppler FFT
DopplerFFTOut(:,:,:,i_tx) = datapath(DopplerFFTObj, rangeFFTOut(:,:,:,i_tx));
end
其中两个datapath是不同模块的同名函数,根据传入的Obj来区分调用,下面依次解释这两个datapath。
%% datapath function
% input: adc data, assuming size(input) = [numSamplePerChirp, numChirpsPerFrame numAntenna]
function [out] = datapath(obj, input)
numLines = size(input,2);
numAnt = size(input,3);
% dopplerWinLen = length(obj.dopplerWindowCoeff);
if obj.enable
% initialize
out = zeros(obj.rangeFFTSize, numLines, numAnt);
for i_an = 1:numAnt
% vectorized version
inputMat = squeeze(input(:,:,i_an));
% DC offset compensation
%用于在不使用循环的情况下,对矩阵进行操作,这里用于减去输入矩阵的平均值,即DC偏移补偿。
inputMat = bsxfun(@minus, inputMat, mean(inputMat));
% apply range-domain windowing
inputMat = bsxfun(@times, inputMat, obj.rangeWindowCoeffVec);
% Range FFT
fftOutput = fft(inputMat, obj.rangeFFTSize);
% apply Doppler windowing and scaling to the output. Doppler windowing moved to DopplerProc.m (5/24)
% fftOutput = bsxfun(@times, fftOutput, obj.scaleFactorRange*obj.dopplerWindowCoeffVec.');
if obj.FFTOutScaleOn == 1
fftOutput = fftOutput * obj.scaleFactorRange;
end
% populate in the data cube
out(:,:,i_an) = fftOutput;
end
else
out = input;
end
end
:::success
代码解释:
% datapath function
% input: adc data, assuming size(input) = [numSamplePerChipr numChirpsPerFrame numAntenna]
function [out] = datapath(obj, input)
numLines = size(input,1);
numAnt = size(input,3);
if obj.enable
% initialize
out = zeros(numLines, obj.dopplerFFTSize, numAnt);
for i_an = 1:numAnt
%% vectorized version
inputMat = squeeze(input(:,:,i_an));
inputMat = bsxfun(@times, inputMat, obj.dopplerWindowCoeffVec.');
if obj.clutterRemove ==1
inputMat = inputMat - (repmat(mean(inputMat'),size(inputMat,2),1))';
end
fftOutput = fft(inputMat, obj.dopplerFFTSize, 2);
if obj.FFTOutScaleOn ==1
fftOutput = fftshift(fftOutput, 2) * obj.scaleFactorDoppler;
else
fftOutput = fftshift(fftOutput, 2);
end
% populate in the data cube
out(:,:,i_an) = fftOutput;
end
else
out = input;
end
end
代码解释:
- (a) 从输入数据中提取第i_an个天线的数据,squeez函数是将第三维去掉,剩下前面两维数据。(因为第三维是循环维度,每次都是1)
- 将输入矩阵inputMat中的每个元素与Doppler窗函数系数向量中对应位置的元素进行乘法运算,得到一个同样大小的结果矩阵inputMat。
- 如果开启了杂波消除功能(obj.clutterRemove==1),则从输入数据中减去每个脉冲的平均值,以消除杂波的影响。
- 对第i_an个天线的数据进行Doppler FFT变换,变换大小为obj.dopplerFFTSize。如果开启了FFT输出缩放功能(obj.FFTOutScaleOn==1),则将FFT输出乘以一个缩放因子obj.scaleFactorDoppler。
- fftshift函数对fftOutput矩阵进行移动,以将频域数据的零频率分量移到中心位置。
- 将Doppler FFT输出存储到输出矩阵out中的第i_an个通道。
- **需要注意的是,多普勒FFT的输入数据是距离FFT的结果,下面这行代码是表示对距离FFT结果的第二维(numloops dopplerBins)进行FFT运算:fftOutput = fft(inputMat, obj.dopplerFFTSize, 2); **
经过距离、多普勒FFT之后,需要将接收天线和发射天线维度合并为虚拟通道维度
DopplerFFTOut = reshape(DopplerFFTOut,size(DopplerFFTOut,1), size(DopplerFFTOut,2), size(DopplerFFTOut,3)*size(DopplerFFTOut,4));
经过上述操作得到了多普勒FFT数据,维度为:numSamplePerChirp256 x nchirp_loops 64 x 虚拟通道数 192
:::success
在多普勒FFT之后,得到的是各个通道的多普勒频移信息。为了提高目标检测的信噪比,通常需要将多个通道的数据进行非相干累加。相比于单个通道的数据,非相干累加可以减少随机噪声的影响,从而提高目标信号的强度和可靠性。
:::
% CFAR done along only TX and RX used in MIMO array
DopplerFFTOut = reshape(DopplerFFTOut,size(DopplerFFTOut,1), size(DopplerFFTOut,2), size(DopplerFFTOut,3)*size(DopplerFFTOut,4));
%detection
sig_integrate = 10*log10(sum((abs(DopplerFFTOut)).^2,3) + 1);
这行代码是在对一组多普勒测量数据进行快速傅里叶变换(FFT)后计算信号功率的分贝(dB)值。
具体来说:
经过上面的非相干累加,将sig_integrate绘图分析:
第一个图表示的是距离-强度图,图中最高的一条曲线表示零多普勒维度。第二个图是距离-多普勒-强度图,图中突出的一排表示的是零多普勒,用于CFAR检测的数据就是距离-多普勒-强度图的数据,实现目标识别。
算法文件路径:C:\ti\mmwave_studio_03_00_00_14\mmWaveStudio\MatlabExamples\4chip_cascade_MIMO_example\modules\detection@CFAR_CASO\datapath.m
这个模块实现了CA-CFAR算法,但是噪声被估计为较小的平均噪声水平由左右两个噪音分档估计。检测分两步完成。
该算法的基本思想是,对于每一个采样点,以其为中心,选择一定数量的左右邻域采样点,计算邻域内的平均值作为噪声水平,然后将该点的信号值与噪声水平比较,若超过一定阈值,则判断为目标。其中,K0值用于调节检测的灵敏度和虚警率。
function [N_obj, Ind_obj, noise_obj, CFAR_SNR] = CFAR_CASO_Range(obj, sig)
cellNum = obj.refWinSize; %单元窗口
gapNum = obj.guardWinSize; %保护窗口
cellNum = cellNum(1);
gapNum = gapNum(1);
K0 = obj.K0(1);
M_samp=size(sig, 1);
N_pul=size(sig, 2);
%% input: assuming size(input) = [numSamplePerChipr numChirpsPerFrame numAntenna]
%for each point under test, gapNum samples on the two sides are excluded
%from averaging. Left cellNum/2 and right cellNum/2 samples are used for
%averaging
gaptot=gapNum + cellNum;
N_obj=0;
Ind_obj=[];
noise_obj = [];
CFAR_SNR = [];
discardCellLeft = obj.discardCellLeft;
discardCellRight = obj.discardCellRight;
%for the first gaptot samples only use the right sample
for k=1:N_pul
sigv=(sig(:,k))';
vec = sigv(discardCellLeft+1:M_samp-discardCellRight);
vecLeft = vec(1:(gaptot));
vecRight = vec(end-(gaptot)+1:end);
vec = [vecLeft vec vecRight];
for j=1:(M_samp-discardCellLeft-discardCellRight)
cellInd=[j-gaptot: j-gapNum-1 j+gapNum+1:j+gaptot];
cellInd=cellInd + gaptot;
cellInda=[j-gaptot: j-gapNum-1];
cellInda=cellInda + gaptot;
cellIndb=[ j+gapNum+1:j+gaptot];
cellIndb=cellIndb + gaptot;
cellave1a =sum(vec(cellInda))/(cellNum);
cellave1b =sum(vec(cellIndb))/(cellNum);
cellave1 = min(cellave1a,cellave1b);
%if((j > discardCellLeft) && (j < (M_samp-discardCellRight)))
if obj.maxEnable==1 %check if it is local maximum peak
maxInCell = max(vec([cellInd(1):cellInd(end)]));
if (vec(j+gaptot)>K0*cellave1 && ( vec(j+gaptot)>=maxInCell))
N_obj=N_obj+1;
Ind_obj(N_obj,:)=[j+discardCellLeft, k];
noise_obj(N_obj) = cellave1; %save the noise level
CFAR_SNR(N_obj) = vec(j+gaptot)/cellave1;
end
else
if vec(j+gaptot)>K0*cellave1
N_obj=N_obj+1;
Ind_obj(N_obj,:)=[j+discardCellLeft, k];
noise_obj(N_obj) = cellave1; %save the noise level
CFAR_SNR(N_obj) = vec(j+gaptot)/cellave1;
end
end
end
end
%get the noise variance for each antenna
for i_obj = 1:N_obj
ind_range = Ind_obj(i_obj,1);
ind_Dop = Ind_obj(i_obj,2);
if ind_range<= gaptot
%on the left boundary, use the right side samples twice
cellInd=[ind_range+gapNum+1:ind_range+gaptot ind_range+gapNum+1:ind_range+gaptot];
elseif ind_range>=M_samp-gaptot+1
%on the right boundary, use the left side samples twice
cellInd=[ind_range-gaptot: ind_range-gapNum-1 ind_range-gaptot: ind_range-gapNum-1];
else
cellInd=[ind_range-gaptot: ind_range-gapNum-1 ind_range+gapNum+1:ind_range+gaptot];
end
end
N_obj表示检测到的目标数量,Ind_obj是一个二维矩阵,每行表示一个目标所在的行列索引,noise_obj表示检测到的目标处的噪声水平,CFAR_SNR表示检测到的目标处的信噪比。
CFAR-CASO算法流程图如下:
:::success
- 参考单元:参考单元的作用是用于计算背景噪声的统计信息,通常使用参考单元中的噪声水平来作为阈值,以便将噪声和目标信号区分开来。CFAR-CASO算法流程图中用于计算平均值的部分
代码流程如下:
sigv=(sig(:,k))';
获取第一个多普勒维度的 256个采样点
vecLeft = vec(1:(gaptot));
vecRight = vec(end-(gaptot)+1:end);
vec = [vecLeft vec vecRight];
得到一个包含参考窗口、保护窗口和左右两侧扩展的邻域点的向量,为了解决CFAR算法中参考窗口和保护窗口边缘处数据不足的问题
cellInd=[j-gaptot: j-gapNum-1 j+gapNum+1:j+gaptot];
对于当前采样点j,以其为中心,左右各排除gapNum个邻域点,得到保护窗口;然后以保护窗口为基础,向左右各扩展gaptot-gapNum个邻域点,得到单元窗口。
cellInd=cellInd + gaptot;
cellInda=[j-gaptot: j-gapNum-1];
cellInda=cellInda + gaptot; %[1,2,3,4,5,6,7,8]
cellIndb=[ j+gapNum+1:j+gaptot];
cellIndb=cellIndb + gaptot; %[26,27,28,29,30,31,32,33]
cellave1a =sum(vec(cellInda))/(cellNum);
cellave1b =sum(vec(cellIndb))/(cellNum);
cellave1 = min(cellave1a,cellave1b);
获取两边保护单元的数据索引,保护单元大小为8,并计算对应的平均值,最后取较小的平均值。
判决过程
if vec(j+gaptot)>K0*cellave1
N_obj=N_obj+1;
Ind_obj(N_obj,:)=[j+discardCellLeft, k];
noise_obj(N_obj) = cellave1; %save the noise level
CFAR_SNR(N_obj) = vec(j+gaptot)/cellave1;
end
这里使用了vec(j+gaptot)表示第一个距离门的采样数据是因为在前面两边各加了16个数据,防止边界检测的时候数据不足的情况。当这个数据大于阈值x保护单元的最小平均值时判定为目标,则保存数据索引、噪声、信噪比。
AWRx_TX_Channel_Calibration_Script_User_Guide.pdf
signal_processing_4chip_cascade.pdf
signal_processing_4chip_cascade zh.pdf