├── .gitattributes ├── .gitignore ├── OMP2.m ├── README.md ├── data.txt ├── gen_channel.m ├── gen_channel_param2.m ├── gen_heatmap3D.m ├── gen_supp_from_radar2.m ├── main_simulation_eta.m ├── main_simulator.m ├── pass_channel.m ├── peakdetection2d.m └── radar_params.m /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.zip 2 | -------------------------------------------------------------------------------- /OMP2.m: -------------------------------------------------------------------------------- 1 | function [x, omega] = OMP2(b, A, S, supp) 2 | err= 1e-3; 3 | 4 | 5 | [N,K] = size(A); % N:dim of signal, K:#atoms in dictionary 6 | if (N ~= size(b)) 7 | error('Dimension not matched'); 8 | end 9 | 10 | x = zeros(K,1); 11 | if ~supp 12 | r = b; 13 | omega = []; 14 | A_omega = []; 15 | else 16 | x(supp, :) = A(:, supp) \ b; 17 | r = b - A * x; 18 | omega = supp; 19 | A_omega = A(:,supp); 20 | end 21 | 22 | while size(omega, 1) < S 23 | x_tmp = zeros(K,1); 24 | inds = setdiff([1:K],omega); 25 | 26 | x_tmp(inds) = A(:, inds)' * r; 27 | [~, ichosen] = max(abs(x_tmp)); 28 | omega = [omega; ichosen]; 29 | 30 | x = zeros(K, 1); 31 | x(omega, :) = A(:, omega) \ b; 32 | r = b - A(:, omega) * x(omega, :); 33 | 34 | if norm(r) <=err*norm(b) 35 | break 36 | end 37 | 38 | end 39 | 40 | end -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sensing Aided OTFS Channel Estimation for Massive MIMO Systems 2 | 3 | This is a Python code package related to the following article: 4 | S. Jiang and A. Alkhateeb, "[Sensing Aided OTFS Channel Estimation for Massive MIMO Systems](https://arxiv.org/abs/2209.11321)," in IEEE ICC Workshops, 2023 5 | 6 | # Abstract of the Article 7 | Orthogonal time frequency space (OTFS) modulation has the potential to enable robust communications in highlymobile scenarios. Estimating the channels for OTFS systems, however, is associated with high pilot signaling overhead that scales with the maximum delay and Doppler spreads. This becomes particularly challenging for massive MIMO systems where the overhead also scales with the number of antennas. An important observation however is that the delay, Doppler, and angle of departure/arrival information are directly related to the distance, velocity, and direction information of the mobile user and the various scatterers in the environment. With this motivation, we propose to leverage radar sensing to obtain this information about the mobile users and scatterers in the environment and leverage it to aid the OTFS channel estimation in massive MIMO systems. As one approach to realize this vision, this paper formulates the OTFS channel estimation problem in massive MIMO systems as a sparse recovery problem and utilizes the radar sensing information to determine the support (locations of the non-zero delay-Doppler taps). The proposed radar sensing aided sparse recovery algorithm is evaluated based on an accurate 3D raytracing framework with co-existing radar and communication data. The results show that the developed sensing-aided solution consistently outperforms the standard sparse recovery algorithms (that do not leverage radar sensing data) and leads to a significant reduction in the pilot overhead, which highlights a promising direction for OTFS based massive MIMO systems.# License and Referencing 8 | 9 | # License and Referencing 10 | This code package is licensed under a [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-nc-sa/4.0/). 11 | If you in any way use this code for research that results in publications, please cite our original article: 12 | > S. Jiang and A. Alkhateeb, "Sensing Aided OTFS Channel Estimation for Massive MIMO Systems," in IEEE ICC Workshops, 2023 13 | -------------------------------------------------------------------------------- /data.txt: -------------------------------------------------------------------------------- 1 | https://www.dropbox.com/s/kdngzmziznxwxvf/radar_signal_generator.zip?dl=0 -------------------------------------------------------------------------------- /gen_channel.m: -------------------------------------------------------------------------------- 1 | function [CH_OTFS_DD, CH_OTFS_TD] = gen_channel(channel_param, sys_param) 2 | % for converting the actual delay (sec) and deoppler frequency (Hz) into delay and doppler tap index --> refer to (2.11) and (2.14) in the book 3 | 4 | %% read system params 5 | M = sys_param.fft_size + sys_param.cp_size; 6 | N = sys_param.num_symbol; 7 | d = sys_param.antenna_interval; 8 | num_tx = sys_param.num_tx; 9 | 10 | num_path = size(channel_param.pathlosses, 2); 11 | 12 | %% discrete-time baseband 13 | z=exp(1j*2*pi/N/M); 14 | delay_spread = channel_param.delay_taps(end); 15 | 16 | 17 | %% time domain channel 18 | gs=zeros(num_tx, delay_spread+1, N*M); 19 | 20 | for p=1:num_path 21 | pathloss = channel_param.pathlosses(p); 22 | delay_tap = channel_param.delay_taps(p); 23 | doppler_tap = channel_param.doppler_taps(p); 24 | AoD = channel_param.AoDs(p); 25 | 26 | array_response = exp(-1j * 2 * pi * (0:num_tx-1)' * cos(AoD) * d); %% column vector 27 | tmp = pathloss * z.^( doppler_tap * ((0:N*M-1) - delay_tap) ) .* array_response; 28 | gs(:, delay_tap+1, :) = gs(:, delay_tap+1, :) + reshape(tmp, [size(tmp,1), 1, size(tmp,2)]); 29 | end 30 | 31 | CH_OTFS_TD = gs; 32 | 33 | %% delay-doppler domain channel 34 | H_dd_tmp = zeros(num_tx, M, N); 35 | for p=1:num_path 36 | pathloss = channel_param.pathlosses(p); 37 | delay_tap = channel_param.delay_taps(p); 38 | doppler_tap = channel_param.doppler_taps(p); 39 | AoD = channel_param.AoDs(p); 40 | 41 | array_response = exp(-1j * 2 * pi * (0:num_tx-1)' * cos(AoD) * d); 42 | tmp = pathloss .* array_response; 43 | H_dd_tmp(:, delay_tap+1, doppler_tap+1) = H_dd_tmp(:, delay_tap+1, doppler_tap+1) + tmp; 44 | end 45 | CH_OTFS_DD = H_dd_tmp; 46 | 47 | end 48 | -------------------------------------------------------------------------------- /gen_channel_param2.m: -------------------------------------------------------------------------------- 1 | function channel_param = gen_channel_param2(sys_param, foldername, filename) 2 | 3 | load([foldername, filename]); 4 | channel = channels{2}.paths; 5 | 6 | fn = fieldnames(channel); 7 | for k=1:numel(fn) 8 | tmp = channel.(fn{k}); 9 | channel.(fn{k}) = tmp(1:3); 10 | end 11 | 12 | 13 | M = sys_param.fft_size + sys_param.cp_size; 14 | M_ = sys_param.fft_size; 15 | C = sys_param.cp_size; 16 | N = sys_param.num_symbol; 17 | num_tx = sys_param.num_tx; 18 | delay_resolution = sys_param.delay_resolution; 19 | Doppler_resolution = sys_param.Doppler_resolution; 20 | c = sys_param.c; 21 | fc = sys_param.fc; 22 | 23 | 24 | channel_param.delay_taps = round((channel.ToA - min(channel.ToA))/ delay_resolution); 25 | channel_param.doppler_taps = mod(round(channel.Doppler_vel / c * fc / Doppler_resolution), N); 26 | channel_param.AoDs = channel.DoD_phi / 180 * pi; 27 | channel_param.pathlosses = sqrt(1e-3*(10.^(0.1*(channel.power)))) .* exp(1j.*(channel.phase/180*pi)); 28 | channel_param.pathlosses = channel_param.pathlosses / sqrt(sum(abs(channel_param.pathlosses).^2)); 29 | 30 | 31 | [~,sortIdx] = sort(channel.ToA,'ascend'); 32 | channel_param.delay_taps = channel_param.delay_taps(sortIdx); 33 | channel_param.doppler_taps = channel_param.doppler_taps(sortIdx); 34 | channel_param.AoDs = channel_param.AoDs(sortIdx); 35 | channel_param.pathlosses = channel_param.pathlosses(sortIdx); 36 | 37 | end -------------------------------------------------------------------------------- /gen_heatmap3D.m: -------------------------------------------------------------------------------- 1 | function [heatmap3D, x_angle_mat, R_mat, xx, yy] = gen_heatmap3D(param_file, RX_signal, angle_fft_size) 2 | run(param_file) 3 | 4 | %% Angle-Range x-y axes 5 | % Radar KPI 6 | Wavelength = physconst('LightSpeed')/params.fc; 7 | Delta_r = physconst('LightSpeed')/(2*params.BW_active); 8 | Max_r = Delta_r*(params.N_ADC-1); 9 | Delta_v = Wavelength/(2*params.T_PRI*params.N_loop); 10 | Max_v = [-1 ((params.N_loop-2)/params.N_loop)]*(Wavelength/(4*params.T_PRI)); 11 | 12 | % Range-Angle Axes 13 | antDis = params.ant_spacing_UE; 14 | 15 | cos_theta = fliplr(linspace((+0.5/antDis),-(0.5/antDis),(angle_fft_size+1))); 16 | cos_theta = cos_theta(1:angle_fft_size); 17 | sine_theta = sqrt(1-cos_theta.^2); 18 | 19 | range_indices = 0:1:(params.N_ADC-1); 20 | [R_mat, cos_theta_mat] = meshgrid((range_indices)*Delta_r,cos_theta); 21 | 22 | x_angle_mat = acosd(cos_theta_mat); 23 | 24 | %% Range-Doppler axes 25 | [xx, yy] = meshgrid(Max_v(1):Delta_v:Max_v(2), Delta_r*(0:params.N_ADC-1)); 26 | 27 | %% 3D heatmap 28 | range = fft(RX_signal, size(RX_signal, 1), 1); 29 | %range = range - mean(range, 2); 30 | range_doppler = fft(range, size(RX_signal, 2), 2); 31 | range_doppler_angle = fft(range_doppler, angle_fft_size, 3); 32 | 33 | zero_doppler_bins = sort(mod(0, angle_fft_size) + 1); 34 | 35 | heatmap3D = fftshift(range_doppler_angle, 2); 36 | heatmap3D = fftshift(heatmap3D, 3); 37 | heatmap3D = abs(heatmap3D).^2; 38 | 39 | end -------------------------------------------------------------------------------- /gen_supp_from_radar2.m: -------------------------------------------------------------------------------- 1 | function supp = gen_supp_from_radar2(sys_param, bf_codebook, foldername, filename, snr_db) 2 | M = sys_param.fft_size + sys_param.cp_size; 3 | M_ = sys_param.fft_size; 4 | C = sys_param.cp_size; 5 | N = sys_param.num_symbol; 6 | num_tx = sys_param.num_tx; 7 | delay_resolution = sys_param.delay_resolution; 8 | Doppler_resolution = sys_param.Doppler_resolution; 9 | c = sys_param.c; 10 | fc = sys_param.fc; 11 | 12 | 13 | load([foldername, filename]); 14 | 15 | %%%%%%%%%%% Add Noise %%%%%%%%%%%%%%% 16 | % clustter removal 17 | RX_signal = RX_signal - mean(RX_signal, 2); 18 | % add noise 19 | RX_signal = RX_signal / norm(RX_signal(:)); 20 | snr = power(10, snr_db/10); 21 | noise = (randn(size(RX_signal))+1i*randn(size(RX_signal))) *sqrt(1/2) / sqrt(numel(RX_signal)) / sqrt(snr); 22 | RX_signal = RX_signal + noise; 23 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 24 | 25 | param_file = 'radar_params.m'; 26 | [heatmap3D, x_angle_mat, R_mat, xx, yy] = gen_heatmap3D(param_file, RX_signal, 128); 27 | 28 | static_doppler_bin = size(heatmap3D,2) / 2 + 1; 29 | 30 | dynamic_heatmap3D = heatmap3D; 31 | dynamic_heatmap3D(:, static_doppler_bin, :) = 0; 32 | 33 | RA_map = squeeze(sum(dynamic_heatmap3D, 2)); 34 | RA_peak = peakdetection2d(RA_map, [5,5], [3,3], 10); 35 | %%%%%%%%%%%%%%%%% 36 | RA_peak_filt = colfilt(RA_peak, [5, 5], 'sliding', @max); 37 | RA_peak_filt = (RA_peak==RA_peak_filt) & (RA_peak > (max(RA_peak, [], 2)/2)); 38 | %%%%%%%%%%%%%%%%% 39 | RA_peak_idx = find(RA_peak_filt); 40 | [i1, i2] = ind2sub(size(RA_peak), RA_peak_idx); 41 | RA_peak_pwr = RA_peak(RA_peak_idx); 42 | 43 | all_peak = []; 44 | for p=1:size(RA_peak_idx, 1) 45 | Doppler_vec = squeeze(dynamic_heatmap3D(i1(p), :, i2(p))); 46 | doppler_peak = peakdetection2d(Doppler_vec, [0,10], [0,5], 10); 47 | 48 | doppler_peak_filt = colfilt(doppler_peak, [1, 5], 'sliding', @max); 49 | doppler_peak_filt = (doppler_peak==doppler_peak_filt) & (doppler_peak > (max(doppler_peak, [], 2)/2)); 50 | 51 | doppler_peak_idx = find(doppler_peak_filt); 52 | doppler_peak_pwr = doppler_peak(doppler_peak_idx); 53 | for dp=1:size(doppler_peak_idx, 2) 54 | all_peak = [all_peak; [i1(p), doppler_peak_idx(dp), i2(p), RA_peak_pwr(p), doppler_peak_pwr(dp)]]; 55 | end 56 | 57 | end 58 | 59 | all_peak = sortrows(all_peak, [4,5], "descend"); 60 | 61 | 62 | range = R_mat(1, all_peak(1:end,1)); 63 | Doppler_vel = xx(1, all_peak(1:end,2)); 64 | DoD_phi = x_angle_mat(all_peak(1:end,3),2); 65 | 66 | range = range - min(range(:)); 67 | 68 | supp = []; 69 | for p=1:size(range,2) 70 | AoD = DoD_phi(p) / 180 * pi; 71 | array_response = exp(-1j * 2 * pi * (0:num_tx-1) * cos(AoD) * sys_param.antenna_interval).'; 72 | angle_response = bf_codebook * array_response; 73 | [angle_response_sort, angle_response_sort_idx] = sort(angle_response, 'descend'); 74 | [~, max_angle_response_idx] = max(angle_response); 75 | angle_response_sort_idx = mod(max_angle_response_idx - 1 + (-5:5), num_tx) + 1;% (-5:5) 76 | for angle_idx = angle_response_sort_idx 77 | delay = range(p) / c / delay_resolution; 78 | 79 | delay_taps = round(delay); 80 | 81 | doppler = Doppler_vel(p) / c * fc / Doppler_resolution; 82 | 83 | doppler_taps = mod(round(doppler),N); 84 | for delay_tap=delay_taps 85 | for doppler_tap=doppler_taps 86 | idx = num_tx*M*doppler_taps + num_tx*delay_tap + angle_idx; 87 | supp = [supp, idx]; 88 | end 89 | end 90 | end 91 | end 92 | supp = unique(supp','stable'); 93 | supp = sort(supp); 94 | 95 | 96 | end -------------------------------------------------------------------------------- /main_simulation_eta.m: -------------------------------------------------------------------------------- 1 | clear;%clc; 2 | rng(2022,'twister'); 3 | tic 4 | eta_all = 0.05:0.05:0.3; 5 | 6 | %% simulation parameters 7 | sim_param.comm_foldername = './radar_signal_generator/Raytracing_scenarios/6GHz_NLoS_fast/'; 8 | sim_param.radar_foldername = './radar_signal_generator/samples/28GHz_NLoS_fast/'; 9 | sim_param.filename = '0'; 10 | sim_param.guard_tao = 20; 11 | sim_param.support_size = 64; 12 | 13 | sim_param.snr_db = 10; 14 | % sim_param.eta = 0.04; % 0.5; 15 | 16 | %% system parameters 17 | sys_param.fft_size = 256; 18 | sys_param.num_symbol = 14; 19 | sys_param.cp_size = sys_param.fft_size * 0.25; 20 | 21 | sys_param.num_tx = 32; 22 | sys_param.antenna_interval = 0.5; 23 | sys_param.delta_f=15e3; 24 | sys_param.fc=6e9; 25 | sys_param.c = 299792458; 26 | sys_param.delay_resolution = 1/(sys_param.fft_size*sys_param.delta_f); 27 | sys_param.T = 1 / sys_param.delta_f * (sys_param.fft_size + sys_param.cp_size) / sys_param.fft_size; 28 | sys_param.Doppler_resolution = 1/(sys_param.num_symbol*sys_param.T); 29 | 30 | 31 | list_of_files = dir([sim_param.radar_foldername, '*.mat']); 32 | num_loop = size(list_of_files, 1); 33 | 34 | NMSE_OMP_angle_domain_all = zeros(size(eta_all,2), num_loop); 35 | NMSE_OMP_angle_domain_radar_all = zeros(size(eta_all,2), num_loop); 36 | 37 | for eta_idx = 1:size(eta_all, 2) 38 | sim_param.eta = eta_all(eta_idx); 39 | for loop = 1:num_loop 40 | loop 41 | sim_param.filename = list_of_files(loop).name; 42 | NMSE = main_simulator(sim_param, sys_param); 43 | 44 | NMSE_OMP_angle_domain_all(eta_idx, loop) = NMSE(1); 45 | NMSE_OMP_angle_domain_radar_all(eta_idx, loop) = NMSE(2); 46 | 47 | end 48 | end 49 | 50 | NMSE_OMP_angle_domain_avg = mean(NMSE_OMP_angle_domain_all, 2) 51 | NMSE_OMP_angle_domain_radar_avg = mean(NMSE_OMP_angle_domain_radar_all, 2) 52 | 53 | toc 54 | 55 | %% 56 | figure; 57 | semilogy(eta_all, NMSE_OMP_angle_domain_avg, 'r-s') 58 | hold on 59 | semilogy(eta_all, NMSE_OMP_angle_domain_radar_avg, 'k-s') 60 | 61 | xlabel("eta") 62 | ylabel("NMSE") 63 | legend("OMP (angle domain)", "proposed") 64 | grid on 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /main_simulator.m: -------------------------------------------------------------------------------- 1 | function NMSE = main_simulator(sim_param, sys_param) 2 | %% Simulation parameters 3 | comm_foldername = sim_param.comm_foldername; %' 6GHz'; 4 | radar_foldername = sim_param.radar_foldername; %' 6GHz'; 5 | filename = sim_param.filename; %' 546'; 6 | snr_db = sim_param.snr_db; 7 | eta = sim_param.eta; % 0.5; 8 | guard_tao = sim_param.guard_tao; % 14 -> The delay guard needs to be larger or equal to the maximum delay tap 9 | support_size = sim_param.support_size; 10 | % rng(1,'twister'); 11 | 12 | M = sys_param.fft_size + sys_param.cp_size; 13 | M_ = sys_param.fft_size; 14 | C = sys_param.cp_size; 15 | N = sys_param.num_symbol; 16 | num_tx = sys_param.num_tx; 17 | z = exp(1j*2*pi/N/M); 18 | 19 | %% Generate channel parameters 20 | channel_param = gen_channel_param2(sys_param, comm_foldername, filename); 21 | %% Generate channel 22 | [CH_OTFS_DD2, CH_OTFS_TD] = gen_channel(channel_param, sys_param); 23 | 24 | %% Generate data Symbol 25 | x = sign(randn(sys_param.num_tx,sys_param.fft_size,sys_param.num_symbol)); 26 | 27 | %% Generate pilot Symbol 28 | num_pilot_tao = eta * sys_param.fft_size; 29 | num_pilot_tao = round(num_pilot_tao); 30 | % num_pilot_tao = sys_param.num_tx * ceil(num_pilot_tao/sys_param.num_tx); 31 | sys_param.num_pilot_tao = num_pilot_tao; 32 | num_pilot_v = sys_param.num_symbol; 33 | sys_param.num_pilot_v= num_pilot_v; 34 | 35 | % p_Bernoul= sign(randn(sys_param.num_tx,num_pilot_tao,num_pilot_v)); 36 | 37 | p_Bernoul= sqrt(1/2) * (randn(sys_param.num_tx,num_pilot_tao,num_pilot_v) + 1j*randn(sys_param.num_tx,num_pilot_tao,num_pilot_v)); 38 | 39 | %% Generate transmit signal 40 | x_p_input = x; % DD domain data 41 | x_p_input(:, 1:num_pilot_tao, 1:num_pilot_v) = p_Bernoul; % add pilot signal 42 | 43 | x_p_input(:, num_pilot_tao+1:num_pilot_tao+guard_tao,:) = 0; % add guard symbol around pilot 44 | x_p_input(:, end-guard_tao+1:end,:) = 0; % add guard symbol around pilot 45 | 46 | x_p_input = cat(2, x_p_input(:,end-C+1:end,:), x_p_input); 47 | 48 | Stx_symbol = ifft(x_p_input, [], 3) * sqrt(sys_param.num_symbol); % convert to delay-time domain 49 | OTFS_signal = Stx_symbol; 50 | 51 | %% Generate noise 52 | Es_No = power(10, snr_db/10); 53 | n = (randn(sys_param.fft_size,sys_param.num_symbol)+1i*randn(sys_param.fft_size,sys_param.num_symbol))*sqrt(1/2)/sqrt(Es_No); 54 | 55 | %% Pass the Channel 56 | Rx_signal_OTFS_Bernoul = pass_channel(OTFS_signal, CH_OTFS_TD, sys_param); % delay time domain receive signal 57 | Rx_signal_OTFS_Bernoul = Rx_signal_OTFS_Bernoul(C+1:end,:); 58 | 59 | %% Add noise 60 | % Rx_signal_OTFS_Bernoul_no_noise = Rx_signal_OTFS_Bernoul; 61 | Rx_signal_OTFS_Bernoul = Rx_signal_OTFS_Bernoul + n; 62 | Rx_signal_OTFS_Bernoul_dd = fft(Rx_signal_OTFS_Bernoul, [], 2) / sqrt(N); 63 | 64 | %% construct the pilot matrix -> this takes 0.17 seconds for (M, N) = (128, 4) debug2 -> this takes 10 seconds for (M, N) = (256, 14) [200 times faster than the initial version] 65 | pilot = zeros(size(x_p_input)); 66 | pilot(:, C+1:C+num_pilot_tao, 1:num_pilot_v) = p_Bernoul; 67 | 68 | Z = zeros(M*N, N*M); 69 | P = zeros(M*N, num_tx, N*M); 70 | 71 | N1 = reshape(0:N-1, N,1,1,1); 72 | M2 = reshape(0:M-1, 1,M,1,1); 73 | N3 = reshape(0:N-1, 1,1,N,1); 74 | M4 = reshape(0:M-1, 1,1,1,M); 75 | 76 | i = M*N1 + M2 + 1; 77 | i_p = M*N3 + M4 + 1; 78 | 79 | tmp = z .^ (mod(M2-M4, M) .* N3); 80 | tmp = repmat(tmp, N, 1, 1, 1); 81 | Z(i, i_p) = reshape(tmp, N*M, N*M); 82 | 83 | 84 | tmp = pilot(:, mod(M2-M4, M)+1, mod(N1-N3, N)+1); 85 | tmp = reshape(tmp, num_tx, M, M, N, N); 86 | tmp = permute(tmp, [4 2 1 5 3]); 87 | P(i, :, i_p) = reshape(tmp, [N*M, num_tx, N*M]); 88 | 89 | %% 90 | Z = reshape(Z, [size(Z, 1), 1, size(Z, 2)]); 91 | W = reshape(Z.*P, M*N, num_tx*M*N); 92 | 93 | %% extract the linear equation corresponding to the pilot 94 | W = reshape(W, M, N, num_tx, M, N); 95 | r_dd = reshape(Rx_signal_OTFS_Bernoul_dd, M_, N); 96 | % remove CP 97 | W = W(C+1:end, :, :, :, :); 98 | % extract pilot 99 | W = W(1:num_pilot_tao, 1:num_pilot_v, :, :, :); 100 | r_dd = r_dd(1:num_pilot_tao, 1:num_pilot_v); 101 | % reshape back 102 | W = reshape(W, num_pilot_tao*num_pilot_v, num_tx*M*N); 103 | r_dd = reshape(r_dd, num_pilot_tao*num_pilot_v, 1); 104 | 105 | 106 | %% Channel estimation -- OMP with angle domain 107 | beam_domain_pilot = reshape(W, size(W, 1), num_tx, []); 108 | beam_domain_pilot = ifft(beam_domain_pilot, [], 2) * sqrt(num_tx); 109 | beam_domain_pilot = reshape(beam_domain_pilot, size(W)); 110 | 111 | 112 | [h_dd_est_omp_, Omega2] = OMP2(r_dd, beam_domain_pilot, support_size, 0); %Omega = sort(Omega); 113 | h_dd_est_omp_ = ifft(reshape(h_dd_est_omp_, num_tx, []), [], 1) * sqrt(num_tx); 114 | 115 | H_DD_est_omp_ = reshape(h_dd_est_omp_, num_tx, M, N); 116 | NMSE_OMP_angle_domain = sum(sum(sum((abs(CH_OTFS_DD2-H_DD_est_omp_)).^2)))/sum(sum(sum((abs(CH_OTFS_DD2)).^2))); 117 | 118 | %% Channel estimation -- OMP with angle domain using radar sensing 119 | % calculate support 120 | bf_codebook = dftmtx(num_tx) / sqrt(num_tx); 121 | supp__ = gen_supp_from_radar2(sys_param, bf_codebook, radar_foldername, filename, snr_db); 122 | 123 | 124 | [h_dd_est_omp_radar, Omega3] = OMP2(r_dd, beam_domain_pilot, 2*size(supp__, 1), supp__); %min(2*size(supp__, 1), support_size) 125 | h_dd_est_omp_radar = ifft(reshape(h_dd_est_omp_radar, num_tx, []), [], 1) * sqrt(num_tx); 126 | H_DD_est_omp_radar = reshape(h_dd_est_omp_radar, num_tx, M, N); 127 | NMSE_OMP_angle_domain_radar = sum(sum(sum((abs(CH_OTFS_DD2-H_DD_est_omp_radar)).^2)))/sum(sum(sum((abs(CH_OTFS_DD2)).^2))); 128 | 129 | 130 | NMSE = [ 131 | NMSE_OMP_angle_domain 132 | NMSE_OMP_angle_domain_radar 133 | ]; 134 | end 135 | -------------------------------------------------------------------------------- /pass_channel.m: -------------------------------------------------------------------------------- 1 | function [RxSig]=pass_channel(Stx,CH_OTFS_TD,sys_param) 2 | 3 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 4 | % function [RxSig]=PassChannel(TxSig,CH_TD,Nfft,NumOFDMSyms) 5 | % 6 | % INPUTS: TxSig: transmit signal 7 | % CH_TD: Time invariant multipath delay Channel 8 | % Nfft: FFT size for OFDM operation 9 | % NumOFDMSyms: number of OFDM symbols in each subframe(TTI) 10 | % 11 | % OUTPUT: RxSig: Received signal after transmitting through the channel 12 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 13 | M = sys_param.fft_size + sys_param.cp_size; 14 | N = sys_param.num_symbol; 15 | num_tx = sys_param.num_tx; 16 | 17 | delay_spread = size(CH_OTFS_TD,2) - 1; 18 | 19 | stx = reshape(Stx, num_tx, M*N); % convert from time-delay domain to time domain 20 | 21 | %% Pass time domain channel 22 | RxSig=zeros(num_tx, M*N); 23 | for ell=0:delay_spread 24 | mask = ((0:N*M-1) >= ell); 25 | idx = max((1:N*M) - ell, 1); 26 | tmp = squeeze(CH_OTFS_TD(:, ell+1, :)) .* stx(:, idx) .*mask; 27 | RxSig(:, :) = RxSig(:, :) + tmp; 28 | end 29 | 30 | RxSig = squeeze(sum(RxSig, 1)); % summation over all transmit antennas 31 | RxSig = reshape(RxSig, M, N); % time-delay domain signal 32 | end 33 | -------------------------------------------------------------------------------- /peakdetection2d.m: -------------------------------------------------------------------------------- 1 | function peak = peakdetection2d(map, train_cell, guard_cell, threshold_factor) 2 | 3 | [Nr, Nc] = size(map); 4 | 5 | Tr = train_cell(1); 6 | Tc = train_cell(2); 7 | 8 | Gr = guard_cell(1); 9 | Gc = guard_cell(2); 10 | 11 | peak = zeros(size(map)); 12 | 13 | for i = Tr+Gr+1:(Nr-Tr-Gr) 14 | for j = Tc+Gc+1:(Nc-Tc-Gc) 15 | noise_level = zeros(1,1); 16 | for k = (i-Tr-Gr) : (i+Tr+Gr) 17 | for h = (j-Tc-Gc) : (j+Gc+Tc) 18 | if(abs(k-i)>Gr||abs(h-j)>Gc) 19 | noise_level = noise_level + map(k,h); 20 | end 21 | end 22 | end 23 | length = 2*(Tr+Gr)+1; 24 | width = 2*(Tc+Gc)+1; 25 | threshold = noise_level/(length*width-(2*Gr+1)*(2*Gc+1))*threshold_factor; 26 | peak(i,j) = (map(i,j)>threshold) * (map(i,j)/threshold); 27 | end 28 | end 29 | 30 | end -------------------------------------------------------------------------------- /radar_params.m: -------------------------------------------------------------------------------- 1 | % DeepMIMO parameters utilized in this example 2 | params.radiation_pattern = 0; 3 | params.activate_array_rotation = 0; 4 | 5 | % System parameters 6 | params.num_ant_BS = [1, 1, 1]; 7 | params.num_ant_UE = [16, 1, 1]; 8 | params.ant_spacing_BS = 0.5; 9 | params.ant_spacing_UE = 0.5; 10 | params.scene_frequency = 30; %Hz 11 | params.radar_channel_taps = 650; % Radar channel tap length to generate a time domain radar channel impulse response for one "chirp burst" 12 | params.pulse_shaping = 2; 13 | % params.rolloff_factor = 0.5; %not needed 14 | 15 | %Chirp configuration 16 | params.S = 30e12; 17 | params.Fs = 30e6; 18 | params.N_ADC = 512; 19 | params.N_loop = 256; % Number of chirp bursts (minimum of 1) 20 | params.T_idle = 0e-6; 21 | params.T_start = 0e-6; 22 | params.T_excess = 0e-6; 23 | params.duty_cycle = 1; 24 | % params.F0 = 77e9 - params.S*params.T_start; 25 | params.F0 = 28e9 - params.S*params.T_start; 26 | 27 | %Derived configuration 28 | params.T_active = params.N_ADC/params.Fs; 29 | params.T_ramp = params.T_start + params.T_active + params.T_excess; 30 | params.T_chirp = params.T_idle + params.T_ramp; 31 | % params.T_gap = params.T_chirp - params.T_active; 32 | params.T_gap = params.T_idle + params.T_start + params.T_excess; 33 | params.BW_active = params.S*params.T_active; 34 | params.BW_total = params.S*params.T_chirp; 35 | params.fc = params.F0 + params.S*((params.T_active/2)+params.T_start); 36 | params.f_step = params.S/params.Fs; 37 | params.T_PRI = params.radar_channel_taps/params.Fs; % Pulse repetition interval in seconds, for Doppler processing in FMCW radar 38 | --------------------------------------------------------------------------------