libbladeRF  2.2.1
Nuand bladeRF library
bladeRF Synchronous Interface: Basic Usage Without Metadata

This page describes how to use the bladeRF Synchronous Interface to transmit and receive samples.

The example listed here configures and uses both the RX and TX channels. To use just one or the other, simply remove the code for the unneeded channel.

Initialization and Configuration

User buffers

First, one must allocate any "working buffers" in which samples will be stored. These are the rx_samples and tx_samples buffers shown in the below snippet. The samples_len variable defines the length of these buffers, in units of samples. Remember, when using the SC16 Q11 format, one sample consists of two int16_t values: I and Q, and when using 2x2 MIMO, samples from two channels are interleaved.

/* "User" samples buffers and their associated sizes, in units of samples.
* Recall that one sample = two int16_t values. */
int16_t *rx_samples = NULL;
int16_t *tx_samples = NULL;
const unsigned int samples_len = 10000; /* May be any (reasonable) size */
/* Allocate a buffer to store received samples in */
rx_samples = malloc(samples_len * 2 * 1 * sizeof(int16_t));
if (rx_samples == NULL) {
perror("malloc");
}
/* Allocate a buffer to prepare transmit data in */
tx_samples = malloc(samples_len * 2 * 1 * sizeof(int16_t));
if (tx_samples == NULL) {
perror("malloc");
free(rx_samples);
}

Configuring the Synchronous Interface

libbladeRF's synchronous interface maintains its own set of internal buffers, which it uses to keep an underlying asynchronous data stream running. This alleviates the need for the user to handle asynchronous callbacks and managing buffer transfers. Instead, the synchronous interface simply copies data to/from the aforementioned "user" buffers, for arbitrary request lengths.

It is important to note, however, that users of the synchronous interface should still be conscious of the workings of the underlying Asynchronous Interface.

Before making calls to bladerf_sync_rx() and bladerf_sync_tx(), one must configure the underlying stream using the bladerf_sync_config() function, as shown in the following snippet.

The second argument to bladerf_sync_config() specifies the channel direction and layout. In the case of SISO (1x1), we use BLADERF_RX_X1 and BLADERF_TX_X1 layouts for RX and TX, respectively.

If one forgets to configure the synchronous interface before attempting to call bladerf_sync_rx() or bladerf_sync_tx(), a BLADERF_ERR_INVAL status will be returned.

static int init_sync(struct bladerf *dev)
{
int status;
/* These items configure the underlying asynch stream used by the sync
* interface. The "buffer" here refers to those used internally by worker
* threads, not the user's sample buffers.
*
* It is important to remember that TX buffers will not be submitted to
* the hardware until `buffer_size` samples are provided via the
* bladerf_sync_tx call. Similarly, samples will not be available to
* RX via bladerf_sync_rx() until a block of `buffer_size` samples has been
* received.
*/
const unsigned int num_buffers = 16;
const unsigned int buffer_size = 8192; /* Must be a multiple of 1024 */
const unsigned int num_transfers = 8;
const unsigned int timeout_ms = 3500;
/* Configure both the device's x1 RX and TX channels for use with the
* synchronous
* interface. SC16 Q11 samples *without* metadata are used. */
num_buffers, buffer_size, num_transfers,
timeout_ms);
if (status != 0) {
fprintf(stderr, "Failed to configure RX sync interface: %s\n",
bladerf_strerror(status));
return status;
}
num_buffers, buffer_size, num_transfers,
timeout_ms);
if (status != 0) {
fprintf(stderr, "Failed to configure TX sync interface: %s\n",
bladerf_strerror(status));
}
return status;
}

The buffer_size parameter configures the size of the buffers, in units of samples, that are sent to/from the hardware via the USB interface. These must be a multiple of 1024. Smaller values will help minimize latency, but will require that the user can process samples quickly. If one does not have low-latency constraints, buffer sizes of 8192 or 16384 are generally good starting points.

