libbladeRF: Synchronous Interface: Scheduled TX bursts

| libbladeRF 2.2.1-git 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. */
status = bladerf_sync_config(dev, BLADERF_TX_X1,
BLADERF_FORMAT_SC16_Q11_META, num_buffers,
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

- Call bladerf_sync_tx() with bladerf_metadata.flags set to BLADERF_META_FLAG_TX_BURST_START and bladerf_metadata.timestamp set to the timestamp at which samples should be transmitted.

- Make successive bladerf_sync_tx() calls with bladerf_metadata.flags set to 0. These samples will be contiguously appended to the previous samples. The bladerf_metadata.timestamp field is not used at this point; the caller does not need to worry about advancing it.

- Call bladerf_sync_tx() with the final samples in the burst and the bladerf_metadata.flags set to BLADERF_META_FLAG_TX_BURST_END. This will complete the burst, flush any remaining samples in the synchronous interface's current internal buffer, and write the buffer to the device. Similar to the previous step, any samples provided at this points will be contiguously appended to prior samples, and the bladerf_metadata.timestamp field will not be used.

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:

- bladerf_metadata.flags set to: `(BLADERF_META_FLAG_TX_BURST_START | BLADERF_META_FLAG_TX_BURST_END)`

- bladerf_metadata.timestamp set to the timestamp at which the samples should be transmitted.

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,
bladerf_direction dir,
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;
status = BLADERF_ERR_TIMEOUT;
} 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 =
BLADERF_META_FLAG_TX_BURST_START | BLADERF_META_FLAG_TX_BURST_END;
/* 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 */
meta.flags = BLADERF_META_FLAG_TX_BURST_START | BLADERF_META_FLAG_TX_NOW |
BLADERF_META_FLAG_TX_BURST_END;
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:

- Set bladerf_metadata.timestamp to the desired start of transmission, and bladerf_metadata.flags to BLADERF_META_FLAG_TX_BURST_START. Call bladerf_sync_tx() with the initial samples in the burst.

- Optional: Call bladerf_sync_tx() with bladerf_metadata.flags as needed to transmit samples that are contiguous with the previous samples. bladerf_metadata.timestamp is not used in this state.

- Call bladerf_sync_tx() with bladerf_metadata.flags set to BLADERF_META_FLAG_TX_UPDATE_TIMESTAMP and bladerf_metadata.timestamp set to the desired timestamp. libbladeRF will schedule the provided samples for the specified timestamp, manually zero-padding a buffer if needed.

- Optional: Call bladerf_sync_tx() with bladerf_metadata.flags as needed to transmit samples that are contiguous with the newly scheduled samples. bladerf_metadata.timestamp is not used in this state.

- When done transmitting, call bladerf_sync_tx() with bladerf_metadata.flags set to BLADERF_META_FLAG_TX_BURST_END and two or more zero samples. This will ensure the analog front end DACs are held at a zero value.

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.
*/
meta.flags = BLADERF_META_FLAG_TX_BURST_START;
/* 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. */
meta.flags = BLADERF_META_FLAG_TX_UPDATE_TIMESTAMP;
/* 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. */
meta.flags = BLADERF_META_FLAG_TX_BURST_END;
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;
}
bladerf_strerror
const API_EXPORT char *CALL_CONV bladerf_strerror(int error)
BLADERF_TX_X1
@ BLADERF_TX_X1
Definition: libbladeRF.h:691
BLADERF_ERR_TIMEOUT
#define BLADERF_ERR_TIMEOUT
Definition: libbladeRF.h:3994
bladerf_metadata::timestamp
bladerf_timestamp timestamp
Definition: libbladeRF.h:2284
BLADERF_META_FLAG_TX_UPDATE_TIMESTAMP
#define BLADERF_META_FLAG_TX_UPDATE_TIMESTAMP
Definition: libbladeRF.h:2245
BLADERF_META_FLAG_TX_BURST_END
#define BLADERF_META_FLAG_TX_BURST_END
Definition: libbladeRF.h:2215
bladerf_direction
bladerf_direction
Definition: libbladeRF.h:681
bladerf_metadata
Definition: libbladeRF.h:2279
bladerf_get_timestamp
API_EXPORT int CALL_CONV bladerf_get_timestamp(struct bladerf *dev, bladerf_direction dir, bladerf_timestamp *timestamp)
bladerf_sync_tx
API_EXPORT int CALL_CONV bladerf_sync_tx(struct bladerf *dev, const void *samples, unsigned int num_samples, struct bladerf_metadata *metadata, unsigned int timeout_ms)
BLADERF_FORMAT_SC16_Q11_META
@ BLADERF_FORMAT_SC16_Q11_META
Definition: libbladeRF.h:2132
BLADERF_TX
@ BLADERF_TX
Definition: libbladeRF.h:683
BLADERF_META_FLAG_TX_BURST_START
#define BLADERF_META_FLAG_TX_BURST_START
Definition: libbladeRF.h:2187
BLADERF_META_FLAG_TX_NOW
#define BLADERF_META_FLAG_TX_NOW
Definition: libbladeRF.h:2225
bladerf_metadata::status
uint32_t status
Definition: libbladeRF.h:2307
bladerf_sync_config
API_EXPORT int CALL_CONV bladerf_sync_config(struct bladerf *dev, bladerf_channel_layout layout, bladerf_format format, unsigned int num_buffers, unsigned int buffer_size, unsigned int num_transfers, unsigned int stream_timeout)