libbladeRF  2.2.1
Nuand bladeRF library
Synchronous Interface: Scheduled TX bursts

This page describes how to use the bladeRF Synchronous Interface to transmit bursts of samples at a specified timestamp. The timestamp is a free-running counter in the FPGA, incrementing at the sample rate specified by bladerf_set_sample_rate(), with each outgoing sample.

Configuring the Synchronous Interface

When timestamps are desired, one must enable metadata support. This is done through the format parameter of the bladerf_sync_config() function:

/* Configure the device's TX for use with the sync interface.
* SC16 Q11 samples *with* metadata are used. */
buffer_size, num_transfers, timeout_ms);
if (status != 0) {
fprintf(stderr, "Failed to configure TX sync interface: %s\n",
bladerf_strerror(status));
goto error;
}

Descriptions of the other parameters may be found in the Synchronous Interface: Basic usage without metadata page.

Remember to enable the front end via bladerf_enable_module() after calling bladerf_sync_config(), and before attempting to call bladerf_sync_tx().

Also ensure that bladerf_sync_config() has been called before attempting to read timestamps via bladerf_get_timestamp().

Burst Transmissions

The synchronous interface allows a burst of samples to be transmitted at a specified timestamp value. The hardware will output zeros after the burst.

The general procedure for transmitting a burst is as follows

The above procedure is useful when the samples for the burst are actively being produced.

If all the samples for the burst are available ahead of time, all of the above steps can be performed with a single function call, with:

Note that when completing a burst with BLADERF_META_FLAG_TX_BURST_END, up to one of the synchronous interface's buffers may be flushed. This is important to consider, since this defines the minimum value of the next timestamp can be provided.

If small bursts need to be scheduled back to back, consider either manually zero-padding between them, or using the BLADERF_META_FLAG_TX_UPDATE_TIMESTAMP flag, as described in one of the following sections.


Waiting for TX to complete

When scheduling transmissions, one must ensure the samples have reached the RF front end before shutting the stream down and disabling TX.

This can be achieved by polling the TX timestamp and waiting for it to exceed the value of the last sample. Generally, one should wait some additional time to account for some group delay in the signal path.

Below is an example wait_for_timestamp() function that provides this functionality, with a simple timeout:

int wait_for_timestamp(struct bladerf *dev,
uint64_t timestamp,
unsigned int timeout_ms)
{
int status;
uint64_t curr_ts = 0;
unsigned int slept_ms = 0;
bool done;
do {
status = bladerf_get_timestamp(dev, dir, &curr_ts);
done = (status != 0) || curr_ts >= timestamp;
if (!done) {
if (slept_ms > timeout_ms) {
done = true;
} else {
usleep(10000);
slept_ms += 10;
}
}
} while (!done);
return status;
}


Scheduled Burst Example

This example illustrates transmitting scheduled bursts, via a single function call.

int sync_tx_meta_sched_example(struct bladerf *dev,
int16_t *samples,
unsigned int num_samples,
unsigned int tx_count,
unsigned int samplerate,
unsigned int timeout_ms)
{
int status = 0;
unsigned int i;
struct bladerf_metadata meta;
/* 5 ms timestamp increment */
const uint64_t ts_inc_5ms = ((uint64_t)samplerate) * 5 / 1000;
/* 150 ms timestamp increment */
const uint64_t ts_inc_150ms = ((uint64_t)samplerate) * 150 / 1000;
memset(&meta, 0, sizeof(meta));
/* Send entire burst worth of samples in one function call */
meta.flags =
/* Retrieve the current timestamp so we can schedule our transmission
* in the future. */
status = bladerf_get_timestamp(dev, BLADERF_TX, &meta.timestamp);
if (status != 0) {
fprintf(stderr, "Failed to get current TX timestamp: %s\n",
bladerf_strerror(status));
return status;
} else {
printf("Current TX timestamp: %016" PRIu64 "\n", meta.timestamp);
}
/* Set initial timestamp ~150 ms in the future */
meta.timestamp += ts_inc_150ms;
for (i = 0; i < tx_count && status == 0; i++) {
/* Get sample to transmit... */
produce_samples(samples, num_samples);
status = bladerf_sync_tx(dev, samples, num_samples, &meta, timeout_ms);
if (status != 0) {
fprintf(stderr, "TX failed: %s\n", bladerf_strerror(status));
return status;
} else {
printf("TX'd @ t=%016" PRIu64 "\n", meta.timestamp);
}
/* Schedule next burst 5 ms into the future */
meta.timestamp += ts_inc_5ms;
}
/* Wait for samples to finish being transmitted. */
if (status == 0) {
meta.timestamp += 2 * num_samples;
status =
wait_for_timestamp(dev, BLADERF_TX, meta.timestamp, timeout_ms);
if (status != 0) {
fprintf(stderr, "Failed to wait for timestamp.\n");
}
}
return status;
}


Using the TX_NOW flag

In some cases, one may wish to transmit a burst immediately while using the BLADERF_FORMAT_SC16_Q11_META format. As shown in the below example, this is possible by setting the BLADERF_META_FLAG_TX_NOW flag. When using this flag the bladerf_metadata.timestamp field is not used.

