diff --git a/lib/include/srsran/phy/io/filesink.h b/lib/include/srsran/phy/io/filesink.h index 6d3226f10..3fbee00da 100644 --- a/lib/include/srsran/phy/io/filesink.h +++ b/lib/include/srsran/phy/io/filesink.h @@ -36,7 +36,7 @@ typedef struct SRSRAN_API { srsran_datatype_t type; } srsran_filesink_t; -SRSRAN_API int srsran_filesink_init(srsran_filesink_t* q, char* filename, srsran_datatype_t type); +SRSRAN_API int srsran_filesink_init(srsran_filesink_t* q, const char* filename, srsran_datatype_t type); SRSRAN_API void srsran_filesink_free(srsran_filesink_t* q); diff --git a/lib/include/srsran/phy/utils/vector.h b/lib/include/srsran/phy/utils/vector.h index cd49686cb..08921a711 100644 --- a/lib/include/srsran/phy/utils/vector.h +++ b/lib/include/srsran/phy/utils/vector.h @@ -343,7 +343,7 @@ SRSRAN_API void srsran_vec_interleave(const cf_t* x, const cf_t* y, cf_t* z, con SRSRAN_API void srsran_vec_interleave_add(const cf_t* x, const cf_t* y, cf_t* z, const int len); -SRSRAN_API void srsran_vec_gen_sine(cf_t amplitude, float freq, cf_t* z, int len); +SRSRAN_API cf_t srsran_vec_gen_sine(cf_t amplitude, float freq, cf_t* z, int len); SRSRAN_API void srsran_vec_apply_cfo(const cf_t* x, float cfo, cf_t* z, int len); diff --git a/lib/include/srsran/phy/utils/vector_simd.h b/lib/include/srsran/phy/utils/vector_simd.h index 318a819e6..9d24f8df4 100644 --- a/lib/include/srsran/phy/utils/vector_simd.h +++ b/lib/include/srsran/phy/utils/vector_simd.h @@ -121,7 +121,7 @@ SRSRAN_API void srsran_vec_interleave_simd(const cf_t* x, const cf_t* y, cf_t* z SRSRAN_API void srsran_vec_interleave_add_simd(const cf_t* x, const cf_t* y, cf_t* z, const int len); -SRSRAN_API void srsran_vec_gen_sine_simd(cf_t amplitude, float freq, cf_t* z, int len); +SRSRAN_API cf_t srsran_vec_gen_sine_simd(cf_t amplitude, float freq, cf_t* z, int len); SRSRAN_API void srsran_vec_apply_cfo_simd(const cf_t* x, float cfo, cf_t* z, int len); diff --git a/lib/include/srsran/radio/radio.h b/lib/include/srsran/radio/radio.h index c17fe2b3b..2827244bf 100644 --- a/lib/include/srsran/radio/radio.h +++ b/lib/include/srsran/radio/radio.h @@ -93,8 +93,8 @@ private: std::mutex metrics_mutex; srslog::basic_logger& logger = srslog::fetch_basic_logger("RF", false); phy_interface_radio* phy = nullptr; - cf_t* zeros = nullptr; - std::array dummy_buffers; + std::vector zeros; + std::array, SRSRAN_MAX_CHANNELS> dummy_buffers; std::mutex tx_mutex; std::mutex rx_mutex; std::array, SRSRAN_MAX_CHANNELS> tx_buffer; diff --git a/lib/src/phy/io/filesink.c b/lib/src/phy/io/filesink.c index 141fc8898..d7f95a704 100644 --- a/lib/src/phy/io/filesink.c +++ b/lib/src/phy/io/filesink.c @@ -17,7 +17,7 @@ #include "srsran/phy/io/filesink.h" -int srsran_filesink_init(srsran_filesink_t* q, char* filename, srsran_datatype_t type) +int srsran_filesink_init(srsran_filesink_t* q, const char* filename, srsran_datatype_t type) { bzero(q, sizeof(srsran_filesink_t)); q->f = fopen(filename, "w"); diff --git a/lib/src/phy/rf/rf_uhd_imp.cc b/lib/src/phy/rf/rf_uhd_imp.cc index 96a6b9091..ea3aefb98 100644 --- a/lib/src/phy/rf/rf_uhd_imp.cc +++ b/lib/src/phy/rf/rf_uhd_imp.cc @@ -142,10 +142,13 @@ struct rf_uhd_handler_t { std::array tx_freq = {}; std::array rx_freq = {}; - srsran_rf_error_handler_t uhd_error_handler = nullptr; - void* uhd_error_handler_arg = nullptr; - rf_uhd_imp_underflow_state_t tx_state = RF_UHD_IMP_TX_STATE_START_BURST; - uhd::time_spec_t eob_ack_timeout = {}; //< Set when a Underflow/Late happens + std::mutex tx_gain_mutex; + std::array, SRSRAN_MAX_CHANNELS> tx_gain_db = {}; + + srsran_rf_error_handler_t uhd_error_handler = nullptr; + void* uhd_error_handler_arg = nullptr; + std::atomic tx_state = {RF_UHD_IMP_TX_STATE_START_BURST}; + uhd::time_spec_t eob_ack_timeout = {}; //< Set when a Underflow/Late happens double current_master_clock = 0.0; @@ -1106,7 +1109,7 @@ int rf_uhd_set_tx_gain(void* h, double gain) { rf_uhd_handler_t* handler = (rf_uhd_handler_t*)h; for (size_t i = 0; i < handler->nof_tx_channels; i++) { - if (rf_uhd_set_tx_gain_ch(h, i, gain)) { + if (rf_uhd_set_tx_gain_ch(h, i, gain) < SRSRAN_SUCCESS) { return SRSRAN_ERROR; } } @@ -1116,9 +1119,27 @@ int rf_uhd_set_tx_gain(void* h, double gain) int rf_uhd_set_tx_gain_ch(void* h, uint32_t ch, double gain) { rf_uhd_handler_t* handler = (rf_uhd_handler_t*)h; - if (handler->uhd->set_tx_gain(ch, gain) != UHD_ERROR_NONE) { + if (ch >= SRSRAN_MAX_CHANNELS) { return SRSRAN_ERROR; } + + // If the transmitter is not in a burst, update the gain instantly + std::unique_lock lock(handler->tx_gain_mutex); + if (handler->tx_state != RF_UHD_IMP_TX_STATE_BURST) { + // Set gain + if (handler->uhd->set_tx_gain(ch, gain) != UHD_ERROR_NONE) { + return SRSRAN_ERROR; + } + + // Update current gains + handler->tx_gain_db[ch].second = gain; + handler->tx_gain_db[ch].first = gain; + return SRSRAN_SUCCESS; + } + + // Otherwise + handler->tx_gain_db[ch].first = gain; + return SRSRAN_SUCCESS; } @@ -1402,6 +1423,30 @@ int rf_uhd_send_timed_multi(void* h, } } + // Set RF Tx gains if a change is detected + { + std::unique_lock tx_gain_lock(handler->tx_gain_mutex); + for (uint32_t i = 0; i < handler->nof_tx_channels; i++) { + // Skip if the gain remains unchanged + if (handler->tx_gain_db[i].first == handler->tx_gain_db[i].second) { + continue; + } + + // Set the command to applied at the beginning of this transmission + if (handler->uhd->set_command_time(md.time_spec) != UHD_ERROR_NONE) { + return SRSRAN_ERROR; + } + + // Send Tx gain request + if (handler->uhd->set_tx_gain(i, handler->tx_gain_db[i].first) != UHD_ERROR_NONE) { + return SRSRAN_ERROR; + } + + // Update gain + handler->tx_gain_db[i].second = handler->tx_gain_db[i].first; + } + } + // it transmits in chunks of `handler->tx_nof_samples` except last block do { size_t tx_samples = handler->tx_nof_samples; diff --git a/lib/src/phy/rf/rf_zmq_imp.c b/lib/src/phy/rf/rf_zmq_imp.c index 6e8f748a8..fc966dcf2 100644 --- a/lib/src/phy/rf/rf_zmq_imp.c +++ b/lib/src/phy/rf/rf_zmq_imp.c @@ -33,6 +33,7 @@ typedef struct { uint32_t base_srate; uint32_t decim_factor; // decimation factor between base_srate used on transport on radio's rate double rx_gain; + double tx_gain; uint32_t tx_freq_mhz[SRSRAN_MAX_CHANNELS]; uint32_t rx_freq_mhz[SRSRAN_MAX_CHANNELS]; bool tx_off; @@ -486,6 +487,12 @@ int rf_zmq_set_rx_gain_ch(void* h, uint32_t ch, double gain) int rf_zmq_set_tx_gain(void* h, double gain) { + if (h) { + rf_zmq_handler_t* handler = (rf_zmq_handler_t*)h; + pthread_mutex_lock(&handler->tx_config_mutex); + handler->tx_gain = gain; + pthread_mutex_unlock(&handler->tx_config_mutex); + } return SRSRAN_SUCCESS; } @@ -508,7 +515,14 @@ double rf_zmq_get_rx_gain(void* h) double rf_zmq_get_tx_gain(void* h) { - return 0.0; + float ret = NAN; + if (h) { + rf_zmq_handler_t* handler = (rf_zmq_handler_t*)h; + pthread_mutex_lock(&handler->tx_config_mutex); + ret = handler->tx_gain; + pthread_mutex_unlock(&handler->tx_config_mutex); + } + return ret; } srsran_rf_info_t* rf_zmq_get_info(void* h) @@ -854,8 +868,17 @@ int rf_zmq_send_timed_multi(void* h, } } } + + // Load transmission gain + float tx_gain = srsran_convert_dB_to_amplitude(handler->tx_gain); + pthread_mutex_unlock(&handler->tx_config_mutex); + // If the Tx gain is NAN, INF or 0.0, use 1.0 + if (!isnormal(tx_gain)) { + tx_gain = 1.0f; + } + // Protect the access to decim_factor since is a shared variable pthread_mutex_lock(&handler->decim_mutex); uint32_t decim_factor = handler->decim_factor; @@ -933,6 +956,10 @@ int rf_zmq_send_timed_multi(void* h, } } + // Scale according to current gain + srsran_vec_sc_prod_cfc(buf, tx_gain, buf, nsamples_baseband); + + // Finally, transmit baseband int n = rf_zmq_tx_baseband(&handler->transmitter[i], buf, nsamples_baseband); if (n == SRSRAN_ERROR) { goto clean_exit; diff --git a/lib/src/phy/utils/vector.c b/lib/src/phy/utils/vector.c index f02f5959e..d11182813 100644 --- a/lib/src/phy/utils/vector.c +++ b/lib/src/phy/utils/vector.c @@ -817,9 +817,9 @@ void srsran_vec_interleave_add(const cf_t* x, const cf_t* y, cf_t* z, const int srsran_vec_interleave_add_simd(x, y, z, len); } -void srsran_vec_gen_sine(cf_t amplitude, float freq, cf_t* z, int len) +cf_t srsran_vec_gen_sine(cf_t amplitude, float freq, cf_t* z, int len) { - srsran_vec_gen_sine_simd(amplitude, freq, z, len); + return srsran_vec_gen_sine_simd(amplitude, freq, z, len); } void srsran_vec_apply_cfo(const cf_t* x, float cfo, cf_t* z, int len) diff --git a/lib/src/phy/utils/vector_simd.c b/lib/src/phy/utils/vector_simd.c index 49e61c19f..e2c6a5622 100644 --- a/lib/src/phy/utils/vector_simd.c +++ b/lib/src/phy/utils/vector_simd.c @@ -1634,7 +1634,7 @@ void srsran_vec_interleave_add_simd(const cf_t* x, const cf_t* y, cf_t* z, const } } -void srsran_vec_gen_sine_simd(cf_t amplitude, float freq, cf_t* z, int len) +cf_t srsran_vec_gen_sine_simd(cf_t amplitude, float freq, cf_t* z, int len) { const float TWOPI = 2.0f * (float)M_PI; cf_t osc = cexpf(_Complex_I * TWOPI * freq); @@ -1678,6 +1678,7 @@ void srsran_vec_gen_sine_simd(cf_t amplitude, float freq, cf_t* z, int len) phase *= osc; } + return phase; } void srsran_vec_apply_cfo_simd(const cf_t* x, float cfo, cf_t* z, int len) diff --git a/lib/src/radio/radio.cc b/lib/src/radio/radio.cc index 29a32e5ff..445168090 100644 --- a/lib/src/radio/radio.cc +++ b/lib/src/radio/radio.cc @@ -21,29 +21,16 @@ namespace srsran { -radio::radio() : zeros(nullptr) +radio::radio() { - zeros = srsran_vec_cf_malloc(SRSRAN_SF_LEN_MAX); - srsran_vec_cf_zero(zeros, SRSRAN_SF_LEN_MAX); + zeros.resize(SRSRAN_SF_LEN_MAX, 0); for (uint32_t i = 0; i < SRSRAN_MAX_CHANNELS; i++) { - dummy_buffers[i] = srsran_vec_cf_malloc(SRSRAN_SF_LEN_MAX * SRSRAN_NOF_SF_X_FRAME); - srsran_vec_cf_zero(dummy_buffers[i], SRSRAN_SF_LEN_MAX * SRSRAN_NOF_SF_X_FRAME); + dummy_buffers[i].resize(SRSRAN_SF_LEN_MAX * SRSRAN_NOF_SF_X_FRAME, 0); } } radio::~radio() { - if (zeros) { - free(zeros); - zeros = nullptr; - } - - for (uint32_t i = 0; i < SRSRAN_MAX_CHANNELS; i++) { - if (dummy_buffers[i]) { - free(dummy_buffers[i]); - } - } - for (srsran_resampler_fft_t& q : interpolators) { srsran_resampler_fft_free(&q); } @@ -223,10 +210,6 @@ void radio::stop() srsran_rf_stop_rx_stream(&rf_device); } } - if (zeros) { - free(zeros); - zeros = NULL; - } if (is_initialized) { for (srsran_rf_t& rf_device : rf_devices) { srsran_rf_close(&rf_device); @@ -362,7 +345,7 @@ bool radio::rx_dev(const uint32_t& device_idx, const rf_buffer_interface& buffer // Discard channels not allocated, need to point to valid buffer for (uint32_t i = 0; i < SRSRAN_MAX_CHANNELS; i++) { - radio_buffers[i] = dummy_buffers[i]; + radio_buffers[i] = dummy_buffers[i].data(); } if (not map_channels(rx_channel_mapping, device_idx, 0, buffer, radio_buffers)) { @@ -550,7 +533,7 @@ bool radio::tx_dev(const uint32_t& device_idx, rf_buffer_interface& buffer, cons // Zeros transmission int ret = srsran_rf_send_timed2(rf_device, - zeros, + zeros.data(), nzeros, end_of_burst_time[device_idx].full_secs, end_of_burst_time[device_idx].frac_secs, @@ -577,7 +560,7 @@ bool radio::tx_dev(const uint32_t& device_idx, rf_buffer_interface& buffer, cons // Discard channels not allocated, need to point to valid buffer for (uint32_t i = 0; i < SRSRAN_MAX_CHANNELS; i++) { - radio_buffers[i] = zeros; + radio_buffers[i] = zeros.data(); } if (not map_channels(tx_channel_mapping, device_idx, sample_offset, buffer, radio_buffers)) { @@ -605,7 +588,7 @@ void radio::tx_end_nolock() if (!is_start_of_burst) { for (uint32_t i = 0; i < (uint32_t)rf_devices.size(); i++) { srsran_rf_send_timed2( - &rf_devices[i], zeros, 0, end_of_burst_time[i].full_secs, end_of_burst_time[i].frac_secs, false, true); + &rf_devices[i], zeros.data(), 0, end_of_burst_time[i].full_secs, end_of_burst_time[i].frac_secs, false, true); } is_start_of_burst = true; } diff --git a/lib/src/radio/test/CMakeLists.txt b/lib/src/radio/test/CMakeLists.txt index e4eedbcbc..821de5226 100644 --- a/lib/src/radio/test/CMakeLists.txt +++ b/lib/src/radio/test/CMakeLists.txt @@ -18,6 +18,18 @@ if(RF_FOUND) tx_port=tcp://*:2000,rx_port=tcp://localhost:2000\;tx_port=tcp://*:2001,rx_port=tcp://localhost:2001\;tx_port=tcp://*:2002,rx_port=tcp://localhost:2002\;tx_port=tcp://*:2003,rx_port=tcp://localhost:2003\; -p 4) endif (ZEROMQ_FOUND) + + add_executable(test_radio_rt_gain test_radio_rt_gain.cc) + target_link_libraries(test_radio_rt_gain + srsran_common + srsran_phy + srsran_radio + ${CMAKE_THREAD_LIBS_INIT} + ${Boost_LIBRARIES}) + if (ZEROMQ_FOUND) + add_test(test_radio_rt_gain_zmq test_radio_rt_gain --srate=3.84e6 --dev_name=zmq --dev_args=tx_port=ipc:///tmp/test_radio_rt_gain_zmq,rx_port=ipc:///tmp/test_radio_rt_gain_zmq,base_srate=3.84e6) + endif (ZEROMQ_FOUND) + endif(RF_FOUND) diff --git a/lib/src/radio/test/test_radio_rt_gain.cc b/lib/src/radio/test/test_radio_rt_gain.cc new file mode 100644 index 000000000..540ef7399 --- /dev/null +++ b/lib/src/radio/test/test_radio_rt_gain.cc @@ -0,0 +1,300 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2021 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ +#include "srsran/common/test_common.h" +#include "srsran/radio/radio.h" +#include +#include + +// shorten boost program options namespace +namespace bpo = boost::program_options; + +// Test arguments +struct test_args_s { + bool valid = false; + double srate_hz = 3.84e6; + double freq_hz = 3.5e9; + float rx_gain_db = 20.0f; + float tx_gain_db_begin = 0.0f; + float tx_gain_db_end = 30.0f; + float tx_gain_db_step = 1.0f; + uint32_t tx_delay_ms = 4; + uint32_t step_period_ms = 1; + uint32_t nof_repetitions = 3; + uint32_t power_ramping_ms = 50; + uint32_t pre_tx_ms = 50; // After main loop acquire a few more ms + uint32_t post_tx_ms = 50; // After main loop acquire a few more ms + std::string filename = "/tmp/baseband.iq.dat"; + std::string device_name = "zmq"; + std::string device_args = "tx_port=tcp://*:5555,rx_port=tcp://localhost:5555,base_srate=3.84e6"; + + test_args_s(int argc, char** argv) + { + bpo::options_description options; + + // clang-format off + options.add_options() + ("srate", bpo::value(&srate_hz)->default_value(srate_hz), "Sampling rate in Hz") + ("freq", bpo::value(&freq_hz)->default_value(freq_hz), "Center frequency in Hz") + ("rx_gain", bpo::value(&rx_gain_db)->default_value(rx_gain_db), "Receiver gain in dB") + ("tx_gain_begin", bpo::value(&tx_gain_db_begin)->default_value(tx_gain_db_begin), "Initial transmitter gain in dB") + ("tx_gain_end", bpo::value(&tx_gain_db_end)->default_value(tx_gain_db_end), "Final transmitter gain in dB") + ("tx_gain_step", bpo::value(&tx_gain_db_step)->default_value(tx_gain_db_step), "Step transmitter gain in dB") + ("tx_delay", bpo::value(&tx_delay_ms)->default_value(tx_delay_ms), "Delay between Rx and Tx in milliseconds") + ("step_period", bpo::value(&step_period_ms)->default_value(step_period_ms), "Transmitter gain step period in milliseconds") + ("repetitions", bpo::value(&nof_repetitions)->default_value(nof_repetitions), "Number of transmit gain steering repetitions") + ("power_ramping", bpo::value(&power_ramping_ms)->default_value(power_ramping_ms), "Transmitter initial power ramping in milliseconds") + ("pre_tx", bpo::value(&pre_tx_ms)->default_value(pre_tx_ms), "Initial acquisition time before start transmission in milliseconds") + ("post_tx", bpo::value(&pre_tx_ms)->default_value(pre_tx_ms), "Initial acquisition time after ending transmission in milliseconds") + ("filename", bpo::value(&filename)->default_value(filename), "File sink filename") + ("dev_name", bpo::value(&device_name)->default_value(device_name), "RF Device name") + ("dev_args", bpo::value(&device_args)->default_value(device_args), "RF Device arguments") + ("help", "Show this message") + ; + // clang-format on + + bpo::variables_map vm; + try { + bpo::store(bpo::command_line_parser(argc, argv).options(options).run(), vm); + bpo::notify(vm); + valid = true; + } catch (bpo::error& e) { + std::cerr << e.what() << std::endl; + } + + // help option was given or error - print usage and exit + if (vm.count("help") > 0 or not valid) { + std::cout << "Usage: " << argv[0] << " [OPTIONS] config_file" << std::endl << std::endl; + std::cout << options << std::endl << std::endl; + valid = false; + } + } +}; + +class phy_radio_listener : public srsran::phy_interface_radio +{ +private: + srslog::basic_logger& logger = srslog::fetch_basic_logger("TEST", false); + uint32_t overflow_count = 0; + uint32_t failure_count = 0; + +public: + phy_radio_listener() {} + void radio_overflow() override + { + overflow_count++; + logger.error("Overflow"); + } + void radio_failure() override + { + failure_count++; + logger.error("Failure"); + } +}; + +class test_sink +{ +private: + srslog::basic_logger& logger = srslog::fetch_basic_logger("SINK", false); + srsran_filesink_t filesink = {}; + std::vector meas_power_dB; + +public: + test_sink(const std::string& filename) + { + if (srsran_filesink_init(&filesink, filename.c_str(), SRSRAN_COMPLEX_FLOAT_BIN) < SRSRAN_SUCCESS) { + ERROR("Error initiating filesink"); + filesink = {}; + } + } + + ~test_sink() + { + // Free filesink + srsran_filesink_free(&filesink); + + // Print measure power + printf("Measured power: "); + srsran_vec_fprint_f(stdout, meas_power_dB.data(), (int)meas_power_dB.size()); + } + + void write(std::vector& buffer, uint32_t nsamp) + { + // Measure average power + float avg_pwr = srsran_vec_avg_power_cf(buffer.data(), nsamp); + + // Push measurement in dB + meas_power_dB.push_back(srsran_convert_power_to_dB(avg_pwr)); + + // Write signal in file + srsran_filesink_write(&filesink, (void*)buffer.data(), (int)nsamp); + } +}; + +class test_source +{ +private: + // cf_t phase = 1.0f; + cf_t phase = 0.01; + float freq_norm = 0.125; + +public: + test_source() {} + + ~test_source() {} + + void generate(std::vector& buffer, uint32_t nsamp) + { + phase *= srsran_vec_gen_sine(phase, freq_norm, buffer.data(), (int)nsamp); + } +}; + +int main(int argc, char** argv) +{ + srslog::basic_logger& logger = srslog::fetch_basic_logger("TEST", false); + srslog::init(); + srsran::radio radio; + phy_radio_listener radio_listener; + + enum { BEFORE_RAMPING, RAMPING, GAIN_STEERING, POST_GAIN_STEERING, END } state = BEFORE_RAMPING; + + test_args_s args(argc, argv); + TESTASSERT(args.valid); + + // Calculate number of steps + TESTASSERT(std::isnormal(args.tx_gain_db_step)); + uint32_t nof_steps = (args.tx_gain_db_end - args.tx_gain_db_begin) / args.tx_gain_db_step; + + // Calculate subframe size in 1ms + TESTASSERT(std::isnormal(args.srate_hz)); + uint32_t sf_sz = (uint32_t)std::round(1e-3 * args.srate_hz); + + // Allocate baseband buffer + std::vector buffer(sf_sz); + + // Prepare radio arguments + srsran::rf_args_t rf_args = {}; + rf_args.log_level = "info"; + rf_args.srate_hz = args.srate_hz; + rf_args.dl_freq = args.freq_hz; + rf_args.ul_freq = args.freq_hz; + rf_args.rx_gain = args.rx_gain_db; + rf_args.tx_gain = args.tx_gain_db_begin; + rf_args.nof_carriers = 1; + rf_args.nof_antennas = 1; + rf_args.device_name = args.device_name; + rf_args.device_args = args.device_args; + + // Initialise radio + TESTASSERT(radio.init(rf_args, &radio_listener) == SRSRAN_SUCCESS); + + // Setup LO frequencies + radio.set_tx_freq(0, args.freq_hz); + radio.set_rx_freq(0, args.freq_hz); + + // Setup sampling rate + radio.set_tx_srate(args.srate_hz); + radio.set_rx_srate(args.srate_hz); + + // Setup initial gains + radio.set_tx_gain(args.tx_gain_db_begin); + radio.set_rx_gain(args.rx_gain_db); + + // Create signal sink + test_sink sink(args.filename); + + // Create signal source + test_source source; + + // Perform Tx/Rx + uint32_t sf_count = 0; + uint32_t repetition = 0; + while (state != END) { + switch (state) { + case BEFORE_RAMPING: + if (sf_count >= args.pre_tx_ms) { + logger.info("-- Starting power ramping stage"); + state = RAMPING; + sf_count = 0; + continue; + } + break; + case RAMPING: + if (sf_count >= args.power_ramping_ms) { + logger.info("-- Starting gain steering stage"); + state = GAIN_STEERING; + sf_count = 0; + continue; + } + break; + case GAIN_STEERING: + if (sf_count >= nof_steps * args.step_period_ms) { + repetition++; + sf_count = 0; + logger.info("-- Finished repetition %d of %d", repetition, args.nof_repetitions); + } + + if (repetition >= args.nof_repetitions) { + logger.info("-- Starting post gain steering stage"); + state = POST_GAIN_STEERING; + radio.tx_end(); + continue; + } + if (sf_count % args.step_period_ms == 0 and sf_count != 0) { + float tx_gain_db = args.tx_gain_db_begin + args.tx_gain_db_step * (sf_count / args.step_period_ms); + logger.info("-- New Tx gain %+.2f dB", tx_gain_db); + radio.set_tx_gain(tx_gain_db); + } + break; + case POST_GAIN_STEERING: + if (sf_count >= args.post_tx_ms) { + logger.info("-- Ending..."); + state = END; + sf_count = 0; + continue; + } + break; + case END: + continue; + } + + // Prepare reception buffers + srsran::rf_buffer_t rf_buffer = {}; + rf_buffer.set(0, buffer.data()); + rf_buffer.set_nof_samples(sf_sz); + + // Receive + srsran::rf_timestamp_t ts = {}; + TESTASSERT(radio.rx_now(rf_buffer, ts)); + + // Save signal, including the time before transmission + sink.write(buffer, sf_sz); + + if (state == RAMPING or state == GAIN_STEERING) { + // Generate transmit signal + source.generate(buffer, sf_sz); + + // Update timestamp for Tx + ts.add(1e-3 * (double)args.tx_delay_ms); + + // Transmit + radio.tx(rf_buffer, ts); + } + + // Increase SF counting + sf_count++; + } + + // Tear down radio + radio.stop(); + + return SRSRAN_SUCCESS; +}