libhackrf: Transmit & receive operation

libhackrf HackRF SDR platform library

Data Structures | Macros | Typedefs | Enumerations | Functions
Transmit & receive operation

RX and TX, callbacks. More...

## Data Structures
struct | hackrf_transfer
| USB transfer information passed to RX or TX callback. More...

## Macros
#define | SAMPLES_PER_BLOCK 8192
| Number of samples per tuning when sweeping. More...
#define | BYTES_PER_BLOCK 16384
| Number of bytes per tuning for sweeping. More...
#define | MAX_SWEEP_RANGES 10
| Maximum number of sweep ranges to be specified for hackrf_init_sweep. More...

## Typedefs
typedef int(* | hackrf_sample_block_cb_fn) (hackrf_transfer *transfer)
| Sample block callback, used in RX and TX (set via hackrf_start_rx, hackrf_start_rx_sweep and hackrf_start_tx). More...
typedef void(* | hackrf_tx_block_complete_cb_fn) (hackrf_transfer *transfer, int)
| Block complete callback. More...
typedef void(* | hackrf_flush_cb_fn) (void *flush_ctx, int)
| Flush (end of transmission) callback. More...

## Enumerations
enum | sweep_style { LINEAR = 0 , INTERLEAVED = 1 }
| sweep mode enum More...

## Functions
int | hackrf_start_rx (hackrf_device *device, hackrf_sample_block_cb_fn callback, void *rx_ctx)
| Start receiving. More...
int | hackrf_stop_rx (hackrf_device *device)
| Stop receiving. More...
int | hackrf_start_tx (hackrf_device *device, hackrf_sample_block_cb_fn callback, void *tx_ctx)
| Start transmitting. More...
int | hackrf_set_tx_block_complete_callback (hackrf_device *device, hackrf_tx_block_complete_cb_fn callback)
| Setup callback to be called when an USB transfer is completed. More...
int | hackrf_enable_tx_flush (hackrf_device *device, hackrf_flush_cb_fn callback, void *flush_ctx)
| Setup flush (end-of-transmission) callback. More...
int | hackrf_stop_tx (hackrf_device *device)
| Stop transmission. More...
int | hackrf_set_tx_underrun_limit (hackrf_device *device, uint32_t value)
| Set transmit underrun limit. More...
int | hackrf_set_rx_overrun_limit (hackrf_device *device, uint32_t value)
| Set receive overrun limit. More...
int | hackrf_is_streaming (hackrf_device *device)
| Query device streaming status. More...
int | hackrf_set_hw_sync_mode (hackrf_device *device, const uint8_t value)
| Set hardware sync mode (hardware triggering) More...
int | hackrf_init_sweep (hackrf_device *device, const uint16_t *frequency_list, const int num_ranges, const uint32_t num_bytes, const uint32_t step_width, const uint32_t offset, const enum sweep_style style)
| Initialize sweep mode. More...
int | hackrf_start_rx_sweep (hackrf_device *device, hackrf_sample_block_cb_fn callback, void *rx_ctx)
| Start RX sweep. More...

## Detailed Description

## Streaming

There are 3 different streaming modes supported by HackRF:

- transmitting (TX)
- receiving (RX)
- swept receiving (SWEEP)

Each mode needs to be initialized before use, then the mode needs to be entered with the `hackrf_start_*` function. Data transfer happens through callbacks.

There are 3 types of callbacks in the library:

- transfer callback
- flush callback
- block complete callback

Steps for starting an RX or TX operation:

- initialize libhackrf
- open device
- setup device (frequency, samplerate, gain, etc)
- setup callbacks, start operation (`hackrf_start_*`)
- the main program should go to sleep
- when done, the transfer callback should return non-zero value, and signal the main thread to stop
- stop operation via `hackrf_stop_*`
- close device, exit library, etc.

Data is transfered through the USB connection via setting up multiple async libusb transfers (hackrf_get_transfer_queue_depth). In TX mode, the transfers needs to be filled before submitting, and in RX mode, they need to be read out when they are done. This is done using the transfer callback - it receives a hackrf_transfer object and needs to transfer the data to/from it. As it's needed for all operations, this gets called whenever we need to move data, so every time a transfer is finished (and before the first transfer in TX mode). There's a "transfer complete callback" that only gets called when a transfer is completed. It does not need to do anything special tho, and is optional.

