mirror of https://github.com/pvnis/srsRAN_4G.git
SIDEKIQ: Initial implementation
SIDEKIQ: Add SKIQ_FOUND to RF found condition SIDEKIQ: finished SKIQ component abstraction SIDEKIQ: fix issues and added external PPS example SIDEKIQ: add PPS test card index argument SIDEKIQ: improvements SIDEKIQ: improved srate change SIDEKIQ: more improvements SIDEKIQ: more fixes SIDEKIQ: fix Rx ch gain SIDEKIQ: Fix multi-card synchronism SIDEKIQ: Better Rx gain trackingmaster
parent
1098d9d444
commit
96ee4b7258
@ -0,0 +1,58 @@
|
||||
INCLUDE(FindPkgConfig)
|
||||
#PKG_CHECK_MODULES(SKIQ SKIQ)
|
||||
IF(NOT SKIQ_FOUND)
|
||||
|
||||
FIND_PATH(
|
||||
SKIQ_INCLUDE_DIRS
|
||||
NAMES sidekiq_api.h
|
||||
HINTS $ENV{SKIQ_DIR}/inc
|
||||
$ENV{SKIQ_DIR}/sidekiq_core/inc
|
||||
PATHS /usr/local/include
|
||||
/usr/include
|
||||
)
|
||||
|
||||
FIND_LIBRARY(
|
||||
SKIQ_LIBRARY
|
||||
NAMES sidekiq__x86_64.gcc
|
||||
HINTS $ENV{SKIQ_DIR}/lib
|
||||
PATHS /usr/local/lib
|
||||
/usr/lib
|
||||
/usr/lib/x86_64-linux-gnu
|
||||
/usr/local/lib64
|
||||
/usr/local/lib32
|
||||
)
|
||||
|
||||
FIND_LIBRARY(
|
||||
SKIQ_LIBRARY_GLIB
|
||||
NAMES libglib-2.0.a
|
||||
HINTS $ENV{SKIQ_DIR}/lib/support/x86_64.gcc/usr/lib/epiq
|
||||
PATHS /usr/local/lib
|
||||
/usr/lib
|
||||
/usr/lib/epiq
|
||||
/usr/lib/x86_64-linux-gnu
|
||||
/usr/local/lib64
|
||||
/usr/local/lib32
|
||||
)
|
||||
|
||||
FIND_LIBRARY(
|
||||
SKIQ_LIBRARY_USB
|
||||
NAMES libusb-1.0.a
|
||||
HINTS $ENV{SKIQ_DIR}/lib/support/x86_64.gcc/usr/lib/epiq
|
||||
PATHS /usr/local/lib
|
||||
/usr/lib
|
||||
/usr/lib/epiq
|
||||
/usr/lib/x86_64-linux-gnu
|
||||
/usr/local/lib64
|
||||
/usr/local/lib32
|
||||
)
|
||||
|
||||
set(SKIQ_LIBRARIES ${SKIQ_LIBRARY} ${SKIQ_LIBRARY_GLIB} ${SKIQ_LIBRARY_USB})
|
||||
|
||||
message(STATUS "SKIQ LIBRARIES " ${SKIQ_LIBRARIES})
|
||||
message(STATUS "SKIQ INCLUDE DIRS " ${SKIQ_INCLUDE_DIRS})
|
||||
|
||||
INCLUDE(FindPackageHandleStandardArgs)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(SKIQ DEFAULT_MSG SKIQ_LIBRARIES SKIQ_INCLUDE_DIRS)
|
||||
MARK_AS_ADVANCED(SKIQ_LIBRARIES SKIQ_INCLUDE_DIRS)
|
||||
|
||||
ENDIF(NOT SKIQ_FOUND)
|
@ -0,0 +1,946 @@
|
||||
/**
|
||||
*
|
||||
* \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 <unistd.h>
|
||||
|
||||
#include <sidekiq_api.h>
|
||||
|
||||
#include "rf_helper.h"
|
||||
#include "rf_skiq_imp.h"
|
||||
#include "rf_skiq_imp_card.h"
|
||||
|
||||
/**
|
||||
* According the document Sidekiq API 4.13.0 @ref AD9361TimestampSlip:
|
||||
* Functions that will affect the timestamp:
|
||||
* • skiq_write_rx_LO_freq()
|
||||
* • skiq_write_rx_sample_rate_and_bandwidth()
|
||||
* • skiq_write_tx_LO_freq()
|
||||
* • skiq_run_tx_quadcal()
|
||||
* • skiq_write_rx_freq_tune_mode()
|
||||
* • skiq_write_tx_freq_tune_mode()
|
||||
* Functions that will be affected by the timestamp slip:
|
||||
* • skiq_read_last_1pps_timestamp()
|
||||
* • skiq_receive()
|
||||
* • skiq_transmit()
|
||||
* • skiq_read_curr_rx_timestamp()
|
||||
* • skiq_read_curr_tx_timestamp()
|
||||
*
|
||||
* The functions mentioned on the first group above can be divided in two groups. The first group are the ones that
|
||||
* require restart the tx/rx streams of cards:
|
||||
* • skiq_write_rx_sample_rate_and_bandwidth()
|
||||
*
|
||||
* The module assumes:
|
||||
* - Tx and Rx sampling rates are equal
|
||||
* - Tx/Rx shall be stopped during the configuration
|
||||
* - skiq_stop_rx_streaming_multi_immediate can be called while skiq_receive is being executed
|
||||
* - skiq_receive shall not be called while skiq_stop_rx_streaming_multi_immediate
|
||||
*
|
||||
* In order to update the sampling rate, the RF module shall:
|
||||
* - Stop all cards Rx streams
|
||||
* - Stop all cards Tx streams
|
||||
* - Update Tx/Rx sample rates
|
||||
* - Start Rx stream
|
||||
* - enable Tx stream on the next transmission
|
||||
*
|
||||
* The second group do not require restarting the tx/rx streams. Indeed, they only affect to a single card and there is
|
||||
* no interest on stalling the rest of cards stream. Because of this, the module shall suspend the affected card.
|
||||
* • skiq_write_rx_LO_freq()
|
||||
* • skiq_write_tx_LO_freq()
|
||||
*
|
||||
* The module assumes:
|
||||
* - The Tx/Rx LO frequency is changed for a single card
|
||||
* - The Tx/Rx is stalled only in selected card
|
||||
* - The rest of cards shall keep operating without stalling their streams
|
||||
*
|
||||
* In order to update the Tx/Rx LO frequencies, the RF module shall:
|
||||
* - Suspend the Tx/Rx streams of the card:
|
||||
* - If receive port ring-buffer has samples, the module shall keep reading from ringbuffer;
|
||||
* - Otherwise, the module shall not read from ring-buffer and write zeros in the receive buffer
|
||||
* - Set the Tx/Rx LO frequency
|
||||
* - Resume the reception
|
||||
*
|
||||
*/
|
||||
|
||||
uint32_t rf_skiq_logging_level = SKIQ_LOG_INFO;
|
||||
|
||||
typedef struct {
|
||||
uint32_t nof_cards;
|
||||
uint32_t nof_ports;
|
||||
|
||||
rf_skiq_card_t cards[SKIQ_MAX_NUM_CARDS];
|
||||
|
||||
float cur_tx_gain;
|
||||
|
||||
srsran_rf_info_t info;
|
||||
|
||||
uint64_t next_tstamp;
|
||||
|
||||
double current_srate_hz;
|
||||
cf_t dummy_buffer[RF_SKIQ_DUMMY_BUFFER_SIZE];
|
||||
|
||||
pthread_mutex_t mutex_rx; ///< Makes sure receive function and sampling rate setter are not running simultaneously
|
||||
|
||||
} rf_skiq_handler_t;
|
||||
|
||||
void rf_skiq_suppress_stdout(void* h)
|
||||
{
|
||||
SKIQ_RF_INFO("Suppressing stdout... lowering logging level to warning\n");
|
||||
// rf_skiq_logging_level = SKIQ_LOG_WARNING;
|
||||
}
|
||||
|
||||
static bool rf_skiq_is_streaming(rf_skiq_handler_t* h)
|
||||
{
|
||||
// If a single card is streaming, return true
|
||||
for (uint32_t i = 0; i < h->nof_cards; i++) {
|
||||
if (rf_skiq_card_is_streaming(&h->cards[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool rf_skiq_check_synch(rf_skiq_handler_t* h)
|
||||
{
|
||||
// Get first card system timestamp
|
||||
int64_t ts0_sys = (int64_t)rf_skiq_card_read_sys_timestamp(&h->cards[0]);
|
||||
int64_t ts0_rf = (int64_t)rf_skiq_card_read_rf_timestamp(&h->cards[0]);
|
||||
SKIQ_RF_INFO(" ... Card 0 TS(sys/rf)=%ld/%ld\n", ts0_sys, ts0_rf);
|
||||
|
||||
bool pass = true;
|
||||
// Compare all the other card timestamps
|
||||
for (uint32_t i = 1; i < h->nof_cards; i++) {
|
||||
int64_t ts2_sys = (int64_t)rf_skiq_card_read_sys_timestamp(&h->cards[i]);
|
||||
int64_t ts2_rf = (int64_t)rf_skiq_card_read_rf_timestamp(&h->cards[i]);
|
||||
|
||||
// Use current sampling rate
|
||||
double srate = h->current_srate_hz;
|
||||
|
||||
// If the current srate was not set (zero, nan or Inf), read it back from the first card
|
||||
if (!isnormal(srate)) {
|
||||
uint32_t srate_int = 0;
|
||||
if (skiq_read_rx_sample_rate(0, skiq_rx_hdl_A1, &srate_int, &srate)) {
|
||||
ERROR("Error reading sampling rate\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure all cards system and RF timestamps are inside maximum allowed error window
|
||||
bool card_pass = labs(ts2_sys - ts0_sys) < SKIQ_CARD_SYNC_MAX_ERROR;
|
||||
card_pass = card_pass && labs(ts2_rf - ts0_rf) < (int64_t)(srate / 2);
|
||||
|
||||
// It is enough that a card does not pass to fail the check
|
||||
pass = pass && card_pass;
|
||||
|
||||
SKIQ_RF_INFO(" ... Card %d TS(sys/rf)=(%ld/%ld) (%.4f/%.4f). %s\n",
|
||||
i,
|
||||
ts2_sys,
|
||||
ts2_rf,
|
||||
(double)labs(ts2_sys - ts0_sys) / (double)SKIQ_SYS_TIMESTAMP_FREQ,
|
||||
(double)labs(ts2_rf - ts0_rf) / srate,
|
||||
card_pass ? "Ok" : "KO");
|
||||
}
|
||||
|
||||
return pass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronizes single and multiple cards using the PPS signal. This function helper shall be used once at
|
||||
* initialization.
|
||||
*
|
||||
* @param h SKIQ driver handler
|
||||
* @return SRSRAN_SUCCESS if it is possible to synchronize boards, SRSRAN_ERROR otherwise
|
||||
*/
|
||||
static int rf_skiq_synch_cards(rf_skiq_handler_t* h)
|
||||
{
|
||||
bool do_1pps = h->nof_cards > 1; //< PPS is required when more than one card is used
|
||||
uint32_t trials = 0; //< Count PPS synchronization check trials
|
||||
|
||||
// Try synchronizing timestamps of all cards up to 10 trials
|
||||
do {
|
||||
SKIQ_RF_INFO("Resetting system timestamp trial %d/%d\n", trials + 1, SKIQ_CARD_SYNCH_MAX_TRIALS);
|
||||
|
||||
// Reset timestamp in next PPS
|
||||
for (int i = 0; i < h->nof_cards; i++) {
|
||||
// Sets the timestamps to the last Rx known time.
|
||||
if (rf_skiq_card_update_timestamp(
|
||||
&h->cards[i], do_1pps, h->cards->rx_ports->rb_tstamp_rem + h->current_srate_hz) != SRSRAN_SUCCESS) {
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
// It could be that one or more cards PPS reset was issued just after a PPS signal, so only if there is PPS
|
||||
// (multiple cards), verifies that all cards are synchronised on the same PPS
|
||||
if (!do_1pps) {
|
||||
return SRSRAN_SUCCESS;
|
||||
}
|
||||
|
||||
// Wait for a second to pass
|
||||
SKIQ_RF_INFO(" ... Waiting PPS to pass ...\n");
|
||||
sleep(1);
|
||||
SKIQ_RF_INFO(" ... Checking:\n");
|
||||
|
||||
// Successful synchronization across boards!
|
||||
if (rf_skiq_check_synch(h)) {
|
||||
return SRSRAN_SUCCESS;
|
||||
}
|
||||
|
||||
// Increment trial count
|
||||
trials++;
|
||||
} while (trials < SKIQ_CARD_SYNCH_MAX_TRIALS);
|
||||
|
||||
// All trials have been consumed without a Successful synchronization
|
||||
ERROR("Error card synchronization failed\n");
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
void rf_skiq_register_error_handler(void* h_, srsran_rf_error_handler_t error_handler, void* arg)
|
||||
{
|
||||
rf_skiq_handler_t* h = (rf_skiq_handler_t*)h_;
|
||||
|
||||
SKIQ_RF_INFO("Registering error handler...\n");
|
||||
|
||||
// Set error handler for each card
|
||||
for (uint32_t i = 0; i < h->nof_cards; i++) {
|
||||
rf_skiq_card_set_error_handler(&h->cards[i], error_handler, arg);
|
||||
}
|
||||
}
|
||||
|
||||
const char* rf_skiq_devname(void* h)
|
||||
{
|
||||
return "Sidekiq";
|
||||
}
|
||||
|
||||
int rf_skiq_start_rx_stream(void* h_, bool now)
|
||||
{
|
||||
rf_skiq_handler_t* h = (rf_skiq_handler_t*)h_;
|
||||
|
||||
rf_skiq_synch_cards(h);
|
||||
|
||||
// Get current system timestamp, assume all cards are synchronized
|
||||
uint64_t ts = rf_skiq_card_read_sys_timestamp(&h->cards[0]);
|
||||
|
||||
// Advance a 10th of a second (100ms)
|
||||
ts += SKIQ_SYS_TIMESTAMP_FREQ / 10;
|
||||
|
||||
// Start streams for each card at the indicated timestamp...
|
||||
for (uint32_t i = 0; i < h->nof_cards; i++) {
|
||||
if (rf_skiq_card_start_rx_streaming(&h->cards[i], ts)) {
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
return SRSRAN_SUCCESS;
|
||||
}
|
||||
|
||||
int rf_skiq_stop_rx_stream(void* h_)
|
||||
{
|
||||
rf_skiq_handler_t* h = (rf_skiq_handler_t*)h_;
|
||||
|
||||
for (int i = 0; i < h->nof_cards; i++) {
|
||||
rf_skiq_card_stop_rx_streaming(&h->cards[i]);
|
||||
}
|
||||
|
||||
return SRSRAN_SUCCESS;
|
||||
}
|
||||
|
||||
static int rf_skiq_send_end_of_burst(void* h_)
|
||||
{
|
||||
rf_skiq_handler_t* h = (rf_skiq_handler_t*)h_;
|
||||
|
||||
for (int i = 0; i < h->nof_cards; i++) {
|
||||
rf_skiq_card_end_of_burst(&h->cards[i]);
|
||||
}
|
||||
|
||||
return SRSRAN_SUCCESS;
|
||||
}
|
||||
|
||||
void rf_skiq_flush_buffer(void* h)
|
||||
{
|
||||
SKIQ_RF_INFO("Flushing buffers...\n");
|
||||
|
||||
int n;
|
||||
void* data[SKIQ_MAX_CHANNELS] = {};
|
||||
do {
|
||||
n = rf_skiq_recv_with_time_multi(h, data, 1024, 0, NULL, NULL);
|
||||
} while (n > 0);
|
||||
}
|
||||
|
||||
bool rf_skiq_has_rssi(void* h)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
float rf_skiq_get_rssi(void* h)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
int rf_skiq_open(char* args, void** h)
|
||||
{
|
||||
return rf_skiq_open_multi(args, h, 1);
|
||||
}
|
||||
|
||||
void rf_skiq_log_msg(int32_t priority, const char* message)
|
||||
{
|
||||
if (priority <= rf_skiq_logging_level) {
|
||||
printf("%s", message);
|
||||
}
|
||||
}
|
||||
|
||||
int rf_skiq_open_multi(char* args, void** h_, uint32_t nof_channels)
|
||||
{
|
||||
// Check number of antennas bounds
|
||||
if (nof_channels < SKIQ_MIN_CHANNELS || nof_channels > SKIQ_MAX_CHANNELS) {
|
||||
ERROR("Number of channels (%d) not supported (%d-%d)\n", nof_channels, SKIQ_MIN_CHANNELS, SKIQ_MAX_CHANNELS);
|
||||
return SRSRAN_ERROR_OUT_OF_BOUNDS;
|
||||
}
|
||||
|
||||
rf_skiq_handler_t* h = (rf_skiq_handler_t*)calloc(1, sizeof(rf_skiq_handler_t));
|
||||
if (!h) {
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
*h_ = h;
|
||||
|
||||
// Parse main parameters
|
||||
parse_uint32(args, "nof_cards", 0, &h->nof_cards);
|
||||
parse_uint32(args, "nof_ports", 0, &h->nof_ports);
|
||||
|
||||
char log_level[RF_PARAM_LEN] = "info";
|
||||
parse_string(args, "log_level", 0, log_level);
|
||||
if (strcmp(log_level, "info") == 0) {
|
||||
rf_skiq_logging_level = SKIQ_LOG_INFO;
|
||||
} else if (strcmp(log_level, "debug") == 0) {
|
||||
rf_skiq_logging_level = SKIQ_LOG_DEBUG;
|
||||
} else if (strcmp(log_level, "warn") == 0) {
|
||||
rf_skiq_logging_level = SKIQ_LOG_WARNING;
|
||||
} else if (strcmp(log_level, "error") == 0) {
|
||||
rf_skiq_logging_level = SKIQ_LOG_ERROR;
|
||||
} else {
|
||||
ERROR("Error log_level %s is undefined. Options: debug, info, warn and error\n", log_level);
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
// Register Logger
|
||||
skiq_register_logging(&rf_skiq_log_msg);
|
||||
|
||||
// Get available cards
|
||||
uint8_t nof_available_cards = 0;
|
||||
uint8_t available_cards[SKIQ_MAX_NUM_CARDS] = {};
|
||||
if (skiq_get_cards(skiq_xport_type_auto, &nof_available_cards, available_cards)) {
|
||||
ERROR("Getting available cards\n");
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
//
|
||||
if (h->nof_cards == 0 && h->nof_ports == 0) {
|
||||
if (nof_channels <= (uint32_t)nof_available_cards) {
|
||||
// One channel per card
|
||||
h->nof_cards = nof_channels;
|
||||
h->nof_ports = 1;
|
||||
} else if (nof_channels <= RF_SKIQ_MAX_PORTS_CARD) {
|
||||
// One channel per port
|
||||
h->nof_cards = 1;
|
||||
h->nof_ports = nof_channels;
|
||||
} else if (nof_channels % RF_SKIQ_MAX_PORTS_CARD == 0) {
|
||||
// use all ports
|
||||
h->nof_cards = nof_channels / RF_SKIQ_MAX_PORTS_CARD;
|
||||
h->nof_ports = RF_SKIQ_MAX_PORTS_CARD;
|
||||
} else if (nof_channels % nof_available_cards == 0) {
|
||||
// use all cards
|
||||
h->nof_cards = nof_available_cards;
|
||||
h->nof_ports = nof_channels / nof_available_cards;
|
||||
} else {
|
||||
ERROR("Error deducing the number of cards and ports");
|
||||
}
|
||||
} else if (h->nof_ports == 0 && nof_channels % h->nof_cards == 0) {
|
||||
h->nof_ports = nof_channels / h->nof_cards;
|
||||
} else if (h->nof_cards == 0 && nof_channels % h->nof_ports == 0) {
|
||||
h->nof_cards = nof_channels / h->nof_ports;
|
||||
}
|
||||
|
||||
if (h->nof_cards == 0 || h->nof_cards > nof_available_cards) {
|
||||
ERROR("Error invalid number of cards %d, available %d\n", h->nof_cards, nof_available_cards);
|
||||
return SRSRAN_ERROR_OUT_OF_BOUNDS;
|
||||
}
|
||||
|
||||
if (h->nof_ports == 0 || h->nof_ports > RF_SKIQ_MAX_PORTS_CARD) {
|
||||
ERROR("Error invalid number of cards %d, available %d\n", h->nof_cards, nof_available_cards);
|
||||
return SRSRAN_ERROR_OUT_OF_BOUNDS;
|
||||
}
|
||||
|
||||
// Create default port options
|
||||
rf_skiq_port_opts_t port_opts = {};
|
||||
port_opts.tx_rb_size = 2048;
|
||||
port_opts.rx_rb_size = 2048;
|
||||
port_opts.chan_mode = (h->nof_ports > 1) ? skiq_chan_mode_dual : skiq_chan_mode_single;
|
||||
port_opts.stream_mode = skiq_rx_stream_mode_balanced;
|
||||
|
||||
// Parse other options
|
||||
parse_uint32(args, "tx_rb_size", 0, &port_opts.tx_rb_size);
|
||||
parse_uint32(args, "rx_rb_size", 0, &port_opts.rx_rb_size);
|
||||
parse_string(args, "mode", 0, port_opts.stream_mode_str);
|
||||
|
||||
if (strlen(port_opts.stream_mode_str) > 0) {
|
||||
if (strcmp(port_opts.stream_mode_str, "low_latency") == 0) {
|
||||
port_opts.stream_mode = skiq_rx_stream_mode_low_latency;
|
||||
} else if (strcmp(port_opts.stream_mode_str, "balanced") == 0) {
|
||||
port_opts.stream_mode = skiq_rx_stream_mode_balanced;
|
||||
} else if (strcmp(port_opts.stream_mode_str, "high_tput") == 0) {
|
||||
port_opts.stream_mode = skiq_rx_stream_mode_high_tput;
|
||||
} else {
|
||||
ERROR("Invalid mode: %s; Valid modes are: low_latency, balanced, high_tput\n", port_opts.stream_mode_str);
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
SKIQ_RF_INFO("Opening %d SKIQ cards with %d ports...\n", h->nof_cards, h->nof_ports);
|
||||
|
||||
if (pthread_mutex_init(&h->mutex_rx, NULL)) {
|
||||
ERROR("Error initialising mutex\n");
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
// Initialise driver
|
||||
if (skiq_init(skiq_xport_type_auto, skiq_xport_init_level_full, available_cards, h->nof_cards)) {
|
||||
ERROR("Unable to initialise libsidekiq driver\n");
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
// Initialise each card
|
||||
for (uint32_t i = 0; i < h->nof_cards; i++) {
|
||||
if (rf_skiq_card_init(&h->cards[i], available_cards[i], h->nof_ports, &port_opts)) {
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse default frequencies
|
||||
for (uint32_t i = 0, ch = 0; i < h->nof_cards; i++) {
|
||||
for (uint32_t j = 0; j < h->nof_ports; j++, ch++) {
|
||||
double tx_freq = 0.0;
|
||||
parse_double(args, "tx_freq", ch, &tx_freq);
|
||||
|
||||
if (isnormal(tx_freq)) {
|
||||
rf_skiq_set_tx_freq(h, ch, tx_freq);
|
||||
}
|
||||
double rx_freq = 0.0;
|
||||
parse_double(args, "rx_freq", ch, &rx_freq);
|
||||
|
||||
if (isnormal(rx_freq)) {
|
||||
rf_skiq_set_rx_freq(h, ch, rx_freq);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set a default gain
|
||||
rf_skiq_set_rx_gain(h, SKIQ_RX_GAIN_DEFAULT_dB);
|
||||
|
||||
// Parse default sample rate
|
||||
double srate_hz = 0.0;
|
||||
parse_double(args, "srate", 0, &srate_hz);
|
||||
srate_hz = isnormal(srate_hz) ? srate_hz : SKIQ_DEFAULT_SAMPLING_RATE_HZ;
|
||||
|
||||
// Set a default sampling rate, default can be too low
|
||||
rf_skiq_set_tx_srate(h, srate_hz);
|
||||
rf_skiq_set_rx_srate(h, srate_hz);
|
||||
|
||||
return SRSRAN_SUCCESS;
|
||||
}
|
||||
|
||||
int rf_skiq_close(void* h_)
|
||||
{
|
||||
rf_skiq_handler_t* h = (rf_skiq_handler_t*)h_;
|
||||
|
||||
SKIQ_RF_INFO("Closing...\n");
|
||||
|
||||
// Ensure Tx/Rx streaming is stopped
|
||||
rf_skiq_send_end_of_burst(h);
|
||||
rf_skiq_stop_rx_stream(h);
|
||||
|
||||
// Free all open cards
|
||||
for (int i = 0; i < h->nof_cards; i++) {
|
||||
rf_skiq_card_close(&h->cards[i]);
|
||||
}
|
||||
|
||||
// Close sidekiq SDK
|
||||
skiq_exit();
|
||||
|
||||
pthread_mutex_destroy(&h->mutex_rx);
|
||||
|
||||
// Deallocate object memory
|
||||
if (h != NULL) {
|
||||
free(h);
|
||||
}
|
||||
|
||||
return SRSRAN_SUCCESS;
|
||||
}
|
||||
|
||||
static double rf_skiq_set_srate_hz(rf_skiq_handler_t* h, double srate_hz)
|
||||
{
|
||||
// If the sampling rate is not modified dont bother
|
||||
if (h->current_srate_hz == srate_hz) {
|
||||
return srate_hz;
|
||||
}
|
||||
SKIQ_RF_INFO("Setting sampling rate to %.2f MHz ...\n", srate_hz / 1e6);
|
||||
|
||||
// Save streaming state
|
||||
bool is_streaming = rf_skiq_is_streaming(h);
|
||||
|
||||
// Stop streaming
|
||||
SKIQ_RF_INFO(" ... Stop Tx/Rx streaming\n");
|
||||
rf_skiq_send_end_of_burst(h);
|
||||
rf_skiq_stop_rx_stream(h);
|
||||
|
||||
// Set sampling
|
||||
SKIQ_RF_INFO(" ... Setting sampling rates to %.2f MHz\n", srate_hz / 1e6);
|
||||
pthread_mutex_lock(&h->mutex_rx);
|
||||
for (uint32_t i = 0; i < h->nof_cards; i++) {
|
||||
rf_skiq_card_set_srate_hz(&h->cards[i], (uint32_t)srate_hz);
|
||||
}
|
||||
pthread_mutex_unlock(&h->mutex_rx);
|
||||
|
||||
// Start streaming if it was started
|
||||
if (is_streaming) {
|
||||
SKIQ_RF_INFO(" ... Start Rx streaming\n");
|
||||
rf_skiq_start_rx_stream(h, true);
|
||||
}
|
||||
|
||||
// Update current sampling rate
|
||||
h->current_srate_hz = srate_hz;
|
||||
SKIQ_RF_INFO(" ... Done!\n");
|
||||
|
||||
return srate_hz;
|
||||
}
|
||||
|
||||
double rf_skiq_set_rx_srate(void* h, double sample_rate)
|
||||
{
|
||||
return rf_skiq_set_srate_hz((rf_skiq_handler_t*)h, sample_rate);
|
||||
}
|
||||
|
||||
double rf_skiq_set_tx_srate(void* h, double sample_rate)
|
||||
{
|
||||
return rf_skiq_set_srate_hz((rf_skiq_handler_t*)h, sample_rate);
|
||||
}
|
||||
|
||||
int rf_skiq_set_rx_gain(void* h_, double rx_gain)
|
||||
{
|
||||
rf_skiq_handler_t* h = (rf_skiq_handler_t*)h_;
|
||||
|
||||
for (uint32_t i = 0; i < h->nof_cards; i++) {
|
||||
rf_skiq_card_set_rx_gain_db(&h->cards[i], h->nof_ports, rx_gain);
|
||||
}
|
||||
|
||||
return SRSRAN_SUCCESS;
|
||||
}
|
||||
|
||||
int rf_skiq_set_tx_gain(void* h_, double tx_gain)
|
||||
{
|
||||
rf_skiq_handler_t* h = (rf_skiq_handler_t*)h_;
|
||||
|
||||
for (uint32_t i = 0; i < h->nof_cards; i++) {
|
||||
h->cur_tx_gain = rf_skiq_card_set_tx_gain_db(&h->cards[i], h->nof_ports, tx_gain);
|
||||
}
|
||||
|
||||
return SRSRAN_SUCCESS;
|
||||
}
|
||||
|
||||
int rf_skiq_set_tx_gain_ch(void* h_, uint32_t ch, double tx_gain)
|
||||
{
|
||||
rf_skiq_handler_t* h = (rf_skiq_handler_t*)h_;
|
||||
uint32_t card_idx = ch / h->nof_ports;
|
||||
uint32_t port_idx = ch % h->nof_ports;
|
||||
|
||||
if (card_idx >= h->nof_cards || port_idx >= h->nof_ports) {
|
||||
return SRSRAN_ERROR_OUT_OF_BOUNDS;
|
||||
}
|
||||
|
||||
tx_gain = rf_skiq_card_set_tx_gain_db(&h->cards[card_idx], port_idx, tx_gain);
|
||||
|
||||
if (ch == 0) {
|
||||
h->cur_tx_gain = tx_gain;
|
||||
}
|
||||
|
||||
return SRSRAN_SUCCESS;
|
||||
}
|
||||
|
||||
int rf_skiq_set_rx_gain_ch(void* h_, uint32_t ch, double rx_gain)
|
||||
{
|
||||
rf_skiq_handler_t* h = (rf_skiq_handler_t*)h_;
|
||||
uint32_t card_idx = ch / h->nof_ports;
|
||||
uint32_t port_idx = ch % h->nof_ports;
|
||||
|
||||
if (card_idx >= h->nof_cards || port_idx >= h->nof_ports) {
|
||||
return SRSRAN_ERROR_OUT_OF_BOUNDS;
|
||||
}
|
||||
|
||||
rx_gain = rf_skiq_card_set_rx_gain_db(&h->cards[card_idx], port_idx, rx_gain);
|
||||
|
||||
if (ch == 0) {
|
||||
h->cur_tx_gain = rx_gain;
|
||||
}
|
||||
|
||||
return SRSRAN_SUCCESS;
|
||||
}
|
||||
|
||||
double rf_skiq_get_rx_gain(void* h_)
|
||||
{
|
||||
rf_skiq_handler_t* h = (rf_skiq_handler_t*)h_;
|
||||
|
||||
return h->cards[0].cur_rx_gain_db;
|
||||
}
|
||||
|
||||
double rf_skiq_get_tx_gain(void* h_)
|
||||
{
|
||||
rf_skiq_handler_t* h = (rf_skiq_handler_t*)h_;
|
||||
return h->cur_tx_gain;
|
||||
}
|
||||
|
||||
srsran_rf_info_t* rf_skiq_get_info(void* h_)
|
||||
{
|
||||
srsran_rf_info_t* ret = NULL;
|
||||
rf_skiq_handler_t* h = (rf_skiq_handler_t*)h_;
|
||||
|
||||
if (h != NULL) {
|
||||
ret = &h->info;
|
||||
|
||||
rf_skiq_card_update_gain_table(&h->cards[0]);
|
||||
|
||||
ret->min_tx_gain = 0.25 * (double)h->cards[0].param.tx_param->atten_quarter_db_max;
|
||||
ret->max_tx_gain = 0.25 * (double)h->cards[0].param.tx_param->atten_quarter_db_min;
|
||||
ret->min_rx_gain = h->cards[0].rx_gain_table_db[h->cards[0].param.rx_param[0].gain_index_min];
|
||||
ret->max_rx_gain = h->cards[0].rx_gain_table_db[h->cards[0].param.rx_param[0].gain_index_max];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
double rf_skiq_set_rx_freq(void* h_, uint32_t ch, double freq)
|
||||
{
|
||||
rf_skiq_handler_t* h = (rf_skiq_handler_t*)h_;
|
||||
|
||||
uint32_t card_idx = ch / h->nof_ports;
|
||||
uint32_t port_idx = ch % h->nof_ports;
|
||||
|
||||
#pragma message "TODO: The Rx stream needs to stop, RF timestamp shall be aligned with other cards and start again"
|
||||
|
||||
if (card_idx < h->nof_cards && port_idx < h->nof_ports) {
|
||||
return rf_skiq_card_set_rx_freq_hz(&h->cards[card_idx], port_idx, freq);
|
||||
}
|
||||
|
||||
return freq;
|
||||
}
|
||||
|
||||
double rf_skiq_set_tx_freq(void* h_, uint32_t ch, double freq)
|
||||
{
|
||||
rf_skiq_handler_t* h = (rf_skiq_handler_t*)h_;
|
||||
|
||||
uint32_t card_idx = ch / h->nof_ports;
|
||||
uint32_t port_idx = ch % h->nof_ports;
|
||||
|
||||
#pragma message "TODO: The Rx stream needs to stop, RF timestamp shall be aligned with other cards and start again"
|
||||
|
||||
if (card_idx < h->nof_cards && port_idx < h->nof_ports) {
|
||||
return rf_skiq_card_set_tx_freq_hz(&h->cards[card_idx], port_idx, freq);
|
||||
}
|
||||
|
||||
return freq;
|
||||
}
|
||||
|
||||
void tstamp_to_time(rf_skiq_handler_t* h, uint64_t tstamp, time_t* secs, double* frac_secs)
|
||||
{
|
||||
uint64_t srate_hz = (uint64_t)h->current_srate_hz;
|
||||
|
||||
if (srate_hz == 0) {
|
||||
ERROR("Warning: Sampling rate has not been set yet.\n");
|
||||
srate_hz = UINT64_MAX;
|
||||
}
|
||||
|
||||
if (secs) {
|
||||
*secs = (time_t)tstamp / srate_hz;
|
||||
}
|
||||
if (frac_secs) {
|
||||
uint64_t rem = tstamp % srate_hz;
|
||||
*frac_secs = (double)rem / h->current_srate_hz;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t time_to_tstamp(rf_skiq_handler_t* h, time_t secs, double frac_secs)
|
||||
{
|
||||
return secs * h->current_srate_hz + frac_secs * h->current_srate_hz;
|
||||
}
|
||||
|
||||
void rf_skiq_get_time(void* h_, time_t* secs, double* frac_secs)
|
||||
{
|
||||
rf_skiq_handler_t* h = (rf_skiq_handler_t*)h_;
|
||||
uint64_t tstamp = rf_skiq_card_read_rf_timestamp(&h->cards[0]);
|
||||
tstamp_to_time(h, tstamp, secs, frac_secs);
|
||||
}
|
||||
|
||||
static int
|
||||
rf_skiq_discard_rx_samples(rf_skiq_handler_t* h, uint32_t card, uint32_t port, uint32_t nsamples, uint64_t* ts_start)
|
||||
{
|
||||
*ts_start = 0;
|
||||
while (nsamples > 0) {
|
||||
uint64_t ts = 0;
|
||||
|
||||
// Receive in dummy buffer
|
||||
int32_t n = rf_skiq_card_receive(
|
||||
&h->cards[card], port, h->dummy_buffer, SRSRAN_MIN(nsamples, RF_SKIQ_DUMMY_BUFFER_SIZE), &ts);
|
||||
|
||||
// Check for error
|
||||
if (n < 0) {
|
||||
ERROR("An error occurred discarding %d Rx samples for channel %d:%d\n", nsamples, card, port);
|
||||
return n;
|
||||
}
|
||||
|
||||
// Save first timestamp
|
||||
if (*ts_start == 0) {
|
||||
*ts_start = ts;
|
||||
}
|
||||
|
||||
// Decrement pending samples
|
||||
nsamples -= n;
|
||||
}
|
||||
|
||||
return nsamples;
|
||||
}
|
||||
|
||||
static int rf_skiq_synch_rx_ports(rf_skiq_handler_t* h)
|
||||
{
|
||||
int64_t tstamp_min = INT64_MAX;
|
||||
int64_t tstamp_max = 0;
|
||||
|
||||
// no need to synchronize
|
||||
if (h->nof_cards * h->nof_ports < 2) {
|
||||
return SRSRAN_SUCCESS;
|
||||
}
|
||||
|
||||
// Find minimum and maximum next timestamps
|
||||
for (uint32_t card = 0; card < h->nof_cards; card++) {
|
||||
// Iterate for all ports
|
||||
for (uint32_t port = 0; port < h->nof_ports; port++) {
|
||||
int64_t ts = (int64_t)rf_skiq_card_get_rx_timestamp(&h->cards[card], port);
|
||||
|
||||
// If the card is not streaming or suspended will return TS 0; so skip
|
||||
if (ts == 0UL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Is minimum?
|
||||
tstamp_min = SRSRAN_MIN(tstamp_min, ts);
|
||||
|
||||
// Is maximum?
|
||||
tstamp_max = SRSRAN_MAX(tstamp_max, ts);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if any synchronization is required
|
||||
if (tstamp_max == tstamp_min) {
|
||||
return SRSRAN_SUCCESS;
|
||||
}
|
||||
|
||||
// Align all channels to the maximum timestamp
|
||||
for (uint32_t card = 0; card < h->nof_cards; card++) {
|
||||
// Iterate for all ports
|
||||
for (uint32_t port = 0; port < h->nof_ports; port++) {
|
||||
uint64_t ts = rf_skiq_card_get_rx_timestamp(&h->cards[card], port);
|
||||
|
||||
// If the card is not streaming or suspended will return TS 0; so skip
|
||||
if (ts == 0UL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Calculate number of samples
|
||||
int nsamples = (int)(tstamp_max - (int64_t)ts);
|
||||
|
||||
// Skip port if negative or zero (possible if stream is enabled during this time)
|
||||
if (nsamples <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Too many samples, sign of some extreme error
|
||||
if (nsamples > SKIQ_PORT_SYNC_MAX_GAP) {
|
||||
ERROR("too many samples to align (%d) for channel %d:%d\n", nsamples, card, port);
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
ts = 0;
|
||||
if (rf_skiq_discard_rx_samples(h, card, port, nsamples, &ts) < SRSRAN_SUCCESS) {
|
||||
ERROR("Error occurred during Rx streams alignment.");
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return SRSRAN_SUCCESS;
|
||||
}
|
||||
|
||||
int rf_skiq_recv_with_time(void* h, void* data, uint32_t nsamples, bool blocking, time_t* secs, double* frac_secs)
|
||||
{
|
||||
return rf_skiq_recv_with_time_multi(h, &data, nsamples, blocking, secs, frac_secs);
|
||||
}
|
||||
|
||||
int rf_skiq_recv_with_time_multi(void* h_,
|
||||
void** data_,
|
||||
uint32_t nsamples,
|
||||
bool blocking,
|
||||
time_t* secs,
|
||||
double* frac_secs)
|
||||
{
|
||||
rf_skiq_handler_t* h = (rf_skiq_handler_t*)h_;
|
||||
uint64_t ts_start = 0;
|
||||
cf_t** data = (cf_t**)data_;
|
||||
|
||||
pthread_mutex_lock(&h->mutex_rx);
|
||||
|
||||
// Perform channel synchronization
|
||||
rf_skiq_synch_rx_ports(h);
|
||||
|
||||
bool completed = false;
|
||||
uint32_t count[SKIQ_MAX_CHANNELS] = {};
|
||||
|
||||
while (!completed) {
|
||||
// Completion true by default
|
||||
completed = true;
|
||||
|
||||
// Iterate over cards
|
||||
for (uint32_t card = 0, chan = 0; card < h->nof_cards; card++) {
|
||||
// Iterate over ports
|
||||
for (uint32_t port = 0; port < h->nof_ports; port++, chan++) {
|
||||
// Calculate number of pending samples
|
||||
uint32_t pending = nsamples - count[chan];
|
||||
|
||||
if (pending > 0) {
|
||||
uint64_t ts = 0;
|
||||
int n = 0;
|
||||
|
||||
// If data is not provided...
|
||||
if (data[chan] == NULL) {
|
||||
// ... discard up to RF_SKIQ_DUMMY_BUFFER_SIZE samples
|
||||
n = rf_skiq_card_receive(
|
||||
&h->cards[card], port, h->dummy_buffer, SRSRAN_MIN(pending, RF_SKIQ_DUMMY_BUFFER_SIZE), &ts);
|
||||
} else {
|
||||
// ... read base-band
|
||||
n = rf_skiq_card_receive(&h->cards[card], port, &data[chan][count[chan]], pending, &ts);
|
||||
}
|
||||
|
||||
// If error is detected, return it
|
||||
if (n < SRSRAN_SUCCESS) {
|
||||
pthread_mutex_unlock(&h->mutex_rx);
|
||||
return n;
|
||||
}
|
||||
|
||||
// Save first valid timestamp
|
||||
if (ts_start == 0) {
|
||||
ts_start = ts;
|
||||
}
|
||||
|
||||
// Increment count for the channel
|
||||
count[chan] += n;
|
||||
|
||||
// Lower completed flag if at least a channel has not reach the target
|
||||
if (count[chan] < nsamples) {
|
||||
completed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&h->mutex_rx);
|
||||
|
||||
// Convert u64 to srsran timestamp
|
||||
tstamp_to_time(h, ts_start, secs, frac_secs);
|
||||
|
||||
// No error, return number of received samples
|
||||
return nsamples;
|
||||
}
|
||||
|
||||
int rf_skiq_send_timed(void* h,
|
||||
void* data,
|
||||
int nsamples,
|
||||
time_t secs,
|
||||
double frac_secs,
|
||||
bool has_time_spec,
|
||||
bool blocking,
|
||||
bool is_start_of_burst,
|
||||
bool is_end_of_burst)
|
||||
{
|
||||
void* _data[SRSRAN_MAX_PORTS] = {};
|
||||
_data[0] = data;
|
||||
|
||||
return rf_skiq_send_timed_multi(
|
||||
h, _data, nsamples, secs, frac_secs, has_time_spec, blocking, is_start_of_burst, is_end_of_burst);
|
||||
}
|
||||
|
||||
int rf_skiq_send_timed_multi(void* h_,
|
||||
void** data_,
|
||||
int nsamples,
|
||||
time_t secs,
|
||||
double frac_secs,
|
||||
bool has_time_spec,
|
||||
bool blocking,
|
||||
bool is_start_of_burst,
|
||||
bool is_end_of_burst)
|
||||
{
|
||||
rf_skiq_handler_t* h = (rf_skiq_handler_t*)h_;
|
||||
cf_t** data = (cf_t**)data_;
|
||||
|
||||
// Force timestamp only if start of burst
|
||||
if (is_start_of_burst) {
|
||||
if (has_time_spec) {
|
||||
h->next_tstamp = time_to_tstamp(h, secs, frac_secs);
|
||||
} else {
|
||||
h->next_tstamp = rf_skiq_card_read_rf_timestamp(&h->cards[0]);
|
||||
h->next_tstamp += (uint64_t)round(h->current_srate_hz / 10); // increment a 10th of a second
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t rpm = 0;
|
||||
bool completed = false;
|
||||
uint32_t count[SKIQ_MAX_CHANNELS] = {};
|
||||
|
||||
while (!completed) {
|
||||
// Completion true by default
|
||||
completed = true;
|
||||
|
||||
// Iterate all cards...
|
||||
for (uint32_t card = 0, chan = 0; card < h->nof_cards; card++) {
|
||||
// Iterate all ports...
|
||||
for (uint32_t port = 0; port < h->nof_ports; port++, chan++) {
|
||||
// Calculate number of pending samples
|
||||
uint32_t pending = nsamples - count[chan];
|
||||
|
||||
if (pending > 0) {
|
||||
uint64_t ts = h->next_tstamp + count[chan];
|
||||
cf_t* ptr = (data[chan] == NULL) ? NULL : &data[chan][count[chan]];
|
||||
int n = rf_skiq_card_send(&h->cards[card], port, ptr, pending, ts);
|
||||
if (n > SRSRAN_SUCCESS) {
|
||||
count[chan] += n;
|
||||
}
|
||||
|
||||
if (count[chan] < nsamples) {
|
||||
completed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Increment timestamp
|
||||
h->next_tstamp += nsamples;
|
||||
|
||||
if (is_end_of_burst) {
|
||||
rf_skiq_send_end_of_burst(h);
|
||||
}
|
||||
|
||||
return (int)rpm;
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
/**
|
||||
*
|
||||
* \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 <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "srsran/config.h"
|
||||
#include "srsran/phy/rf/rf.h"
|
||||
|
||||
SRSRAN_API int rf_skiq_open(char* args, void** handler);
|
||||
|
||||
SRSRAN_API int rf_skiq_open_multi(char* args, void** handler, uint32_t nof_rx_antennas);
|
||||
|
||||
SRSRAN_API const char* rf_skiq_devname(void* h);
|
||||
|
||||
SRSRAN_API int rf_skiq_close(void* h);
|
||||
|
||||
SRSRAN_API int rf_skiq_start_rx_stream(void* h, bool now);
|
||||
|
||||
SRSRAN_API int rf_skiq_start_rx_stream_nsamples(void* h, uint32_t nsamples);
|
||||
|
||||
SRSRAN_API int rf_skiq_stop_rx_stream(void* h);
|
||||
|
||||
SRSRAN_API void rf_skiq_flush_buffer(void* h);
|
||||
|
||||
SRSRAN_API bool rf_skiq_has_rssi(void* h);
|
||||
|
||||
SRSRAN_API float rf_skiq_get_rssi(void* h);
|
||||
|
||||
SRSRAN_API void rf_skiq_set_master_clock_rate(void* h, double rate);
|
||||
|
||||
SRSRAN_API bool rf_skiq_is_master_clock_dynamic(void* h);
|
||||
|
||||
SRSRAN_API double rf_skiq_set_rx_srate(void* h, double freq);
|
||||
|
||||
SRSRAN_API int rf_skiq_set_rx_gain(void* h, double gain);
|
||||
|
||||
SRSRAN_API int rf_skiq_set_rx_gain_ch(void* h_, uint32_t ch, double rx_gain);
|
||||
|
||||
SRSRAN_API double rf_skiq_get_rx_gain(void* h);
|
||||
|
||||
SRSRAN_API double rf_skiq_get_tx_gain(void* h);
|
||||
|
||||
SRSRAN_API srsran_rf_info_t* rf_skiq_get_info(void* h);
|
||||
|
||||
SRSRAN_API void rf_skiq_suppress_stdout(void* h);
|
||||
|
||||
SRSRAN_API void rf_skiq_register_error_handler(void* h, srsran_rf_error_handler_t error_handler, void* arg);
|
||||
|
||||
SRSRAN_API double rf_skiq_set_rx_freq(void* h, uint32_t ch, double freq);
|
||||
|
||||
SRSRAN_API int
|
||||
rf_skiq_recv_with_time(void* h, void* data, uint32_t nsamples, bool blocking, time_t* secs, double* frac_secs);
|
||||
|
||||
SRSRAN_API int
|
||||
rf_skiq_recv_with_time_multi(void* h, void** data, uint32_t nsamples, bool blocking, time_t* secs, double* frac_secs);
|
||||
|
||||
SRSRAN_API double rf_skiq_set_tx_srate(void* h, double freq);
|
||||
|
||||
SRSRAN_API int rf_skiq_set_tx_gain(void* h, double gain);
|
||||
|
||||
SRSRAN_API int rf_skiq_set_tx_gain_ch(void* h, uint32_t ch, double gain);
|
||||
|
||||
SRSRAN_API double rf_skiq_set_tx_freq(void* h, uint32_t ch, double freq);
|
||||
|
||||
SRSRAN_API void rf_skiq_get_time(void* h, time_t* secs, double* frac_secs);
|
||||
|
||||
SRSRAN_API int rf_skiq_send_timed(void* h,
|
||||
void* data,
|
||||
int nsamples,
|
||||
time_t secs,
|
||||
double frac_secs,
|
||||
bool has_time_spec,
|
||||
bool blocking,
|
||||
bool is_start_of_burst,
|
||||
bool is_end_of_burst);
|
||||
|
||||
SRSRAN_API int rf_skiq_send_timed_multi(void* h,
|
||||
void* data[4],
|
||||
int nsamples,
|
||||
time_t secs,
|
||||
double frac_secs,
|
||||
bool has_time_spec,
|
||||
bool blocking,
|
||||
bool is_start_of_burst,
|
||||
bool is_end_of_burst);
|
@ -0,0 +1,578 @@
|
||||
/**
|
||||
*
|
||||
* \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 "rf_skiq_imp_card.h"
|
||||
|
||||
static void* reader_thread(void* arg)
|
||||
{
|
||||
rf_skiq_card_t* q = (rf_skiq_card_t*)arg;
|
||||
|
||||
while (q->state != RF_SKIQ_PORT_STATE_STOP) {
|
||||
// Wait to leave idle state
|
||||
if (q->state == RF_SKIQ_PORT_STATE_IDLE) {
|
||||
SKIQ_RF_INFO("[Rx %d] IDLE\n", q->card);
|
||||
pthread_mutex_lock(&q->mutex);
|
||||
while (q->state == RF_SKIQ_PORT_STATE_IDLE) {
|
||||
pthread_cond_wait(&q->cvar, &q->mutex);
|
||||
}
|
||||
pthread_mutex_unlock(&q->mutex);
|
||||
SKIQ_RF_INFO("[Rx %d] %s\n", q->card, q->state == RF_SKIQ_PORT_STATE_STREAMING ? "STREAMING" : "STOP");
|
||||
}
|
||||
|
||||
// Check exit condition
|
||||
if (q->state == RF_SKIQ_PORT_STATE_STOP) {
|
||||
break;
|
||||
}
|
||||
|
||||
skiq_rx_hdl_t curr_rx_hdl = 0;
|
||||
skiq_rx_block_t* p_rx_block = NULL;
|
||||
uint32_t len = 0;
|
||||
skiq_rx_status_t rx_status = skiq_receive(q->card, &curr_rx_hdl, &p_rx_block, &len);
|
||||
|
||||
switch (rx_status) {
|
||||
case skiq_rx_status_success:
|
||||
// Check Rx index boundary
|
||||
if (p_rx_block->rfic_control >= q->param.rx_param->gain_index_min &&
|
||||
p_rx_block->rfic_control <= q->param.rx_param->gain_index_max) {
|
||||
double new_rx_gain = q->rx_gain_table_db[p_rx_block->rfic_control];
|
||||
|
||||
// If the Rx index has changed, update gain
|
||||
if (new_rx_gain != q->cur_rx_gain_db) {
|
||||
SKIQ_RF_DEBUG("card %d index=%d; gain=%.2f/%.2f dB;\n",
|
||||
q->card,
|
||||
p_rx_block->rfic_control,
|
||||
q->rx_gain_table_db[p_rx_block->rfic_control],
|
||||
q->cur_rx_gain_db);
|
||||
q->cur_rx_gain_db = new_rx_gain;
|
||||
}
|
||||
}
|
||||
if (curr_rx_hdl < q->nof_ports && p_rx_block != NULL && len > SKIQ_RX_HEADER_SIZE_IN_BYTES) {
|
||||
// Convert number of bytes into samples
|
||||
uint32_t nsamples = len / 4 - SKIQ_RX_HEADER_SIZE_IN_WORDS;
|
||||
|
||||
// Give block to the port
|
||||
rf_skiq_rx_port_write(&q->rx_ports[curr_rx_hdl], p_rx_block, nsamples);
|
||||
} else {
|
||||
ERROR("Card %d received data with corrupted pointers\n", q->card);
|
||||
}
|
||||
break;
|
||||
case skiq_rx_status_no_data:
|
||||
// Do nothing
|
||||
break;
|
||||
case skiq_rx_status_error_generic:
|
||||
ERROR("Error: Generic error occurred during skiq_receive.\n");
|
||||
break;
|
||||
case skiq_rx_status_error_overrun:
|
||||
ERROR("Error: overrun error occurred during skiq_receive.\n");
|
||||
break;
|
||||
case skiq_rx_status_error_packet_malformed:
|
||||
ERROR("Error: packet malformed error occurred during skiq_receive.\n");
|
||||
break;
|
||||
case skiq_rx_status_error_card_not_active:
|
||||
ERROR("Error: inactive card error occurred during skiq_receive.\n");
|
||||
break;
|
||||
case skiq_rx_status_error_not_streaming:
|
||||
ERROR("Error: not streaming card error occurred during skiq_receive.\n");
|
||||
break;
|
||||
default:
|
||||
ERROR("Error: the impossible happened during skiq_receive.\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SKIQ_RF_INFO("Exiting reader thread!\n");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int rf_skiq_card_update_gain_table(rf_skiq_card_t* q)
|
||||
{
|
||||
for (uint8_t i = q->param.rx_param->gain_index_min; i <= q->param.rx_param->gain_index_max; i++) {
|
||||
if (skiq_read_rx_cal_offset_by_gain_index(q->card, skiq_rx_hdl_A1, i, &q->rx_gain_table_db[i])) {
|
||||
ERROR("Reading calibrated Rx gain index %d", i);
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
}
|
||||
return SRSRAN_SUCCESS;
|
||||
}
|
||||
|
||||
int rf_skiq_card_init(rf_skiq_card_t* q, uint8_t card, uint8_t nof_ports, const rf_skiq_port_opts_t* opts)
|
||||
{
|
||||
q->card = card;
|
||||
q->nof_ports = nof_ports;
|
||||
|
||||
// Reprogram FPGA to reset all states
|
||||
if (skiq_prog_fpga_from_flash(card)) {
|
||||
ERROR("Error programming card %d from flash\n", q->card);
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
// Read card parameters
|
||||
if (skiq_read_parameters(card, &q->param)) {
|
||||
ERROR("Reading card %d param", card);
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
// Check number of rx channels
|
||||
if (q->param.rf_param.num_rx_channels < nof_ports) {
|
||||
ERROR("Card %d does not support %d Rx channels", card, nof_ports);
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
// Check number of tx channels
|
||||
if (q->param.rf_param.num_tx_channels < nof_ports) {
|
||||
ERROR("Card %d does not support %d Tx channels", card, nof_ports);
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
// set a modest rx timeout
|
||||
if (skiq_set_rx_transfer_timeout(card, 1000)) {
|
||||
ERROR("Setting Rx transfer timeout");
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
// do not pack 12bit samples
|
||||
if (skiq_write_iq_pack_mode(card, false)) {
|
||||
ERROR("Setting Rx IQ pack mode");
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
// set the control output bits to include the gain
|
||||
if (skiq_write_rfic_control_output_config(
|
||||
card, RFIC_CONTROL_OUTPUT_MODE_GAIN_CONTROL_RXA1, RFIC_CONTROL_OUTPUT_MODE_GAIN_BITS) != 0) {
|
||||
ERROR("Unable to configure card %d the RF IC control output (A1)", card);
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
// set RX channel mode
|
||||
if (skiq_write_chan_mode(card, q->mode)) {
|
||||
ERROR("Setting card %d channel mode", card);
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
// Select Rx streaming mode to low latency if the sampling rate is lower than 5MHz
|
||||
if (skiq_write_rx_stream_mode(q->card, opts->stream_mode)) {
|
||||
ERROR("Error setting Rx stream mode\n");
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
// initialise tx/rx ports
|
||||
for (uint8_t i = 0; i < nof_ports; i++) {
|
||||
if (rf_skiq_tx_port_init(&q->tx_ports[i], card, (skiq_tx_hdl_t)i, opts)) {
|
||||
ERROR("Initiating card %d, Tx port %d", card, i);
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
if (rf_skiq_rx_port_init(&q->rx_ports[i], card, (skiq_rx_hdl_t)i, opts)) {
|
||||
ERROR("Initiating card %d, Rx port %d", card, i);
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
if (pthread_mutex_init(&q->mutex, NULL)) {
|
||||
ERROR("Initiating mutex");
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
if (pthread_cond_init(&q->cvar, NULL)) {
|
||||
ERROR("Initiating cvar");
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
// Initialise thread parameters
|
||||
pthread_attr_t attr;
|
||||
struct sched_param param;
|
||||
|
||||
param.sched_priority = sched_get_priority_max(SCHED_FIFO);
|
||||
pthread_attr_init(&attr);
|
||||
if (pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED)) {
|
||||
ERROR("Error not enough privileges to set Scheduling priority\n");
|
||||
}
|
||||
|
||||
if (pthread_attr_setschedpolicy(&attr, SCHED_FIFO)) {
|
||||
ERROR("Error not enough privileges to set Scheduling priority\n");
|
||||
}
|
||||
|
||||
if (pthread_attr_setschedparam(&attr, ¶m)) {
|
||||
ERROR("Error not enough privileges to set Scheduling priority\n");
|
||||
}
|
||||
|
||||
// Launch thread
|
||||
if (pthread_create(&q->thread, &attr, reader_thread, q)) {
|
||||
ERROR("Error creating reader thread with attributes (Did you miss sudo?). Trying without attributes.\n");
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
// Rename thread
|
||||
char thread_name[32] = {};
|
||||
if (snprintf(thread_name, sizeof(thread_name), "SKIQ Rx %d", q->card) > 0) {
|
||||
pthread_setname_np(q->thread, thread_name);
|
||||
}
|
||||
|
||||
return SRSRAN_SUCCESS;
|
||||
}
|
||||
|
||||
void rf_skiq_card_set_error_handler(rf_skiq_card_t* q, srsran_rf_error_handler_t error_handler, void* arg)
|
||||
{
|
||||
for (uint32_t i = 0; i < q->nof_ports; i++) {
|
||||
rf_skiq_tx_port_set_error_handler(&q->tx_ports[i], error_handler, arg);
|
||||
rf_skiq_rx_port_set_error_handler(&q->rx_ports[i], error_handler, arg);
|
||||
}
|
||||
}
|
||||
|
||||
double rf_skiq_card_set_tx_gain_db(rf_skiq_card_t* q, uint32_t port_idx, double gain_db)
|
||||
{
|
||||
double max_atten_dB = 0.25 * q->param.tx_param->atten_quarter_db_max;
|
||||
double min_atten_dB = 0.25 * q->param.tx_param->atten_quarter_db_min;
|
||||
|
||||
// Calculate attenuation:
|
||||
// - 0dB attenuation -> Maximum gain;
|
||||
// - 0dB gain -> Maximum attenuation;
|
||||
double att_dB = max_atten_dB - gain_db;
|
||||
|
||||
// Check gain range
|
||||
if (att_dB < min_atten_dB || att_dB > max_atten_dB) {
|
||||
ERROR("Error port %d:%d the selected gain (%.2f dB) is out of range (%.2f to %.2f dB).\n",
|
||||
q->card,
|
||||
port_idx,
|
||||
gain_db,
|
||||
min_atten_dB,
|
||||
max_atten_dB);
|
||||
}
|
||||
|
||||
// Calculate attenuation index
|
||||
uint16_t att_index = (uint16_t)floor(att_dB * 4);
|
||||
|
||||
// Bound index
|
||||
att_index = SRSRAN_MIN(SRSRAN_MAX(att_index, q->param.tx_param->atten_quarter_db_min),
|
||||
q->param.tx_param->atten_quarter_db_max);
|
||||
|
||||
// Calculate equivalent gain
|
||||
double actual_gain_dB = max_atten_dB - att_index * 0.25;
|
||||
|
||||
// Set gain per port
|
||||
if (port_idx >= q->nof_ports) {
|
||||
for (uint8_t i = 0; i < q->nof_ports; i++) {
|
||||
if (skiq_write_tx_attenuation(q->card, (skiq_tx_hdl_t)i, att_index)) {
|
||||
ERROR("Error setting card %d:%d Tx attenuation\n", q->card, i);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (skiq_write_tx_attenuation(q->card, (skiq_tx_hdl_t)port_idx, att_index)) {
|
||||
ERROR("Error setting card %d:%d Tx attenuation\n", q->card, port_idx);
|
||||
}
|
||||
}
|
||||
|
||||
return actual_gain_dB;
|
||||
}
|
||||
|
||||
double rf_skiq_card_set_rx_gain_db(rf_skiq_card_t* q, uint32_t port_idx, double gain_db)
|
||||
{
|
||||
// Find the nearest gain index in the table
|
||||
int gain_idx = -1;
|
||||
double gain_min_diff = INFINITY;
|
||||
for (int i = q->param.rx_param->gain_index_min; i <= q->param.rx_param->gain_index_max; i++) {
|
||||
double gain_diff = fabs(q->rx_gain_table_db[i] - gain_db);
|
||||
if (gain_diff < gain_min_diff) {
|
||||
gain_min_diff = gain_diff;
|
||||
gain_idx = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (gain_idx >= 0) {
|
||||
gain_db = q->rx_gain_table_db[gain_idx];
|
||||
if (port_idx < q->nof_ports) {
|
||||
// Set single port gain
|
||||
q->issued_rx_gain_db[port_idx] = gain_db;
|
||||
skiq_write_rx_gain(q->card, (skiq_rx_hdl_t)port_idx, gain_idx);
|
||||
} else {
|
||||
// Set all gains
|
||||
for (int i = 0; i < q->nof_ports; i++) {
|
||||
q->issued_rx_gain_db[i] = gain_db;
|
||||
skiq_write_rx_gain(q->card, (skiq_rx_hdl_t)i, gain_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return gain_db;
|
||||
}
|
||||
|
||||
int rf_skiq_card_update_timestamp(rf_skiq_card_t* q, bool use_1pps, uint64_t new_ts)
|
||||
{
|
||||
if (use_1pps) {
|
||||
// Read 1pps source
|
||||
skiq_1pps_source_t pps_source = skiq_1pps_source_unavailable;
|
||||
if (skiq_read_1pps_source(q->card, &pps_source)) {
|
||||
ERROR("Error reading card %d 1PPS source\n", q->card);
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
// Make sure the source is external
|
||||
if (pps_source != skiq_1pps_source_external) {
|
||||
ERROR("Error card %d is not configured with external 1PPS source\n", q->card);
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
// Get last time a PPS was received
|
||||
uint64_t ts_sys_1pps = 0;
|
||||
if (skiq_read_last_1pps_timestamp(q->card, NULL, &ts_sys_1pps)) {
|
||||
ERROR("Reading card %d last 1PPS timestamp", q->card);
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
// Read current system time
|
||||
uint64_t ts = 0;
|
||||
if (skiq_read_curr_sys_timestamp(q->card, &ts)) {
|
||||
ERROR("Reading card %d system timestamp", q->card);
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
// Make sure a 1PPS was received less than 2 seconds ago
|
||||
if (ts - ts_sys_1pps > 2 * SKIQ_SYS_TIMESTAMP_FREQ) {
|
||||
ERROR("Error card %d last PPS was received %.1f seconds ago (%ld - %ld)\n",
|
||||
q->card,
|
||||
(double)(ts - ts_sys_1pps) / (double)SKIQ_SYS_TIMESTAMP_FREQ,
|
||||
ts,
|
||||
ts_sys_1pps);
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
// Set a given time in the future, a 100th of a second (10ms)
|
||||
ts += SKIQ_SYS_TIMESTAMP_FREQ / 100;
|
||||
|
||||
// Order that all timestamps are reseted when next 1PPS signal is received
|
||||
SKIQ_RF_INFO(" ... Resetting card %d system timestamp on next PPS\n", q->card);
|
||||
if (skiq_write_timestamp_update_on_1pps(q->card, ts, new_ts)) {
|
||||
ERROR("Error reseting card %d timestamp on 1 PPS", q->card);
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
} else {
|
||||
// Simply, reset timestamp
|
||||
SKIQ_RF_INFO(" ... Resetting card %d system timestamp now\n", q->card);
|
||||
if (skiq_update_timestamps(q->card, new_ts)) {
|
||||
ERROR("Error resetting card %d timestamp", q->card);
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
}
|
||||
return SRSRAN_SUCCESS;
|
||||
}
|
||||
|
||||
uint64_t rf_skiq_card_read_sys_timestamp(rf_skiq_card_t* q)
|
||||
{
|
||||
uint64_t ts = 0UL;
|
||||
|
||||
if (skiq_read_curr_sys_timestamp(q->card, &ts)) {
|
||||
ERROR("Reading card %d system timestamp", q->card);
|
||||
}
|
||||
|
||||
return ts;
|
||||
}
|
||||
|
||||
uint64_t rf_skiq_card_read_rf_timestamp(rf_skiq_card_t* q)
|
||||
{
|
||||
uint64_t ts = 0UL;
|
||||
|
||||
if (skiq_read_curr_rx_timestamp(q->card, skiq_rx_hdl_A1, &ts)) {
|
||||
ERROR("Reading card %d system timestamp", q->card);
|
||||
}
|
||||
|
||||
return ts;
|
||||
}
|
||||
|
||||
int rf_skiq_card_start_rx_streaming(rf_skiq_card_t* q, uint64_t timestamp)
|
||||
{
|
||||
// Wrong state
|
||||
if (q->state == RF_SKIQ_PORT_STATE_STOP) {
|
||||
ERROR("Error starting Rx stream: wrong state (%d)\n", q->state);
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
// Already enabled
|
||||
if (q->state == RF_SKIQ_PORT_STATE_STREAMING) {
|
||||
SKIQ_RF_INFO("Rx streams in card %d have already started\n", q->card);
|
||||
return SRSRAN_SUCCESS;
|
||||
}
|
||||
|
||||
// Make a list with Rx handlers
|
||||
skiq_rx_hdl_t rx_hdl[RF_SKIQ_MAX_PORTS_CARD];
|
||||
for (uint8_t i = 0; i < RF_SKIQ_MAX_PORTS_CARD; i++) {
|
||||
rx_hdl[i] = (skiq_rx_hdl_t)i;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&q->mutex);
|
||||
|
||||
// Start all Rx in a row
|
||||
if (skiq_start_rx_streaming_multi_on_trigger(q->card, rx_hdl, q->nof_ports, skiq_trigger_src_synced, timestamp)) {
|
||||
ERROR("Failed to start card %d Rx streaming\n", q->card);
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
// Update state and broadcast condition variable
|
||||
q->state = RF_SKIQ_PORT_STATE_STREAMING;
|
||||
pthread_cond_broadcast(&q->cvar);
|
||||
pthread_mutex_unlock(&q->mutex);
|
||||
|
||||
SKIQ_RF_INFO("Rx streams in card %d have started\n", q->card);
|
||||
|
||||
return SRSRAN_SUCCESS;
|
||||
}
|
||||
|
||||
int rf_skiq_card_stop_rx_streaming(rf_skiq_card_t* q)
|
||||
{
|
||||
if (q->state == RF_SKIQ_PORT_STATE_STOP) {
|
||||
ERROR("Error stopping Rx stream: wrong state (%d)\n", q->state);
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
// Avoid stop streaming if it was not started
|
||||
if (q->state == RF_SKIQ_PORT_STATE_IDLE) {
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
// Make a list with Tx/Rx handlers
|
||||
skiq_rx_hdl_t rx_hdl[RF_SKIQ_MAX_PORTS_CARD];
|
||||
for (uint8_t i = 0; i < RF_SKIQ_MAX_PORTS_CARD; i++) {
|
||||
rx_hdl[i] = (skiq_rx_hdl_t)i;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&q->mutex);
|
||||
|
||||
// Update state and broadcast condition variable first
|
||||
q->state = RF_SKIQ_PORT_STATE_IDLE;
|
||||
pthread_cond_broadcast(&q->cvar);
|
||||
|
||||
// Stop all Rx in a row
|
||||
if (skiq_stop_rx_streaming_multi_immediate(q->card, rx_hdl, q->nof_ports)) {
|
||||
ERROR("Failed to stop card %d Rx streaming\n", q->card);
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&q->mutex);
|
||||
|
||||
SKIQ_RF_INFO("Rx streams in card %d have stopped\n", q->card);
|
||||
|
||||
return SRSRAN_SUCCESS;
|
||||
}
|
||||
|
||||
void rf_skiq_card_end_of_burst(rf_skiq_card_t* q)
|
||||
{
|
||||
for (uint32_t i = 0; i < q->nof_ports; i++) {
|
||||
rf_skiq_tx_port_end_of_burst(&q->tx_ports[i]);
|
||||
}
|
||||
}
|
||||
|
||||
int rf_skiq_card_set_srate_hz(rf_skiq_card_t* q, uint32_t srate_hz)
|
||||
{
|
||||
for (uint8_t i = 0; i < q->nof_ports; i++) {
|
||||
// Set transmitter sampling rate
|
||||
if (skiq_write_tx_sample_rate_and_bandwidth(q->card, (skiq_tx_hdl_t)i, srate_hz, srate_hz)) {
|
||||
ERROR("Setting Tx sampling rate\n");
|
||||
}
|
||||
|
||||
// Set receiver sampling rate
|
||||
if (skiq_write_rx_sample_rate_and_bandwidth(q->card, (skiq_rx_hdl_t)i, srate_hz, srate_hz)) {
|
||||
ERROR("Setting Rx sampling rate\n");
|
||||
}
|
||||
|
||||
rf_skiq_rx_port_reset(&q->rx_ports[i]);
|
||||
}
|
||||
|
||||
return SRSRAN_SUCCESS;
|
||||
}
|
||||
|
||||
double rf_skiq_card_set_tx_freq_hz(rf_skiq_card_t* q, uint32_t port_idx, double freq_hz)
|
||||
{
|
||||
q->suspend = true;
|
||||
rf_skiq_tx_port_set_lo(&q->tx_ports[port_idx], (uint64_t)freq_hz);
|
||||
q->suspend = false;
|
||||
|
||||
return freq_hz;
|
||||
}
|
||||
|
||||
double rf_skiq_card_set_rx_freq_hz(rf_skiq_card_t* q, uint32_t port_idx, double freq_hz)
|
||||
{
|
||||
q->suspend = true;
|
||||
rf_skiq_rx_port_set_lo(&q->rx_ports[port_idx], (uint64_t)freq_hz);
|
||||
q->suspend = false;
|
||||
|
||||
// Update gains for only port 0
|
||||
if (port_idx == 0) {
|
||||
// Update gain table
|
||||
rf_skiq_card_update_gain_table(q);
|
||||
|
||||
// Set previous issued gain in dB for the new tables
|
||||
rf_skiq_card_set_rx_gain_db(q, q->nof_ports, q->issued_rx_gain_db[port_idx]);
|
||||
}
|
||||
|
||||
return freq_hz;
|
||||
}
|
||||
|
||||
void rf_skiq_card_close(rf_skiq_card_t* q)
|
||||
{
|
||||
SKIQ_RF_INFO("Closing card %d...\n", q->card);
|
||||
|
||||
// Post stop state to reader thread
|
||||
q->state = RF_SKIQ_PORT_STATE_STOP;
|
||||
pthread_cond_broadcast(&q->cvar);
|
||||
|
||||
// Wait for reader thread to finish
|
||||
pthread_join(q->thread, NULL);
|
||||
|
||||
for (uint8_t i = 0; i < q->nof_ports; i++) {
|
||||
rf_skiq_rx_port_free(&q->rx_ports[i]);
|
||||
rf_skiq_tx_port_free(&q->tx_ports[i]);
|
||||
}
|
||||
|
||||
pthread_cond_destroy(&q->cvar);
|
||||
pthread_mutex_destroy(&q->mutex);
|
||||
|
||||
// Unlocks all cards
|
||||
if (skiq_disable_cards(&q->card, 1)) {
|
||||
ERROR("Unable to disable card %d\n", q->card);
|
||||
}
|
||||
}
|
||||
|
||||
int rf_skiq_card_receive(rf_skiq_card_t* q, uint32_t port_idx, cf_t* dst, uint32_t nsamples, uint64_t* ts)
|
||||
{
|
||||
// If suspended and samples are not available, then set all to zero
|
||||
if (q->suspend && rf_skiq_rx_port_available(&q->rx_ports[port_idx]) == 0) {
|
||||
srsran_vec_cf_zero(dst, nsamples);
|
||||
*ts = 0UL;
|
||||
return nsamples;
|
||||
}
|
||||
|
||||
return rf_skiq_rx_port_read(&q->rx_ports[port_idx], dst, nsamples, ts);
|
||||
}
|
||||
|
||||
uint64_t rf_skiq_card_get_rx_timestamp(rf_skiq_card_t* q, uint32_t port_idx)
|
||||
{
|
||||
if (q->suspend || q->rx_ports[port_idx].rb_overflow) {
|
||||
return 0UL;
|
||||
}
|
||||
|
||||
return rf_skiq_rx_port_get_timestamp(&q->rx_ports[port_idx]);
|
||||
}
|
||||
|
||||
bool rf_skiq_card_is_streaming(rf_skiq_card_t* q)
|
||||
{
|
||||
return q->state == RF_SKIQ_PORT_STATE_STREAMING && !q->suspend;
|
||||
}
|
||||
|
||||
int rf_skiq_card_send(rf_skiq_card_t* q, uint32_t port_idx, const cf_t* data, uint32_t nsamples, uint64_t timestamp)
|
||||
{
|
||||
// If suspended, do not bother the transmitter
|
||||
if (q->suspend) {
|
||||
return nsamples;
|
||||
}
|
||||
|
||||
return rf_skiq_tx_port_send(&q->tx_ports[port_idx], data, nsamples, timestamp);
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
/**
|
||||
*
|
||||
* \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.
|
||||
*
|
||||
*/
|
||||
#ifndef SRSRAN_LIB_SRC_PHY_RF_RF_SKIQ_IMP_CARD_H_
|
||||
#define SRSRAN_LIB_SRC_PHY_RF_RF_SKIQ_IMP_CARD_H_
|
||||
|
||||
#include "rf_skiq_imp_port.h"
|
||||
|
||||
typedef struct {
|
||||
uint8_t card;
|
||||
uint8_t nof_ports;
|
||||
skiq_chan_mode_t mode;
|
||||
|
||||
skiq_param_t param;
|
||||
rf_skiq_tx_port_t tx_ports[RF_SKIQ_MAX_PORTS_CARD];
|
||||
rf_skiq_rx_port_t rx_ports[RF_SKIQ_MAX_PORTS_CARD];
|
||||
|
||||
double rx_gain_table_db[UINT8_MAX + 1];
|
||||
double cur_rx_gain_db;
|
||||
double issued_rx_gain_db[SRSRAN_MAX_PORTS];
|
||||
bool suspend;
|
||||
|
||||
uint64_t start_rx_stream_ts;
|
||||
rf_skiq_port_state_t state;
|
||||
pthread_mutex_t mutex; ///< Protect concurrent access to start/stop rx stream and receive
|
||||
pthread_cond_t cvar;
|
||||
pthread_t thread;
|
||||
|
||||
} rf_skiq_card_t;
|
||||
|
||||
int rf_skiq_card_init(rf_skiq_card_t* q, uint8_t card, uint8_t nof_ports, const rf_skiq_port_opts_t* opts);
|
||||
|
||||
void rf_skiq_card_set_error_handler(rf_skiq_card_t* q, srsran_rf_error_handler_t error_handler, void* arg);
|
||||
|
||||
int rf_skiq_card_update_gain_table(rf_skiq_card_t* q);
|
||||
|
||||
double rf_skiq_card_set_tx_gain_db(rf_skiq_card_t* q, uint32_t port_idx, double gain_db);
|
||||
|
||||
double rf_skiq_card_set_rx_gain_db(rf_skiq_card_t* q, uint32_t port_idx, double gain_db);
|
||||
|
||||
int rf_skiq_card_update_timestamp(rf_skiq_card_t* q, bool use_1pps, uint64_t new_ts);
|
||||
|
||||
uint64_t rf_skiq_card_read_sys_timestamp(rf_skiq_card_t* q);
|
||||
|
||||
uint64_t rf_skiq_card_read_rf_timestamp(rf_skiq_card_t* q);
|
||||
|
||||
int rf_skiq_card_start_rx_streaming(rf_skiq_card_t* q, uint64_t timestamp);
|
||||
|
||||
int rf_skiq_card_stop_rx_streaming(rf_skiq_card_t* q);
|
||||
|
||||
void rf_skiq_card_end_of_burst(rf_skiq_card_t* q);
|
||||
|
||||
int rf_skiq_card_set_srate_hz(rf_skiq_card_t* q, uint32_t srate_hz);
|
||||
|
||||
double rf_skiq_card_set_tx_freq_hz(rf_skiq_card_t* q, uint32_t port_idx, double freq_hz);
|
||||
|
||||
double rf_skiq_card_set_rx_freq_hz(rf_skiq_card_t* q, uint32_t port_idx, double freq_hz);
|
||||
|
||||
void rf_skiq_card_close(rf_skiq_card_t* q);
|
||||
|
||||
int rf_skiq_card_receive(rf_skiq_card_t* q, uint32_t port_idx, cf_t* dst, uint32_t nsamples, uint64_t* ts);
|
||||
|
||||
uint64_t rf_skiq_card_get_rx_timestamp(rf_skiq_card_t* q, uint32_t port_idx);
|
||||
|
||||
bool rf_skiq_card_is_streaming(rf_skiq_card_t* q);
|
||||
|
||||
int rf_skiq_card_send(rf_skiq_card_t* q, uint32_t port_idx, const cf_t* data, uint32_t nsamples, uint64_t timestamp);
|
||||
|
||||
#endif // SRSRAN_LIB_SRC_PHY_RF_RF_SKIQ_IMP_CARD_H_
|
@ -0,0 +1,103 @@
|
||||
/**
|
||||
*
|
||||
* \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.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SRSRAN_RF_SKIQ_IMP_CFG_H
|
||||
#define SRSRAN_RF_SKIQ_IMP_CFG_H
|
||||
|
||||
/**
|
||||
* RF_SKIQ_MAX_PORTS_CARD sets the maximum number of ports per card
|
||||
*/
|
||||
#define RF_SKIQ_MAX_PORTS_CARD 2
|
||||
|
||||
/**
|
||||
* SKIQ_CARD_SYNCH_MAX_TRIALS defines the maximum number of trials to synchronize multiple boards
|
||||
*/
|
||||
#define SKIQ_CARD_SYNCH_MAX_TRIALS 10
|
||||
|
||||
/**
|
||||
* SKIQ_CARD_SYNC_MAX_ERROR sets the maximum number of system "ticks" error between boards during synchronization check.
|
||||
* Consider the communication medium delay between the host and SidekIQ cards.
|
||||
*/
|
||||
#define SKIQ_CARD_SYNC_MAX_ERROR (SKIQ_SYS_TIMESTAMP_FREQ / 2)
|
||||
|
||||
/**
|
||||
* Maximum gap allowed in number of samples between ports
|
||||
*/
|
||||
#define SKIQ_PORT_SYNC_MAX_GAP (1024 * 1024)
|
||||
|
||||
/**
|
||||
* For checking the number of Tx lattes in the FPGA set the next line to the desired check period in number of blocks.
|
||||
* Example: set to 1000 for checking it every 1000 blocks.
|
||||
* WARNING: A low period may cause a reduction of performance in the Host-FPGA communication
|
||||
*/
|
||||
#define SKIQ_TX_LATES_CHECK_PERIOD (1920 * 10)
|
||||
|
||||
/**
|
||||
* Minimum number of channels that this RF device can reach
|
||||
*/
|
||||
#define SKIQ_MIN_CHANNELS (1)
|
||||
|
||||
/**
|
||||
* Maximum number of channels that this RF device can reach
|
||||
*/
|
||||
#define SKIQ_MAX_CHANNELS (SKIQ_MAX_NUM_CARDS * RF_SKIQ_MAX_PORTS_CARD)
|
||||
|
||||
/**
|
||||
* Dummy receive buffer size in samples
|
||||
*/
|
||||
#define RF_SKIQ_DUMMY_BUFFER_SIZE (1024)
|
||||
|
||||
/**
|
||||
* Magic word value as a ring buffer check
|
||||
*/
|
||||
#define SKIQ_RX_BUFFFER_MAGIC_WORD 0xABCD1234
|
||||
|
||||
/**
|
||||
* Normalization value between fixed and floating point conversion
|
||||
*/
|
||||
#define SKIQ_NORM 2048.0
|
||||
|
||||
/**
|
||||
* Default Rx gain in decibels (dB)
|
||||
*/
|
||||
#define SKIQ_RX_GAIN_DEFAULT_dB (+50.0f)
|
||||
|
||||
/**
|
||||
* Default sampling rate in samples per second (Hz)
|
||||
*/
|
||||
#define SKIQ_DEFAULT_SAMPLING_RATE_HZ (30.72e6)
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
#define SKIQ_TX_PACKET_SIZE(N, MODE) (SKIQ_TX_PACKET_SIZE_INCREMENT_IN_WORDS * (N)-SKIQ_TX_HEADER_SIZE_IN_WORDS)
|
||||
|
||||
/**
|
||||
* SKIQ driver standard output MACRO
|
||||
*/
|
||||
extern uint32_t rf_skiq_logging_level;
|
||||
|
||||
#define SKIQ_RF_INFO(...) \
|
||||
do { \
|
||||
if (rf_skiq_logging_level >= SKIQ_LOG_INFO) { \
|
||||
fprintf(stdout, "[SKIQ RF INFO] " __VA_ARGS__); \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
#define SKIQ_RF_DEBUG(...) \
|
||||
do { \
|
||||
if (rf_skiq_logging_level >= SKIQ_LOG_DEBUG) { \
|
||||
fprintf(stdout, "[SKIQ RF INFO] " __VA_ARGS__); \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
#endif // SRSRAN_RF_SKIQ_IMP_CFG_H
|
@ -0,0 +1,551 @@
|
||||
/**
|
||||
*
|
||||
* \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 "rf_skiq_imp_port.h"
|
||||
|
||||
static void rf_skiq_rx_port_handle_overflow(rf_skiq_rx_port_t* q)
|
||||
{
|
||||
srsran_rf_error_t error = {};
|
||||
|
||||
error.type = SRSRAN_RF_ERROR_OVERFLOW;
|
||||
if (q->error_handler) {
|
||||
q->error_handler(q->error_handler_arg, error);
|
||||
} else {
|
||||
SKIQ_RF_INFO("Rx overflow detected in %d:%d\n", q->card, (int)q->hdl);
|
||||
}
|
||||
}
|
||||
|
||||
#if SKIQ_TX_LATES_CHECK_PERIOD
|
||||
static bool rf_skiq_tx_port_handle_late(rf_skiq_tx_port_t* q)
|
||||
{
|
||||
// Get number of lattes from FPGA
|
||||
uint32_t total_late = 0;
|
||||
if (skiq_read_tx_num_late_timestamps(q->card, q->hdl, &total_late)) {
|
||||
ERROR("Error reading lates from port %d:%d\n", q->card, (int)q->hdl);
|
||||
}
|
||||
|
||||
// Calculate number of late timestamps
|
||||
uint32_t new_late = total_late;
|
||||
|
||||
// Remove previous read value
|
||||
if (new_late >= q->last_total_late) {
|
||||
new_late = new_late - q->last_total_late;
|
||||
}
|
||||
|
||||
// Update latest value
|
||||
q->last_total_late = total_late;
|
||||
|
||||
// No late, do not report them
|
||||
if (new_late == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (q->error_handler) {
|
||||
srsran_rf_error_t error = {};
|
||||
error.type = SRSRAN_RF_ERROR_LATE;
|
||||
error.opt = new_late;
|
||||
q->error_handler(q->error_handler_arg, error);
|
||||
} else {
|
||||
SKIQ_RF_INFO("Port %d late events detected in %d:%d\n", new_late, q->card, (int)q->hdl);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif // SKIQ_TX_LATES_CHECK_PERIOD
|
||||
|
||||
static void* writer_thread(void* arg)
|
||||
{
|
||||
uint64_t last_tx_ts = 0;
|
||||
rf_skiq_tx_port_t* q = (rf_skiq_tx_port_t*)arg;
|
||||
skiq_tx_block_t* p_tx_block = NULL;
|
||||
|
||||
if (skiq_start_tx_streaming(q->card, q->hdl)) {
|
||||
ERROR("Error starting Tx stream %d:%d\n", q->card, (int)q->hdl);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
q->state = RF_SKIQ_PORT_STATE_STREAMING;
|
||||
|
||||
while (q->state != RF_SKIQ_PORT_STATE_STOP) {
|
||||
// Read block from ring-buffer
|
||||
int n = srsran_ringbuffer_read_block(&q->rb, (void**)&p_tx_block, q->p_block_nbytes, 1000);
|
||||
|
||||
// Stop state is detected
|
||||
if (q->state == RF_SKIQ_PORT_STATE_STOP) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Ignore blocks with TS=0
|
||||
if (q->p_tx_block->timestamp == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the timestamp is the past (this can be caused by sample rate change)
|
||||
if (last_tx_ts > q->p_tx_block->timestamp) {
|
||||
skiq_read_curr_tx_timestamp(q->card, q->hdl, &last_tx_ts);
|
||||
if (last_tx_ts > q->p_tx_block->timestamp) {
|
||||
ERROR("Tx block (ts=%ld) is in the past (%ld), ignoring\n", q->p_tx_block->timestamp, last_tx_ts);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
last_tx_ts = q->p_tx_block->timestamp + q->block_size;
|
||||
|
||||
// If the ring-buffer did not return with error code...
|
||||
if (n > SRSRAN_SUCCESS) {
|
||||
SKIQ_RF_DEBUG(
|
||||
"[Tx %d:%d block] ts=%ld; nsamples=%d;\n", q->card, (int)q->hdl, p_tx_block->timestamp, q->block_size);
|
||||
|
||||
if (skiq_transmit(q->card, q->hdl, p_tx_block, NULL) < 0) {
|
||||
ERROR("Error transmitting card %d\n", q->card);
|
||||
q->state = RF_SKIQ_PORT_STATE_STOP;
|
||||
}
|
||||
|
||||
#if SKIQ_TX_LATES_CHECK_PERIOD
|
||||
if (q->last_check_ts + SKIQ_TX_LATES_CHECK_PERIOD < p_tx_block->timestamp) {
|
||||
// Handle late timestamps events
|
||||
rf_skiq_tx_port_handle_late(q);
|
||||
|
||||
// Update last check TS
|
||||
q->last_check_ts = p_tx_block->timestamp;
|
||||
}
|
||||
#endif // SKIQ_TX_LATES_CHECK_PERIOD
|
||||
}
|
||||
}
|
||||
|
||||
if (skiq_stop_tx_streaming(q->card, q->hdl)) {
|
||||
ERROR("Error stopping Tx stream %d:%d\n", q->card, (int)q->hdl);
|
||||
}
|
||||
|
||||
SKIQ_RF_INFO("Exiting writer thread!\n");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int rf_skiq_tx_port_init(rf_skiq_tx_port_t* q, uint8_t card, skiq_tx_hdl_t hdl, const rf_skiq_port_opts_t* opts)
|
||||
{
|
||||
// Defines the block size in multiples of 256 words
|
||||
uint32_t nof_blocks_per_packet = 4;
|
||||
switch (opts->stream_mode) {
|
||||
case skiq_rx_stream_mode_high_tput:
|
||||
nof_blocks_per_packet = 8;
|
||||
break;
|
||||
case skiq_rx_stream_mode_low_latency:
|
||||
nof_blocks_per_packet = 2;
|
||||
break;
|
||||
case skiq_rx_stream_mode_balanced:
|
||||
default:
|
||||
// Keep default value
|
||||
break;
|
||||
}
|
||||
|
||||
q->card = card;
|
||||
q->hdl = hdl;
|
||||
q->block_size = SKIQ_TX_PACKET_SIZE(nof_blocks_per_packet, opts->chan_mode);
|
||||
q->p_block_nbytes = q->block_size * 4 + SKIQ_TX_HEADER_SIZE_IN_BYTES;
|
||||
|
||||
// configure the data flow mode to use timestamps
|
||||
if (skiq_write_tx_data_flow_mode(card, hdl, skiq_tx_with_timestamps_data_flow_mode) != 0) {
|
||||
ERROR("Setting Tx data flow mode");
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
// configure the transfer mode to synchronous
|
||||
if (skiq_write_tx_transfer_mode(card, hdl, skiq_tx_transfer_mode_sync) != 0) {
|
||||
ERROR("setting tx transfer mode");
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
// configure Tx block size
|
||||
if (skiq_write_tx_block_size(card, hdl, q->block_size) != 0) {
|
||||
ERROR("configuring Tx block size");
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
q->p_tx_block = skiq_tx_block_allocate(q->block_size);
|
||||
if (q->p_tx_block == NULL) {
|
||||
ERROR("Allocating Tx block");
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
// initialise ring buffer
|
||||
if (srsran_ringbuffer_init(&q->rb, (int)(opts->tx_rb_size * q->p_block_nbytes))) {
|
||||
ERROR("Initialising ringbuffer");
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
// Initialise thread parameters
|
||||
pthread_attr_t attr;
|
||||
struct sched_param param;
|
||||
|
||||
param.sched_priority = sched_get_priority_max(SCHED_FIFO);
|
||||
pthread_attr_init(&attr);
|
||||
if (pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED)) {
|
||||
ERROR("Error not enough privileges to set Scheduling priority\n");
|
||||
}
|
||||
|
||||
if (pthread_attr_setschedpolicy(&attr, SCHED_FIFO)) {
|
||||
ERROR("Error not enough privileges to set Scheduling priority\n");
|
||||
}
|
||||
|
||||
if (pthread_attr_setschedparam(&attr, ¶m)) {
|
||||
ERROR("Error not enough privileges to set Scheduling priority\n");
|
||||
}
|
||||
|
||||
// Launch thread
|
||||
if (pthread_create(&q->thread, &attr, writer_thread, q)) {
|
||||
ERROR("Error creating writer thread with attributes (Did you miss sudo?). Trying without attributes.\n");
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
// Rename thread
|
||||
char thread_name[32] = {};
|
||||
if (snprintf(thread_name, sizeof(thread_name), "SKIQ Tx %d:%d", q->card, (int)q->hdl) > 0) {
|
||||
pthread_setname_np(q->thread, thread_name);
|
||||
}
|
||||
|
||||
return SRSRAN_SUCCESS;
|
||||
}
|
||||
|
||||
void rf_skiq_tx_port_free(rf_skiq_tx_port_t* q)
|
||||
{
|
||||
// Stop thread
|
||||
q->state = RF_SKIQ_PORT_STATE_STOP;
|
||||
|
||||
// Unlock ringbuffer
|
||||
srsran_ringbuffer_write(&q->rb, (void*)q->p_tx_block, q->p_block_nbytes);
|
||||
|
||||
// Wait thread to return
|
||||
pthread_join(q->thread, NULL);
|
||||
|
||||
if (q->p_tx_block) {
|
||||
skiq_tx_block_free(q->p_tx_block);
|
||||
}
|
||||
srsran_ringbuffer_free(&q->rb);
|
||||
}
|
||||
|
||||
void rf_skiq_tx_port_end_of_burst(rf_skiq_tx_port_t* q)
|
||||
{
|
||||
pthread_mutex_lock(&q->mutex);
|
||||
|
||||
// Fill pending block if any, otherwise push a block with zeros
|
||||
if (q->next_offset > 0) {
|
||||
// Calculate pending samples to fill the block
|
||||
uint32_t pending = q->block_size - q->next_offset;
|
||||
|
||||
// Zero pending samples in the block
|
||||
srsran_vec_i16_zero(&q->p_tx_block->data[q->next_offset * 2], 2 * pending);
|
||||
|
||||
// Write block into the ring-buffer
|
||||
srsran_ringbuffer_write_block(&q->rb, q->p_tx_block, q->p_block_nbytes);
|
||||
|
||||
SKIQ_RF_DEBUG("[Tx %d:%d] Padding offset=%d; n=%d; ts=%ld\n",
|
||||
q->card,
|
||||
(int)q->hdl,
|
||||
q->next_offset,
|
||||
pending,
|
||||
q->p_tx_block->timestamp);
|
||||
|
||||
// Reset next offset, so next transmission uses a new block
|
||||
q->next_offset = 0;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&q->mutex);
|
||||
}
|
||||
|
||||
int rf_skiq_tx_port_send(rf_skiq_tx_port_t* q, const cf_t* buffer, uint32_t nsamples, uint64_t ts)
|
||||
{
|
||||
// Ignore transmission if the stream is not enabled
|
||||
if (q->state != RF_SKIQ_PORT_STATE_STREAMING) {
|
||||
return nsamples;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&q->mutex);
|
||||
|
||||
// Calculate destination where IQ shall be stored
|
||||
int16_t* p_tx_iq = &q->p_tx_block->data[q->next_offset * 2];
|
||||
|
||||
// Calculate number of samples to take from buffer
|
||||
nsamples = SRSRAN_MIN(nsamples, q->block_size - q->next_offset);
|
||||
|
||||
// Set time stamp only if no offset
|
||||
if (q->next_offset == 0) {
|
||||
skiq_tx_set_block_timestamp(q->p_tx_block, ts);
|
||||
}
|
||||
|
||||
SKIQ_RF_DEBUG(
|
||||
"[Tx %d:%d] Write offset=%d; nsamples=%d; ts=%ld\n", q->card, (int)q->hdl, q->next_offset, nsamples, ts);
|
||||
|
||||
// Fill data ...
|
||||
if (buffer == NULL) {
|
||||
// ... with zeros
|
||||
srsran_vec_i16_zero(p_tx_iq, 2 * nsamples);
|
||||
} else {
|
||||
// ... with samples, after conversion
|
||||
srsran_vec_convert_conj_cs(buffer, SKIQ_NORM, p_tx_iq, nsamples);
|
||||
}
|
||||
q->next_offset += nsamples;
|
||||
|
||||
// If the number of samples does not fill the block, return early
|
||||
if (q->next_offset < q->block_size) {
|
||||
pthread_mutex_unlock(&q->mutex);
|
||||
return nsamples;
|
||||
}
|
||||
|
||||
if (srsran_ringbuffer_space(&q->rb) < q->p_block_nbytes * 2) {
|
||||
ERROR("Tx buffer overflow\n");
|
||||
pthread_mutex_unlock(&q->mutex);
|
||||
return nsamples;
|
||||
}
|
||||
|
||||
// Actual write in ring buffer
|
||||
int n = srsran_ringbuffer_write_timed_block(&q->rb, q->p_tx_block, q->p_block_nbytes, 5);
|
||||
|
||||
pthread_mutex_unlock(&q->mutex);
|
||||
|
||||
// In case of error (e.g. timeout) return code
|
||||
if (n < SRSRAN_SUCCESS) {
|
||||
return n;
|
||||
}
|
||||
|
||||
// In case of number of bytes mismatch return error
|
||||
if (n != q->p_block_nbytes) {
|
||||
ERROR("Error writing in Tx buffer %d:%d\n", q->card, (int)q->hdl);
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
// Reset offset only if the block was successfully written into the ring-buffer
|
||||
q->next_offset = 0;
|
||||
|
||||
// Return the number of samples writen in the buffer
|
||||
return nsamples;
|
||||
}
|
||||
|
||||
void rf_skiq_tx_port_set_error_handler(rf_skiq_tx_port_t* q, srsran_rf_error_handler_t error_handler, void* arg)
|
||||
{
|
||||
q->error_handler = error_handler;
|
||||
q->error_handler_arg = arg;
|
||||
}
|
||||
|
||||
int rf_skiq_tx_port_set_lo(rf_skiq_tx_port_t* q, uint64_t lo_freq)
|
||||
{
|
||||
// Skip setting LO frequency if it is not required
|
||||
if (q->current_lo == lo_freq) {
|
||||
return SRSRAN_SUCCESS;
|
||||
}
|
||||
|
||||
skiq_filt_t filt = (lo_freq < 3000000000UL) ? skiq_filt_0_to_3000_MHz : skiq_filt_3000_to_6000_MHz;
|
||||
|
||||
if (skiq_write_tx_LO_freq(q->card, q->hdl, lo_freq)) {
|
||||
ERROR("Setting card %d:%d Tx Lo frequency", q->card, (int)q->hdl);
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
if (skiq_write_tx_filter_path(q->card, q->hdl, filt)) {
|
||||
ERROR("Setting card %d:%d Tx filter", q->card, q->hdl);
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
q->current_lo = lo_freq;
|
||||
|
||||
return SRSRAN_SUCCESS;
|
||||
}
|
||||
|
||||
int rf_skiq_rx_port_init(rf_skiq_rx_port_t* q, uint8_t card, skiq_rx_hdl_t hdl, const rf_skiq_port_opts_t* opts)
|
||||
{
|
||||
q->card = card;
|
||||
q->hdl = hdl;
|
||||
|
||||
// enabling DC offset correction can cause an IQ impairment
|
||||
if (skiq_write_rx_dc_offset_corr(card, hdl, false)) {
|
||||
ERROR("Setting RX DC offset correction");
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
// set rx gain mode
|
||||
if (skiq_write_rx_gain_mode(card, hdl, skiq_rx_gain_manual)) {
|
||||
ERROR("Setting RX gain mode");
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
// Rx block size in bytes
|
||||
int32_t rx_block_size = skiq_read_rx_block_size(q->card, opts->stream_mode) - SKIQ_RX_HEADER_SIZE_IN_BYTES;
|
||||
|
||||
// initialise ring buffer
|
||||
if (srsran_ringbuffer_init(&q->rb, (int)(opts->rx_rb_size * rx_block_size))) {
|
||||
ERROR("Initialising ringbuffer");
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
return SRSRAN_SUCCESS;
|
||||
}
|
||||
|
||||
void rf_skiq_rx_port_free(rf_skiq_rx_port_t* q)
|
||||
{
|
||||
srsran_ringbuffer_free(&q->rb);
|
||||
}
|
||||
|
||||
int rf_skiq_rx_port_available(rf_skiq_rx_port_t* q)
|
||||
{
|
||||
return srsran_ringbuffer_status(&q->rb);
|
||||
}
|
||||
|
||||
int rf_skiq_rx_port_read(rf_skiq_rx_port_t* q, cf_t* dest, uint32_t nsamples, uint64_t* ts_start)
|
||||
{
|
||||
// Detect start of new block
|
||||
if (q->rb_read_rem == 0) {
|
||||
skiq_header_t header = {};
|
||||
|
||||
// If ring-buffer overflow was detected...
|
||||
if (q->rb_overflow) {
|
||||
// Reset ring buffer
|
||||
srsran_ringbuffer_reset(&q->rb);
|
||||
|
||||
// Clear overflow flag
|
||||
q->rb_overflow = false;
|
||||
|
||||
// Set samples to zero
|
||||
srsran_vec_cf_zero(dest, nsamples);
|
||||
|
||||
// Set default timestamp
|
||||
*ts_start = 0;
|
||||
|
||||
// Since the buffer is empty, return the full amount of samples so it does not delay reception of other channels
|
||||
return nsamples;
|
||||
}
|
||||
|
||||
// Read a packet. First the header
|
||||
if (srsran_ringbuffer_read(&q->rb, &header, sizeof(skiq_header_t)) != sizeof(skiq_header_t)) {
|
||||
ERROR("Error reading header from ring-buffer %d:%d corrupted\n", q->card, (int)q->hdl);
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
// Check header magic word
|
||||
if (header.magic != SKIQ_RX_BUFFFER_MAGIC_WORD) {
|
||||
ERROR("Error ring-buffer %d:%d corrupted\n", q->card, (int)q->hdl);
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
// Successful read
|
||||
q->rb_read_rem = header.nsamples;
|
||||
q->rb_tstamp_rem = header.tstamp;
|
||||
}
|
||||
|
||||
// Limit number of samples to the remainder of the stored packet
|
||||
nsamples = SRSRAN_MIN(q->rb_read_rem, nsamples);
|
||||
|
||||
// Read any remainder of a packet from the ring buffer
|
||||
int n = srsran_ringbuffer_read_convert_conj(&q->rb, dest, SKIQ_NORM, nsamples);
|
||||
|
||||
// Detect error in read
|
||||
if (n < SRSRAN_SUCCESS) {
|
||||
ERROR("Error reading packet remainder from %d:%d\n", q->card, (int)q->hdl);
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
SKIQ_RF_DEBUG("[Rx %d:%d] Read nsamples=%d/%d; ts=%ld\n", q->card, (int)q->hdl, n, nsamples, q->rb_tstamp_rem);
|
||||
|
||||
// Update timestamp
|
||||
*ts_start = q->rb_tstamp_rem;
|
||||
|
||||
// Update reminder
|
||||
q->rb_read_rem -= n;
|
||||
q->rb_tstamp_rem += n;
|
||||
|
||||
// Return number of read samples
|
||||
return n;
|
||||
}
|
||||
|
||||
uint64_t rf_skiq_rx_port_get_timestamp(rf_skiq_rx_port_t* q)
|
||||
{
|
||||
return q->rb_tstamp_rem;
|
||||
}
|
||||
|
||||
int rf_skiq_rx_port_write(rf_skiq_rx_port_t* q, const skiq_rx_block_t* p_rx_block, uint32_t nsamples)
|
||||
{
|
||||
// Prepare header
|
||||
skiq_header_t header = {};
|
||||
header.magic = SKIQ_RX_BUFFFER_MAGIC_WORD;
|
||||
header.tstamp = p_rx_block->rf_timestamp;
|
||||
header.nsamples = nsamples;
|
||||
|
||||
// Ignore block if the overflow flag has risen
|
||||
if (q->rb_overflow) {
|
||||
return nsamples;
|
||||
}
|
||||
|
||||
SKIQ_RF_DEBUG("[Rx %d:%d block] ts=%ld; nsamples=%d;\n", q->card, (int)q->hdl, header.tstamp, header.nsamples);
|
||||
|
||||
// Check space in the ring-buffer prior to writing
|
||||
if (srsran_ringbuffer_space(&q->rb) >= sizeof(skiq_header_t) + nsamples * 4) {
|
||||
// Write header
|
||||
if (srsran_ringbuffer_write_block(&q->rb, &header, sizeof(skiq_header_t)) != sizeof(skiq_header_t)) {
|
||||
ERROR("Writing header in Rx buffer %d:%d!\n", q->card, (int)q->hdl);
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
// Write IQ samples
|
||||
if (srsran_ringbuffer_write_block(&q->rb, (uint8_t*)p_rx_block->data, (int)nsamples * 4) != nsamples * 4) {
|
||||
ERROR("Writing base-band in Rx buffer %d:%d!\n", q->card, (int)q->hdl);
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
} else {
|
||||
SKIQ_RF_INFO("Rx %d:%d ring-buffer overflow!\n", q->card, (int)q->hdl);
|
||||
q->rb_overflow = true;
|
||||
rf_skiq_rx_port_handle_overflow(q);
|
||||
}
|
||||
|
||||
// Process overload, call handle only for rising-edges
|
||||
if (!q->rf_overflow && p_rx_block->overload) {
|
||||
rf_skiq_rx_port_handle_overflow(q);
|
||||
}
|
||||
q->rf_overflow = p_rx_block->overload;
|
||||
|
||||
return nsamples;
|
||||
}
|
||||
|
||||
void rf_skiq_rx_port_set_error_handler(rf_skiq_rx_port_t* q, srsran_rf_error_handler_t error_handler, void* arg)
|
||||
{
|
||||
q->error_handler = error_handler;
|
||||
q->error_handler_arg = arg;
|
||||
}
|
||||
|
||||
void rf_skiq_rx_port_reset(rf_skiq_rx_port_t* q)
|
||||
{
|
||||
SKIQ_RF_INFO("Rx port %d:%d reset\n", q->card, (int)q->hdl);
|
||||
q->rb_read_rem = 0;
|
||||
q->rb_tstamp_rem = 0;
|
||||
srsran_ringbuffer_reset(&q->rb);
|
||||
}
|
||||
|
||||
int rf_skiq_rx_port_set_lo(rf_skiq_rx_port_t* q, uint64_t lo_freq)
|
||||
{
|
||||
// Skip setting LO frequency if it is not required
|
||||
if (q->current_lo == lo_freq) {
|
||||
return SRSRAN_SUCCESS;
|
||||
}
|
||||
|
||||
skiq_filt_t filt = (lo_freq < 3000000000UL) ? skiq_filt_0_to_3000_MHz : skiq_filt_3000_to_6000_MHz;
|
||||
|
||||
if (skiq_write_rx_LO_freq(q->card, q->hdl, lo_freq)) {
|
||||
ERROR("Setting card %d:%d Tx Lo frequency", q->card, (int)q->hdl);
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
if (skiq_write_rx_preselect_filter_path(q->card, q->hdl, filt)) {
|
||||
ERROR("Setting card %d:%d Tx filter", q->card, q->hdl);
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
q->current_lo = lo_freq;
|
||||
|
||||
return SRSRAN_SUCCESS;
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
/**
|
||||
*
|
||||
* \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.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SRSRAN_RF_SKIQ_IMP_PORT_H
|
||||
#define SRSRAN_RF_SKIQ_IMP_PORT_H
|
||||
|
||||
#include <sidekiq_api.h>
|
||||
#include <srsran/phy/rf/rf.h>
|
||||
|
||||
#include "rf_helper.h"
|
||||
#include "rf_skiq_imp_cfg.h"
|
||||
#include "rf_skiq_imp_port.h"
|
||||
#include "srsran/srsran.h"
|
||||
|
||||
typedef struct {
|
||||
uint32_t magic;
|
||||
uint64_t tstamp;
|
||||
uint32_t nsamples;
|
||||
} skiq_header_t;
|
||||
|
||||
typedef enum {
|
||||
RF_SKIQ_PORT_STATE_IDLE = 0,
|
||||
RF_SKIQ_PORT_STATE_STREAMING,
|
||||
RF_SKIQ_PORT_STATE_STOP
|
||||
} rf_skiq_port_state_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t tx_rb_size;
|
||||
uint32_t rx_rb_size;
|
||||
skiq_chan_mode_t chan_mode;
|
||||
char stream_mode_str[RF_PARAM_LEN];
|
||||
skiq_rx_stream_mode_t stream_mode;
|
||||
} rf_skiq_port_opts_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t card;
|
||||
skiq_tx_hdl_t hdl;
|
||||
skiq_tx_block_t* p_tx_block;
|
||||
uint32_t p_block_nbytes; // Size in bytes including header
|
||||
uint32_t block_size; // Size in words (samples)
|
||||
uint32_t next_offset; // Number of samples remainder
|
||||
srsran_ringbuffer_t rb;
|
||||
rf_skiq_port_state_t state;
|
||||
pthread_t thread;
|
||||
pthread_mutex_t mutex; // Protects p_tx_block
|
||||
|
||||
uint64_t current_lo;
|
||||
|
||||
srsran_rf_error_handler_t error_handler;
|
||||
void* error_handler_arg;
|
||||
|
||||
#if SKIQ_TX_LATES_CHECK_PERIOD
|
||||
uint64_t last_check_ts;
|
||||
uint32_t last_total_late;
|
||||
uint32_t last_total_underruns;
|
||||
#endif // SKIQ_TX_LATES_CHECK_PERIOD
|
||||
} rf_skiq_tx_port_t;
|
||||
|
||||
int rf_skiq_tx_port_init(rf_skiq_tx_port_t* q, uint8_t card, skiq_tx_hdl_t hdl, const rf_skiq_port_opts_t* opts);
|
||||
void rf_skiq_tx_port_free(rf_skiq_tx_port_t* q);
|
||||
void rf_skiq_tx_port_end_of_burst(rf_skiq_tx_port_t* q);
|
||||
int rf_skiq_tx_port_send(rf_skiq_tx_port_t* q, const cf_t* buffer, uint32_t nsamples, uint64_t ts);
|
||||
void rf_skiq_tx_port_set_error_handler(rf_skiq_tx_port_t* q, srsran_rf_error_handler_t error_handler, void* arg);
|
||||
int rf_skiq_tx_port_set_lo(rf_skiq_tx_port_t* q, uint64_t lo_freq);
|
||||
|
||||
typedef struct {
|
||||
uint8_t card;
|
||||
skiq_rx_hdl_t hdl;
|
||||
srsran_ringbuffer_t rb;
|
||||
uint32_t rb_read_rem;
|
||||
uint64_t rb_tstamp_rem;
|
||||
bool rf_overflow; ///< Indicates an RF message was flagged with overflow
|
||||
bool rb_overflow; ///< Indicates that ring-buffer is full and it needs to be flushed
|
||||
|
||||
uint64_t current_lo;
|
||||
|
||||
srsran_rf_error_handler_t error_handler;
|
||||
void* error_handler_arg;
|
||||
} rf_skiq_rx_port_t;
|
||||
|
||||
int rf_skiq_rx_port_init(rf_skiq_rx_port_t* q, uint8_t card, skiq_rx_hdl_t hdl, const rf_skiq_port_opts_t* opts);
|
||||
void rf_skiq_rx_port_free(rf_skiq_rx_port_t* q);
|
||||
int rf_skiq_rx_port_available(rf_skiq_rx_port_t* q);
|
||||
int rf_skiq_rx_port_read(rf_skiq_rx_port_t* q, cf_t* dest, uint32_t nsamples, uint64_t* ts_start);
|
||||
uint64_t rf_skiq_rx_port_get_timestamp(rf_skiq_rx_port_t* q);
|
||||
int rf_skiq_rx_port_write(rf_skiq_rx_port_t* q, const skiq_rx_block_t* p_rx_block, uint32_t nbytes);
|
||||
void rf_skiq_rx_port_set_error_handler(rf_skiq_rx_port_t* q, srsran_rf_error_handler_t error_handler, void* arg);
|
||||
void rf_skiq_rx_port_reset(rf_skiq_rx_port_t* q);
|
||||
int rf_skiq_rx_port_set_lo(rf_skiq_rx_port_t* q, uint64_t lo_freq);
|
||||
|
||||
#endif // SRSRAN_RF_SKIQ_IMP_PORT_H
|
@ -0,0 +1,133 @@
|
||||
/**
|
||||
*
|
||||
* \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 <sidekiq_api.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define ERROR(...) fprintf(stderr, __VA_ARGS__);
|
||||
|
||||
const static uint8_t nof_cards = 1;
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
int ret = -1;
|
||||
uint8_t card = 0;
|
||||
|
||||
if (argc > 1) {
|
||||
card = (uint8_t)strtol(argv[1], NULL, 10);
|
||||
}
|
||||
|
||||
uint8_t nof_available_cards = 0;
|
||||
uint8_t available_cards[SKIQ_MAX_NUM_CARDS] = {};
|
||||
if (skiq_get_cards(skiq_xport_type_auto, &nof_available_cards, available_cards)) {
|
||||
ERROR("Getting available cards\n");
|
||||
goto clean_exit;
|
||||
}
|
||||
|
||||
// Check number of cards bounds
|
||||
if (nof_cards > nof_available_cards) {
|
||||
ERROR("The number of cards (%d) exceeds available cards (%d)\n", nof_cards, nof_available_cards);
|
||||
goto clean_exit;
|
||||
}
|
||||
|
||||
// Initialise driver
|
||||
if (skiq_init(skiq_xport_type_auto, skiq_xport_init_level_full, &card, 1)) {
|
||||
ERROR("Unable to initialise libsidekiq driver\n");
|
||||
goto clean_exit;
|
||||
}
|
||||
|
||||
// Programming FPGA from flash ensures the FPGA and transceiver are completely reseted
|
||||
if (skiq_prog_fpga_from_flash(card)) {
|
||||
ERROR("Error programming FPGA from flash\n");
|
||||
goto clean_exit;
|
||||
}
|
||||
|
||||
// Read 1pps source
|
||||
skiq_1pps_source_t pps_source = skiq_1pps_source_unavailable;
|
||||
if (skiq_read_1pps_source(card, &pps_source)) {
|
||||
ERROR("Error reading card %d 1PPS source\n", card);
|
||||
goto clean_exit;
|
||||
}
|
||||
|
||||
// Make sure the source is external
|
||||
if (pps_source != skiq_1pps_source_external) {
|
||||
ERROR("Error card %d is not configured with external 1PPS source\n", card);
|
||||
goto clean_exit;
|
||||
}
|
||||
|
||||
// Sleeping 5 seconds
|
||||
sleep(5);
|
||||
|
||||
// Get last time a PPS was received
|
||||
uint64_t ts_sys_1pps = 0;
|
||||
if (skiq_read_last_1pps_timestamp(card, NULL, &ts_sys_1pps)) {
|
||||
ERROR("Reading card %d last 1PPS timestamp", card);
|
||||
goto clean_exit;
|
||||
}
|
||||
|
||||
// Read current system time
|
||||
uint64_t ts = 0;
|
||||
if (skiq_read_curr_sys_timestamp(card, &ts)) {
|
||||
ERROR("Reading card %d system timestamp", card);
|
||||
goto clean_exit;
|
||||
}
|
||||
|
||||
// Make sure a 1PPS was received less than 2 seconds ago
|
||||
if (ts - ts_sys_1pps > 2 * SKIQ_SYS_TIMESTAMP_FREQ) {
|
||||
ERROR("Error card %d last PPS was received %.1f seconds ago (ts=%ld)\n",
|
||||
card,
|
||||
(double)(ts - ts_sys_1pps) / (double)SKIQ_SYS_TIMESTAMP_FREQ,
|
||||
ts_sys_1pps);
|
||||
goto clean_exit;
|
||||
}
|
||||
|
||||
// Set a given time in the future, a 100th of a second (10ms)
|
||||
ts += SKIQ_SYS_TIMESTAMP_FREQ / 100;
|
||||
|
||||
// Reset timestamp in a near future on next PPS signal
|
||||
if (skiq_write_timestamp_reset_on_1pps(card, ts)) {
|
||||
ERROR("Error reseting card %d timestamp on 1 PPS", card);
|
||||
goto clean_exit;
|
||||
}
|
||||
|
||||
// Give time to pass 1PPS
|
||||
sleep(1);
|
||||
|
||||
// Read current system time
|
||||
if (skiq_read_curr_sys_timestamp(card, &ts)) {
|
||||
ERROR("Reading card %d system timestamp", card);
|
||||
goto clean_exit;
|
||||
}
|
||||
|
||||
// The current system timestamp should be below 2s
|
||||
if (ts > 2 * SKIQ_SYS_TIMESTAMP_FREQ) {
|
||||
ERROR("Timestamp of card %d is greater than 2 seconds (%.1fs)!\n",
|
||||
card,
|
||||
(double)ts / (double)SKIQ_SYS_TIMESTAMP_FREQ);
|
||||
goto clean_exit;
|
||||
}
|
||||
|
||||
// Success
|
||||
printf("Success!\n");
|
||||
ret = 0;
|
||||
|
||||
clean_exit:
|
||||
if (skiq_disable_cards(&card, 1)) {
|
||||
ERROR("Unable to disable cards\n");
|
||||
}
|
||||
|
||||
// Close sidekiq SDK
|
||||
skiq_exit();
|
||||
|
||||
return ret;
|
||||
}
|
Loading…
Reference in New Issue