Initial real-time Tx gain setting (#2976)

* Make filename const in filesink

* Sine generation returns the next phase

* Avoid malloc/free in radio class

* Implement Tx gain in ZMQ

* Initial ratio RT gain test

* UHD: use timed Tx gain commands to align changes to subframes

* Minor improvement in test_radio_rt_gain

* Fix compilation

* Check RF gain thread id before joining

* Remove redundant zero initialization.

Co-authored-by: Fabian Eckermann <fabian@srs.io>
master
Xavier Arteaga 3 years ago committed by GitHub
parent ebab12403f
commit 322f57a952
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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);

@ -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);

@ -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);

@ -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<cf_t*, SRSRAN_MAX_CHANNELS> dummy_buffers;
std::vector<cf_t> zeros;
std::array<std::vector<cf_t>, SRSRAN_MAX_CHANNELS> dummy_buffers;
std::mutex tx_mutex;
std::mutex rx_mutex;
std::array<std::vector<cf_t>, SRSRAN_MAX_CHANNELS> tx_buffer;

@ -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");

@ -142,10 +142,13 @@ struct rf_uhd_handler_t {
std::array<double, SRSRAN_MAX_CHANNELS> tx_freq = {};
std::array<double, SRSRAN_MAX_CHANNELS> 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<std::pair<double, double>, SRSRAN_MAX_CHANNELS> tx_gain_db = {};
srsran_rf_error_handler_t uhd_error_handler = nullptr;
void* uhd_error_handler_arg = nullptr;
std::atomic<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
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<std::mutex> 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<std::mutex> 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;

@ -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;

@ -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)

@ -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)

@ -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;
}

@ -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)

@ -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 <boost/program_options.hpp>
#include <iostream>
// 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<double>(&srate_hz)->default_value(srate_hz), "Sampling rate in Hz")
("freq", bpo::value<double>(&freq_hz)->default_value(freq_hz), "Center frequency in Hz")
("rx_gain", bpo::value<float>(&rx_gain_db)->default_value(rx_gain_db), "Receiver gain in dB")
("tx_gain_begin", bpo::value<float>(&tx_gain_db_begin)->default_value(tx_gain_db_begin), "Initial transmitter gain in dB")
("tx_gain_end", bpo::value<float>(&tx_gain_db_end)->default_value(tx_gain_db_end), "Final transmitter gain in dB")
("tx_gain_step", bpo::value<float>(&tx_gain_db_step)->default_value(tx_gain_db_step), "Step transmitter gain in dB")
("tx_delay", bpo::value<uint32_t>(&tx_delay_ms)->default_value(tx_delay_ms), "Delay between Rx and Tx in milliseconds")
("step_period", bpo::value<uint32_t>(&step_period_ms)->default_value(step_period_ms), "Transmitter gain step period in milliseconds")
("repetitions", bpo::value<uint32_t>(&nof_repetitions)->default_value(nof_repetitions), "Number of transmit gain steering repetitions")
("power_ramping", bpo::value<uint32_t>(&power_ramping_ms)->default_value(power_ramping_ms), "Transmitter initial power ramping in milliseconds")
("pre_tx", bpo::value<uint32_t>(&pre_tx_ms)->default_value(pre_tx_ms), "Initial acquisition time before start transmission in milliseconds")
("post_tx", bpo::value<uint32_t>(&pre_tx_ms)->default_value(pre_tx_ms), "Initial acquisition time after ending transmission in milliseconds")
("filename", bpo::value<std::string>(&filename)->default_value(filename), "File sink filename")
("dev_name", bpo::value<std::string>(&device_name)->default_value(device_name), "RF Device name")
("dev_args", bpo::value<std::string>(&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<float> 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<cf_t>& 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<cf_t>& 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<cf_t> 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;
}
Loading…
Cancel
Save