Streaming can be stopped via returning a non-zero value from the transfer callback, but that does NOT reset the device to IDLE mode, it only stops data transfers. In TX mode, when this happens, and the transmitter runs out of data to transmit, it will start transmitting all 0 values (but in older firmware versions, it started repeating the last buffer). To actually stop the operation, a call to `hackrf_stop_*` is needed. Since the callback operate in an async libusb context, such a call can't be made from there, only from the main thread, so it must be signaled through some means (for example, a global variable, or better, a `pthread_cond`) to stop. In RX mode, this signaling can be done from the transfer callback, but in TX mode, we must make sure that we only stop the operation when the last transfer is completed and the device transmitted it, or we might lose it. For this reason, the third flush callback exists, that gets called when this happens. It is adivsed to only signal the main thread to stop from this callback.

The function hackrf_is_streaming can be used to check if the device is streaming or not.

### Transfer callback

Set when starting an operation with hackrf_start_tx, hackrf_start_rx or hackrf_start_rx_sweep. This callback supplies / receives data. This function takes a hackrf_transfer struct as a parameter, and fill/read data to/from its buffer. This function runs in an async libusb context, meaning it should not iteract with the libhackrf library in other ways. The callback can return a boolean value, if its return value is non-zero then it won't be called again, meaning that no future transfers will take place, and (in TX case) the flush callback will be called shortly.

### Block complete callback

This callback is optional, and only applicable in TX mode. It gets called whenever a data transfer is finished, and can read the data. It needs to do nothing at all. This callback can be set using hackrf_set_tx_block_complete_callback

### Flush callback

This callback is optional, and only applicable in TX mode. It get called when the last transfer is completed, and it's advisable to only stop streaming via this callback. This callback can be set using hackrf_enable_tx_flush

### Example TX code utilizing the transfer and flush callbacks.

// Transmit a 440Hz triangle wave through FM (144.5MHz) using the libhackrf API
// Copyright (c) 2022 László Baráth "Uncle Dino" HA7DN <https://github.com/Sasszem>
#include <libhackrf/hackrf.h>
#include <math.h>
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <complex.h>
#include <stdint.h>
const double f_mod = 440;
const uint64_t sample_rate = 10000000;
double triangle() {
// Generate an f_mod frequency triangle wave in the -1 - 1 region
// each call to this function generates a single sample
static double state;
static uint64_t samples_generated;
const uint64_t period_in_samples = sample_rate / f_mod;
const double step = 4.0 / period_in_samples; // we need to go from -1 to 1 in half the period
if (samples_generated < period_in_samples / 2 )
state += step;
else
state -= step;
// this way we don't need to modulo it
if (samples_generated ++ == period_in_samples)
samples_generated = 0;
return state - 1.0;
}
volatile double complex phasor = 1.0;
int xfered_samples = 0;
int samples_to_xfer = 5*sample_rate;
volatile int should_stop = 0;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int transfer_callback(hackrf_transfer *transfer) {
int8_t *signed_buffer = (int8_t*)transfer->buffer;
for (int i = 0; i<transfer->buffer_length; i+=2) {
phasor *= cexp(I*6.28*3000 / sample_rate*triangle());
// any IQ samples can be written here, now I'm doing FM modulation with a triangle wave
signed_buffer[i] = 128 * creal(phasor);
signed_buffer[i+1] = 128 * cimag(phasor);
}
transfer->valid_length = transfer->buffer_length;
xfered_samples += transfer->buffer_length;
if (xfered_samples >= samples_to_xfer) {
return 1;
}
return 0;
}
void flush_callback(hackrf_transfer *transfer) {
pthread_mutex_lock(&mutex);
pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mutex);
}
int main() {
hackrf_init();
hackrf_device *device = NULL;
hackrf_open(&device);
hackrf_set_freq(device, 144500000);
hackrf_set_sample_rate(device, 10000000);
hackrf_set_amp_enable(device, 1);
hackrf_set_txvga_gain(device, 20);
// hackrf_set_tx_underrun_limit(device, 100000); // new-ish library function, not always available
hackrf_enable_tx_flush(device, flush_callback, NULL);
hackrf_start_tx(device, transfer_callback, NULL);
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond, &mutex); // wait fo transfer to complete
hackrf_stop_tx(device);
hackrf_close(device);
hackrf_exit();
return 0;
}
hackrf_set_sample_rate
int hackrf_set_sample_rate(hackrf_device *device, const double freq_hz)
Set sample rate.
hackrf_set_amp_enable
int hackrf_set_amp_enable(hackrf_device *device, const uint8_t value)
Enable/disable 14dB RF amplifier.
hackrf_set_freq
int hackrf_set_freq(hackrf_device *device, const uint64_t freq_hz)
Set the center frequency.
hackrf_set_txvga_gain
int hackrf_set_txvga_gain(hackrf_device *device, uint32_t value)
Set RF TX gain of the MAX2837 transceiver IC ("IF" or "VGA" gain setting) in decibels.
hackrf_close
int hackrf_close(hackrf_device *device)
Close a previously opened device.
hackrf_open
int hackrf_open(hackrf_device **device)
Open first available HackRF device.
hackrf_device
struct hackrf_device hackrf_device
Opaque struct for hackrf device info.
Definition: hackrf.h:858
hackrf_init
int hackrf_init()
Initialize libhackrf.
hackrf_exit
int hackrf_exit()
Exit libhackrf.
hackrf_stop_tx
int hackrf_stop_tx(hackrf_device *device)
Stop transmission.
hackrf_enable_tx_flush
int hackrf_enable_tx_flush(hackrf_device *device, hackrf_flush_cb_fn callback, void *flush_ctx)
Setup flush (end-of-transmission) callback.
hackrf_start_tx
int hackrf_start_tx(hackrf_device *device, hackrf_sample_block_cb_fn callback, void *tx_ctx)
Start transmitting.
hackrf_transfer
USB transfer information passed to RX or TX callback.
Definition: hackrf.h:867
hackrf_transfer::buffer_length
int buffer_length
length of data buffer in bytes
Definition: hackrf.h:873
hackrf_transfer::buffer
uint8_t * buffer
transfer data buffer (interleaved 8 bit I/Q samples)
Definition: hackrf.h:871
hackrf_transfer::valid_length
int valid_length
number of buffer bytes that were transferred
Definition: hackrf.h:875

