Compare commits
8
Commits
a3615caf08
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39db62a3eb | ||
|
|
3c05d2be09 | ||
|
|
e002d08580 | ||
|
|
c4a98fc313 | ||
|
|
f09e267fc2 | ||
|
|
a70aa68053 | ||
|
|
9595d68526 | ||
|
|
855c3d1d55 |
@@ -22,8 +22,10 @@ function [adc_raw_data, adc_combined] = apply_lna_and_mixer(RxOut, TxOut, RadarP
|
|||||||
phase_noise_cfg = struct();
|
phase_noise_cfg = struct();
|
||||||
if isfield(RadarParams.Waveform.nonideal, 'phaseNoise')
|
if isfield(RadarParams.Waveform.nonideal, 'phaseNoise')
|
||||||
phase_noise_cfg = RadarParams.Waveform.nonideal.phaseNoise;
|
phase_noise_cfg = RadarParams.Waveform.nonideal.phaseNoise;
|
||||||
if isfield(phase_noise_cfg,'enabled') && phase_noise_cfg.enabled && ...
|
has_offset = isfield(phase_noise_cfg,'offset_Hz') && ~isempty(phase_noise_cfg.offset_Hz);
|
||||||
isfield(phase_noise_cfg,'offset_Hz') && isfield(phase_noise_cfg,'level_dBc_Hz')
|
has_level = isfield(phase_noise_cfg,'level_dBc_Hz') && ~isempty(phase_noise_cfg.level_dBc_Hz);
|
||||||
|
same_length = has_offset && has_level && (numel(phase_noise_cfg.offset_Hz) == numel(phase_noise_cfg.level_dBc_Hz));
|
||||||
|
if same_length
|
||||||
use_datasheet_phase_noise = true;
|
use_datasheet_phase_noise = true;
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -31,6 +33,7 @@ function [adc_raw_data, adc_combined] = apply_lna_and_mixer(RxOut, TxOut, RadarP
|
|||||||
NumTx = RadarParams.Antenna.NumTx;
|
NumTx = RadarParams.Antenna.NumTx;
|
||||||
rxPathGain_dB = RadarParams.Rxpath.rxPathGain_dB;
|
rxPathGain_dB = RadarParams.Rxpath.rxPathGain_dB;
|
||||||
system_NF_dB = RadarParams.Rxpath.system_NF_dB;
|
system_NF_dB = RadarParams.Rxpath.system_NF_dB;
|
||||||
|
impedance_ohm = 50; % 임피던스 기준 (일반적으로 50옴)
|
||||||
PA_Profile = RadarParams.RFOutput.PA_Profile;
|
PA_Profile = RadarParams.RFOutput.PA_Profile;
|
||||||
SpurParams = RadarParams.SpurParams;
|
SpurParams = RadarParams.SpurParams;
|
||||||
enable_spur = true;
|
enable_spur = true;
|
||||||
@@ -57,8 +60,8 @@ function [adc_raw_data, adc_combined] = apply_lna_and_mixer(RxOut, TxOut, RadarP
|
|||||||
P_inst_dBm = interp1(PA_Profile(:,1), PA_Profile(:,2), f_inst, 'linear', 'extrap');
|
P_inst_dBm = interp1(PA_Profile(:,1), PA_Profile(:,2), f_inst, 'linear', 'extrap');
|
||||||
end
|
end
|
||||||
|
|
||||||
% dBm -> Watt -> 전압 진폭(V) 변환 (50옴 시스템 기준)
|
% dBm -> Watt -> 전압 진폭(V) 변환 (임피던스 기준)
|
||||||
Vtx_inst = sqrt(10.^((P_inst_dBm - 30) / 10) * 50);
|
Vtx_inst = sqrt(10.^((P_inst_dBm - 30) / 10) * impedance_ohm);
|
||||||
|
|
||||||
% --- 2. 수신단 시스템 잡음 계산 ---
|
% --- 2. 수신단 시스템 잡음 계산 ---
|
||||||
T_ref = RadarParams.Basic.T0;
|
T_ref = RadarParams.Basic.T0;
|
||||||
@@ -69,7 +72,7 @@ function [adc_raw_data, adc_combined] = apply_lna_and_mixer(RxOut, TxOut, RadarP
|
|||||||
|
|
||||||
% ADC 입력단 기준 최종 잡음 전력 및 전압 표준편차
|
% ADC 입력단 기준 최종 잡음 전력 및 전압 표준편차
|
||||||
P_noise_total_W = P_noise_floor_W * system_NF_lin * rxPathGain_lin;
|
P_noise_total_W = P_noise_floor_W * system_NF_lin * rxPathGain_lin;
|
||||||
sigma_n = sqrt(P_noise_total_W * 50);
|
sigma_n = sqrt(P_noise_total_W * impedance_ohm);
|
||||||
|
|
||||||
% --- MIMO 모드에 따른 TX 활성화 패턴 계산 ---
|
% --- MIMO 모드에 따른 TX 활성화 패턴 계산 ---
|
||||||
% TDM: 처프마다 다른 TX만 활성화
|
% TDM: 처프마다 다른 TX만 활성화
|
||||||
|
|||||||
@@ -0,0 +1,118 @@
|
|||||||
|
function [windowVector, metrics] = create_window_with_metrics(windowType, windowLength, varargin)
|
||||||
|
% CREATE_WINDOW_WITH_METRICS - 윈도우 함수 생성 및 성능 지표 계산
|
||||||
|
%
|
||||||
|
% 입력:
|
||||||
|
% windowType - 윈도우 타입 문자열: 'none', 'hann', 'hamming', 'blackman', 'chebwin'
|
||||||
|
% windowLength - 윈도우 길이 (샘플 수)
|
||||||
|
% varargin - 추가 파라미터 (예: chebwin의 경우 sidelobe level)
|
||||||
|
%
|
||||||
|
% 출력:
|
||||||
|
% windowVector - 생성된 윈도우 벡터 (1 x windowLength)
|
||||||
|
% metrics - 윈도우 성능 지표 구조체
|
||||||
|
% .type : 윈도우 타입
|
||||||
|
% .length : 윈도우 길이
|
||||||
|
% .snr_loss_dB : SNR 손실 (dB)
|
||||||
|
% .scalloping_loss_dB: Scalloping 손실 (dB)
|
||||||
|
% .coherent_gain : 코히어런트 이득
|
||||||
|
% .enbw : Equivalent Noise Bandwidth (bins)
|
||||||
|
|
||||||
|
% 기본 파라미터 설정
|
||||||
|
if windowLength <= 0
|
||||||
|
error('Window length must be positive.');
|
||||||
|
end
|
||||||
|
|
||||||
|
% 윈도우 함수 생성
|
||||||
|
switch lower(windowType)
|
||||||
|
case 'none'
|
||||||
|
windowVector = ones(1, windowLength);
|
||||||
|
|
||||||
|
case 'hann'
|
||||||
|
windowVector = hann(windowLength)';
|
||||||
|
|
||||||
|
case 'hamming'
|
||||||
|
windowVector = hamming(windowLength)';
|
||||||
|
|
||||||
|
case 'blackman'
|
||||||
|
windowVector = blackman(windowLength)';
|
||||||
|
|
||||||
|
case 'chebwin'
|
||||||
|
% Chebyshev 윈도우는 sidelobe level 파라미터 필요 (기본값: 60dB)
|
||||||
|
if ~isempty(varargin)
|
||||||
|
sidelobe_dB = varargin{1};
|
||||||
|
else
|
||||||
|
sidelobe_dB = 60;
|
||||||
|
end
|
||||||
|
windowVector = chebwin(windowLength, sidelobe_dB)';
|
||||||
|
|
||||||
|
otherwise
|
||||||
|
warning('Unknown window type "%s". Using Hann window as default.', windowType);
|
||||||
|
windowVector = hann(windowLength)';
|
||||||
|
windowType = 'hann';
|
||||||
|
end
|
||||||
|
|
||||||
|
% 윈도우 성능 지표 계산
|
||||||
|
metrics = calculate_window_metrics(windowVector, windowType);
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function metrics = calculate_window_metrics(windowVector, windowType)
|
||||||
|
% CALCULATE_WINDOW_METRICS - 윈도우 함수의 성능 지표 계산
|
||||||
|
%
|
||||||
|
% 계산 항목:
|
||||||
|
% 1. SNR Loss (dB) : 윈도우 적용으로 인한 SNR 손실
|
||||||
|
% 2. Scalloping Loss (dB): FFT bin 사이(0.5 bin offset)에서의 최대 손실
|
||||||
|
% 3. Coherent Gain : 윈도우의 평균 진폭
|
||||||
|
% 4. ENBW (bins) : Equivalent Noise Bandwidth
|
||||||
|
|
||||||
|
w = windowVector(:).'; % Row vector로 변환
|
||||||
|
N = numel(w);
|
||||||
|
|
||||||
|
if N == 0
|
||||||
|
metrics = struct('type', windowType, 'length', 0, ...
|
||||||
|
'snr_loss_dB', NaN, 'scalloping_loss_dB', NaN, ...
|
||||||
|
'coherent_gain', NaN, 'enbw', NaN);
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
|
% 1. Coherent Gain (코히어런트 이득)
|
||||||
|
coherent_gain = mean(w);
|
||||||
|
|
||||||
|
% 2. Noise Power Gain (잡음 전력 이득)
|
||||||
|
noise_power_gain = mean(abs(w).^2);
|
||||||
|
|
||||||
|
% 3. SNR Loss (dB)
|
||||||
|
% SNR_loss = (Noise Power Gain) / (Coherent Gain)^2
|
||||||
|
% 이는 윈도우 적용 시 신호 대 잡음비가 얼마나 감소하는지를 나타냄
|
||||||
|
if abs(coherent_gain) > eps
|
||||||
|
snr_loss_linear = noise_power_gain / (abs(coherent_gain)^2);
|
||||||
|
snr_loss_dB = 10 * log10(snr_loss_linear);
|
||||||
|
else
|
||||||
|
snr_loss_dB = NaN;
|
||||||
|
end
|
||||||
|
|
||||||
|
% 4. Equivalent Noise Bandwidth (ENBW)
|
||||||
|
% ENBW는 윈도우가 얼마나 많은 주파수 bin의 잡음을 통과시키는지 나타냄
|
||||||
|
enbw = N * noise_power_gain / (sum(w)^2);
|
||||||
|
|
||||||
|
% 5. Scalloping Loss (dB)
|
||||||
|
% FFT bin 중간(0.5 bin offset)에 신호가 위치할 때의 최대 손실
|
||||||
|
% 이는 가장 나쁜 경우의 신호 손실을 나타냄
|
||||||
|
sample_index = 0:(N-1);
|
||||||
|
half_bin_response = abs(sum(w .* exp(-1j * 2 * pi * 0.5 * sample_index / N)));
|
||||||
|
dc_response = abs(sum(w));
|
||||||
|
|
||||||
|
if dc_response > eps
|
||||||
|
scalloping_loss_dB = -20 * log10(half_bin_response / dc_response);
|
||||||
|
else
|
||||||
|
scalloping_loss_dB = NaN;
|
||||||
|
end
|
||||||
|
|
||||||
|
% 결과 구조체 생성
|
||||||
|
metrics = struct(...
|
||||||
|
'type', lower(windowType), ...
|
||||||
|
'length', N, ...
|
||||||
|
'snr_loss_dB', snr_loss_dB, ...
|
||||||
|
'scalloping_loss_dB', scalloping_loss_dB, ...
|
||||||
|
'coherent_gain', coherent_gain, ...
|
||||||
|
'enbw', enbw);
|
||||||
|
end
|
||||||
@@ -3,7 +3,6 @@ function [rd_map, doppler_axis] = process_doppler_fft(range_profile, RadarParams
|
|||||||
% - range_profile: [NumRx, NumTx, NumChirps, NumRangeBins]
|
% - range_profile: [NumRx, NumTx, NumChirps, NumRangeBins]
|
||||||
% - RadarParams: 메인 파라미터 구조체
|
% - RadarParams: 메인 파라미터 구조체
|
||||||
NumChirps = RadarParams.Waveform.NumChirps;
|
NumChirps = RadarParams.Waveform.NumChirps;
|
||||||
window_type = RadarParams.SP.RDM.window_type_doppler;
|
|
||||||
|
|
||||||
[~, ~, ~, ~] = size(range_profile);
|
[~, ~, ~, ~] = size(range_profile);
|
||||||
% 중심 주파수에서의 파장
|
% 중심 주파수에서의 파장
|
||||||
@@ -12,18 +11,10 @@ function [rd_map, doppler_axis] = process_doppler_fft(range_profile, RadarParams
|
|||||||
% 1. 처프 간 반복 주기 (PRI, Pulse Repetition Interval)
|
% 1. 처프 간 반복 주기 (PRI, Pulse Repetition Interval)
|
||||||
T_pri = RadarParams.Waveform.PRI;
|
T_pri = RadarParams.Waveform.PRI;
|
||||||
|
|
||||||
% 2. Doppler-FFT용 윈도우 함수 (사용자 선택 가능)
|
% 2. Main.m에서 생성된 Doppler-FFT용 윈도우 함수 적용
|
||||||
% 도플러 방향(3번째 차원)으로 사이드로브를 억제합니다.
|
win_doppler = RadarParams.SP.RDM.window_doppler;
|
||||||
if strcmpi(window_type, 'none')
|
if length(win_doppler) ~= NumChirps
|
||||||
win_doppler = ones(1, NumChirps);
|
error('Doppler window length mismatch: expected %d, got %d', NumChirps, length(win_doppler));
|
||||||
elseif strcmpi(window_type, 'hamming')
|
|
||||||
win_doppler = hamming(NumChirps)';
|
|
||||||
elseif strcmpi(window_type, 'blackman')
|
|
||||||
win_doppler = blackman(NumChirps)';
|
|
||||||
elseif strcmpi(window_type, 'hann')
|
|
||||||
win_doppler = hann(NumChirps)';
|
|
||||||
else % default: 'chebwin'
|
|
||||||
win_doppler = chebwin(NumChirps, 60)'; % 60dB 사이드로브 억제
|
|
||||||
end
|
end
|
||||||
win_data = range_profile .* reshape(win_doppler, [1, 1, NumChirps, 1]);
|
win_data = range_profile .* reshape(win_doppler, [1, 1, NumChirps, 1]);
|
||||||
|
|
||||||
|
|||||||
@@ -5,22 +5,14 @@ function [range_profile, range_axis] = process_range_fft_lpf(adc_raw_data, Radar
|
|||||||
fs_adc = RadarParams.Waveform.fs_adc;
|
fs_adc = RadarParams.Waveform.fs_adc;
|
||||||
Slope = RadarParams.Waveform.Slope;
|
Slope = RadarParams.Waveform.Slope;
|
||||||
fc_lpf_Hz = RadarParams.Rxpath.fc_lpf;
|
fc_lpf_Hz = RadarParams.Rxpath.fc_lpf;
|
||||||
window_type = RadarParams.SP.RDM.window_type_range;
|
|
||||||
|
|
||||||
[~, ~, ~, N_samples] = size(adc_raw_data);
|
[~, ~, ~, N_samples] = size(adc_raw_data);
|
||||||
c = RadarParams.Basic.c;
|
c = RadarParams.Basic.c;
|
||||||
|
|
||||||
% 1. 윈도우 함수 적용
|
% 1. Main.m에서 생성된 윈도우 함수 적용
|
||||||
if strcmpi(window_type, 'none')
|
win = RadarParams.SP.RDM.window_range;
|
||||||
win = ones(1, N_samples);
|
if length(win) ~= N_samples
|
||||||
elseif strcmpi(window_type, 'hamming')
|
error('Window length mismatch: expected %d, got %d', N_samples, length(win));
|
||||||
win = hamming(N_samples)';
|
|
||||||
elseif strcmpi(window_type, 'blackman')
|
|
||||||
win = blackman(N_samples)';
|
|
||||||
elseif strcmpi(window_type, 'chebwin')
|
|
||||||
win = chebwin(N_samples, 60)';
|
|
||||||
else % default: 'hann'
|
|
||||||
win = hann(N_samples)';
|
|
||||||
end
|
end
|
||||||
win_data = adc_raw_data .* reshape(win, [1, 1, 1, N_samples]);
|
win_data = adc_raw_data .* reshape(win, [1, 1, 1, N_samples]);
|
||||||
|
|
||||||
|
|||||||
@@ -13,13 +13,34 @@ function fig = visualize_rd_map_with_spurs(target_rd_map, r_axis, v_axis, Target
|
|||||||
% 출력:
|
% 출력:
|
||||||
% - fig: 생성된 figure 핸들
|
% - fig: 생성된 figure 핸들
|
||||||
|
|
||||||
|
if ~isfield(RadarParams, 'SP') || ~isfield(RadarParams.SP, 'RDM') || ...
|
||||||
|
~isfield(RadarParams.SP.RDM, 'window_range') || ~isfield(RadarParams.SP.RDM, 'window_doppler')
|
||||||
|
error('RadarParams.SP.RDM.window_range / window_doppler 정보가 필요합니다.');
|
||||||
|
end
|
||||||
|
|
||||||
|
win_r = RadarParams.SP.RDM.window_range;
|
||||||
|
win_d = RadarParams.SP.RDM.window_doppler;
|
||||||
|
|
||||||
|
normal_factor = RadarParams.Rxpath.fc_lpf * RadarParams.Waveform.PRF * sum(win_r.^2) * sum(win_d.^2);
|
||||||
|
|
||||||
|
if ~isfield(RadarParams, 'Rxpath') || ~isfield(RadarParams.Rxpath, 'load_impedance_ohm')
|
||||||
|
error('RadarParams.Rxpath.load_impedance_ohm 값이 필요합니다.');
|
||||||
|
end
|
||||||
|
load_impedance_ohm = RadarParams.Rxpath.load_impedance_ohm;
|
||||||
|
|
||||||
|
psd2d_w_hz2 = (abs(target_rd_map).^2) / (load_impedance_ohm * max(normal_factor, eps));
|
||||||
|
rbw_range_hz = RadarParams.Waveform.fs_adc * sum(win_r.^2) / max(eps, (sum(win_r)^2));
|
||||||
|
rbw_doppler_hz = RadarParams.Waveform.PRF * sum(win_d.^2) / max(eps, (sum(win_d)^2));
|
||||||
|
rd_map_dbm_bin = 10*log10(max(psd2d_w_hz2, realmin) / 1e-3) + 10*log10(max(rbw_range_hz * rbw_doppler_hz, eps));
|
||||||
|
|
||||||
fig = figure('Name', '2D Range-Doppler Map');
|
fig = figure('Name', '2D Range-Doppler Map');
|
||||||
imagesc(r_axis, v_axis, 20*log10(abs(target_rd_map)));
|
imagesc(r_axis, v_axis, rd_map_dbm_bin);
|
||||||
axis xy; % y축 방향(속도)을 위로 정렬
|
axis xy; % y축 방향(속도)을 위로 정렬
|
||||||
colorbar;
|
cb = colorbar;
|
||||||
|
cb.Label.String = 'Power (dBm/bin)';
|
||||||
xlabel('Range (m)');
|
xlabel('Range (m)');
|
||||||
ylabel('Velocity (m/s)');
|
ylabel('Velocity (m/s)');
|
||||||
title('Range-Doppler Map (Single Channel)');
|
title('Range-Doppler Map (dBm/bin)');
|
||||||
colormap(jet);
|
colormap(jet);
|
||||||
|
|
||||||
% 범례용 핸들 및 레이블 수집
|
% 범례용 핸들 및 레이블 수집
|
||||||
@@ -61,7 +82,6 @@ function fig = visualize_rd_map_with_spurs(target_rd_map, r_axis, v_axis, Target
|
|||||||
ni = RadarParams.Waveform.nonideal;
|
ni = RadarParams.Waveform.nonideal;
|
||||||
enable_phase_noise = false;
|
enable_phase_noise = false;
|
||||||
enable_nonlinearity = false;
|
enable_nonlinearity = false;
|
||||||
enable_datasheet_phase_noise = false;
|
|
||||||
|
|
||||||
if isfield(ni,'enable_phase_noise')
|
if isfield(ni,'enable_phase_noise')
|
||||||
enable_phase_noise = logical(ni.enable_phase_noise);
|
enable_phase_noise = logical(ni.enable_phase_noise);
|
||||||
@@ -69,12 +89,8 @@ function fig = visualize_rd_map_with_spurs(target_rd_map, r_axis, v_axis, Target
|
|||||||
if isfield(ni,'enable_nonlinearity')
|
if isfield(ni,'enable_nonlinearity')
|
||||||
enable_nonlinearity = logical(ni.enable_nonlinearity);
|
enable_nonlinearity = logical(ni.enable_nonlinearity);
|
||||||
end
|
end
|
||||||
if isfield(ni,'phaseNoise') && isstruct(ni.phaseNoise) && isfield(ni.phaseNoise,'enabled')
|
|
||||||
enable_datasheet_phase_noise = logical(ni.phaseNoise.enabled);
|
|
||||||
end
|
|
||||||
|
|
||||||
show_nonideal_overlay = show_nonideal_overlay && ...
|
show_nonideal_overlay = show_nonideal_overlay && ...
|
||||||
(enable_phase_noise || enable_nonlinearity || enable_datasheet_phase_noise);
|
(enable_phase_noise || enable_nonlinearity);
|
||||||
end
|
end
|
||||||
|
|
||||||
% --- 2. LO/ADC 톤 스퍼 표시 ---
|
% --- 2. LO/ADC 톤 스퍼 표시 ---
|
||||||
|
|||||||
@@ -0,0 +1,379 @@
|
|||||||
|
function [snr_coverage_2d, azimuth_deg, elevation_deg, coverage_info, fig] = analyze_coverage(RadarParams, target_range_m, target_rcs_dBsm)
|
||||||
|
% Coverage 분석: 2D 안테나 패턴 기반 각도별 SNR 계산
|
||||||
|
%
|
||||||
|
% 방법:
|
||||||
|
% 1) Azimuth 0도, Elevation 0도에서 기준 SNR 계산
|
||||||
|
% 2) 다른 각도의 상대 이득 차이로 SNR 계산 (효율적)
|
||||||
|
% 3) 입력한 RCS 값 반영
|
||||||
|
%
|
||||||
|
% 입력:
|
||||||
|
% - RadarParams: 레이더 파라미터 구조체
|
||||||
|
% - target_range_m: 표적 거리 (m), 기본값 100
|
||||||
|
% - target_rcs_dBsm: 표적 RCS (dBsm), 기본값 0
|
||||||
|
%
|
||||||
|
% 출력:
|
||||||
|
% - snr_coverage_2d: 2D SNR 맵 [elevation x azimuth]
|
||||||
|
% - azimuth_deg: 방위각 벡터
|
||||||
|
% - elevation_deg: 고각 벡터
|
||||||
|
% - coverage_info: 커버리지 정보 구조체
|
||||||
|
% - fig: 생성된 figure 핸들
|
||||||
|
|
||||||
|
%% 입력 파라미터 처리
|
||||||
|
if nargin < 2
|
||||||
|
target_range_m = 100; % 기본: 100m
|
||||||
|
end
|
||||||
|
if nargin < 3
|
||||||
|
target_rcs_dBsm = 0; % 기본: 0 dBsm = 1
|
||||||
|
end
|
||||||
|
|
||||||
|
%% 기본 파라미터 추출
|
||||||
|
lambda = RadarParams.Waveform.lambda_c;
|
||||||
|
fc = RadarParams.Waveform.fc;
|
||||||
|
kb = RadarParams.Basic.kb;
|
||||||
|
T0 = RadarParams.Basic.T0;
|
||||||
|
|
||||||
|
% 안테나 및 경로 파라미터
|
||||||
|
TxPattern = RadarParams.Antenna.TxPattern;
|
||||||
|
RxPattern = RadarParams.Antenna.RxPattern;
|
||||||
|
NumTx = RadarParams.Antenna.NumTx;
|
||||||
|
NumRx = RadarParams.Antenna.NumRx;
|
||||||
|
|
||||||
|
% 수신 경로 파라미터
|
||||||
|
rxPathGain_dB = RadarParams.Rxpath.rxPathGain_dB;
|
||||||
|
system_NF_dB = RadarParams.Rxpath.system_NF_dB;
|
||||||
|
|
||||||
|
% RCS (선형값)
|
||||||
|
rcs_target = 10^(target_rcs_dBsm / 10);
|
||||||
|
|
||||||
|
%% 평균 2D 안테나 패턴 생성 (1D -> 2D 변환 포함)
|
||||||
|
% 공통 그리드 설정 (높은 해상도)
|
||||||
|
az_common = -90:1:90;
|
||||||
|
el_common = -90:1:90;
|
||||||
|
[AZ_common_grid, EL_common_grid] = meshgrid(az_common, el_common);
|
||||||
|
|
||||||
|
% TX 패턴들을 2D로 변환 및 평균
|
||||||
|
avg_tx_gain_2d = zeros(length(el_common), length(az_common));
|
||||||
|
for tx = 1:NumTx
|
||||||
|
if isfield(TxPattern(tx), 'gain_az_dBi') % 1D 패턴
|
||||||
|
az_angles = TxPattern(tx).az_angles;
|
||||||
|
el_angles = TxPattern(tx).el_angles;
|
||||||
|
gain_az = TxPattern(tx).gain_az_dBi;
|
||||||
|
gain_el = TxPattern(tx).gain_el_dBi;
|
||||||
|
|
||||||
|
% 각 패턴의 최대값 저장
|
||||||
|
max_gain_az = max(gain_az);
|
||||||
|
max_gain_el = max(gain_el);
|
||||||
|
max_gain_ref = max(max_gain_az, max_gain_el);
|
||||||
|
|
||||||
|
% Normalize (최대값 = 0 dB)
|
||||||
|
gain_az_norm = gain_az - max_gain_az;
|
||||||
|
gain_el_norm = gain_el - max_gain_el;
|
||||||
|
|
||||||
|
% 정규화된 패턴 보간
|
||||||
|
gain_az_interp = interp1(az_angles, gain_az_norm, AZ_common_grid, 'linear', 'extrap');
|
||||||
|
gain_el_interp = interp1(el_angles, gain_el_norm, EL_common_grid, 'linear', 'extrap');
|
||||||
|
|
||||||
|
% 2D로 결합하고 최대 이득 더하기
|
||||||
|
tx_gain_2d = gain_az_interp + gain_el_interp + max_gain_ref;
|
||||||
|
else % 2D 패턴
|
||||||
|
az_angles = TxPattern(tx).az_angles;
|
||||||
|
el_angles = TxPattern(tx).el_angles;
|
||||||
|
gain_dBi = TxPattern(tx).gain_dBi;
|
||||||
|
[AZ_grid, EL_grid] = ndgrid(az_angles, el_angles);
|
||||||
|
F = scatteredInterpolant(AZ_grid(:), EL_grid(:), gain_dBi(:), 'linear', 'nearest');
|
||||||
|
tx_gain_2d = F(AZ_common_grid, EL_common_grid);
|
||||||
|
end
|
||||||
|
avg_tx_gain_2d = avg_tx_gain_2d + tx_gain_2d;
|
||||||
|
end
|
||||||
|
avg_tx_gain_2d = avg_tx_gain_2d / NumTx;
|
||||||
|
|
||||||
|
% RX 패턴들을 2D로 변환 및 평균 (동일한 방식)
|
||||||
|
avg_rx_gain_2d = zeros(length(el_common), length(az_common));
|
||||||
|
for rx = 1:NumRx
|
||||||
|
if isfield(RxPattern(rx), 'gain_az_dBi') % 1D 패턴
|
||||||
|
az_angles = RxPattern(rx).az_angles;
|
||||||
|
el_angles = RxPattern(rx).el_angles;
|
||||||
|
gain_az = RxPattern(rx).gain_az_dBi;
|
||||||
|
gain_el = RxPattern(rx).gain_el_dBi;
|
||||||
|
|
||||||
|
% 각 패턴의 최대값 저장
|
||||||
|
max_gain_az = max(gain_az);
|
||||||
|
max_gain_el = max(gain_el);
|
||||||
|
max_gain_ref = max(max_gain_az, max_gain_el);
|
||||||
|
|
||||||
|
% Normalize (최대값 = 0 dB)
|
||||||
|
gain_az_norm = gain_az - max_gain_az;
|
||||||
|
gain_el_norm = gain_el - max_gain_el;
|
||||||
|
|
||||||
|
% 정규화된 패턴 보간
|
||||||
|
gain_az_interp = interp1(az_angles, gain_az_norm, AZ_common_grid, 'linear', 'extrap');
|
||||||
|
gain_el_interp = interp1(el_angles, gain_el_norm, EL_common_grid, 'linear', 'extrap');
|
||||||
|
|
||||||
|
% 2D로 결합하고 최대 이득 더하기
|
||||||
|
rx_gain_2d = gain_az_interp + gain_el_interp + max_gain_ref;
|
||||||
|
else % 2D 패턴
|
||||||
|
az_angles = RxPattern(rx).az_angles;
|
||||||
|
el_angles = RxPattern(rx).el_angles;
|
||||||
|
gain_dBi = RxPattern(rx).gain_dBi;
|
||||||
|
[AZ_grid, EL_grid] = ndgrid(az_angles, el_angles);
|
||||||
|
F = scatteredInterpolant(AZ_grid(:), EL_grid(:), gain_dBi(:), 'linear', 'nearest');
|
||||||
|
rx_gain_2d = F(AZ_common_grid, EL_common_grid);
|
||||||
|
end
|
||||||
|
avg_rx_gain_2d = avg_rx_gain_2d + rx_gain_2d;
|
||||||
|
end
|
||||||
|
avg_rx_gain_2d = avg_rx_gain_2d / NumRx;
|
||||||
|
|
||||||
|
%% TX 전력 및 공통 파라미터 계산
|
||||||
|
% TX 전력
|
||||||
|
pa_profile_freq = RadarParams.RFOutput.PA_Profile.freqs;
|
||||||
|
pa_profile_power_dbm = RadarParams.RFOutput.PA_Profile.power_dBm;
|
||||||
|
ptx_dbm = interp1(pa_profile_freq, pa_profile_power_dbm, fc, 'linear', 'extrap');
|
||||||
|
ptx_w = 10^((ptx_dbm - 30) / 10);
|
||||||
|
|
||||||
|
% 경로 손실 및 잡음 계산 (모든 각도에서 공통)
|
||||||
|
path_loss_factor = (4 * pi * target_range_m)^2;
|
||||||
|
rxGain_linear = 10^(rxPathGain_dB / 10);
|
||||||
|
noise_power_w = kb * T0 * RadarParams.Waveform.fs_adc;
|
||||||
|
system_NF_linear = 10^(system_NF_dB / 10);
|
||||||
|
total_noise_power_w = noise_power_w * system_NF_linear * rxGain_linear; % RX 이득 포함
|
||||||
|
|
||||||
|
%% 각도별 SNR 계산 (2D 안테나 패턴 직접 사용 - 벡터화 연산)
|
||||||
|
% X축: Azimuth, Y축: Elevation, Z축: SNR
|
||||||
|
azimuth_deg = -90:1:90; % X축: -90 ~ 90도, 1도 단위
|
||||||
|
elevation_deg = -90:1:90; % Y축: -90 ~ 90도, 1도 단위
|
||||||
|
|
||||||
|
% az_common과 elevation_deg가 동일한 그리드이므로 인덱스 매칭
|
||||||
|
% 안테나 이득 추출 (dB)
|
||||||
|
g_tx_db_grid = avg_tx_gain_2d; % [num_el x num_az]
|
||||||
|
g_rx_db_grid = avg_rx_gain_2d; % [num_el x num_az]
|
||||||
|
|
||||||
|
% 이득을 선형으로 변환
|
||||||
|
g_tx_linear_grid = 10.^(g_tx_db_grid / 10);
|
||||||
|
g_rx_linear_grid = 10.^(g_rx_db_grid / 10);
|
||||||
|
|
||||||
|
% 신호처리 이득
|
||||||
|
SP_gain_rngFFT = RadarParams.Waveform.Timing.AdcSampTime * RadarParams.Waveform.fs_adc;
|
||||||
|
SP_gain_dopFFT = RadarParams.Waveform.NumChirps;
|
||||||
|
SP_loss_rngwin = 10^(RadarParams.SP.RDM.window_metrics_range.snr_loss_dB / 10);
|
||||||
|
SP_loss_dopwin = 10^(RadarParams.SP.RDM.window_metrics_doppler.snr_loss_dB / 10);
|
||||||
|
SP_loss_rng_straddle = 10^(RadarParams.SP.RDM.window_metrics_range.scalloping_loss_dB / 10);
|
||||||
|
SP_loss_dop_straddle = 10^(RadarParams.SP.RDM.window_metrics_doppler.scalloping_loss_dB / 10);
|
||||||
|
SP_total = SP_gain_rngFFT * SP_gain_dopFFT / (SP_loss_rngwin * SP_loss_dopwin * SP_loss_rng_straddle * SP_loss_dop_straddle);
|
||||||
|
|
||||||
|
% Secondary Surface Loss
|
||||||
|
secondary_loss = 10^(RadarParams.Antenna.SecondarySurfaceLoss_dB / 10);
|
||||||
|
|
||||||
|
fprintf('SP gain rng FFT: %.2f, SP gain doppler FFT: %.2f\n', SP_gain_rngFFT, SP_gain_dopFFT);
|
||||||
|
fprintf('SP loss range window: %.2f dB, SP loss doppler window: %.2f dB\n', RadarParams.SP.RDM.window_metrics_range.snr_loss_dB, RadarParams.SP.RDM.window_metrics_doppler.snr_loss_dB);
|
||||||
|
fprintf('SP loss range straddle: %.2f dB, SP loss doppler straddle: %.2f dB\n', RadarParams.SP.RDM.window_metrics_range.scalloping_loss_dB, RadarParams.SP.RDM.window_metrics_doppler.scalloping_loss_dB);
|
||||||
|
fprintf('SP loss doppler window: %.2f dB\n', RadarParams.SP.RDM.window_metrics_doppler.snr_loss_dB);
|
||||||
|
fprintf('SP loss doppler straddle: %.2f dB\n', RadarParams.SP.RDM.window_metrics_doppler.scalloping_loss_dB);
|
||||||
|
fprintf('Secondary surface loss: %.2f dB\n', RadarParams.Antenna.SecondarySurfaceLoss_dB);
|
||||||
|
|
||||||
|
% SNR 계산: 벡터화 연산 (스칼라 항 × 2D 배열)
|
||||||
|
p_rx_w = (ptx_w * (lambda^2) * rcs_target) / ((path_loss_factor^2) * (4 * pi)) * SP_total / secondary_loss;
|
||||||
|
p_rx_w = p_rx_w * g_tx_linear_grid .* g_rx_linear_grid; % 연산량 최적화를 위해 안테나 이득을 나중에 곱함
|
||||||
|
p_rx_after_rxgain_w = p_rx_w * rxGain_linear;
|
||||||
|
snr_linear = p_rx_after_rxgain_w / total_noise_power_w;
|
||||||
|
snr_coverage_2d = 10 * log10(max(snr_linear, eps));
|
||||||
|
|
||||||
|
%% 1D CUT 추출 (elevation=0에서의 Azimuth cut, azimuth=0에서의 Elevation cut)
|
||||||
|
[~, el_idx_zero] = min(abs(elevation_deg - 0));
|
||||||
|
snr_az_cut = snr_coverage_2d(el_idx_zero, :); % elevation=0
|
||||||
|
|
||||||
|
[~, az_idx_zero] = min(abs(azimuth_deg - 0));
|
||||||
|
snr_el_cut = snr_coverage_2d(:, az_idx_zero); % azimuth=0
|
||||||
|
|
||||||
|
%% Cartesian 좌표계 변환 (Range, Azimuth, Elevation -> x, y, z)
|
||||||
|
[AZ_deg_grid, EL_deg_grid] = meshgrid(azimuth_deg, elevation_deg);
|
||||||
|
x_grid_m = target_range_m .* cosd(EL_deg_grid) .* cosd(AZ_deg_grid);
|
||||||
|
y_grid_m = target_range_m .* cosd(EL_deg_grid) .* sind(AZ_deg_grid);
|
||||||
|
z_grid_m = target_range_m .* sind(EL_deg_grid);
|
||||||
|
|
||||||
|
%% Coverage 정보 통계
|
||||||
|
snr_2d_vec = snr_coverage_2d(:);
|
||||||
|
|
||||||
|
coverage_info.azimuth_deg = azimuth_deg;
|
||||||
|
coverage_info.elevation_deg = elevation_deg;
|
||||||
|
coverage_info.snr_coverage_2d = snr_coverage_2d;
|
||||||
|
coverage_info.snr_az_cut = snr_az_cut;
|
||||||
|
coverage_info.snr_el_cut = snr_el_cut;
|
||||||
|
coverage_info.target_range_m = target_range_m;
|
||||||
|
coverage_info.target_rcs_dBsm = target_rcs_dBsm;
|
||||||
|
coverage_info.x_grid_m = x_grid_m;
|
||||||
|
coverage_info.y_grid_m = y_grid_m;
|
||||||
|
coverage_info.z_grid_m = z_grid_m;
|
||||||
|
coverage_info.mean_snr = mean(snr_2d_vec);
|
||||||
|
coverage_info.max_snr = max(snr_2d_vec);
|
||||||
|
coverage_info.min_snr = min(snr_2d_vec);
|
||||||
|
[~, max_idx_2d] = max(snr_2d_vec);
|
||||||
|
[max_el_idx_2d, max_az_idx_2d] = ind2sub(size(snr_coverage_2d), max_idx_2d);
|
||||||
|
coverage_info.max_snr_azimuth = azimuth_deg(max_az_idx_2d);
|
||||||
|
coverage_info.max_snr_elevation = elevation_deg(max_el_idx_2d);
|
||||||
|
coverage_info.max_snr_x_m = x_grid_m(max_el_idx_2d, max_az_idx_2d);
|
||||||
|
coverage_info.max_snr_y_m = y_grid_m(max_el_idx_2d, max_az_idx_2d);
|
||||||
|
coverage_info.max_snr_z_m = z_grid_m(max_el_idx_2d, max_az_idx_2d);
|
||||||
|
|
||||||
|
%% 3D Coverage 시각화
|
||||||
|
fig = figure('Name', 'Coverage Analysis - 3D');
|
||||||
|
set(fig, 'Position', [100, 100, 1600, 900]);
|
||||||
|
|
||||||
|
% 1D cut SNR 추출
|
||||||
|
[~, el_idx_zero] = min(abs(elevation_deg - 0));
|
||||||
|
[~, az_idx_zero] = min(abs(azimuth_deg - 0));
|
||||||
|
snr_az_cut = snr_coverage_2d(el_idx_zero, :); % elevation=0 cut
|
||||||
|
snr_el_cut = snr_coverage_2d(:, az_idx_zero); % azimuth=0 cut
|
||||||
|
|
||||||
|
% Subplot 1: x-y top view (color = SNR at elevation=0 cut)
|
||||||
|
ax_topview = subplot(2, 2, 3);
|
||||||
|
x_top_m = target_range_m .* cosd(azimuth_deg);
|
||||||
|
y_top_m = target_range_m .* sind(azimuth_deg);
|
||||||
|
scatter(x_top_m, y_top_m, 45, snr_az_cut, 'filled');
|
||||||
|
axis equal;
|
||||||
|
grid on;
|
||||||
|
cb = colorbar;
|
||||||
|
ylabel(cb, 'SNR (dB)');
|
||||||
|
xlabel('x (m)');
|
||||||
|
ylabel('y (m)');
|
||||||
|
title('Top View (Azimuth)');
|
||||||
|
|
||||||
|
% Subplot 2: 3D Surface (geometry = x,y,z, color = SNR) - spans full width at top
|
||||||
|
ax_3d = subplot(2, 2, 1:2);
|
||||||
|
surf(x_grid_m, y_grid_m, z_grid_m, snr_coverage_2d, 'EdgeColor', 'none');
|
||||||
|
colormap(jet);
|
||||||
|
cb_3d = colorbar;
|
||||||
|
ylabel(cb_3d, 'SNR (dB)');
|
||||||
|
xlabel('x (m)');
|
||||||
|
ylabel('y (m)');
|
||||||
|
zlabel('z (m)');
|
||||||
|
title('3D Coverage in Cartesian Coordinates');
|
||||||
|
view(45, 30);
|
||||||
|
grid on;
|
||||||
|
hold on;
|
||||||
|
plot3(coverage_info.max_snr_x_m, coverage_info.max_snr_y_m, coverage_info.max_snr_z_m, 'r*', 'MarkerSize', 20, 'LineWidth', 2);
|
||||||
|
hold off;
|
||||||
|
|
||||||
|
% Subplot 3: x-z side view (azimuth=0, color=SNR at azimuth=0 cut)
|
||||||
|
ax_sideview = subplot(2, 2, 4);
|
||||||
|
x_side_m = target_range_m .* cosd(elevation_deg);
|
||||||
|
z_side_m = target_range_m .* sind(elevation_deg);
|
||||||
|
scatter(x_side_m, z_side_m, 45, snr_el_cut, 'filled');
|
||||||
|
grid on;
|
||||||
|
cb_side = colorbar;
|
||||||
|
ylabel(cb_side, 'SNR (dB)');
|
||||||
|
xlabel('x (m)');
|
||||||
|
ylabel('z (m)');
|
||||||
|
title('Side View (Elevation)');
|
||||||
|
|
||||||
|
% 전체 타이틀
|
||||||
|
sgtitle(sprintf('3D Coverage (x,y,z) | Max SNR: %.2f dB at (%.2f, %.2f, %.2f) m', ...
|
||||||
|
coverage_info.max_snr, coverage_info.max_snr_x_m, coverage_info.max_snr_y_m, coverage_info.max_snr_z_m), 'FontSize', 12);
|
||||||
|
|
||||||
|
% 단일 Data Cursor Mode 설정 (모든 축에 적용)
|
||||||
|
dcm_obj = datacursormode(fig);
|
||||||
|
dcm_obj.Enable = 'on';
|
||||||
|
set(dcm_obj, 'UpdateFcn', {@unified_datatip_callback, ...
|
||||||
|
ax_topview, ax_3d, ax_sideview, ...
|
||||||
|
x_top_m, y_top_m, snr_az_cut, azimuth_deg, ...
|
||||||
|
x_grid_m, y_grid_m, z_grid_m, snr_coverage_2d, elevation_deg, ...
|
||||||
|
x_side_m, z_side_m, snr_el_cut});
|
||||||
|
dcm_obj.SnapToDataVertex = 'on';
|
||||||
|
|
||||||
|
% Help text
|
||||||
|
fprintf('\n>>> Data Cursor Mode ENABLED <<<\n');
|
||||||
|
fprintf('Instructions:\n');
|
||||||
|
fprintf(' 1. Left-click on any plot to inspect points\n');
|
||||||
|
fprintf(' 2. Top View shows: x, y, Azimuth, SNR\n');
|
||||||
|
fprintf(' 3. 3D Surface shows: x, y, z, Azimuth, Elevation, SNR\n');
|
||||||
|
fprintf(' 4. Side View shows: x, z, Elevation, SNR\n');
|
||||||
|
fprintf(' 5. Press Escape or click "Disable Data Cursor" in Figure Tools to deactivate\n\n');
|
||||||
|
|
||||||
|
%% 통계 출력
|
||||||
|
fprintf('\n===== 3D COVERAGE ANALYSIS REPORT (Cartesian x,y,z) =====\n');
|
||||||
|
fprintf('Target Configuration:\n');
|
||||||
|
fprintf(' - Range: %.1f m\n', target_range_m);
|
||||||
|
fprintf(' - RCS: %.1f dBsm\n', target_rcs_dBsm);
|
||||||
|
fprintf(' - Analysis Angles: Azimuth ±90 deg, Elevation ±90 deg\n');
|
||||||
|
fprintf('\nSNR Statistics (Full Grid):\n');
|
||||||
|
fprintf(' - Mean SNR: %.2f dB\n', coverage_info.mean_snr);
|
||||||
|
fprintf(' - Max SNR: %.2f dB @ (x,y,z)=(%.2f, %.2f, %.2f) m\n', coverage_info.max_snr, coverage_info.max_snr_x_m, coverage_info.max_snr_y_m, coverage_info.max_snr_z_m);
|
||||||
|
fprintf(' - Min SNR: %.2f dB\n', coverage_info.min_snr);
|
||||||
|
fprintf(' - SNR Range: %.2f dB\n', coverage_info.max_snr - coverage_info.min_snr);
|
||||||
|
fprintf('System Parameters:\n');
|
||||||
|
fprintf(' - TX Power: %.2f dBm\n', ptx_dbm);
|
||||||
|
fprintf(' - RX Gain: %.1f dB\n', rxPathGain_dB);
|
||||||
|
fprintf(' - System NF: %.1f dB\n', system_NF_dB);
|
||||||
|
fprintf('=============================================\n\n');
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
function output_txt = unified_datatip_callback(~, event, ...
|
||||||
|
ax_topview, ax_3d, ax_sideview, ...
|
||||||
|
x_top_m, y_top_m, snr_az_cut, azimuth_deg, ...
|
||||||
|
x_grid_m, y_grid_m, z_grid_m, snr_coverage_2d, elevation_deg, ...
|
||||||
|
x_side_m, z_side_m, snr_el_cut)
|
||||||
|
% Unified datatip callback for all subplots
|
||||||
|
|
||||||
|
try
|
||||||
|
pos = event.Position;
|
||||||
|
current_ax = event.Target.Parent; % Get current axis
|
||||||
|
|
||||||
|
% Determine which subplot was clicked
|
||||||
|
if isequal(current_ax, ax_3d)
|
||||||
|
% 3D Plot
|
||||||
|
x_val = pos(1);
|
||||||
|
y_val = pos(2);
|
||||||
|
z_val = pos(3);
|
||||||
|
|
||||||
|
dist2 = (x_grid_m - x_val).^2 + (y_grid_m - y_val).^2 + (z_grid_m - z_val).^2;
|
||||||
|
[~, nearest_idx] = min(dist2(:));
|
||||||
|
[nearest_el_idx, nearest_az_idx] = ind2sub(size(dist2), nearest_idx);
|
||||||
|
snr_val = snr_coverage_2d(nearest_el_idx, nearest_az_idx);
|
||||||
|
az_val = azimuth_deg(nearest_az_idx);
|
||||||
|
el_val = elevation_deg(nearest_el_idx);
|
||||||
|
|
||||||
|
output_txt = {
|
||||||
|
['x: ' num2str(x_val, '%.3f') ' m']
|
||||||
|
['y: ' num2str(y_val, '%.3f') ' m']
|
||||||
|
['z: ' num2str(z_val, '%.3f') ' m']
|
||||||
|
['Azimuth: ' num2str(az_val, '%.2f') ' deg']
|
||||||
|
['Elevation: ' num2str(el_val, '%.2f') ' deg']
|
||||||
|
['SNR[dB]: ' num2str(snr_val, '%.3f')]
|
||||||
|
};
|
||||||
|
elseif isequal(current_ax, ax_topview)
|
||||||
|
% Top View - x, y, Azimuth, SNR
|
||||||
|
x_val = pos(1);
|
||||||
|
y_val = pos(2);
|
||||||
|
|
||||||
|
[~, nearest_az_idx] = min(abs(x_top_m - x_val).^2 + abs(y_top_m - y_val).^2);
|
||||||
|
az_val = azimuth_deg(nearest_az_idx);
|
||||||
|
snr_val = snr_az_cut(nearest_az_idx);
|
||||||
|
|
||||||
|
output_txt = {
|
||||||
|
['x: ' num2str(x_val, '%.3f') ' m']
|
||||||
|
['y: ' num2str(y_val, '%.3f') ' m']
|
||||||
|
['Azimuth: ' num2str(az_val, '%.2f') ' deg']
|
||||||
|
['SNR[dB]: ' num2str(snr_val, '%.3f')]
|
||||||
|
};
|
||||||
|
elseif isequal(current_ax, ax_sideview)
|
||||||
|
% Side View - x, z, Elevation, SNR
|
||||||
|
x_val = pos(1);
|
||||||
|
z_val = pos(2);
|
||||||
|
|
||||||
|
[~, nearest_el_idx] = min(abs(x_side_m - x_val).^2 + abs(z_side_m - z_val).^2);
|
||||||
|
el_val = elevation_deg(nearest_el_idx);
|
||||||
|
snr_val = snr_el_cut(nearest_el_idx);
|
||||||
|
|
||||||
|
output_txt = {
|
||||||
|
['x: ' num2str(x_val, '%.3f') ' m']
|
||||||
|
['z: ' num2str(z_val, '%.3f') ' m']
|
||||||
|
['Elevation: ' num2str(el_val, '%.2f') ' deg']
|
||||||
|
['SNR[dB]: ' num2str(snr_val, '%.3f')]
|
||||||
|
};
|
||||||
|
else
|
||||||
|
output_txt = 'Unknown plot';
|
||||||
|
end
|
||||||
|
catch ME
|
||||||
|
output_txt = ['Error: ' ME.message];
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -10,6 +10,17 @@ currentFilePath = mfilename('fullpath');
|
|||||||
currentFolder = fileparts(currentFilePath);
|
currentFolder = fileparts(currentFilePath);
|
||||||
addpath(genpath(currentFolder));
|
addpath(genpath(currentFolder));
|
||||||
|
|
||||||
|
%% =================== 시각화 토글 ===================
|
||||||
|
% 각 figure를 개별적으로 on/off 할 수 있습니다.
|
||||||
|
PlotToggle.range_profile = false; % Step 8 Range Profile
|
||||||
|
PlotToggle.tx_single = false; % Figure 1 Single chirp waveform
|
||||||
|
PlotToggle.tx_multi = false; % Figure 2 Multi chirp waveform
|
||||||
|
PlotToggle.tx_antenna = false; % Figure 3 TX antenna pattern
|
||||||
|
PlotToggle.rx_antenna = false; % Figure 4 RX antenna pattern
|
||||||
|
PlotToggle.rd_map = false; % Figure 5 Range-Doppler map
|
||||||
|
PlotToggle.cfar = false; % Figure 6 CFAR detections
|
||||||
|
PlotToggle.coverage = true; % Figure 7 Coverage analysis
|
||||||
|
|
||||||
%% =================== 파라미터 입력 ===================
|
%% =================== 파라미터 입력 ===================
|
||||||
|
|
||||||
% 1) 기본 물리 파라미터
|
% 1) 기본 물리 파라미터
|
||||||
@@ -42,12 +53,11 @@ RadarParams.Waveform.nonideal.pn_level = 0.05; % 위상노이즈
|
|||||||
RadarParams.Waveform.nonideal.f_ripple = 300e3; % 위상 리플 주파수
|
RadarParams.Waveform.nonideal.f_ripple = 300e3; % 위상 리플 주파수
|
||||||
RadarParams.Waveform.nonideal.peak_phase_error = 0.1; % 최대 위상오차
|
RadarParams.Waveform.nonideal.peak_phase_error = 0.1; % 최대 위상오차
|
||||||
RadarParams.Waveform.nonideal.power_drop_edge = 0.8; % 엣지 파워 드롭
|
RadarParams.Waveform.nonideal.power_drop_edge = 0.8; % 엣지 파워 드롭
|
||||||
RadarParams.Waveform.nonideal.enable_phase_noise = false; % phase noise on/off
|
RadarParams.Waveform.nonideal.enable_phase_noise = true; % phase noise on/off
|
||||||
RadarParams.Waveform.nonideal.enable_nonlinearity = false; % nonlinearity on/off
|
RadarParams.Waveform.nonideal.enable_nonlinearity = false; % nonlinearity on/off
|
||||||
% Datasheet 기반 phase noise 설정 (예: -89 dBc/Hz @ 1 MHz)
|
% Datasheet 기반 phase noise 설정 (예: -89 dBc/Hz @ 1 MHz)
|
||||||
RadarParams.Waveform.nonideal.phaseNoise.offset_Hz = [1e6];
|
RadarParams.Waveform.nonideal.phaseNoise.offset_Hz = [1e6];
|
||||||
RadarParams.Waveform.nonideal.phaseNoise.level_dBc_Hz = [-89];
|
RadarParams.Waveform.nonideal.phaseNoise.level_dBc_Hz = [-89];
|
||||||
RadarParams.Waveform.nonideal.phaseNoise.enabled = false;
|
|
||||||
|
|
||||||
% 2-3) MIMO 모드 설정
|
% 2-3) MIMO 모드 설정
|
||||||
RadarParams.Waveform.mimoMode = 'TDM'; % 'TDM' 또는 'DDMA'
|
RadarParams.Waveform.mimoMode = 'TDM'; % 'TDM' 또는 'DDMA'
|
||||||
@@ -71,11 +81,11 @@ RadarParams.Antenna.TxPattern = build_antenna_patterns(RadarParams.Antenna.NumTx
|
|||||||
RadarParams.Antenna.RxPattern = build_antenna_patterns(RadarParams.Antenna.NumRx, '2D', 14, 2, 40, 15, RadarParams.Antenna.rx_files);
|
RadarParams.Antenna.RxPattern = build_antenna_patterns(RadarParams.Antenna.NumRx, '2D', 14, 2, 40, 15, RadarParams.Antenna.rx_files);
|
||||||
|
|
||||||
% 5) 타겟 모델링
|
% 5) 타겟 모델링
|
||||||
RadarParams.Target.R = [12, 80, 120]; % 타겟 거리 (m)
|
RadarParams.Target.R = [12]; % 타겟 거리 (m)
|
||||||
RadarParams.Target.v = [7, -5, 0]; % 타겟 속도 (m/s)
|
RadarParams.Target.v = [7]; % 타겟 속도 (m/s)
|
||||||
RadarParams.Target.rcs = [10, 5, 20]; % 타겟 RCS (dBsm)
|
RadarParams.Target.rcs = [-10000]; % 타겟 RCS (dBsm)
|
||||||
RadarParams.Target.az = [10, -10, 0]; % 방위각 (deg)
|
RadarParams.Target.az = [10]; % 방위각 (deg)
|
||||||
RadarParams.Target.el = [0, 0, 5]; % 고각 (deg)
|
RadarParams.Target.el = [0]; % 고각 (deg)
|
||||||
RadarParams.Target.NumTargets = length(RadarParams.Target.R);
|
RadarParams.Target.NumTargets = length(RadarParams.Target.R);
|
||||||
|
|
||||||
% 6) 수신단 파라미터
|
% 6) 수신단 파라미터
|
||||||
@@ -84,6 +94,7 @@ RadarParams.Rxpath.fc_lpf = 0.8*RadarParams.Waveform.fs_adc/2; % L
|
|||||||
RadarParams.Rxpath.adc_bits = 12; % ADC 비트수
|
RadarParams.Rxpath.adc_bits = 12; % ADC 비트수
|
||||||
RadarParams.Rxpath.adc_v_full_scale = 2.0; % full-scale 전압 (Vp-p)
|
RadarParams.Rxpath.adc_v_full_scale = 2.0; % full-scale 전압 (Vp-p)
|
||||||
RadarParams.Rxpath.receiver_mode = 'Real'; % 'IQ' 또는 'Real'
|
RadarParams.Rxpath.receiver_mode = 'Real'; % 'IQ' 또는 'Real'
|
||||||
|
RadarParams.Rxpath.load_impedance_ohm = 50; % 임피던스 (Ohm)
|
||||||
RadarParams.Rxpath.rxPathGain_dB = 50; % RX 경로 이득 (dB)
|
RadarParams.Rxpath.rxPathGain_dB = 50; % RX 경로 이득 (dB)
|
||||||
RadarParams.Rxpath.system_NF_dB = 15; % 시스템 잡음지수 (dB)
|
RadarParams.Rxpath.system_NF_dB = 15; % 시스템 잡음지수 (dB)
|
||||||
|
|
||||||
@@ -104,6 +115,18 @@ RadarParams.SpurParams.enabled = false; % spur on/of
|
|||||||
% 7) 신호처리 파라미터
|
% 7) 신호처리 파라미터
|
||||||
RadarParams.SP.RDM.window_type_range = 'hann'; % Range FFT용 윈도우
|
RadarParams.SP.RDM.window_type_range = 'hann'; % Range FFT용 윈도우
|
||||||
RadarParams.SP.RDM.window_type_doppler = 'chebwin'; % Doppler FFT용 윈도우
|
RadarParams.SP.RDM.window_type_doppler = 'chebwin'; % Doppler FFT용 윈도우
|
||||||
|
|
||||||
|
num_samples_range = round(RadarParams.Waveform.fs_adc * RadarParams.Waveform.Timing.AdcSampTime);
|
||||||
|
num_chirps_doppler = RadarParams.Waveform.NumChirps;
|
||||||
|
|
||||||
|
% Range 윈도우 생성 및 성능 지표 계산
|
||||||
|
[RadarParams.SP.RDM.window_range, RadarParams.SP.RDM.window_metrics_range] = ...
|
||||||
|
create_window_with_metrics(RadarParams.SP.RDM.window_type_range, num_samples_range, 60);
|
||||||
|
|
||||||
|
% Doppler 윈도우 생성 및 성능 지표 계산
|
||||||
|
[RadarParams.SP.RDM.window_doppler, RadarParams.SP.RDM.window_metrics_doppler] = ...
|
||||||
|
create_window_with_metrics(RadarParams.SP.RDM.window_type_doppler, num_chirps_doppler, 60);
|
||||||
|
|
||||||
RadarParams.SP.CFAR.method = 'OS'; % 'CA' 또는 'OS'
|
RadarParams.SP.CFAR.method = 'OS'; % 'CA' 또는 'OS'
|
||||||
RadarParams.SP.CFAR.dimension = '2D'; % '1D' 또는 '2D'
|
RadarParams.SP.CFAR.dimension = '2D'; % '1D' 또는 '2D'
|
||||||
RadarParams.SP.CFAR.axis = 'doppler'; % 1D일 때만 사용: 'range' 또는 'doppler'
|
RadarParams.SP.CFAR.axis = 'doppler'; % 1D일 때만 사용: 'range' 또는 'doppler'
|
||||||
@@ -113,6 +136,13 @@ RadarParams.SP.CFAR.guard = [2, 2]; % [doppler, range] guard cell 수 (1D
|
|||||||
RadarParams.SP.CFAR.rank = 0.75; % OS-CFAR rank 비율(0~1)
|
RadarParams.SP.CFAR.rank = 0.75; % OS-CFAR rank 비율(0~1)
|
||||||
RadarParams.SP.CFAR.os_scale = 15.0; % OS-CFAR 임계 스케일
|
RadarParams.SP.CFAR.os_scale = 15.0; % OS-CFAR 임계 스케일
|
||||||
|
|
||||||
|
% 8) Secondary surface loss (안테나 반사 손실)
|
||||||
|
RadarParams.Antenna.SecondarySurfaceLoss_dB = 3;
|
||||||
|
|
||||||
|
% 9) 커버리지 분석 파라미터
|
||||||
|
RadarParams.Coverage.R_max = 10; % 최대 탐지 거리 (m)
|
||||||
|
RadarParams.Coverage.RCS_dBsm = 0; % 표적 RCS (dBsm)
|
||||||
|
|
||||||
|
|
||||||
%% 2. 모듈별 함수 호출 (TX 파이프라인)
|
%% 2. 모듈별 함수 호출 (TX 파이프라인)
|
||||||
% step 0. 파형 시각화를 위한 시간 벡터 및 TX 마스크 생성
|
% step 0. 파형 시각화를 위한 시간 벡터 및 TX 마스크 생성
|
||||||
@@ -177,15 +207,17 @@ adc_digital_expanded = reshape(adc_digital, [size(adc_digital,1), 1, size(adc_di
|
|||||||
[range_data, r_axis] = process_range_fft_lpf(adc_digital_expanded, RadarParams);
|
[range_data, r_axis] = process_range_fft_lpf(adc_digital_expanded, RadarParams);
|
||||||
|
|
||||||
% step 8. Range Profile 시각화 (1번 채널, 1번 처프)
|
% step 8. Range Profile 시각화 (1번 채널, 1번 처프)
|
||||||
figure('Name', 'Range Profile with Ideal LPF');
|
if PlotToggle.range_profile
|
||||||
plot(r_axis, 20*log10(abs(squeeze(range_data(1,1,1,:)))));
|
figure('Name', 'Range Profile with Ideal LPF');
|
||||||
grid on; hold on;
|
plot(r_axis, 20*log10(abs(squeeze(range_data(1,1,1,:)))));
|
||||||
xlabel('Range (m)');
|
grid on; hold on;
|
||||||
ylabel('Magnitude (dB)');
|
xlabel('Range (m)');
|
||||||
title(['Range Profile (LPF Cut-off: ', num2str(RadarParams.Rxpath.fc_lpf/1e6), ' MHz)']);
|
ylabel('Magnitude (dB)');
|
||||||
|
title(['Range Profile (LPF Cut-off: ', num2str(RadarParams.Rxpath.fc_lpf/1e6), ' MHz)']);
|
||||||
|
|
||||||
% LPF 컷오프 지점 표시
|
% LPF 컷오프 지점 표시
|
||||||
xline((RadarParams.Rxpath.fc_lpf * RadarParams.Basic.c)/(2*RadarParams.Waveform.Slope), '--r', 'LPF Cut-off');
|
xline((RadarParams.Rxpath.fc_lpf * RadarParams.Basic.c)/(2*RadarParams.Waveform.Slope), '--r', 'LPF Cut-off');
|
||||||
|
end
|
||||||
|
|
||||||
% step 9. Doppler-FFT 수행
|
% step 9. Doppler-FFT 수행
|
||||||
[rd_cube, v_axis] = process_doppler_fft(range_data, RadarParams);
|
[rd_cube, v_axis] = process_doppler_fft(range_data, RadarParams);
|
||||||
@@ -204,40 +236,71 @@ idx_single = (t <= T_chirp);
|
|||||||
t_single = t(idx_single);
|
t_single = t(idx_single);
|
||||||
tx_mask_single = tx_mask(idx_single);
|
tx_mask_single = tx_mask(idx_single);
|
||||||
|
|
||||||
fig_single = visualize_tx_waveform(t_single, RadarParams.Waveform.Timing, RadarParams.Waveform.fc, RadarParams.Waveform.f_start, RadarParams.Waveform.Slope, tx_mask_single, RadarParams.Waveform.nonideal.peak_phase_error, RadarParams.Waveform.nonideal.f_ripple);
|
fig_single = [];
|
||||||
|
if PlotToggle.tx_single
|
||||||
|
fig_single = visualize_tx_waveform(t_single, RadarParams.Waveform.Timing, RadarParams.Waveform.fc, RadarParams.Waveform.f_start, RadarParams.Waveform.Slope, tx_mask_single, RadarParams.Waveform.nonideal.peak_phase_error, RadarParams.Waveform.nonideal.f_ripple);
|
||||||
|
end
|
||||||
|
|
||||||
% [Figure 2] 다중 처프 프레임 시퀀스 및 MIMO 변조 확인
|
% [Figure 2] 다중 처프 프레임 시퀀스 및 MIMO 변조 확인
|
||||||
% (수정됨: mimoMode와 NumTx 변수를 추가로 전달)
|
% (수정됨: mimoMode와 NumTx 변수를 추가로 전달)
|
||||||
fig_multi = visualize_multi_tx_waveform(t, RadarParams.Waveform.Timing, RadarParams.Waveform.fc, RadarParams.Waveform.f_start, RadarParams.Waveform.Slope, tx_mask, NumChirps, RadarParams.Waveform.mimoMode, NumTx);
|
fig_multi = [];
|
||||||
|
if PlotToggle.tx_multi
|
||||||
|
fig_multi = visualize_multi_tx_waveform(t, RadarParams.Waveform.Timing, RadarParams.Waveform.fc, RadarParams.Waveform.f_start, RadarParams.Waveform.Slope, tx_mask, NumChirps, RadarParams.Waveform.mimoMode, NumTx);
|
||||||
|
end
|
||||||
|
|
||||||
% --- [ Figure 3, 4: 안테나 방사 패턴 단면 도시 ] ---
|
% --- [ Figure 3, 4: 안테나 방사 패턴 단면 도시 ] ---
|
||||||
% 'TX'와 'RX'라는 이름을 넘겨주어 그래프 타이틀과 범례를 구분합니다.
|
% 'TX'와 'RX'라는 이름을 넘겨주어 그래프 타이틀과 범례를 구분합니다.
|
||||||
fig_tx_ant = visualize_antenna_pattern(TxPattern, 'TX');
|
fig_tx_ant = [];
|
||||||
fig_rx_ant = visualize_antenna_pattern(RxPattern, 'RX');
|
if PlotToggle.tx_antenna
|
||||||
|
fig_tx_ant = visualize_antenna_pattern(TxPattern, 'TX');
|
||||||
|
end
|
||||||
|
|
||||||
|
fig_rx_ant = [];
|
||||||
|
if PlotToggle.rx_antenna
|
||||||
|
fig_rx_ant = visualize_antenna_pattern(RxPattern, 'RX');
|
||||||
|
end
|
||||||
|
|
||||||
% [Figure 5] Range-Doppler Map 시각화 (NumRx NCI RDM)
|
% [Figure 5] Range-Doppler Map 시각화 (NumRx NCI RDM)
|
||||||
fig_rd_map = visualize_rd_map_with_spurs(target_rd_map, r_axis, v_axis, Target, RadarParams.SpurParams, RadarParams, RadarParams.Waveform.Slope);
|
fig_rd_map = [];
|
||||||
|
if PlotToggle.rd_map
|
||||||
|
fig_rd_map = visualize_rd_map_with_spurs(target_rd_map, r_axis, v_axis, Target, RadarParams.SpurParams, RadarParams, RadarParams.Waveform.Slope);
|
||||||
|
end
|
||||||
|
|
||||||
% [Figure 6] CFAR 탐지 결과 시각화 (2D MAP 위 원 마커)
|
% [Figure 6] CFAR 탐지 결과 시각화 (2D MAP 위 원 마커)
|
||||||
fig_cfar = figure('Name', 'CFAR Detections on NCI RDM');
|
fig_cfar = [];
|
||||||
imagesc(r_axis, v_axis, 20*log10(abs(target_rd_map)));
|
if PlotToggle.cfar
|
||||||
axis xy;
|
fig_cfar = figure('Name', 'CFAR Detections on NCI RDM');
|
||||||
colormap(jet);
|
imagesc(r_axis, v_axis, 20*log10(abs(target_rd_map)));
|
||||||
colorbar;
|
axis xy;
|
||||||
xlabel('Range (m)');
|
colormap(jet);
|
||||||
ylabel('Velocity (m/s)');
|
colorbar;
|
||||||
title('CFAR Detections (Circle Markers)');
|
xlabel('Range (m)');
|
||||||
hold on;
|
ylabel('Velocity (m/s)');
|
||||||
|
title('CFAR Detections (Circle Markers)');
|
||||||
|
hold on;
|
||||||
|
|
||||||
if ~isempty(cfar_detections)
|
if ~isempty(cfar_detections)
|
||||||
det_d_idx = cfar_detections(:,1); % Doppler bin index
|
det_d_idx = cfar_detections(:,1); % Doppler bin index
|
||||||
det_r_idx = cfar_detections(:,2); % Range bin index
|
det_r_idx = cfar_detections(:,2); % Range bin index
|
||||||
det_r = r_axis(det_r_idx);
|
det_r = r_axis(det_r_idx);
|
||||||
det_v = v_axis(det_d_idx);
|
det_v = v_axis(det_d_idx);
|
||||||
plot(det_r, det_v, 'wo', 'MarkerSize', 7, 'LineWidth', 1.5);
|
plot(det_r, det_v, 'wo', 'MarkerSize', 7, 'LineWidth', 1.5);
|
||||||
|
end
|
||||||
|
|
||||||
|
hold off;
|
||||||
end
|
end
|
||||||
|
|
||||||
hold off;
|
% ================= Coverage Analysis (Performance Analysis) =================
|
||||||
|
% [Figure 7] Angular Coverage 분석: 2D 안테나 패턴 기반 효율적 SNR 계산
|
||||||
|
% 거리 100m, RCS 0 dBsm 표적의 각도별 SNR 분석
|
||||||
|
snr_coverage_2d = [];
|
||||||
|
azimuth_deg = [];
|
||||||
|
elevation_deg = [];
|
||||||
|
coverage_info = [];
|
||||||
|
fig_coverage = [];
|
||||||
|
if PlotToggle.coverage
|
||||||
|
[snr_coverage_2d, azimuth_deg, elevation_deg, coverage_info, fig_coverage] = analyze_coverage(RadarParams, RadarParams.Coverage.R_max, RadarParams.Coverage.RCS_dBsm);
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user