int sync_tx_meta_now_example(struct bladerf *dev,
int16_t *samples,
unsigned int num_samples,
unsigned int tx_count,
unsigned int timeout_ms)
{
int status = 0;
struct bladerf_metadata meta;
unsigned int i;
memset(&meta, 0, sizeof(meta));
/* Send entire burst worth of samples in one function call */
for (i = 0; i < tx_count && status == 0; i++) {
/* Fetch or produce IQ samples...*/
produce_samples(samples, num_samples);
status = bladerf_sync_tx(dev, samples, num_samples, &meta, timeout_ms);
if (status != 0) {
fprintf(stderr, "TX failed: %s\n", bladerf_strerror(status));
} else {
uint64_t curr_ts;
status = bladerf_get_timestamp(dev, BLADERF_TX, &curr_ts);
if (status != 0) {
fprintf(stderr, "Failed to get current TX timestamp: %s\n",
bladerf_strerror(status));
} else {
printf("TX'd at approximately t=%016" PRIu64 "\n", curr_ts);
}
/* Delay next transmission by approximately 5 ms
*
* This is a very imprecise, "quick and dirty" means to do so in
* cases where no particular intra-burst time is required. */
usleep(5000);
}
}
/* Wait for samples to be TX'd before completing. */
if (status == 0) {
status = bladerf_get_timestamp(dev, BLADERF_TX, &meta.timestamp);
if (status != 0) {
fprintf(stderr, "Failed to get current TX timestamp: %s\n",
bladerf_strerror(status));
return status;
} else {
status = wait_for_timestamp(
dev, BLADERF_TX, meta.timestamp + 2 * num_samples, timeout_ms);
if (status != 0) {
fprintf(stderr, "Failed to wait for timestamp.\n");
}
}
}
return status;
}


Using the UPDATE_TIMESTAMP flag

In some applications, flushing an entire buffer BLADERF_META_FLAG_TX_BURST_END may result in too long of a delay between bursts. As suggested earlier in this page, a user may instead manually zero pad their samples to achieve short discontinuities, or have libbladeRF do this for them using the BLADERF_META_FLAG_TX_UPDATE_TIMESTAMP flag.

The general usage of this is:

The above procedure is shown in the below example:

int sync_tx_meta_update_example(struct bladerf *dev,
int16_t *samples,
unsigned int num_samples,
unsigned int tx_count,
unsigned int samplerate,
unsigned int timeout_ms)
{
int status = 0;
unsigned int i;
struct bladerf_metadata meta;
int16_t zero_sample[] = { 0, 0 }; /* A 0+0j sample */
/* 5 ms timestamp increment */
const uint64_t ts_inc_5ms = ((uint64_t)samplerate) * 5 / 1000;
/* 1.25 ms timestmap increment */
const uint64_t ts_inc_1_25ms = ((uint64_t)samplerate) * 125 / 100000;
memset(&meta, 0, sizeof(meta));
/* The first call to bladerf_sync_tx() will start our long "burst" at
* a specific timestamp.
*
* In successive calls, we'll break this long "burst" up into shorter bursts
* by using the BLADERF_META_FLAG_TX_UPDATE_TIMESTAMP flag with new
* timestamps. libbladeRF will zero-pad discontinuities and/or schedule
* timestamps for us, as needed.
*/
/* Retrieve the current timestamp so we can schedule our transmission
* in the future. */
status = bladerf_get_timestamp(dev, BLADERF_TX, &meta.timestamp);
if (status != 0) {
fprintf(stderr, "Failed to get current TX timestamp: %s\n",
bladerf_strerror(status));
return status;
} else {
printf("Current TX timestamp: %016" PRIu64 "\n", meta.timestamp);
}
/* Start 5 ms in the future */
meta.timestamp += ts_inc_5ms;
for (i = 0; i < tx_count && status == 0; i++) {
/* Get sample to transmit... */
produce_samples(samples, num_samples);
status = bladerf_sync_tx(dev, samples, num_samples, &meta, timeout_ms);
if (status != 0) {
fprintf(stderr, "TX failed: %s\n", bladerf_strerror(status));
return status;
} else {
printf("TX'd @ t=%016" PRIu64 "\n", meta.timestamp);
}
/* Instruct bladerf_sync_tx() to position samples within this burst at
* the specified timestamp. 0+0j will be transmitted up until that
* point. */
/* Schedule samples to be transmitted 1.25 ms after the completion of
* the previous burst */
meta.timestamp += num_samples + ts_inc_1_25ms;
}
/* For simplicity, we use a single zero sample to end the burst and request
* that all pending samples be flushed to the hardware. */
status = bladerf_sync_tx(dev, zero_sample, 1, &meta, timeout_ms);
meta.timestamp++;
/* Wait for samples to finish being transmitted. */
if (status == 0) {
meta.timestamp += 2 * num_samples;
status =
wait_for_timestamp(dev, BLADERF_TX, meta.timestamp, timeout_ms);
if (status != 0) {
fprintf(stderr, "Failed to wait for timestamp.\n");
}
} else {
fprintf(stderr, "Failed to complete burst: %s\n",
bladerf_strerror(status));
}
return status;
}