This code can be compiled using `gcc -o triangle triangle.c -lm -lhackrf`. It generates and transmits a 440Hz triangle wave using FM modulation on the 2m HAM band (check your local laws and regulations on transmitting and only transmit on bands you have license to!).

For a more complete example, including error handling and more settings, see hackrf_transfer.c

## Underrun and overrun

Underrun/overrun detection can be enabled using hackrf_set_tx_underrun_limit or hackrf_set_rx_overrun_limit limit. This causes the HackRF to stop operation if more than the specified amount of samples get lost, for example in case of your program crashing, USB connection faliure, etc.

## Sweeping

Sweeping mode is kind of special. In this mode, the device can be programmed to a list of frequencies to tune on, record set amount of samples and then tune to the next frequency and repeat. It can be setup via hackrf_init_sweep and started with hackrf_start_rx_sweep. In this mode, the callback does not receive raw samples, but blocks of samples prefixed with a frequency header specifying the tuned frequency.

See hackrf_sweep.c for a full example, and especialy the start of the RX callback for parsing the frequency header.

## HW sync mode

hackrf_set_hw_sync_mode can be used to setup HW sync mode (see the documentation on this mode). This mode allows multiple HackRF Ones to synchronize operations, or one HackRF One to synchrnonize on an external trigger source.

## Macro Definition Documentation

## ◆ BYTES_PER_BLOCK

#define BYTES_PER_BLOCK 16384

## ◆ MAX_SWEEP_RANGES

#define MAX_SWEEP_RANGES 10

## ◆ SAMPLES_PER_BLOCK

#define SAMPLES_PER_BLOCK 8192

## Typedef Documentation

## ◆ hackrf_flush_cb_fn

typedef void(* hackrf_flush_cb_fn) (void *flush_ctx, int)

Will be called when the last samples are transmitted and stopping transmission will result in no samples getting lost. Should signal the main thread that it should stop transmission via hackrf_stop_tx

## ◆ hackrf_sample_block_cb_fn

typedef int(* hackrf_sample_block_cb_fn) (hackrf_transfer *transfer)

In each mode, it is called when data needs to be handled, meaning filling samples in TX mode or reading them in RX modes.

In TX mode, it should refill the transfer buffer with new raw IQ data, and set hackrf_transfer::valid_length.

