/* -*- c++ -*- */ /* * Copyright 2012,2013 Free Software Foundation, Inc. * * This file is part of GNU Radio * * SPDX-License-Identifier: GPL-3.0-or-later * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "ofdm_chanest_vcvc_impl.h" #include <gnuradio/io_signature.h> namespace gr { namespace digital { ofdm_chanest_vcvc::sptr ofdm_chanest_vcvc::make(const std::vector<gr_complex>& sync_symbol1, const std::vector<gr_complex>& sync_symbol2, int n_data_symbols, int eq_noise_red_len, int max_carr_offset, bool force_one_sync_symbol) { return gnuradio::make_block_sptr<ofdm_chanest_vcvc_impl>(sync_symbol1, sync_symbol2, n_data_symbols, eq_noise_red_len, max_carr_offset, force_one_sync_symbol); } ofdm_chanest_vcvc_impl::ofdm_chanest_vcvc_impl( const std::vector<gr_complex>& sync_symbol1, const std::vector<gr_complex>& sync_symbol2, int n_data_symbols, int eq_noise_red_len, int max_carr_offset, bool force_one_sync_symbol) : block("ofdm_chanest_vcvc", io_signature::make(1, 1, sizeof(gr_complex) * sync_symbol1.size()), io_signature::make(1, 2, sizeof(gr_complex) * sync_symbol1.size())), d_fft_len(sync_symbol1.size()), d_n_data_syms(n_data_symbols), d_n_sync_syms(1), d_eq_noise_red_len(eq_noise_red_len), d_ref_sym((!sync_symbol2.empty() && !force_one_sync_symbol) ? sync_symbol2 : sync_symbol1), d_corr_v(sync_symbol2), d_known_symbol_diffs(0, 0), d_new_symbol_diffs(0, 0), d_first_active_carrier(0), d_last_active_carrier(sync_symbol2.size() - 1), d_interpolate(false) { // Set index of first and last active carrier for (int i = 0; i < d_fft_len; i++) { if (d_ref_sym[i] != gr_complex(0, 0)) { d_first_active_carrier = i; break; } } for (int i = d_fft_len - 1; i >= 0; i--) { if (d_ref_sym[i] != gr_complex(0, 0)) { d_last_active_carrier = i; break; } } // Sanity checks if (!sync_symbol2.empty()) { if (sync_symbol1.size() != sync_symbol2.size()) { throw std::invalid_argument("sync symbols must have equal length."); } if (!force_one_sync_symbol) { d_n_sync_syms = 2; } } else { if (sync_symbol1[d_first_active_carrier + 1] == gr_complex(0, 0)) { d_last_active_carrier++; d_interpolate = true; } } // Set up coarse freq estimation info // Allow all possible values: d_max_neg_carr_offset = -d_first_active_carrier; d_max_pos_carr_offset = d_fft_len - d_last_active_carrier - 1; if (max_carr_offset != -1) { d_max_neg_carr_offset = std::max(-max_carr_offset, d_max_neg_carr_offset); d_max_pos_carr_offset = std::min(max_carr_offset, d_max_pos_carr_offset); } // Carrier offsets must be even if (d_max_neg_carr_offset % 2) d_max_neg_carr_offset++; if (d_max_pos_carr_offset % 2) d_max_pos_carr_offset--; if (d_n_sync_syms == 2) { for (int i = 0; i < d_fft_len; i++) { if (sync_symbol1[i] == gr_complex(0, 0)) { d_corr_v[i] = gr_complex(0, 0); } else { d_corr_v[i] /= sync_symbol1[i]; } } } else { d_corr_v.resize(0, 0); d_known_symbol_diffs.resize(d_fft_len, 0); d_new_symbol_diffs.resize(d_fft_len, 0); for (int i = d_first_active_carrier; i < d_last_active_carrier - 2 && i < d_fft_len - 2; i += 2) { d_known_symbol_diffs[i] = std::norm(sync_symbol1[i] - sync_symbol1[i + 2]); } } set_output_multiple(d_n_data_syms); set_relative_rate((uint64_t)d_n_data_syms, (uint64_t)(d_n_data_syms + d_n_sync_syms)); set_tag_propagation_policy(TPP_DONT); } ofdm_chanest_vcvc_impl::~ofdm_chanest_vcvc_impl() {} void ofdm_chanest_vcvc_impl::forecast(int noutput_items, gr_vector_int& ninput_items_required) { ninput_items_required[0] = (noutput_items / d_n_data_syms) * (d_n_data_syms + d_n_sync_syms); } int ofdm_chanest_vcvc_impl::get_carr_offset(const gr_complex* sync_sym1, const gr_complex* sync_sym2) { int carr_offset = 0; if (!d_corr_v.empty()) { // Use Schmidl & Cox method float Bg_max = 0; // g here is 2g in the paper for (int g = d_max_neg_carr_offset; g <= d_max_pos_carr_offset; g += 2) { gr_complex tmp = gr_complex(0, 0); for (int k = 0; k < d_fft_len; k++) { if (d_corr_v[k] != gr_complex(0, 0)) { tmp += std::conj(sync_sym1[k + g]) * std::conj(d_corr_v[k]) * sync_sym2[k + g]; } } if (std::abs(tmp) > Bg_max) { Bg_max = std::abs(tmp); carr_offset = g; } } } else { // Correlate std::fill(d_new_symbol_diffs.begin(), d_new_symbol_diffs.end(), 0); for (int i = 0; i < d_fft_len - 2; i++) { d_new_symbol_diffs[i] = std::norm(sync_sym1[i] - sync_sym1[i + 2]); } float sum; float max = 0; for (int g = d_max_neg_carr_offset; g <= d_max_pos_carr_offset; g += 2) { sum = 0; for (int j = 0; j < d_fft_len; j++) { if (d_known_symbol_diffs[j]) { sum += (d_known_symbol_diffs[j] * d_new_symbol_diffs[j + g]); } if (sum > max) { max = sum; carr_offset = g; } } } } return carr_offset; } void ofdm_chanest_vcvc_impl::get_chan_taps(const gr_complex* sync_sym1, const gr_complex* sync_sym2, int carr_offset, std::vector<gr_complex>& taps) { const gr_complex* sym = ((d_n_sync_syms == 2) ? sync_sym2 : sync_sym1); std::fill(taps.begin(), taps.end(), gr_complex(0, 0)); int loop_start = 0; int loop_end = d_fft_len; if (carr_offset > 0) { loop_start = carr_offset; } else if (carr_offset < 0) { loop_end = d_fft_len + carr_offset; } for (int i = loop_start; i < loop_end; i++) { if ((d_ref_sym[i - carr_offset] != gr_complex(0, 0))) { taps[i - carr_offset] = sym[i] / d_ref_sym[i - carr_offset]; } } if (d_interpolate) { for (int i = d_first_active_carrier + 1; i < d_last_active_carrier; i += 2) { taps[i] = taps[i - 1]; } taps[d_last_active_carrier] = taps[d_last_active_carrier - 1]; } if (d_eq_noise_red_len) { // TODO // 1) IFFT // 2) Set all elements > d_eq_noise_red_len to zero // 3) FFT } } // Operates on a per-frame basis int ofdm_chanest_vcvc_impl::general_work(int noutput_items, gr_vector_int& ninput_items, gr_vector_const_void_star& input_items, gr_vector_void_star& output_items) { const gr_complex* in = (const gr_complex*)input_items[0]; gr_complex* out = (gr_complex*)output_items[0]; const int framesize = d_n_sync_syms + d_n_data_syms; // Channel info estimation int carr_offset = get_carr_offset(in, in + d_fft_len); std::vector<gr_complex> chan_taps(d_fft_len, 0); get_chan_taps(in, in + d_fft_len, carr_offset, chan_taps); add_item_tag(0, nitems_written(0), pmt::string_to_symbol("ofdm_sync_carr_offset"), pmt::from_long(carr_offset)); add_item_tag(0, nitems_written(0), pmt::string_to_symbol("ofdm_sync_chan_taps"), pmt::init_c32vector(d_fft_len, chan_taps)); // Copy data symbols if (output_items.size() == 2) { gr_complex* out_chantaps = ((gr_complex*)output_items[1]); memcpy((void*)out_chantaps, (void*)&chan_taps[0], sizeof(gr_complex) * d_fft_len); produce(1, 1); } memcpy((void*)out, (void*)&in[d_n_sync_syms * d_fft_len], sizeof(gr_complex) * d_fft_len * d_n_data_syms); // Propagate tags std::vector<gr::tag_t> tags; get_tags_in_range(tags, 0, nitems_read(0), nitems_read(0) + framesize); for (unsigned t = 0; t < tags.size(); t++) { int offset = tags[t].offset - nitems_read(0); if (offset < d_n_sync_syms) { offset = 0; } else { offset -= d_n_sync_syms; } tags[t].offset = offset + nitems_written(0); add_item_tag(0, tags[t]); } produce(0, d_n_data_syms); consume_each(framesize); return WORK_CALLED_PRODUCE; } } /* namespace digital */ } /* namespace gr */