diff --git a/srslte/include/srslte/radio/radio.h b/srslte/include/srslte/radio/radio.h new file mode 100644 index 000000000..849c1c0e9 --- /dev/null +++ b/srslte/include/srslte/radio/radio.h @@ -0,0 +1,175 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2015 Software Radio Systems Limited + * + * \section LICENSE + * + * This file is part of the srsUE library. + * + * srsUE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsUE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include + +#include "srslte/srslte.h" +#include "srslte/phy/rf/rf.h" +#include "srslte/common/trace.h" + +#ifndef RADIO_H +#define RADIO_H + +typedef struct { + float tx_corr_dc_gain; + float tx_corr_dc_phase; + float tx_corr_iq_i; + float tx_corr_iq_q; + float rx_corr_dc_gain; + float rx_corr_dc_phase; + float rx_corr_iq_i; + float rx_corr_iq_q; +}rf_cal_t; + + +namespace srslte { + +/* Interface to the RF frontend. + */ + class radio + { + public: + radio() : tr_local_time(1024*10), tr_usrp_time(1024*10), tr_tx_time(1024*10), tr_is_eob(1024*10) { + bzero(&rf_device, sizeof(srslte_rf_t)); + bzero(&end_of_burst_time, sizeof(srslte_timestamp_t)); + bzero(zeros, burst_preamble_max_samples*sizeof(cf_t)); + + sf_len = 0; + burst_preamble_sec = 0; + is_start_of_burst = false; + burst_preamble_samples = 0; + burst_preamble_time_rounded = 0; + + cur_tx_srate = 0; + tx_adv_sec = 0; + tx_adv_nsamples = 0; + tx_adv_auto = false; + tx_adv_negative = false; + tx_freq = 0; + rx_freq = 0; + trace_enabled = false; + tti = 0; + agc_enabled = false; + offset = 0; + + }; + + bool init(char *args = NULL, char *devname = NULL); + void stop(); + bool start_agc(bool tx_gain_same_rx); + + void set_burst_preamble(double preamble_us); + void set_tx_adv(int nsamples); + void set_tx_adv_neg(bool tx_adv_is_neg); + + void set_manual_calibration(rf_cal_t *calibration); + + void get_time(srslte_timestamp_t *now); + bool tx(void *buffer, uint32_t nof_samples, srslte_timestamp_t tx_time); + void tx_end(); + bool rx_now(void *buffer, uint32_t nof_samples, srslte_timestamp_t *rxd_time); + bool rx_at(void *buffer, uint32_t nof_samples, srslte_timestamp_t rx_time); + + void set_tx_gain(float gain); + void set_rx_gain(float gain); + void set_tx_rx_gain_offset(float offset); + double set_rx_gain_th(float gain); + + void set_tx_freq(float freq); + void set_rx_freq(float freq); + + float get_tx_freq(); + float get_rx_freq(); + + void set_master_clock_rate(float rate); + void set_tx_srate(float srate); + void set_rx_srate(float srate); + + float get_tx_gain(); + float get_rx_gain(); + + float get_max_tx_power(); + float set_tx_power(float power); + float get_rssi(); + bool has_rssi(); + + void start_trace(); + void write_trace(std::string filename); + void start_rx(); + void stop_rx(); + + void set_tti(uint32_t tti); + void tx_offset(int offset); + void set_tti_len(uint32_t sf_len); + uint32_t get_tti_len(); + + void register_error_handler(srslte_rf_error_handler_t h); + + protected: + + void save_trace(uint32_t is_eob, srslte_timestamp_t *usrp_time); + + srslte_rf_t rf_device; + + + const static uint32_t burst_preamble_max_samples = 30720000; // 30.72 MHz is maximum frequency + double burst_preamble_sec;// Start of burst preamble time (off->on RF transition time) + srslte_timestamp_t end_of_burst_time; + bool is_start_of_burst; + uint32_t burst_preamble_samples; + double burst_preamble_time_rounded; // preamble time rounded to sample time + cf_t zeros[burst_preamble_max_samples]; + double cur_tx_srate; + + double tx_adv_sec; // Transmission time advance to compensate for antenna->timestamp delay + int tx_adv_nsamples; // Transmision time advance in number of samples + + // Define default values for known radios + bool tx_adv_auto; + bool tx_adv_negative; + const static double uhd_default_burst_preamble_sec = 600*1e-6; + const static double uhd_default_tx_adv_samples = 98; + const static double uhd_default_tx_adv_offset_sec = 4*1e-6; + + const static double blade_default_burst_preamble_sec = 0.0; + const static double blade_default_tx_adv_samples = 27; + const static double blade_default_tx_adv_offset_sec = 1e-6; + + float tx_freq, rx_freq; + + trace tr_local_time; + trace tr_usrp_time; + trace tr_tx_time; + trace tr_is_eob; + bool trace_enabled; + uint32_t tti; + bool agc_enabled; + int offset; + uint32_t sf_len; + }; +} + +#endif diff --git a/srslte/include/srslte/radio/radio_multi.h b/srslte/include/srslte/radio/radio_multi.h new file mode 100644 index 000000000..d4e2ef743 --- /dev/null +++ b/srslte/include/srslte/radio/radio_multi.h @@ -0,0 +1,30 @@ + + +#include + +#include "srslte/srslte.h" +extern "C" { +#include "srslte/phy/rf/rf.h" +} +#include "srslte/common/trace.h" + +#include "srslte/radio/radio.h" + +#ifndef RADIO_MULTI_H +#define RADIO_MULTI_H + + +namespace srslte { + +/* Interface to the RF frontend. + */ + class radio_multi : public radio + { + public: + + bool init_multi(uint32_t nof_rx_antennas, char *args = NULL, char *devname = NULL); + bool rx_now(cf_t *buffer[SRSLTE_MAX_PORTS], uint32_t nof_samples, srslte_timestamp_t *rxd_time); + }; +} + +#endif diff --git a/srslte/lib/radio/CMakeLists.txt b/srslte/lib/radio/CMakeLists.txt new file mode 100644 index 000000000..26bf4e131 --- /dev/null +++ b/srslte/lib/radio/CMakeLists.txt @@ -0,0 +1,23 @@ +# Copyright 2015 Software Radio Systems Limited +# +# This file is part of srsUE +# +# srsUE is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. +# +# srsUE is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# A copy of the GNU Affero General Public License can be found in +# the LICENSE file in the top-level directory of this distribution +# and at http://www.gnu.org/licenses/. +# + +add_library(srslte_radio SHARED radio.cc radio_multi.cc) +INSTALL(TARGETS srslte_radio DESTINATION ${LIBRARY_DIR}) +target_link_libraries(srslte_radio srslte_rf) +SRSLTE_SET_PIC(srslte_radio) diff --git a/srslte/lib/radio/radio.cc b/srslte/lib/radio/radio.cc new file mode 100644 index 000000000..e2c949712 --- /dev/null +++ b/srslte/lib/radio/radio.cc @@ -0,0 +1,413 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2015 Software Radio Systems Limited + * + * \section LICENSE + * + * This file is part of the srsUE library. + * + * srsUE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsUE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include "srslte/srslte.h" +extern "C" { +#include "srslte/phy/rf/rf.h" +} +#include "radio/radio.h" +#include + +namespace srslte { + +bool radio::init(char *args, char *devname) +{ + if (srslte_rf_open_devname(&rf_device, devname, args)) { + fprintf(stderr, "Error opening RF device\n"); + return false; + } + + tx_adv_negative = false; + agc_enabled = false; + burst_preamble_samples = 0; + burst_preamble_time_rounded = 0; + cur_tx_srate = 0; + is_start_of_burst = true; + + // Suppress radio stdout + srslte_rf_suppress_stdout(&rf_device); + + tx_adv_auto = true; + // Set default preamble length each known device + // We distinguish by device family, maybe we should calibrate per device + if (strstr(srslte_rf_name(&rf_device), "uhd")) { + burst_preamble_sec = uhd_default_burst_preamble_sec; + } else if (strstr(srslte_rf_name(&rf_device), "bladerf")) { + burst_preamble_sec = blade_default_burst_preamble_sec; + } else { + printf("\nWarning burst preamble is not calibrated for device %s. Set a value manually\n\n", srslte_rf_name(&rf_device)); + } + + return true; +} + +void radio::stop() +{ + srslte_rf_close(&rf_device); +} + +void radio::set_manual_calibration(rf_cal_t* calibration) +{ + srslte_rf_cal_t tx_cal; + tx_cal.dc_gain = calibration->tx_corr_dc_gain; + tx_cal.dc_phase = calibration->tx_corr_dc_phase; + tx_cal.iq_i = calibration->tx_corr_iq_i; + tx_cal.iq_q = calibration->tx_corr_iq_q; + srslte_rf_set_tx_cal(&rf_device, &tx_cal); +} + +void radio::set_tx_rx_gain_offset(float offset) { + srslte_rf_set_tx_rx_gain_offset(&rf_device, offset); +} + +void radio::set_burst_preamble(double preamble_us) +{ + burst_preamble_sec = (double) preamble_us/1e6; +} + +void radio::set_tx_adv(int nsamples) +{ + tx_adv_auto = false; + tx_adv_nsamples = nsamples; + if (!nsamples) { + tx_adv_sec = 0; + } + +} + +void radio::set_tx_adv_neg(bool tx_adv_is_neg) { + tx_adv_negative = tx_adv_is_neg; +} + +void radio::tx_offset(int offset_) +{ + offset = offset_; +} + +bool radio::start_agc(bool tx_gain_same_rx) +{ + if (srslte_rf_start_gain_thread(&rf_device, tx_gain_same_rx)) { + fprintf(stderr, "Error opening RF device\n"); + return false; + } + + agc_enabled = true; + + return true; +} +bool radio::rx_at(void* buffer, uint32_t nof_samples, srslte_timestamp_t rx_time) +{ + fprintf(stderr, "Not implemented\n"); + return false; +} + +bool radio::rx_now(void* buffer, uint32_t nof_samples, srslte_timestamp_t* rxd_time) +{ + if (srslte_rf_recv_with_time(&rf_device, buffer, nof_samples, true, + rxd_time?&rxd_time->full_secs:NULL, rxd_time?&rxd_time->frac_secs:NULL) > 0) { + return true; + } else { + return false; + } +} + +void radio::get_time(srslte_timestamp_t *now) { + srslte_rf_get_time(&rf_device, &now->full_secs, &now->frac_secs); +} + +// TODO: Use Calibrated values for this +float radio::set_tx_power(float power) +{ + if (power > 10) { + power = 10; + } + if (power < -50) { + power = -50; + } + float gain = power + 74; + srslte_rf_set_tx_gain(&rf_device, gain); + return gain; +} + +float radio::get_max_tx_power() +{ + return 10; +} + +float radio::get_rssi() +{ + return srslte_rf_get_rssi(&rf_device); +} + +bool radio::has_rssi() +{ + return srslte_rf_has_rssi(&rf_device); +} + +bool radio::tx(void* buffer, uint32_t nof_samples, srslte_timestamp_t tx_time) +{ + if (!tx_adv_negative) { + srslte_timestamp_sub(&tx_time, 0, tx_adv_sec); + } else { + srslte_timestamp_add(&tx_time, 0, tx_adv_sec); + } + + if (is_start_of_burst) { + if (burst_preamble_samples != 0) { + srslte_timestamp_t tx_time_pad; + srslte_timestamp_copy(&tx_time_pad, &tx_time); + srslte_timestamp_sub(&tx_time_pad, 0, burst_preamble_time_rounded); + save_trace(1, &tx_time_pad); + srslte_rf_send_timed2(&rf_device, zeros, burst_preamble_samples, tx_time_pad.full_secs, tx_time_pad.frac_secs, true, false); + is_start_of_burst = false; + } + } + + // Save possible end of burst time + srslte_timestamp_copy(&end_of_burst_time, &tx_time); + srslte_timestamp_add(&end_of_burst_time, 0, (double) nof_samples/cur_tx_srate); + + save_trace(0, &tx_time); + int ret = srslte_rf_send_timed2(&rf_device, buffer, nof_samples+offset, tx_time.full_secs, tx_time.frac_secs, is_start_of_burst, false); + offset = 0; + is_start_of_burst = false; + if (ret > 0) { + return true; + } else { + return false; + } +} + +uint32_t radio::get_tti_len() +{ + return sf_len; +} + +void radio::set_tti_len(uint32_t sf_len_) +{ + sf_len = sf_len_; +} + +void radio::tx_end() +{ + if (!is_start_of_burst) { + save_trace(2, &end_of_burst_time); + srslte_rf_send_timed2(&rf_device, zeros, 0, end_of_burst_time.full_secs, end_of_burst_time.frac_secs, false, true); + is_start_of_burst = true; + } +} + +void radio::start_trace() { + trace_enabled = true; +} + +void radio::set_tti(uint32_t tti_) { + tti = tti_; +} + +void radio::write_trace(std::string filename) +{ + tr_local_time.writeToBinary(filename + ".local"); + tr_is_eob.writeToBinary(filename + ".eob"); + tr_usrp_time.writeToBinary(filename + ".usrp"); + tr_tx_time.writeToBinary(filename + ".tx"); +} + +void radio::save_trace(uint32_t is_eob, srslte_timestamp_t *tx_time) { + if (trace_enabled) { + tr_local_time.push_cur_time_us(tti); + srslte_timestamp_t usrp_time; + srslte_rf_get_time(&rf_device, &usrp_time.full_secs, &usrp_time.frac_secs); + tr_usrp_time.push(tti, srslte_timestamp_uint32(&usrp_time)); + tr_tx_time.push(tti, srslte_timestamp_uint32(tx_time)); + tr_is_eob.push(tti, is_eob); + } +} + +void radio::set_rx_freq(float freq) +{ + rx_freq = srslte_rf_set_rx_freq(&rf_device, freq); +} + +void radio::set_rx_gain(float gain) +{ + srslte_rf_set_rx_gain(&rf_device, gain); +} + +double radio::set_rx_gain_th(float gain) +{ + return srslte_rf_set_rx_gain_th(&rf_device, gain); +} + +void radio::set_master_clock_rate(float rate) +{ + srslte_rf_set_master_clock_rate(&rf_device, rate); +} + +void radio::set_rx_srate(float srate) +{ + srslte_rf_set_rx_srate(&rf_device, srate); +} + +void radio::set_tx_freq(float freq) +{ + tx_freq = srslte_rf_set_tx_freq(&rf_device, freq); +} + +void radio::set_tx_gain(float gain) +{ + srslte_rf_set_tx_gain(&rf_device, gain); +} + +float radio::get_rx_freq() +{ + return rx_freq; +} + +float radio::get_tx_freq() +{ + return tx_freq; +} + +float radio::get_tx_gain() +{ + return srslte_rf_get_tx_gain(&rf_device); +} + +float radio::get_rx_gain() +{ + return srslte_rf_get_rx_gain(&rf_device); +} + +void radio::set_tx_srate(float srate) +{ + cur_tx_srate = srslte_rf_set_tx_srate(&rf_device, srate); + burst_preamble_samples = (uint32_t) (cur_tx_srate * burst_preamble_sec); + if (burst_preamble_samples > burst_preamble_max_samples) { + burst_preamble_samples = burst_preamble_max_samples; + fprintf(stderr, "Error setting TX srate %.1f MHz. Maximum frequency for zero prepadding is 30.72 MHz\n", srate*1e-6); + } + burst_preamble_time_rounded = (double) burst_preamble_samples/cur_tx_srate; + + + int nsamples=0; + /* Set time advance for each known device if in auto mode */ + if (tx_adv_auto) { + + /* This values have been calibrated using the prach_test_usrp tool in srsLTE */ + + if (!strcmp(srslte_rf_name(&rf_device), "uhd_b200")) { + + double srate_khz = round(cur_tx_srate/1e3); + if (srate_khz == 1.92e3) { + nsamples = 54; + } else if (srate_khz == 3.84e3) { + nsamples = 69; + } else if (srate_khz == 5.76e3) { + nsamples = 93; + } else if (srate_khz == 11.52e3) { + nsamples = 120; + } else if (srate_khz == 15.36e3) { + nsamples = 131; + } else if (srate_khz == 23.04e3) { + nsamples = 175; + } else { + /* Interpolate from known values */ + printf("\nWarning TX/RX time offset for sampling rate %.0f KHz not calibrated. Using interpolated value\n\n", cur_tx_srate); + nsamples = cur_tx_srate*(uhd_default_tx_adv_samples * (1/cur_tx_srate) + uhd_default_tx_adv_offset_sec); + } + } else if (!strcmp(srslte_rf_name(&rf_device), "uhd_x300")) { + + double srate_khz = round(cur_tx_srate/1e3); + if (srate_khz == 1.92e3) { + nsamples = 50; + } else if (srate_khz == 3.84e3) { + nsamples = 65; + } else if (srate_khz == 5.76e3) { + nsamples = 75; + } else if (srate_khz == 11.52e3) { + nsamples = 89; + } else if (srate_khz == 15.36e3) { + nsamples = 86; + } else if (srate_khz == 23.04e3) { + nsamples = 119; + } else { + /* Interpolate from known values */ + printf("\nWarning TX/RX time offset for sampling rate %.0f KHz not calibrated. Using interpolated value\n\n", cur_tx_srate); + nsamples = cur_tx_srate*(uhd_default_tx_adv_samples * (1/cur_tx_srate) + uhd_default_tx_adv_offset_sec); + } + } else if (!strcmp(srslte_rf_name(&rf_device), "bladerf")) { + + double srate_khz = round(cur_tx_srate/1e3); + if (srate_khz == 1.92e3) { + nsamples = 16; + } else if (srate_khz == 3.84e3) { + nsamples = 18; + } else if (srate_khz == 5.76e3) { + nsamples = 16; + } else if (srate_khz == 11.52e3) { + nsamples = 21; + } else if (srate_khz == 15.36e3) { + nsamples = 14; + } else if (srate_khz == 23.04e3) { + nsamples = 21; + } else { + /* Interpolate from known values */ + printf("\nWarning TX/RX time offset for sampling rate %.0f KHz not calibrated. Using interpolated value\n\n", cur_tx_srate); + tx_adv_sec = blade_default_tx_adv_samples * (1/cur_tx_srate) + blade_default_tx_adv_offset_sec; + } + } else { + printf("\nWarning TX/RX time offset has not been calibrated for device %s. Set a value manually\n\n", srslte_rf_name(&rf_device)); + } + } else { + nsamples = tx_adv_nsamples; + printf("Setting manual TX/RX offset to %d samples\n", nsamples); + } + + // Calculate TX advance in seconds from samples and sampling rate + tx_adv_sec = nsamples/cur_tx_srate; + + printf("Setting TX/RX offset %d samples, %.2f us\n", nsamples, tx_adv_sec*1e6); +} + +void radio::start_rx() +{ + srslte_rf_start_rx_stream(&rf_device); +} + +void radio::stop_rx() +{ + srslte_rf_stop_rx_stream(&rf_device); +} + +void radio::register_error_handler(srslte_rf_error_handler_t h) +{ + srslte_rf_register_error_handler(&rf_device, h); +} + + +} + diff --git a/srslte/lib/radio/radio_multi.cc b/srslte/lib/radio/radio_multi.cc new file mode 100644 index 000000000..6bad6ec7a --- /dev/null +++ b/srslte/lib/radio/radio_multi.cc @@ -0,0 +1,49 @@ +#include "radio/radio_multi.h" + +namespace srslte { + +bool radio_multi::init_multi(uint32_t nof_rx_antennas, char* args, char* devname) +{ + if (srslte_rf_open_devname_multi(&rf_device, devname, args, nof_rx_antennas)) { + fprintf(stderr, "Error opening RF device\n"); + return false; + } + + tx_adv_negative = false; + agc_enabled = false; + burst_preamble_samples = 0; + burst_preamble_time_rounded = 0; + cur_tx_srate = 0; + is_start_of_burst = true; + + + tx_adv_auto = true; + // Set default preamble length each known device + // We distinguish by device family, maybe we should calibrate per device + if (strstr(srslte_rf_name(&rf_device), "uhd")) { + burst_preamble_sec = uhd_default_burst_preamble_sec; + } else if (strstr(srslte_rf_name(&rf_device), "bladerf")) { + burst_preamble_sec = blade_default_burst_preamble_sec; + } else { + printf("\nWarning burst preamble is not calibrated for device %s. Set a value manually\n\n", srslte_rf_name(&rf_device)); + } + + return true; +} + +bool radio_multi::rx_now(cf_t *buffer[SRSLTE_MAX_PORTS], uint32_t nof_samples, srslte_timestamp_t* rxd_time) +{ + void *ptr[SRSLTE_MAX_PORTS]; + for (int i=0;ifull_secs:NULL, rxd_time?&rxd_time->frac_secs:NULL) > 0) { + return true; + } else { + return false; + } +} + + +} \ No newline at end of file