In RX mode, it should copy/process the contents of the transfer buffer's valid part.

In RX SWEEP mode, it receives multiple "blocks" of data, each with a 10-byte header containing the tuned frequency followed by the samples. See hackrf_init_sweep for more info.

The callback should return 0 if it wants to be called again, and any other value otherwise. Stopping the RX/TX/SWEEP is still done with hackrf_stop_rx and hackrf_stop_tx, and those should be called from the main thread, so this callback should signal the main thread that it should stop. Signaling the main thread to stop TX should be done from the flush callback in order to guarantee that no samples are discarded, see hackrf_flush_cb_fn

## ◆ hackrf_tx_block_complete_cb_fn

typedef void(* hackrf_tx_block_complete_cb_fn) (hackrf_transfer *transfer, int)

Set via hackrf_set_tx_block_complete_callback, called when a transfer is finished to the device's buffer, regardless if the transfer was successful or not. It can signal the main thread to stop on failure, can catch USB transfer errors and can also gather statistics about the transfered data.

## Enumeration Type Documentation

## ◆ sweep_style

enum sweep_style

Used by hackrf_init_sweep, to set sweep parameters.

Linear mode is no longer used by the hackrf_sweep command line tool and in general the interleaved method is always preferable, but the linear mode remains available for backward compatibility and might be useful in some special circumstances.

Enumerator
LINEAR | step_width is added to the current frequency at each step.
INTERLEAVED | each step is divided into two interleaved sub-steps, allowing the host to select the best portions of the FFT of each sub-step and discard the rest.

## Function Documentation

## ◆ hackrf_enable_tx_flush()

int hackrf_enable_tx_flush | ( | hackrf_device * | device,
|  | hackrf_flush_cb_fn | callback,
|  | void * | flush_ctx
| ) |  |

This callback will be called when all the data was transmitted and all data transfers were completed. First parameter is supplied context, second parameter is success flag.

Parameters

  device | device to configure
  callback | callback to call when all transfers were completed
  flush_ctx | context (1st parameter of callback)

Returns
  HACKRF_SUCCESS on success or hackrf_error variant

## ◆ hackrf_init_sweep()

int hackrf_init_sweep | ( | hackrf_device * | device,
|  | const uint16_t * | frequency_list,
|  | const int | num_ranges,
|  | const uint32_t | num_bytes,
|  | const uint32_t | step_width,
|  | const uint32_t | offset,
|  | const enum sweep_style | style
| ) |  |

In this mode, in a single data transfer (single call to the RX transfer callback), multiple blocks of size `num_bytes` bytes are received with different center frequencies. At the beginning of each block, a 10-byte frequency header is present in `0x7F - 0x7F - uint64_t frequency (LSBFIRST, in Hz)` format, followed by the actual samples.

Requires USB API version 0x0102 or above!

Parameters

  device | device to configure
  frequency_list | list of start-stop frequency pairs in MHz
  num_ranges | length of array `frequency_list` (in pairs, so total array length / 2!). Must be less than MAX_SWEEP_RANGES
  num_bytes | number of bytes to capture per tuning, must be a multiple of BYTES_PER_BLOCK
  step_width | width of each tuning step in Hz
  offset | frequency offset added to tuned frequencies. sample_rate / 2 is a good value
  style | sweep style

Returns
  HACKRF_SUCCESS on success or hackrf_error variant

## ◆ hackrf_is_streaming()

int hackrf_is_streaming | ( | hackrf_device * | device | ) |

Parameters

  device | device to query

Returns
  HACKRF_TRUE if the device is streaming, else one of HACKRF_ERROR_STREAMING_THREAD_ERR, HACKRF_ERROR_STREAMING_STOPPED or HACKRF_ERROR_STREAMING_EXIT_CALLED

## ◆ hackrf_set_hw_sync_mode()

int hackrf_set_hw_sync_mode | ( | hackrf_device * | device,
|  | const uint8_t | value
| ) |  |

See the documentation on hardware triggering for details

Requires USB API version 0x0102 or above!

Parameters

  device | device to configure
  value | enable (1) or disable (0) hardware triggering

Returns
  HACKRF_SUCCESS on success or hackrf_error variant

## ◆ hackrf_set_rx_overrun_limit()

int hackrf_set_rx_overrun_limit | ( | hackrf_device * | device,
|  | uint32_t | value
| ) |  |