The num_buffers parameter defines how many internal samples buffers should be allocated and used by the interface. A large number of buffers ensures that the buffers can continue to be filled/consumed while the user operates on samples, with a potential trade-off of increased latency. If this number is too small one may risk overrunning RX buffers, or underrunning TX buffers.

The num_transfers parameter defines how many of these buffers may be in-flight at any given point in time. As a rule of thumb, a value that is less than or equal to half the number of buffers is a good starting value. Note that the maximum value may be limited by the underlying (USB) layer. It is generally not necessary (or sometimes even possible) to exceed a value of 32 transfers.

The timeout_ms parameter defines a maximum amount of time that is allowed between completion of the synchronous interface's underlying stream buffers. Take care to ensure that this value is sufficiently large, considering the sample rate being used and the desired size of stream buffers.

Enabling and Disabling Front Ends

After configuring the synchronous interface, one must also enable the RF front end of the associated stream direction(s), via bladerf_enable_module():

status = bladerf_enable_module(dev, BLADERF_RX, true);
if (status != 0) {
fprintf(stderr, "Failed to enable RX: %s\n", bladerf_strerror(status));
goto out;
}
status = bladerf_enable_module(dev, BLADERF_TX, true);
if (status != 0) {
fprintf(stderr, "Failed to enable TX: %s\n", bladerf_strerror(status));
goto out;
}

Receiving and Transmitting Samples

The bladerf_sync_rx() and bladerf_sync_tx() functions are used to receive and transmit an arbitrary number of samples.

Remember that when transmitting, the samples will only be sent to the hardware once an underlying stream buffer has been filled (whose size was defined by the buffer_size parameter provided to bladerf_sync_config()). Therefore, when transmitting a small number of samples, one may need to zero-pad or configure the interface to use small buffers.

Below is an example loop that receives samples, processes them, and occasionally transmits a response.

Since metadata is not being used, a NULL pointer is provided to the bladerf_metadata parameter of the bladerf_sync_rx and bladerf_sync_tx calls.

The timeout (in ms) parameter defines how long bladerf_sync_rx() and bladerf_sync_tx() may block. This value should be greater than the timeout provided to bladerf_sync_config() to ensure these functions do not time out before the underlying data stream times out (or completes).

while (status == 0 && !done) {
/* Receive samples */
status = bladerf_sync_rx(dev, rx_samples, samples_len, NULL, 5000);
if (status == 0) {
/* Process these samples, and potentially produce a response
* to transmit */
done = do_work(rx_samples, samples_len, &have_tx_data, tx_samples,
samples_len);
if (!done && have_tx_data) {
/* Transmit a response */
status =
bladerf_sync_tx(dev, tx_samples, samples_len, NULL, 5000);
if (status != 0) {
fprintf(stderr, "Failed to TX samples: %s\n",
bladerf_strerror(status));
}
}
} else {
fprintf(stderr, "Failed to RX samples: %s\n",
bladerf_strerror(status));
}
}
if (status == 0) {
/* Wait a few seconds for any remaining TX samples to finish
* reaching the RF front-end */
usleep(2000000);
}

At the end of this loop, a usleep() call is made to ensure buffers of transmitted samples finally reach the RF front end. Note that the lower bound this delay is determined by the number of buffers that may have been filled by bladerf_sync_tx() and the sample rate.

Deinitializing and De-allocating Resources

When finished using a stream, bladerf_enable_module() may be used to turn off the associated RF front end. This will also de-initialize and deallocate the underlying data associated with the stream.

/* Disable RX, shutting down our underlying RX stream */
status = bladerf_enable_module(dev, BLADERF_RX, false);
if (status != 0) {
fprintf(stderr, "Failed to disable RX: %s\n", bladerf_strerror(status));
}
/* Disable TX, shutting down our underlying TX stream */
status = bladerf_enable_module(dev, BLADERF_TX, false);
if (status != 0) {
fprintf(stderr, "Failed to disable TX: %s\n", bladerf_strerror(status));
}
/* Free up our resources */
free(rx_samples);
free(tx_samples);




Complete listing