When this limit is set, after the specified number of samples (bytes, not whole IQ pairs) missing the device will automatically return to IDLE mode, thus stopping operation. Useful for handling cases like program/computer crashes or other problems. The default value 0 means no limit.

Requires USB API version 0x0106 or above!

Parameters

  device | device to configure
  value | number of samples to wait before auto-stopping

Returns
  HACKRF_SUCCESS on success or hackrf_error variant

## ◆ hackrf_set_tx_block_complete_callback()

int hackrf_set_tx_block_complete_callback | ( | hackrf_device * | device,
|  | hackrf_tx_block_complete_cb_fn | callback
| ) |  |

This callback will be called whenever an USB transfer to the device is completed, regardless if it was successful or not (indicated by the second parameter).

Parameters

  device | device to configure
  callback | callback to call when a transfer is completed

Returns
  HACKRF_SUCCESS on success or hackrf_error variant

## ◆ hackrf_set_tx_underrun_limit()

int hackrf_set_tx_underrun_limit | ( | hackrf_device * | device,
|  | uint32_t | value
| ) |  |

When this limit is set, after the specified number of samples (bytes, not whole IQ pairs) missing the device will automatically return to IDLE mode, thus stopping operation. Useful for handling cases like program/computer crashes or other problems. The default value 0 means no limit.

Requires USB API version 0x0106 or above!

Parameters

  device | device to configure
  value | number of samples to wait before auto-stopping

Returns
  HACKRF_SUCCESS on success or hackrf_error variant

## ◆ hackrf_start_rx()

int hackrf_start_rx | ( | hackrf_device * | device,
|  | hackrf_sample_block_cb_fn | callback,
|  | void * | rx_ctx
| ) |  |

Should be called after setting gains, frequency and sampling rate, as these values won't get reset but instead keep their last value, thus their state is unknown.

The callback is called with a hackrf_transfer object whenever the buffer is full. The callback is called in an async context so no libhackrf functions should be called from it. The callback should treat its argument as read-only.

Parameters

  device | device to configure
  callback | rx_callback
  rx_ctx | User provided RX context. Not used by the library, but available to `callback` as hackrf_transfer::rx_ctx.

Returns
  HACKRF_SUCCESS on success or hackrf_error variant

## ◆ hackrf_start_rx_sweep()

int hackrf_start_rx_sweep | ( | hackrf_device * | device,
|  | hackrf_sample_block_cb_fn | callback,
|  | void * | rx_ctx
| ) |  |

See hackrf_init_sweep for more info

Requires USB API version 0x0104 or above!

Parameters

  device | device to start sweeping
  callback | rx callback processing the received data
  rx_ctx | User provided RX context. Not used by the library, but available to `callback` as hackrf_transfer::rx_ctx.

Returns
  HACKRF_SUCCESS on success or hackrf_error variant

## ◆ hackrf_start_tx()

int hackrf_start_tx | ( | hackrf_device * | device,
|  | hackrf_sample_block_cb_fn | callback,
|  | void * | tx_ctx
| ) |  |

Should be called after setting gains, frequency and sampling rate, as these values won't get reset but instead keep their last value, thus their state is unknown. Setting flush function (using hackrf_enable_tx_flush) and/or setting block complete callback (using hackrf_set_tx_block_complete_callback) (if these features are used) should also be done before this.

The callback is called with a hackrf_transfer object whenever a transfer buffer is needed to be filled with samples. The callback is called in an async context so no libhackrf functions should be called from it. The callback should treat its argument as read-only, except the hackrf_transfer::buffer and hackrf_transfer::valid_length.

Parameters

  device | device to configure
  callback | tx_callback
  tx_ctx | User provided TX context. Not used by the library, but available to `callback` as hackrf_transfer::tx_ctx.

Returns
  HACKRF_SUCCESS on success or hackrf_error variant

## ◆ hackrf_stop_rx()

int hackrf_stop_rx | ( | hackrf_device * | device | ) |

Parameters

  device | device to stop RX on

Returns
  HACKRF_SUCCESS on success or hackrf_error variant

## ◆ hackrf_stop_tx()

int hackrf_stop_tx | ( | hackrf_device * | device | ) |

Parameters

  device | device to stop TX on

Returns
  HACKRF_SUCCESS on success or hackrf_error variant