static int init_sync(struct bladerf *dev)
{
int status;
/* These items configure the underlying asynch stream used by the sync
* interface. The "buffer" here refers to those used internally by worker
* threads, not the user's sample buffers.
*
* It is important to remember that TX buffers will not be submitted to
* the hardware until `buffer_size` samples are provided via the
* bladerf_sync_tx call. Similarly, samples will not be available to
* RX via bladerf_sync_rx() until a block of `buffer_size` samples has been
* received.
*/
const unsigned int num_buffers = 16;
const unsigned int buffer_size = 8192; /* Must be a multiple of 1024 */
const unsigned int num_transfers = 8;
const unsigned int timeout_ms = 3500;
/* Configure both the device's x1 RX and TX channels for use with the
* synchronous
* interface. SC16 Q11 samples *without* metadata are used. */
num_buffers, buffer_size, num_transfers,
timeout_ms);
if (status != 0) {
fprintf(stderr, "Failed to configure RX sync interface: %s\n",
bladerf_strerror(status));
return status;
}
num_buffers, buffer_size, num_transfers,
timeout_ms);
if (status != 0) {
fprintf(stderr, "Failed to configure TX sync interface: %s\n",
bladerf_strerror(status));
}
return status;
}
int sync_rx_example(struct bladerf *dev)
{
int status, ret;
bool done = false;
bool have_tx_data = false;
/* "User" samples buffers and their associated sizes, in units of samples.
* Recall that one sample = two int16_t values. */
int16_t *rx_samples = NULL;
int16_t *tx_samples = NULL;
const unsigned int samples_len = 10000; /* May be any (reasonable) size */
/* Allocate a buffer to store received samples in */
rx_samples = malloc(samples_len * 2 * 1 * sizeof(int16_t));
if (rx_samples == NULL) {
perror("malloc");
}
/* Allocate a buffer to prepare transmit data in */
tx_samples = malloc(samples_len * 2 * 1 * sizeof(int16_t));
if (tx_samples == NULL) {
perror("malloc");
free(rx_samples);
}
/* Initialize synch interface on RX and TX */
status = init_sync(dev);
if (status != 0) {
goto out;
}
status = bladerf_enable_module(dev, BLADERF_RX, true);
if (status != 0) {
fprintf(stderr, "Failed to enable RX: %s\n", bladerf_strerror(status));
goto out;
}
status = bladerf_enable_module(dev, BLADERF_TX, true);
if (status != 0) {
fprintf(stderr, "Failed to enable TX: %s\n", bladerf_strerror(status));
goto out;
}
while (status == 0 && !done) {
/* Receive samples */
status = bladerf_sync_rx(dev, rx_samples, samples_len, NULL, 5000);
if (status == 0) {
/* Process these samples, and potentially produce a response
* to transmit */
done = do_work(rx_samples, samples_len, &have_tx_data, tx_samples,
samples_len);
if (!done && have_tx_data) {
/* Transmit a response */
status =
bladerf_sync_tx(dev, tx_samples, samples_len, NULL, 5000);
if (status != 0) {
fprintf(stderr, "Failed to TX samples: %s\n",
bladerf_strerror(status));
}
}
} else {
fprintf(stderr, "Failed to RX samples: %s\n",
bladerf_strerror(status));
}
}
if (status == 0) {
/* Wait a few seconds for any remaining TX samples to finish
* reaching the RF front-end */
usleep(2000000);
}
out:
ret = status;
/* Disable RX, shutting down our underlying RX stream */
status = bladerf_enable_module(dev, BLADERF_RX, false);
if (status != 0) {
fprintf(stderr, "Failed to disable RX: %s\n", bladerf_strerror(status));
}
/* Disable TX, shutting down our underlying TX stream */
status = bladerf_enable_module(dev, BLADERF_TX, false);
if (status != 0) {
fprintf(stderr, "Failed to disable TX: %s\n", bladerf_strerror(status));
}
/* Free up our resources */
free(rx_samples);
free(tx_samples);
return ret;
}