From ca50c0f64f1b2fce46b4cb83ed111854bac13852 Mon Sep 17 00:00:00 2001 From: Arturs Artamonovs Date: Fri, 1 Nov 2024 08:38:25 +0000 Subject: rtlsdr library example compiles --- Radio/HW/AirSpyHF/src/airspyhf.c | 1910 +++++++++++++++++++++++++++++ Radio/HW/AirSpyHF/src/airspyhf.h | 171 +++ Radio/HW/AirSpyHF/src/airspyhf_commands.h | 63 + Radio/HW/AirSpyHF/src/iqbalancer.c | 564 +++++++++ Radio/HW/AirSpyHF/src/iqbalancer.h | 68 + 5 files changed, 2776 insertions(+) create mode 100644 Radio/HW/AirSpyHF/src/airspyhf.c create mode 100644 Radio/HW/AirSpyHF/src/airspyhf.h create mode 100644 Radio/HW/AirSpyHF/src/airspyhf_commands.h create mode 100644 Radio/HW/AirSpyHF/src/iqbalancer.c create mode 100644 Radio/HW/AirSpyHF/src/iqbalancer.h (limited to 'Radio/HW/AirSpyHF/src') diff --git a/Radio/HW/AirSpyHF/src/airspyhf.c b/Radio/HW/AirSpyHF/src/airspyhf.c new file mode 100644 index 0000000..efacd03 --- /dev/null +++ b/Radio/HW/AirSpyHF/src/airspyhf.c @@ -0,0 +1,1910 @@ +/* +Copyright (c) 2013-2023, Youssef Touil +Copyright (c) 2013-2017, Ian Gilmour +Copyright (c) 2013-2017, Benjamin Vernoux +Copyright (c) 2013, Michael Ossmann +Copyright (c) 2012, Jared Boone + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + Neither the name of Airspy HF+ nor the names of its contributors may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef _WIN32 +#define HAVE_STRUCT_TIMESPEC +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include +#include +#include +#include "libusb.h" +#include +#include + +#include "iqbalancer.h" +#include "airspyhf.h" +#include "airspyhf_commands.h" + +#ifndef bool +typedef int bool; +#define true 1 +#define false 0 +#endif + +#ifndef M_PI +#define M_PI (3.14159265359) +#endif + +#define MAX(a,b) ((a) > (b) ? a : b) +#define MIN(a,b) ((a) < (b) ? a : b) + +#define SAMPLES_TO_TRANSFER (1024 * 2) +#define SERIAL_NUMBER_UNUSED (0) +#define FILE_DESCRIPTOR_UNUSED (-1) +#define RAW_BUFFER_COUNT (8) +#define AIRSPYHF_SERIAL_SIZE (28) + +#define MAX_SAMPLERATE_INDEX (100) +#define DEFAULT_SAMPLERATE (768000) + +#define DEFAULT_ATT_STEP_COUNT (9) +#define DEFAULT_ATT_STEP_INCREMENT (6.0f) + +#define CALIBRATION_MAGIC (0xA5CA71B0) + +#define DEFAULT_IF_SHIFT (5000) +#define MIN_ZERO_IF_LO (180) +#define MIN_LOW_IF_LO (84) + +#define STR_PREFIX_SERIAL_AIRSPYHF_SIZE (12) +static const char str_prefix_serial_airspyhf[STR_PREFIX_SERIAL_AIRSPYHF_SIZE] = +{ 'A', 'I', 'R', 'S', 'P', 'Y', 'H', 'F', ' ', 'S', 'N', ':' }; + +#define INITIAL_PHASE (0.00006f) +#define INITIAL_AMPLITUDE (-0.0045f) + +#define LIBUSB_CTRL_TIMEOUT_MS (500) + +#define IQ_BALANCER_EVAL_SKIP (RAW_BUFFER_COUNT) + +#pragma pack(push,1) + +typedef struct { + int16_t im; + int16_t re; +} airspyhf_complex_int16_t; + +#pragma pack(pop) + +typedef struct airspyhf_device +{ + libusb_context* usb_context; + libusb_device_handle* usb_device; + struct libusb_transfer** transfers; + airspyhf_sample_block_cb_fn callback; + pthread_t transfer_thread; + pthread_t consumer_thread; + bool transfer_thread_running; + bool consumer_thread_running; + pthread_cond_t consumer_cv; + pthread_mutex_t consumer_mp; + uint32_t supported_samplerate_count; + uint32_t *supported_samplerates; + uint8_t *samplerate_architectures; + uint32_t supported_att_step_count; + float *supported_att_steps; + volatile uint32_t current_samplerate; + volatile double freq_hz; + volatile uint32_t freq_khz; + volatile double freq_delta_hz; + volatile double freq_shift; + volatile int32_t calibration_ppb; + volatile int32_t calibration_vctcxo; + volatile uint32_t frontend_options; + volatile float optimal_point; + uint8_t enable_dsp; + uint8_t is_low_if; + float filter_gain; + airspyhf_complex_float_t vec; + struct iq_balancer_t *iq_balancer; + volatile int32_t iq_balancer_eval_skip; + uint32_t transfer_count; + int32_t transfer_live; + uint32_t buffer_size; + uint32_t dropped_buffers; + uint32_t dropped_buffers_queue[RAW_BUFFER_COUNT]; + airspyhf_complex_int16_t *received_samples_queue[RAW_BUFFER_COUNT]; + volatile bool streaming; + volatile bool stop_requested; + volatile int received_samples_queue_head; + volatile int received_samples_queue_tail; + volatile int received_buffer_count; + airspyhf_complex_float_t *output_buffer; + void* ctx; +} airspyhf_device_t; + +typedef struct flash_config +{ + uint32_t magic_number; + int32_t calibration_ppb; + int32_t calibration_vctcxo; + uint32_t frontend_options; +} flash_config_t; + +static const uint16_t airspyhf_usb_vid = 0x03EB; +static const uint16_t airspyhf_usb_pid = 0x800C; + +static int airspyhf_config_read(airspyhf_device_t* device, uint8_t *buffer, uint16_t length); + +static int cancel_transfers(airspyhf_device_t* device) +{ + uint32_t transfer_index; + struct timeval canceltv = { 0, 50 }; + + if (device->transfers != NULL) + { + for (transfer_index = 0; transfer_indextransfer_count; transfer_index++) + { + if (device->transfers[transfer_index] != NULL) + { + libusb_cancel_transfer(device->transfers[transfer_index]); + } + } + + for (transfer_index = 0; transfer_index < device->transfer_count && device->transfer_live > 0; transfer_index++) + libusb_handle_events_timeout_completed(device->usb_context, &canceltv, NULL); + + return AIRSPYHF_SUCCESS; + } + else + { + return AIRSPYHF_ERROR; + } +} + +static int free_transfers(airspyhf_device_t* device) +{ + int i; + uint32_t transfer_index; + + if (device->transfers != NULL) + { + free(device->output_buffer); + device->output_buffer = NULL; + + for (transfer_index = 0; transfer_index < device->transfer_count; transfer_index++) + { + if (device->transfers[transfer_index] != NULL) + { + libusb_free_transfer(device->transfers[transfer_index]); + free(device->transfers[transfer_index]->buffer); + device->transfers[transfer_index] = NULL; + } + } + free(device->transfers); + device->transfers = NULL; + + for (i = 0; i < RAW_BUFFER_COUNT; i++) + { + if (device->received_samples_queue[i] != NULL) + { + free(device->received_samples_queue[i]); + device->received_samples_queue[i] = NULL; + } + } + } + + return AIRSPYHF_SUCCESS; +} + +static int allocate_transfers(airspyhf_device_t* const device) +{ + int i; + uint32_t transfer_index; + + if (device->transfers == NULL) + { + device->output_buffer = (airspyhf_complex_float_t *) malloc((device->buffer_size / sizeof(airspyhf_complex_int16_t)) * sizeof(airspyhf_complex_float_t)); + + for (i = 0; i < RAW_BUFFER_COUNT; i++) + { + device->received_samples_queue[i] = (airspyhf_complex_int16_t *) malloc(device->buffer_size); + if (device->received_samples_queue[i] == NULL) + { + return AIRSPYHF_ERROR; + } + + memset(device->received_samples_queue[i], 0, device->buffer_size); + } + + device->transfers = (struct libusb_transfer**) calloc(device->transfer_count, sizeof(struct libusb_transfer)); + if (device->transfers == NULL) + { + return AIRSPYHF_ERROR; + } + + for (transfer_index = 0; transfer_indextransfer_count; transfer_index++) + { + device->transfers[transfer_index] = libusb_alloc_transfer(0); + if (device->transfers[transfer_index] == NULL) + { + return AIRSPYHF_ERROR; + } + + libusb_fill_bulk_transfer( + device->transfers[transfer_index], + device->usb_device, + 0, + (unsigned char*) malloc(device->buffer_size), + device->buffer_size, + NULL, + device, + 0); + + if (device->transfers[transfer_index]->buffer == NULL) + { + return AIRSPYHF_ERROR; + } + } + return AIRSPYHF_SUCCESS; + } + else + { + return AIRSPYHF_ERROR; + } +} + +static int prepare_transfers(airspyhf_device_t* device, const uint_fast8_t endpoint_address, libusb_transfer_cb_fn callback) +{ + int error; + uint32_t transfer_index; + + if (device->transfers != NULL) + { + for (transfer_index = 0; transfer_indextransfer_count; transfer_index++) + { + device->transfers[transfer_index]->endpoint = endpoint_address; + device->transfers[transfer_index]->callback = callback; + + error = libusb_submit_transfer(device->transfers[transfer_index]); + if (error != 0) + { + return AIRSPYHF_ERROR; + } + else + device->transfer_live++; + } + return AIRSPYHF_SUCCESS; + } + + return AIRSPYHF_ERROR; +} + +static void multiply_complex_complex(airspyhf_complex_float_t *a, const airspyhf_complex_float_t *b) +{ + float re = a->re * b->re - a->im * b->im; + a->im = a->im * b->re + a->re * b->im; + a->re = re; +} + +static void multiply_complex_real(airspyhf_complex_float_t *a, const float b) +{ + a->re *= b; + a->im *= b; +} + +static inline void rotate_complex(airspyhf_complex_float_t *vec, const airspyhf_complex_float_t *rot) +{ + multiply_complex_complex(vec, rot); + + float norm = 1.99f - (vec->re * vec->re + vec->im * vec->im); + + multiply_complex_real(vec, norm); +} + +static void convert_samples(airspyhf_device_t* device, airspyhf_complex_int16_t *src, airspyhf_complex_float_t *dest, int count) +{ + const float scale = 1.0f / 32768; + + int i; + airspyhf_complex_float_t vec; + airspyhf_complex_float_t rot; + double angle; + float conversion_gain; + uint8_t iqb_eval_skip = 0; + + conversion_gain = scale * device->filter_gain; + + for (i = 0; i < count; i++) + { + dest[i].re = src[i].re * conversion_gain; + dest[i].im = src[i].im * conversion_gain; + } + + if (device->iq_balancer_eval_skip > 0) + { + device->iq_balancer_eval_skip--; + iqb_eval_skip = 1; + } + + if (device->enable_dsp) + { + if (!device->is_low_if) + { + // Zero IF requires external IQ correction + iq_balancer_process(device->iq_balancer, dest, count, iqb_eval_skip); + } + + // Fine tuning + if (device->freq_shift != 0) + { + angle = 2.0 * M_PI * device->freq_shift / (double) device->current_samplerate; + + vec = device->vec; + + rot.re = (float) cos(angle); + rot.im = (float) -sin(angle); + + for (i = 0; i < count; i++) + { + rotate_complex(&vec, &rot); + multiply_complex_complex(&dest[i], &vec); + } + + device->vec = vec; + } + } +} + +static void* consumer_threadproc(void *arg) +{ + int sample_count; + airspyhf_complex_int16_t *input_samples; + uint32_t dropped_buffers; + airspyhf_device_t* device = (airspyhf_device_t*) arg; + airspyhf_transfer_t transfer; + +#ifdef _WIN32 + + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST); + +#endif + + pthread_mutex_lock(&device->consumer_mp); + + while (device->streaming && !device->stop_requested) + { + while (device->received_buffer_count == 0 && device->streaming && !device->stop_requested) + { + pthread_cond_wait(&device->consumer_cv, &device->consumer_mp); + } + if (!device->streaming || device->stop_requested) + { + break; + } + + input_samples = (airspyhf_complex_int16_t *) device->received_samples_queue[device->received_samples_queue_tail]; + dropped_buffers = device->dropped_buffers_queue[device->received_samples_queue_tail]; + device->received_samples_queue_tail = (device->received_samples_queue_tail + 1) & (RAW_BUFFER_COUNT - 1); + + pthread_mutex_unlock(&device->consumer_mp); + + sample_count = device->buffer_size / sizeof(airspyhf_complex_int16_t); + + convert_samples(device, input_samples, device->output_buffer, sample_count); + + transfer.device = device; + transfer.ctx = device->ctx; + transfer.samples = device->output_buffer; + transfer.sample_count = sample_count; + transfer.dropped_samples = (uint64_t) dropped_buffers * (uint64_t) sample_count; + + if (device->callback(&transfer) != 0) + { + device->streaming = false; + } + + pthread_mutex_lock(&device->consumer_mp); + device->received_buffer_count--; + } + + device->streaming = false; + + pthread_mutex_unlock(&device->consumer_mp); + + return NULL; +} + +static void LIBUSB_CALL airspyhf_libusb_transfer_callback(struct libusb_transfer* usb_transfer) +{ + airspyhf_complex_int16_t *temp; + airspyhf_device_t* device = (airspyhf_device_t*) usb_transfer->user_data; + + device->transfer_live--; + if (!device->streaming || device->stop_requested) + { + return; + } + + if (usb_transfer->status == LIBUSB_TRANSFER_COMPLETED && usb_transfer->actual_length == usb_transfer->length) + { + pthread_mutex_lock(&device->consumer_mp); + + if (device->received_buffer_count < RAW_BUFFER_COUNT) + { + temp = device->received_samples_queue[device->received_samples_queue_head]; + device->received_samples_queue[device->received_samples_queue_head] = (airspyhf_complex_int16_t *) usb_transfer->buffer; + usb_transfer->buffer = (uint8_t *) temp; + + device->dropped_buffers_queue[device->received_samples_queue_head] = device->dropped_buffers; + device->dropped_buffers = 0; + + device->received_samples_queue_head = (device->received_samples_queue_head + 1) & (RAW_BUFFER_COUNT - 1); + device->received_buffer_count++; + + pthread_cond_signal(&device->consumer_cv); + } + else + { + device->dropped_buffers++; + } + + pthread_mutex_unlock(&device->consumer_mp); + + if (libusb_submit_transfer(usb_transfer) != 0) + { + device->streaming = false; + } + else + device->transfer_live ++; + } + else + { + device->streaming = false; + } +} + +static void* transfer_threadproc(void* arg) +{ + airspyhf_device_t* device = (struct airspyhf_device*) arg; + int error; + struct timeval timeout = { 0, 500000 }; + +#ifdef _WIN32 + + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST); + +#endif + + while (device->streaming && !device->stop_requested) + { + error = libusb_handle_events_timeout_completed(device->usb_context, &timeout, NULL); + if (error < 0) + { + if (error != LIBUSB_ERROR_INTERRUPTED) + device->streaming = false; + } + } + + device->streaming = false; + + return NULL; +} + +static int kill_io_threads(airspyhf_device_t* device) +{ + struct timeval timeout = { 0, 0 }; + + if (device->stop_requested) + { + device->stop_requested = false; + device->streaming = false; + cancel_transfers(device); + + pthread_mutex_lock(&device->consumer_mp); + pthread_cond_signal(&device->consumer_cv); + pthread_mutex_unlock(&device->consumer_mp); + + if (device->transfer_thread_running) { + pthread_join(device->transfer_thread, NULL); + device->transfer_thread_running = false; + } + if (device->consumer_thread_running) { + pthread_join(device->consumer_thread, NULL); + device->consumer_thread_running = false; + } + + libusb_handle_events_timeout_completed(device->usb_context, &timeout, NULL); + } + + return AIRSPYHF_SUCCESS; +} + +static int create_io_threads(airspyhf_device_t* device, airspyhf_sample_block_cb_fn callback) +{ + int result; + pthread_attr_t attr; + + if (!device->streaming && !device->stop_requested) + { + device->callback = callback; + device->streaming = true; + + result = prepare_transfers(device, LIBUSB_ENDPOINT_IN | AIRSPYHF_ENDPOINT_IN, (libusb_transfer_cb_fn)airspyhf_libusb_transfer_callback); + if (result != AIRSPYHF_SUCCESS) + { + return result; + } + + device->received_samples_queue_head = 0; + device->received_samples_queue_tail = 0; + device->received_buffer_count = 0; + + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + + result = pthread_create(&device->consumer_thread, &attr, consumer_threadproc, device); + if (result != 0) + { + return AIRSPYHF_ERROR; + } + device->consumer_thread_running = true; + + result = pthread_create(&device->transfer_thread, &attr, transfer_threadproc, device); + if (result != 0) + { + return AIRSPYHF_ERROR; + } + device->transfer_thread_running = true; + + pthread_attr_destroy(&attr); + } + else { + return AIRSPYHF_ERROR; + } + + return AIRSPYHF_SUCCESS; +} + +static void airspyhf_open_exit(airspyhf_device_t* device) +{ + if (device->usb_device != NULL) + { + libusb_release_interface(device->usb_device, 0); + libusb_close(device->usb_device); + device->usb_device = NULL; + } + libusb_exit(device->usb_context); + device->usb_context = NULL; +} + +static int airspyhf_read_samplerates_from_fw(airspyhf_device_t* device, uint32_t* buffer, const uint32_t len) +{ + int result; + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPYHF_GET_SAMPLERATES, + 0, + len, + (unsigned char*) buffer, + (len > 0 ? len : 1) * (int16_t) sizeof(uint32_t), + LIBUSB_CTRL_TIMEOUT_MS); + + if (result < 1) + { + return AIRSPYHF_ERROR; + } + return AIRSPYHF_SUCCESS; +} + +static int airspyhf_read_att_steps_from_fw(airspyhf_device_t* device, void* buffer, const uint32_t count) +{ + int result; + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPYHF_GET_ATT_STEPS, + 0, + count, + (unsigned char*) buffer, + (int16_t)(count > 0 ? (count * sizeof(float)) : sizeof(int32_t)), + LIBUSB_CTRL_TIMEOUT_MS); + + if (result < 1) + { + return AIRSPYHF_ERROR; + } + return AIRSPYHF_SUCCESS; +} + +static int airspyhf_read_samplerate_architectures_from_fw(airspyhf_device_t* device, uint8_t* buffer, const uint32_t len) +{ + int result; + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPYHF_GET_SAMPLERATE_ARCHITECTURES, + 0, + len, + (unsigned char*) buffer, + (len > 0 ? len : 1) * (int16_t) sizeof(uint32_t), + LIBUSB_CTRL_TIMEOUT_MS); + + if (result < 1) + { + return AIRSPYHF_ERROR; + } + + return AIRSPYHF_SUCCESS; +} + + +static void airspyhf_open_device(airspyhf_device_t* device, + int* ret, + uint16_t vid, + uint16_t pid, + uint64_t serial_number_val) +{ + int i; + int result; + libusb_device_handle** libusb_dev_handle; + int serial_number_len; + libusb_device_handle* dev_handle; + libusb_device *dev; + libusb_device** devices = NULL; + + ssize_t cnt; + int serial_descriptor_index; + struct libusb_device_descriptor device_descriptor; + unsigned char serial_number[AIRSPYHF_SERIAL_SIZE + 1]; + + libusb_dev_handle = &device->usb_device; + *libusb_dev_handle = NULL; + + cnt = libusb_get_device_list(device->usb_context, &devices); + if (cnt < 0) + { + *ret = AIRSPYHF_ERROR; + return; + } + + i = 0; + while ((dev = devices[i++]) != NULL) + { + libusb_get_device_descriptor(dev, &device_descriptor); + + if ((device_descriptor.idVendor == vid) && + (device_descriptor.idProduct == pid)) + { + if (serial_number_val != SERIAL_NUMBER_UNUSED) + { + serial_descriptor_index = device_descriptor.iSerialNumber; + if (serial_descriptor_index > 0) + { + if (libusb_open(dev, libusb_dev_handle) != 0) + { + *libusb_dev_handle = NULL; + continue; + } + dev_handle = *libusb_dev_handle; + serial_number_len = libusb_get_string_descriptor_ascii(dev_handle, + serial_descriptor_index, + serial_number, + sizeof(serial_number)); + + if (serial_number_len == AIRSPYHF_SERIAL_SIZE && !memcmp(str_prefix_serial_airspyhf, serial_number, STR_PREFIX_SERIAL_AIRSPYHF_SIZE)) + { + uint64_t serial = SERIAL_NUMBER_UNUSED; + // use same code to determine device's serial number as in airspyhf_list_devices() + { + char *start, *end; + serial_number[AIRSPYHF_SERIAL_SIZE] = 0; + start = (char*)(serial_number + STR_PREFIX_SERIAL_AIRSPYHF_SIZE); + end = NULL; + serial = strtoull(start, &end, 16); + } + if (serial == serial_number_val) + { + result = libusb_set_configuration(dev_handle, 1); + if (result != 0) + { + libusb_close(dev_handle); + *libusb_dev_handle = NULL; + continue; + } + result = libusb_claim_interface(dev_handle, 0); + if (result != 0) + { + libusb_close(dev_handle); + *libusb_dev_handle = NULL; + continue; + } + + result = libusb_set_interface_alt_setting(dev_handle, 0, 1); + if (result != 0) + { + libusb_close(dev_handle); + *libusb_dev_handle = NULL; + continue; + } + + break; + } + else + { + libusb_close(dev_handle); + *libusb_dev_handle = NULL; + continue; + } + } + else + { + libusb_close(dev_handle); + *libusb_dev_handle = NULL; + continue; + } + } + } + else + { + if (libusb_open(dev, libusb_dev_handle) == 0) + { + dev_handle = *libusb_dev_handle; + result = libusb_set_configuration(dev_handle, 1); + if (result != 0) + { + libusb_close(dev_handle); + *libusb_dev_handle = NULL; + continue; + } + result = libusb_claim_interface(dev_handle, 0); + if (result != 0) + { + libusb_close(dev_handle); + *libusb_dev_handle = NULL; + continue; + } + + result = libusb_set_interface_alt_setting(dev_handle, 0, 1); + if (result != 0) + { + libusb_close(dev_handle); + *libusb_dev_handle = NULL; + continue; + } + break; + } + } + } + } + libusb_free_device_list(devices, 1); + + dev_handle = device->usb_device; + if (dev_handle == NULL) + { + *ret = AIRSPYHF_ERROR; + return; + } + + *ret = AIRSPYHF_SUCCESS; + return; +} + +static void airspyhf_open_device_fd(airspyhf_device_t* device, + int* ret, + int fd) { + int result = -1; + +#ifdef __ANDROID__ + result = libusb_wrap_sys_device(device->usb_context, (intptr_t)fd, &device->usb_device); +#else + device->usb_device = NULL; + *ret = AIRSPYHF_UNSUPPORTED; + return; +#endif + + if (result != 0 || device->usb_device == NULL) + { + *ret = AIRSPYHF_ERROR; + return; + } + + result = libusb_set_configuration(device->usb_device, 1); + if (result != 0) + { + libusb_close(device->usb_device); + device->usb_device = NULL; + *ret = AIRSPYHF_ERROR; + return; + } + + result = libusb_claim_interface(device->usb_device, 0); + if (result != 0) + { + libusb_close(device->usb_device); + device->usb_device = NULL; + *ret = AIRSPYHF_ERROR; + return; + } + + result = libusb_set_interface_alt_setting(device->usb_device, 0, 1); + if (result != 0) + { + libusb_close(device->usb_device); + device->usb_device = NULL; + *ret = AIRSPYHF_ERROR; + return; + } + + *ret = AIRSPYHF_SUCCESS; + return; +} + +int airspyhf_list_devices(uint64_t *serials, int count) +{ + libusb_device_handle* libusb_dev_handle; + struct libusb_context *context; + libusb_device** devices = NULL; + libusb_device *dev; + struct libusb_device_descriptor device_descriptor; + + int serial_descriptor_index; + int serial_number_len; + int output_count; + int i; + unsigned char serial_number[AIRSPYHF_SERIAL_SIZE + 1]; + + if (serials) + { + memset(serials, 0, sizeof(uint64_t) * count); + } + +#ifdef __ANDROID__ + // LibUSB does not support device discovery on android + libusb_set_option(NULL, LIBUSB_OPTION_NO_DEVICE_DISCOVERY, NULL); +#endif + + if (libusb_init(&context) != 0) + { + return AIRSPYHF_ERROR; + } + + if (libusb_get_device_list(context, &devices) < 0) + { + return AIRSPYHF_ERROR; + } + + i = 0; + output_count = 0; + while ((dev = devices[i++]) != NULL && (!serials || output_count < count)) + { + libusb_get_device_descriptor(dev, &device_descriptor); + + if ((device_descriptor.idVendor == airspyhf_usb_vid) && + (device_descriptor.idProduct == airspyhf_usb_pid)) + { + serial_descriptor_index = device_descriptor.iSerialNumber; + if (serial_descriptor_index > 0) + { + if (libusb_open(dev, &libusb_dev_handle) != 0) + { + continue; + } + + serial_number_len = libusb_get_string_descriptor_ascii(libusb_dev_handle, + serial_descriptor_index, + serial_number, + sizeof(serial_number)); + + if (serial_number_len == AIRSPYHF_SERIAL_SIZE && !memcmp(str_prefix_serial_airspyhf, serial_number, STR_PREFIX_SERIAL_AIRSPYHF_SIZE)) + { + char *start, *end; + uint64_t serial; + + serial_number[AIRSPYHF_SERIAL_SIZE] = 0; + start = (char*)(serial_number + STR_PREFIX_SERIAL_AIRSPYHF_SIZE); + end = NULL; + serial = strtoull(start, &end, 16); + if (serial == 0 && start == end) + { + libusb_close(libusb_dev_handle); + continue; + } + + if (serials) + { + serials[output_count] = serial; + } + output_count++; + } + + libusb_close(libusb_dev_handle); + } + } + } + + libusb_free_device_list(devices, 1); + libusb_exit(context); + return output_count; +} + +static int airspyhf_open_init(airspyhf_device_t** device, uint64_t serial_number, int fd) +{ + airspyhf_device_t* lib_device; + flash_config_t config; + int libusb_error; + int result; + + *device = NULL; + + lib_device = (airspyhf_device_t*) calloc(1, sizeof(airspyhf_device_t)); + if (lib_device == NULL) + { + return AIRSPYHF_ERROR; + } + +#ifdef __ANDROID__ + // LibUSB does not support device discovery on android + libusb_set_option(NULL, LIBUSB_OPTION_NO_DEVICE_DISCOVERY, NULL); +#endif + + libusb_error = libusb_init(&lib_device->usb_context); + if (libusb_error != 0) + { + free(lib_device); + return AIRSPYHF_ERROR; + } + + if (fd == FILE_DESCRIPTOR_UNUSED) { + airspyhf_open_device(lib_device, + &result, + airspyhf_usb_vid, + airspyhf_usb_pid, + serial_number); + } + else { + airspyhf_open_device_fd(lib_device, + &result, + fd); + } + + if (lib_device->usb_device == NULL) + { + libusb_exit(lib_device->usb_context); + free(lib_device); + return result; + } + + lib_device->transfers = NULL; + lib_device->callback = NULL; + lib_device->transfer_count = 16; + lib_device->buffer_size = SAMPLES_TO_TRANSFER * sizeof(airspyhf_complex_int16_t); + lib_device->streaming = false; + lib_device->stop_requested = false; + + lib_device->supported_samplerates = NULL; + lib_device->samplerate_architectures = NULL; + + result = airspyhf_read_samplerates_from_fw(lib_device, &lib_device->supported_samplerate_count, 0); + if (result == AIRSPYHF_SUCCESS) + { + lib_device->supported_samplerates = (uint32_t *)malloc(lib_device->supported_samplerate_count * sizeof(uint32_t)); + result = airspyhf_read_samplerates_from_fw(lib_device, lib_device->supported_samplerates, lib_device->supported_samplerate_count); + if (result == AIRSPYHF_SUCCESS) + { + lib_device->samplerate_architectures = (uint8_t *)malloc(lib_device->supported_samplerate_count * sizeof(uint8_t)); + result = airspyhf_read_samplerate_architectures_from_fw(lib_device, lib_device->samplerate_architectures, lib_device->supported_samplerate_count); + if (result != AIRSPYHF_SUCCESS) + { + memset(lib_device->samplerate_architectures, 0, lib_device->supported_samplerate_count * sizeof(uint8_t)); // Assume Zero IF for all + result = AIRSPYHF_SUCCESS; // Clear this error for backward compatibility. + } + } + else + { + free(lib_device->supported_samplerates); + lib_device->supported_samplerates = NULL; + } + } + + if (result != AIRSPYHF_SUCCESS) + { + // Assume one default sample rate with Zero IF + + lib_device->supported_samplerate_count = 1; + lib_device->supported_samplerates = (uint32_t *) malloc(lib_device->supported_samplerate_count * sizeof(uint32_t)); + lib_device->supported_samplerates[0] = DEFAULT_SAMPLERATE; + + lib_device->samplerate_architectures = (uint8_t *) malloc(lib_device->supported_samplerate_count * sizeof(uint8_t)); + lib_device->samplerate_architectures[0] = 0; + } + + lib_device->current_samplerate = lib_device->supported_samplerates[0]; + lib_device->is_low_if = lib_device->samplerate_architectures[0]; + + result = airspyhf_read_att_steps_from_fw(lib_device, &lib_device->supported_att_step_count, 0); + if (result == AIRSPYHF_SUCCESS) + { + lib_device->supported_att_steps = (float*)malloc(lib_device->supported_att_step_count * sizeof(float)); + result = airspyhf_read_att_steps_from_fw(lib_device, lib_device->supported_att_steps, lib_device->supported_att_step_count); + if (result != AIRSPYHF_SUCCESS) + { + free(lib_device->supported_att_steps); + lib_device->supported_att_steps = NULL; + lib_device->supported_att_step_count = 0; + } + } + + if (result != AIRSPYHF_SUCCESS) + { + // Assume ATT steps of the Airspy HF+ Discovery + + lib_device->supported_att_step_count = DEFAULT_ATT_STEP_COUNT; + lib_device->supported_att_steps = (float*)malloc(lib_device->supported_att_step_count * sizeof(float)); + for (uint32_t i = 0; i < lib_device->supported_att_step_count; i++) + { + lib_device->supported_att_steps[i] = i * DEFAULT_ATT_STEP_INCREMENT; + } + } + + result = allocate_transfers(lib_device); + if (result != 0) + { + airspyhf_open_exit(lib_device); + free(lib_device); + return AIRSPYHF_ERROR; + } + + pthread_cond_init(&lib_device->consumer_cv, NULL); + pthread_mutex_init(&lib_device->consumer_mp, NULL); + + lib_device->freq_hz = 0; + lib_device->freq_khz = 0; + lib_device->freq_delta_hz = 0; + lib_device->freq_shift = 0; + lib_device->vec.re = 1.0f; + lib_device->vec.im = 0.0f; + lib_device->optimal_point = 0.0f; + lib_device->filter_gain = 1.0f; + lib_device->enable_dsp = 1; + + if (airspyhf_config_read(lib_device, (uint8_t *) &config, sizeof(config)) == AIRSPYHF_SUCCESS) + { + if (config.magic_number == CALIBRATION_MAGIC) + { + lib_device->calibration_ppb = config.calibration_ppb; + airspyhf_set_vctcxo_calibration(lib_device, config.calibration_vctcxo); + airspyhf_set_frontend_options(lib_device, config.frontend_options); + } + else + { + lib_device->calibration_ppb = 0; + lib_device->calibration_vctcxo = 0; + lib_device->frontend_options = 0; + } + } + else + { + lib_device->calibration_ppb = 0; + lib_device->calibration_vctcxo = 0; + lib_device->frontend_options = 0; + } + + lib_device->iq_balancer = iq_balancer_create(INITIAL_PHASE, INITIAL_AMPLITUDE); + lib_device->iq_balancer_eval_skip = 0; + + *device = lib_device; + + return AIRSPYHF_SUCCESS; +} + +void ADDCALL airspyhf_lib_version(airspyhf_lib_version_t* lib_version) +{ + lib_version->major_version = AIRSPYHF_VER_MAJOR; + lib_version->minor_version = AIRSPYHF_VER_MINOR; + lib_version->revision = AIRSPYHF_VER_REVISION; +} + +int ADDCALL airspyhf_open_sn(airspyhf_device_t** device, uint64_t serial_number) +{ + int result; + + result = airspyhf_open_init(device, serial_number, FILE_DESCRIPTOR_UNUSED); + return result; +} + +int ADDCALL airspyhf_open_fd(airspyhf_device_t** device, int fd) +{ + int result; + + result = airspyhf_open_init(device, SERIAL_NUMBER_UNUSED, fd); + return result; +} + +int ADDCALL airspyhf_open(airspyhf_device_t** device) +{ + int result; + + result = airspyhf_open_init(device, SERIAL_NUMBER_UNUSED, FILE_DESCRIPTOR_UNUSED); + return result; +} + +int ADDCALL airspyhf_close(airspyhf_device_t* device) +{ + int result; + + result = AIRSPYHF_SUCCESS; + + if (device != NULL) + { + result = airspyhf_stop(device); + free_transfers(device); + airspyhf_open_exit(device); + + free(device->supported_samplerates); + free(device->samplerate_architectures); + free(device->supported_att_steps); + iq_balancer_destroy(device->iq_balancer); + + pthread_cond_destroy(&device->consumer_cv); + pthread_mutex_destroy(&device->consumer_mp); + + free(device); + } + + return result; +} + +int ADDCALL airspyhf_get_output_size(airspyhf_device_t * device) +{ + // Todo: Make this configurable + return SAMPLES_TO_TRANSFER; +} + +int ADDCALL airspyhf_is_low_if(airspyhf_device_t* device) +{ + return device->is_low_if; +} + +int ADDCALL airspyhf_get_samplerates(airspyhf_device_t* device, uint32_t* buffer, const uint32_t len) +{ + if (len == 0) + { + *buffer = device->supported_samplerate_count; + } + else if (len <= device->supported_samplerate_count) + { + memcpy(buffer, device->supported_samplerates, len * sizeof(uint32_t)); + } + else + { + return AIRSPYHF_ERROR; + } + + return AIRSPYHF_SUCCESS; +} + +int ADDCALL airspyhf_set_samplerate(airspyhf_device_t* device, uint32_t samplerate) +{ + int result; + uint32_t i; + uint8_t gain; + + if (samplerate > MAX_SAMPLERATE_INDEX) + { + for (i = 0; i < device->supported_samplerate_count; i++) + { + if (samplerate == device->supported_samplerates[i]) + { + samplerate = i; + break; + } + } + } + + if (samplerate >= device->supported_samplerate_count) + { + return AIRSPYHF_ERROR; + } + + device->current_samplerate = device->supported_samplerates[samplerate]; + device->is_low_if = device->samplerate_architectures[samplerate]; + + libusb_clear_halt(device->usb_device, LIBUSB_ENDPOINT_IN | 1); + + if (!device->is_low_if && device->freq_khz < MIN_ZERO_IF_LO) + { + uint8_t buf[4]; + buf[0] = (uint8_t)((MIN_ZERO_IF_LO >> 24) & 0xff); + buf[1] = (uint8_t)((MIN_ZERO_IF_LO >> 16) & 0xff); + buf[2] = (uint8_t)((MIN_ZERO_IF_LO >> 8) & 0xff); + buf[3] = (uint8_t)((MIN_ZERO_IF_LO) & 0xff); + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPYHF_SET_FREQ, + 0, + 0, + (unsigned char*) &buf, + sizeof(buf), + LIBUSB_CTRL_TIMEOUT_MS); + + if (result < sizeof(buf)) + { + return AIRSPYHF_ERROR; + } + + device->freq_khz = MIN_ZERO_IF_LO; + } + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPYHF_SET_SAMPLERATE, + 0, + samplerate, + NULL, + 0, + LIBUSB_CTRL_TIMEOUT_MS); + + if (result != 0) + { + return AIRSPYHF_ERROR; + } + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPYHF_GET_FILTER_GAIN, + 0, + 0, + &gain, + sizeof(uint8_t), + LIBUSB_CTRL_TIMEOUT_MS); + + if (result == sizeof(uint8_t)) + { + device->filter_gain = powf(10.0f, (float) gain * -0.05f); + } + else + { + device->filter_gain = 1.0; + } + + airspyhf_set_freq_double(device, device->freq_hz); + + return AIRSPYHF_SUCCESS; +} + +int ADDCALL airspyhf_set_receiver_mode(airspyhf_device_t* device, receiver_mode_t value) +{ + int result; + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPYHF_RECEIVER_MODE, + value, + 0, + NULL, + 0, + LIBUSB_CTRL_TIMEOUT_MS); + + if (result != 0) + { + return AIRSPYHF_ERROR; + } + + return AIRSPYHF_SUCCESS; +} + +int ADDCALL airspyhf_start(airspyhf_device_t* device, airspyhf_sample_block_cb_fn callback, void* ctx) +{ + int result; + + memset(device->dropped_buffers_queue, 0, RAW_BUFFER_COUNT * sizeof(uint32_t)); + device->dropped_buffers = 0; + + device->vec.re = 1.0f; + device->vec.im = 0.0f; + + result = airspyhf_set_receiver_mode(device, RECEIVER_MODE_OFF); + if (result != AIRSPYHF_SUCCESS) + { + return result; + } + + libusb_clear_halt(device->usb_device, LIBUSB_ENDPOINT_IN | AIRSPYHF_ENDPOINT_IN); + + result = airspyhf_set_receiver_mode(device, RECEIVER_MODE_ON); + if (result == AIRSPYHF_SUCCESS) + { + device->ctx = ctx; + result = create_io_threads(device, callback); + } + + return result; +} + +int ADDCALL airspyhf_is_streaming(airspyhf_device_t* device) +{ + return device->streaming && !device->stop_requested; +} + +int ADDCALL airspyhf_stop(airspyhf_device_t* device) +{ + int result1, result2; + device->stop_requested = true; + result1 = airspyhf_set_receiver_mode(device, RECEIVER_MODE_OFF); + result2 = kill_io_threads(device); + +#ifndef _WIN32 + libusb_interrupt_event_handler(device->usb_context); +#endif + + if (result2 != AIRSPYHF_SUCCESS) + { + return result2; + } + return result1; +} + + +int ADDCALL airspyhf_set_freq(airspyhf_device_t* device, const uint32_t freq_hz) +{ + return airspyhf_set_freq_double(device, freq_hz); +} + +int ADDCALL airspyhf_set_freq_double(airspyhf_device_t* device, const double freq_hz) +{ + int result; + uint8_t buf[4]; + double if_shift = (device->enable_dsp && !device->is_low_if) ? DEFAULT_IF_SHIFT : 0; + double adjusted_freq_hz = freq_hz * (1.0e9 + device->calibration_ppb) * 1.0e-9; + uint32_t lo_low_khz = device->is_low_if ? MIN_LOW_IF_LO : MIN_ZERO_IF_LO; + uint32_t freq_khz = MAX(lo_low_khz, (uint32_t)round((adjusted_freq_hz + if_shift) * 1e-3)); + + if (device->freq_khz != freq_khz) + { + device->iq_balancer_eval_skip = IQ_BALANCER_EVAL_SKIP; + + buf[0] = (uint8_t)((freq_khz >> 24) & 0xff); + buf[1] = (uint8_t)((freq_khz >> 16) & 0xff); + buf[2] = (uint8_t)((freq_khz >> 8) & 0xff); + buf[3] = (uint8_t)((freq_khz) & 0xff); + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPYHF_SET_FREQ, + 0, + 0, + (unsigned char*)&buf, + sizeof(buf), + LIBUSB_CTRL_TIMEOUT_MS); + + if (result < sizeof(buf)) + { + return AIRSPYHF_ERROR; + } + + device->freq_khz = freq_khz; + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPYHF_GET_FREQ_DELTA, + 0, + 0, + (unsigned char*)&buf, + sizeof(buf), + LIBUSB_CTRL_TIMEOUT_MS); + + if (result == sizeof(buf)) + { + device->freq_delta_hz = (((int8_t)buf[3] << 16) | (buf[2] << 8) | buf[1]) * 1e3 / (1 << buf[0]); + } + + iq_balancer_set_optimal_point(device->iq_balancer, device->optimal_point); + } + + device->freq_hz = freq_hz; + device->freq_shift = adjusted_freq_hz - freq_khz * 1e3 + device->freq_delta_hz; + + return AIRSPYHF_SUCCESS; +} + +int ADDCALL airspyhf_get_frontend_options(airspyhf_device_t* device, uint32_t* flags) +{ + if (flags) + { + *flags = device->frontend_options; + return AIRSPYHF_SUCCESS; + } + + return AIRSPYHF_ERROR; +} + +int ADDCALL airspyhf_set_frontend_options(airspyhf_device_t* device, uint32_t flags) +{ + int result; + + device->frontend_options = flags; + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPYHF_SET_FRONTEND_OPTIONS, + (uint16_t)(flags & 0xffff), + (uint16_t)(flags >> 16), + NULL, + 0, + LIBUSB_CTRL_TIMEOUT_MS); + + if (result < 0) + { + return AIRSPYHF_ERROR; + } + return AIRSPYHF_SUCCESS; +} + +static int airspyhf_config_write(airspyhf_device_t* device, uint8_t *buffer, uint16_t length) +{ + int result; + uint8_t buf[256]; + + memset(buf, 0, sizeof(buf)); + + memcpy(buf, buffer, MIN(length, sizeof(buf))); + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPYHF_CONFIG_WRITE, + 0, + 0, + buf, + sizeof(buf), + LIBUSB_CTRL_TIMEOUT_MS); + + if (result < sizeof(buf)) + { + return AIRSPYHF_ERROR; + } + + return AIRSPYHF_SUCCESS; +} + +static int airspyhf_config_read(airspyhf_device_t* device, uint8_t *buffer, uint16_t length) +{ + uint8_t buf[256]; + int result; + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPYHF_CONFIG_READ, + 0, + 0, + buf, + sizeof(buf), + LIBUSB_CTRL_TIMEOUT_MS); + + memcpy(buffer, buf, MIN(length, sizeof(buf))); + + if (result < sizeof(buf)) + { + return AIRSPYHF_ERROR; + } + + return AIRSPYHF_SUCCESS; +} + +int ADDCALL airspyhf_get_calibration(airspyhf_device_t* device, int32_t* ppb) +{ + if (ppb) + { + *ppb = device->calibration_ppb; + return AIRSPYHF_SUCCESS; + } + + return AIRSPYHF_ERROR; +} + +int ADDCALL airspyhf_set_calibration(airspyhf_device_t* device, int32_t ppb) +{ + device->calibration_ppb = ppb; + return airspyhf_set_freq_double(device, device->freq_hz); +} + +int ADDCALL airspyhf_get_vctcxo_calibration(airspyhf_device_t* device, uint16_t* vc) +{ + if (vc) + { + *vc = device->calibration_vctcxo; + return AIRSPYHF_SUCCESS; + } + + return AIRSPYHF_ERROR; +} + +int ADDCALL airspyhf_set_vctcxo_calibration(airspyhf_device_t* device, uint16_t vc) +{ + int result; + device->calibration_vctcxo = vc; + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPYHF_SET_VCTCXO_CALIBRATION, + vc, + 0, + NULL, + 0, + LIBUSB_CTRL_TIMEOUT_MS); + + if (result < 0) + { + return AIRSPYHF_ERROR; + } + return AIRSPYHF_SUCCESS; +} + +int ADDCALL airspyhf_flash_configuration(airspyhf_device_t* device) +{ + flash_config_t config; + config.magic_number = CALIBRATION_MAGIC; + config.calibration_ppb = device->calibration_ppb; + config.calibration_vctcxo = device->calibration_vctcxo; + config.frontend_options = device->frontend_options; + + if (airspyhf_is_streaming(device)) + { + return AIRSPYHF_ERROR; + } + + if (airspyhf_config_write(device, (uint8_t *)&config, sizeof(config)) != AIRSPYHF_SUCCESS) + { + return AIRSPYHF_ERROR; + } + + return AIRSPYHF_SUCCESS; +} + +int ADDCALL airspyhf_set_optimal_iq_correction_point(airspyhf_device_t* device, float w) +{ + device->optimal_point = w; + iq_balancer_set_optimal_point(device->iq_balancer, device->optimal_point); + return AIRSPYHF_SUCCESS; +} + +int ADDCALL airspyhf_iq_balancer_configure(airspyhf_device_t* device, int buffers_to_skip, int fft_integration, int fft_overlap, int correlation_integration) +{ + iq_balancer_configure(device->iq_balancer, buffers_to_skip, fft_integration, fft_overlap, correlation_integration); + return AIRSPYHF_SUCCESS; +} + +int ADDCALL airspyhf_set_lib_dsp(airspyhf_device_t* device, const uint8_t flag) +{ + device->enable_dsp = flag; + return AIRSPYHF_SUCCESS; +} + +int ADDCALL airspyhf_board_partid_serialno_read(airspyhf_device_t* device, airspyhf_read_partid_serialno_t* read_partid_serialno) +{ + uint8_t length; + int result; + + length = sizeof(airspyhf_read_partid_serialno_t); + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPYHF_GET_SERIALNO_BOARDID, + 0, + 0, + (unsigned char*)read_partid_serialno, + length, + LIBUSB_CTRL_TIMEOUT_MS); + + if (result < length) + { + return AIRSPYHF_ERROR; + } + + return AIRSPYHF_SUCCESS; +} + +int ADDCALL airspyhf_version_string_read(airspyhf_device_t* device, char* version, uint8_t length) +{ + int result; + char version_local[MAX_VERSION_STRING_SIZE]; + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPYHF_GET_VERSION_STRING, + 0, + 0, + (unsigned char*) version_local, + (MAX_VERSION_STRING_SIZE - 1), + LIBUSB_CTRL_TIMEOUT_MS); + + if (result < 0) + { + return AIRSPYHF_ERROR; + } + else + { + if (length > 0) + { + memcpy(version, version_local, length - 1); + version[length - 1] = 0; + return AIRSPYHF_SUCCESS; + } + else + { + return AIRSPYHF_ERROR; + } + } +} + +int ADDCALL airspyhf_get_att_steps(airspyhf_device_t* device, void* buffer, const uint32_t len) +{ + if (len == 0) + { + *(uint32_t*)buffer = device->supported_att_step_count; + } + else if (len <= device->supported_att_step_count) + { + memcpy(buffer, device->supported_att_steps, len * sizeof(float)); + } + else + { + return AIRSPYHF_ERROR; + } + + return AIRSPYHF_SUCCESS; +} + +int ADDCALL airspyhf_set_att(airspyhf_device_t* device, float att) +{ + int result; + uint16_t att_index = 0; + + for (uint32_t i = 0; i < device->supported_att_step_count; i++) + { + if (device->supported_att_steps[i] >= att) + { + att_index = i; + break; + } + } + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPYHF_SET_ATT, + att_index, + 0, + NULL, + 0, + LIBUSB_CTRL_TIMEOUT_MS); + + if (result < 0) + { + return AIRSPYHF_ERROR; + } + + return AIRSPYHF_SUCCESS; +} + +int ADDCALL airspyhf_set_bias_tee(airspyhf_device_t* device, int8_t value) +{ + int result; + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPYHF_SET_BIAS_TEE, + (uint16_t)value, + 0, + NULL, + 0, + LIBUSB_CTRL_TIMEOUT_MS); + + if (result < 0) + { + return AIRSPYHF_ERROR; + } + + return AIRSPYHF_SUCCESS; +} + +int ADDCALL airspyhf_get_bias_tee_count(airspyhf_device_t* device, int32_t* value) +{ + uint8_t length; + int result; + + length = sizeof(airspyhf_read_partid_serialno_t); + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPYHF_GET_BIAS_TEE_COUNT, + 0, + 0, + (unsigned char*)value, + length, + LIBUSB_CTRL_TIMEOUT_MS); + + if (result < length) + { + *value = 0; + return AIRSPYHF_ERROR; + } + + return AIRSPYHF_SUCCESS; +} + +int ADDCALL airspyhf_get_bias_tee_name(airspyhf_device_t* device, int32_t index, char* version, uint8_t length) +{ + int result; + char name_local[MAX_NAME_STRING_SIZE]; + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPYHF_GET_BIAS_TEE_NAME, + 0, + (int16_t)index, + (unsigned char*)name_local, + (MAX_NAME_STRING_SIZE - 1), + LIBUSB_CTRL_TIMEOUT_MS); + + if (result < 0) + { + return AIRSPYHF_ERROR; + } + else + { + if (length > 0) + { + memcpy(version, name_local, length - 1); + version[length - 1] = 0; + return AIRSPYHF_SUCCESS; + } + else + { + return AIRSPYHF_ERROR; + } + } +} + +// Legacy for HF+ dual and HF+ Discovery + +int ADDCALL airspyhf_set_hf_att(airspyhf_device_t* device, uint8_t att_index) +{ + int result; + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPYHF_SET_ATT, + (uint16_t)att_index, + 0, + NULL, + 0, + LIBUSB_CTRL_TIMEOUT_MS); + + if (result < 0) + { + return AIRSPYHF_ERROR; + } + + return AIRSPYHF_SUCCESS; +} + +int ADDCALL airspyhf_set_hf_lna(airspyhf_device_t* device, uint8_t flag) +{ + int result; + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPYHF_SET_LNA, + (uint16_t) flag, + 0, + NULL, + 0, + LIBUSB_CTRL_TIMEOUT_MS); + + if (result < 0) + { + return AIRSPYHF_ERROR; + } + + return AIRSPYHF_SUCCESS; +} + +int ADDCALL airspyhf_set_user_output(airspyhf_device_t* device, airspyhf_user_output_t pin, airspyhf_user_output_state_t value) +{ + int result; + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPYHF_SET_USER_OUTPUT, + (uint16_t)pin, + (uint16_t)value, + NULL, + 0, + LIBUSB_CTRL_TIMEOUT_MS); + + if (result < 0) + { + return AIRSPYHF_ERROR; + } + + return AIRSPYHF_SUCCESS; +} + +int ADDCALL airspyhf_set_hf_agc(airspyhf_device_t* device, uint8_t flag) +{ + int result; + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPYHF_SET_AGC, + (uint16_t)flag, + 0, + NULL, + 0, + LIBUSB_CTRL_TIMEOUT_MS); + + if (result < 0) + { + return AIRSPYHF_ERROR; + } + + return AIRSPYHF_SUCCESS; +} + +int ADDCALL airspyhf_set_hf_agc_threshold(airspyhf_device_t* device, uint8_t flag) +{ + int result; + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPYHF_SET_AGC_THRESHOLD, + (uint16_t)flag, + 0, + NULL, + 0, + LIBUSB_CTRL_TIMEOUT_MS); + + if (result < 0) + { + return AIRSPYHF_ERROR; + } + + return AIRSPYHF_SUCCESS; +} diff --git a/Radio/HW/AirSpyHF/src/airspyhf.h b/Radio/HW/AirSpyHF/src/airspyhf.h new file mode 100644 index 0000000..2879120 --- /dev/null +++ b/Radio/HW/AirSpyHF/src/airspyhf.h @@ -0,0 +1,171 @@ +/* +Copyright (c) 2013-2024, Youssef Touil +Copyright (c) 2013-2017, Ian Gilmour +Copyright (c) 2013-2017, Benjamin Vernoux +Copyright (c) 2013, Michael Ossmann +Copyright (c) 2012, Jared Boone + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + Neither the name of Airspy HF+ nor the names of its contributors may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef __AIRSPYHF_H__ +#define __AIRSPYHF_H__ + +#include + +#define AIRSPYHF_VERSION "1.8.0" +#define AIRSPYHF_VER_MAJOR 1 +#define AIRSPYHF_VER_MINOR 8 +#define AIRSPYHF_VER_REVISION 0 + +#define AIRSPYHF_ENDPOINT_IN (1) + +#if defined(_WIN32) && !defined(STATIC_AIRSPYHFPLUS) + #define ADD_EXPORTS + + /* You should define ADD_EXPORTS *only* when building the DLL. */ + #ifdef ADD_EXPORTS + #define ADDAPI __declspec(dllexport) + #else + #define ADDAPI __declspec(dllimport) + #endif + + /* Define calling convention in one place, for convenience. */ + #define ADDCALL __cdecl + +#else /* _WIN32 not defined. */ + + /* Define with no value on non-Windows OSes. */ + #define ADDAPI + #define ADDCALL + +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + +enum airspyhf_error +{ + AIRSPYHF_SUCCESS = 0, + AIRSPYHF_ERROR = -1, + AIRSPYHF_UNSUPPORTED = -2 +}; + +typedef struct { + float re; + float im; +} airspyhf_complex_float_t; + +typedef struct { + uint32_t part_id; + uint32_t serial_no[4]; +} airspyhf_read_partid_serialno_t; + +enum airspyhf_board_id +{ + AIRSPYHF_BOARD_ID_UNKNOWN_AIRSPYHF = 0, + AIRSPYHF_BOARD_ID_AIRSPYHF_REV_A = 1, + AIRSPYHF_BOARD_ID_AIRSPYHF_DISCOVERY_REV_A = 2, + AIRSPYHF_BOARD_ID_INVALID = 0xFF, +}; + +typedef struct airspyhf_device airspyhf_device_t; + +typedef struct { + airspyhf_device_t* device; + void* ctx; + airspyhf_complex_float_t* samples; + int sample_count; + uint64_t dropped_samples; +} airspyhf_transfer_t; + +typedef struct { + uint32_t major_version; + uint32_t minor_version; + uint32_t revision; +} airspyhf_lib_version_t; + +#define MAX_NAME_STRING_SIZE (64) +#define MAX_VERSION_STRING_SIZE MAX_NAME_STRING_SIZE + +typedef int (*airspyhf_sample_block_cb_fn) (airspyhf_transfer_t* transfer_fn); + +extern ADDAPI void ADDCALL airspyhf_lib_version(airspyhf_lib_version_t* lib_version); +extern ADDAPI int ADDCALL airspyhf_list_devices(uint64_t *serials, int count); +extern ADDAPI int ADDCALL airspyhf_open(airspyhf_device_t** device); +extern ADDAPI int ADDCALL airspyhf_open_sn(airspyhf_device_t** device, uint64_t serial_number); +extern ADDAPI int ADDCALL airspyhf_open_fd(airspyhf_device_t** device, int fd); +extern ADDAPI int ADDCALL airspyhf_close(airspyhf_device_t* device); +extern ADDAPI int ADDCALL airspyhf_get_output_size(airspyhf_device_t* device); /* Returns the number of IQ samples to expect in the callback */ +extern ADDAPI int ADDCALL airspyhf_start(airspyhf_device_t* device, airspyhf_sample_block_cb_fn callback, void* ctx); +extern ADDAPI int ADDCALL airspyhf_stop(airspyhf_device_t* device); +extern ADDAPI int ADDCALL airspyhf_is_streaming(airspyhf_device_t* device); +extern ADDAPI int ADDCALL airspyhf_is_low_if(airspyhf_device_t* device); /* Tells if the current sample rate is Zero-IF (0) or Low-IF (1) */ +extern ADDAPI int ADDCALL airspyhf_set_freq(airspyhf_device_t* device, const uint32_t freq_hz); +extern ADDAPI int ADDCALL airspyhf_set_freq_double(airspyhf_device_t* device, const double freq_hz); +extern ADDAPI int ADDCALL airspyhf_set_lib_dsp(airspyhf_device_t* device, const uint8_t flag); /* Enables/Disables the IQ Correction, IF shift and Fine Tuning. */ +extern ADDAPI int ADDCALL airspyhf_get_samplerates(airspyhf_device_t* device, uint32_t* buffer, const uint32_t len); +extern ADDAPI int ADDCALL airspyhf_set_samplerate(airspyhf_device_t* device, uint32_t samplerate); +extern ADDAPI int ADDCALL airspyhf_set_att(airspyhf_device_t* device, float value); +extern ADDAPI int ADDCALL airspyhf_get_att_steps(airspyhf_device_t* device, void* buffer, const uint32_t len); +extern ADDAPI int ADDCALL airspyhf_set_bias_tee(airspyhf_device_t* device, int8_t value); +extern ADDAPI int ADDCALL airspyhf_get_bias_tee_count(airspyhf_device_t* device, int32_t* count); +extern ADDAPI int ADDCALL airspyhf_get_bias_tee_name(airspyhf_device_t* device, int32_t index, char* version, uint8_t length); +extern ADDAPI int ADDCALL airspyhf_get_calibration(airspyhf_device_t* device, int32_t* ppb); +extern ADDAPI int ADDCALL airspyhf_set_calibration(airspyhf_device_t* device, int32_t ppb); +extern ADDAPI int ADDCALL airspyhf_get_vctcxo_calibration(airspyhf_device_t* device, uint16_t* vc); +extern ADDAPI int ADDCALL airspyhf_set_vctcxo_calibration(airspyhf_device_t* device, uint16_t vc); +extern ADDAPI int ADDCALL airspyhf_get_frontend_options(airspyhf_device_t* device, uint32_t* flags); +extern ADDAPI int ADDCALL airspyhf_set_frontend_options(airspyhf_device_t* device, uint32_t flags); +extern ADDAPI int ADDCALL airspyhf_set_optimal_iq_correction_point(airspyhf_device_t* device, float w); +extern ADDAPI int ADDCALL airspyhf_iq_balancer_configure(airspyhf_device_t* device, int buffers_to_skip, int fft_integration, int fft_overlap, int correlation_integration); +extern ADDAPI int ADDCALL airspyhf_flash_configuration(airspyhf_device_t* device); /* streaming needs to be stopped */ +extern ADDAPI int ADDCALL airspyhf_board_partid_serialno_read(airspyhf_device_t* device, airspyhf_read_partid_serialno_t* read_partid_serialno); +extern ADDAPI int ADDCALL airspyhf_version_string_read(airspyhf_device_t* device, char* version, uint8_t length); + +// Legacy stuff for backward compatibility + +typedef enum +{ + AIRSPYHF_USER_OUTPUT_0 = 0, + AIRSPYHF_USER_OUTPUT_1 = 1, + AIRSPYHF_USER_OUTPUT_2 = 2, + AIRSPYHF_USER_OUTPUT_3 = 3 +} airspyhf_user_output_t; + +typedef enum +{ + AIRSPYHF_USER_OUTPUT_LOW = 0, + AIRSPYHF_USER_OUTPUT_HIGH = 1 +} airspyhf_user_output_state_t; + +#define airspyhf_flash_calibration airspyhf_flash_configuration + +extern ADDAPI int ADDCALL airspyhf_set_user_output(airspyhf_device_t* device, airspyhf_user_output_t pin, airspyhf_user_output_state_t value); +extern ADDAPI int ADDCALL airspyhf_set_hf_agc(airspyhf_device_t* device, uint8_t flag); /* 0 = off, 1 = on */ +extern ADDAPI int ADDCALL airspyhf_set_hf_agc_threshold(airspyhf_device_t* device, uint8_t flag); /* when agc on: 0 = low, 1 = high */ +extern ADDAPI int ADDCALL airspyhf_set_hf_att(airspyhf_device_t* device, uint8_t att_index); /* Possible values: 0..8 Range: 0..48 dB Attenuation with 6 dB steps */ +extern ADDAPI int ADDCALL airspyhf_set_hf_lna(airspyhf_device_t* device, uint8_t flag); /* 0 or 1: 1 to activate LNA (alias PreAmp): 1 = +6 dB gain - compensated in digital */ + +#ifdef __cplusplus +} +#endif + +#endif//__AIRSPYHF_H__ diff --git a/Radio/HW/AirSpyHF/src/airspyhf_commands.h b/Radio/HW/AirSpyHF/src/airspyhf_commands.h new file mode 100644 index 0000000..db19690 --- /dev/null +++ b/Radio/HW/AirSpyHF/src/airspyhf_commands.h @@ -0,0 +1,63 @@ +/* +Copyright (c) 2013-2023, Youssef Touil +Copyright (c) 2013-2017, Ian Gilmour +Copyright (c) 2013-2017, Benjamin Vernoux + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +Neither the name of Airspy HF+ nor the names of its contributors may be used to endorse or promote products derived from this software +without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef __AIRSPYHF_COMMANDS_H__ +#define __AIRSPYHF_COMMANDS_H__ + +#include + +typedef enum +{ + RECEIVER_MODE_OFF = 0, + RECEIVER_MODE_ON = 1 +} receiver_mode_t; + +#define AIRSPYHF_CMD_MAX (22) +typedef enum +{ + AIRSPYHF_INVALID = 0, + AIRSPYHF_RECEIVER_MODE = 1, + AIRSPYHF_SET_FREQ = 2, + AIRSPYHF_GET_SAMPLERATES = 3, + AIRSPYHF_SET_SAMPLERATE = 4, + AIRSPYHF_CONFIG_READ = 5, + AIRSPYHF_CONFIG_WRITE = 6, + AIRSPYHF_GET_SERIALNO_BOARDID = 7, + AIRSPYHF_SET_USER_OUTPUT = 8, + AIRSPYHF_GET_VERSION_STRING = 9, + AIRSPYHF_SET_AGC = 10, + AIRSPYHF_SET_AGC_THRESHOLD = 11, + AIRSPYHF_SET_ATT = 12, + AIRSPYHF_SET_LNA = 13, + AIRSPYHF_GET_SAMPLERATE_ARCHITECTURES = 14, + AIRSPYHF_GET_FILTER_GAIN = 15, + AIRSPYHF_GET_FREQ_DELTA = 16, + AIRSPYHF_SET_VCTCXO_CALIBRATION = 17, + AIRSPYHF_SET_FRONTEND_OPTIONS = 18, + AIRSPYHF_GET_ATT_STEPS = 19, + AIRSPYHF_GET_BIAS_TEE_COUNT = 20, + AIRSPYHF_GET_BIAS_TEE_NAME = 21, + AIRSPYHF_SET_BIAS_TEE = AIRSPYHF_CMD_MAX +} airspyhf_vendor_request; + +#endif diff --git a/Radio/HW/AirSpyHF/src/iqbalancer.c b/Radio/HW/AirSpyHF/src/iqbalancer.c new file mode 100644 index 0000000..2bf5901 --- /dev/null +++ b/Radio/HW/AirSpyHF/src/iqbalancer.c @@ -0,0 +1,564 @@ +/* +Copyright (c) 2016-2023, Youssef Touil +Copyright (c) 2018, Leif Asbrink + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + Neither the name of Airspy HF+ nor the names of its contributors may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include +#include +#include + +#include "iqbalancer.h" + +#ifndef MATH_PI +#define MATH_PI 3.14159265359 +#endif + +#define EPSILON 0.01f +#define WorkingBufferLength (FFTBins * (1 + FFTIntegration / FFTOverlap)) + +struct iq_balancer_t +{ + float phase; + float last_phase; + + float amplitude; + float last_amplitude; + + float iavg; + float qavg; + float integrated_total_power; + float integrated_image_power; + float maximum_image_power; + + float raw_phases[MaxLookback]; + float raw_amplitudes[MaxLookback]; + + int skipped_buffers; + int buffers_to_skip; + int working_buffer_pos; + int fft_integration; + int fft_overlap; + int correlation_integration; + + int no_of_avg; + int no_of_raw; + int raw_ptr; + int optimal_bin; + int reset_flag; + int *power_flag; + + complex_t *corr; + complex_t *corr_plus; + complex_t *working_buffer; + float *boost; +}; + +static uint8_t __lib_initialized = 0; +static float __fft_window[FFTBins]; +static float __boost_window[FFTBins]; + +static void __init_library() +{ + int i; + + if (__lib_initialized) + { + return; + } + + const int length = FFTBins - 1; + + for (i = 0; i <= length; i++) + { + __fft_window[i] = (float)( + +0.35875f + - 0.48829f * cos(2.0 * MATH_PI * i / length) + + 0.14128f * cos(4.0 * MATH_PI * i / length) + - 0.01168f * cos(6.0 * MATH_PI * i / length) + ); + __boost_window[i] = (float)(1.0 / BoostFactor + 1.0 / exp(pow(i * 2.0 / BinsToOptimize, 2.0))); + } + + __lib_initialized = 1; +} + +static void window(complex_t *buffer, int length) +{ + int i; + for (i = 0; i < length; i++) + { + buffer[i].re *= __fft_window[i]; + buffer[i].im *= __fft_window[i]; + } +} + +static void fft(complex_t *buffer, int length) +{ + int nm1 = length - 1; + int nd2 = length / 2; + int i, j, jm1, k, l, m, le, le2, ip; + complex_t u, t, r; + + m = 0; + i = length; + while (i > 1) + { + ++m; + i = (i >> 1); + } + + j = nd2; + + for (i = 1; i < nm1; ++i) + { + if (i < j) + { + t = buffer[j]; + buffer[j] = buffer[i]; + buffer[i] = t; + } + + k = nd2; + + while (k <= j) + { + j = j - k; + k = k / 2; + } + + j += k; + } + + for (l = 1; l <= m; ++l) + { + le = 1 << l; + le2 = le / 2; + + u.re = 1.0f; + u.im = 0.0f; + + r.re = (float)cos(MATH_PI / le2); + r.im = (float)-sin(MATH_PI / le2); + + for (j = 1; j <= le2; ++j) + { + jm1 = j - 1; + + for (i = jm1; i <= nm1; i += le) + { + ip = i + le2; + + t.re = u.re * buffer[ip].re - u.im * buffer[ip].im; + t.im = u.im * buffer[ip].re + u.re * buffer[ip].im; + + buffer[ip].re = buffer[i].re - t.re; + buffer[ip].im = buffer[i].im - t.im; + + buffer[i].re += t.re; + buffer[i].im += t.im; + } + + t.re = u.re * r.re - u.im * r.im; + t.im = u.im * r.re + u.re * r.im; + + u.re = t.re; + u.im = t.im; + } + } + + for (i = 0; i < nd2; i++) + { + j = nd2 + i; + t = buffer[i]; + buffer[i] = buffer[j]; + buffer[j] = t; + } +} + +static void cancel_dc(struct iq_balancer_t *iq_balancer, complex_t* iq, int length, uint8_t skip_eval) +{ + int i; + float iavg = iq_balancer->iavg; + float qavg = iq_balancer->qavg; + + if (skip_eval) + { + for (i = 0; i < length; i++) + { + iq[i].re -= iavg; + iq[i].im -= qavg; + } + } + else + { + for (i = 0; i < length; i++) + { + iavg += DcTimeConst * (iq[i].re - iavg); + qavg += DcTimeConst * (iq[i].im - qavg); + + iq[i].re -= iavg; + iq[i].im -= qavg; + } + iq_balancer->iavg = iavg; + iq_balancer->qavg = qavg; + } +} + +static float adjust_benchmark(struct iq_balancer_t *iq_balancer, complex_t *iq, float phase, float amplitude, int skip_power_calculation) +{ + int i; + float sum = 0; + for (i = 0; i < FFTBins; i++) + { + float re = iq[i].re; + float im = iq[i].im; + + iq[i].re += phase * im; + iq[i].im += phase * re; + + iq[i].re *= 1 + amplitude; + iq[i].im *= 1 - amplitude; + if (!skip_power_calculation) + sum += re * re + im * im; + } + return sum; +} + +static complex_t multiply_complex_complex(complex_t *a, const complex_t *b) +{ + complex_t result; + result.re = a->re * b->re - a->im * b->im; + result.im = a->im * b->re + a->re * b->im; + return result; +} + +static int compute_corr(struct iq_balancer_t *iq_balancer, complex_t* iq, complex_t* ccorr, int length, int step) +{ + complex_t cc, fftPtr[FFTBins]; + int n, m; + int i, j; + int count = 0; + float power; + float phase = iq_balancer->phase + step * PhaseStep; + float amplitude = iq_balancer->amplitude + step * AmplitudeStep; + + for (n = 0, m = 0; n <= length - FFTBins && m < iq_balancer->fft_integration; n += FFTBins / iq_balancer->fft_overlap, m++) + { + memcpy(fftPtr, iq + n, FFTBins * sizeof(complex_t)); + power = adjust_benchmark(iq_balancer, fftPtr, phase, amplitude, step); + if (step == 0) + { + if (power > MinimumPower) + { + iq_balancer->power_flag[m] = 1; + iq_balancer->integrated_total_power += power; + } + else + { + iq_balancer->power_flag[m] = 0; + } + } + if (iq_balancer->power_flag[m] == 1) + { + count++; + window(fftPtr, FFTBins); + fft(fftPtr, FFTBins); + for (i = EdgeBinsToSkip, j = FFTBins - EdgeBinsToSkip; i <= FFTBins - EdgeBinsToSkip; i++, j--) + { + cc = multiply_complex_complex(fftPtr + i, fftPtr + j); + ccorr[i].re += cc.re; + ccorr[i].im += cc.im; + + ccorr[j].re = ccorr[i].re; + ccorr[j].im = ccorr[i].im; + } + if (step == 0) + { + for (i = EdgeBinsToSkip; i <= FFTBins - EdgeBinsToSkip; i++) + { + power = fftPtr[i].re * fftPtr[i].re + fftPtr[i].im * fftPtr[i].im; + iq_balancer->boost[i] += power; + if (iq_balancer->optimal_bin == FFTBins / 2) + { + iq_balancer->integrated_image_power += power; + } + else + { + iq_balancer->integrated_image_power += power * __boost_window[abs(FFTBins - i - iq_balancer->optimal_bin)]; + } + } + } + } + } + + return count; +} + +static complex_t utility(struct iq_balancer_t *iq_balancer, complex_t* ccorr) +{ + int i; + int j; + float invskip = 1.0f / EdgeBinsToSkip; + complex_t acc = { 0, 0 }; + for (i = EdgeBinsToSkip, j = FFTBins - EdgeBinsToSkip; i <= FFTBins - EdgeBinsToSkip; i++, j--) + { + int distance = abs(i - FFTBins / 2); + if (distance > CenterBinsToSkip) + { + float weight = (distance > EdgeBinsToSkip) ? 1.0f : (distance * invskip); + if (iq_balancer->optimal_bin != FFTBins / 2) + { + weight *= __boost_window[abs(iq_balancer->optimal_bin - i)]; + } + weight *= iq_balancer->boost[j] / (iq_balancer->boost[i] + EPSILON); + acc.re += ccorr[i].re * weight; + acc.im += ccorr[i].im * weight; + } + } + return acc; +} + +static void estimate_imbalance(struct iq_balancer_t *iq_balancer, complex_t* iq, int length) +{ + int i, j; + float amplitude, phase, mu; + complex_t a, b; + + if (iq_balancer->reset_flag) + { + iq_balancer->reset_flag = 0; + iq_balancer->no_of_avg = -BuffersToSkipOnReset; + iq_balancer->maximum_image_power = 0; + } + + if (iq_balancer->no_of_avg < 0) + { + iq_balancer->no_of_avg++; + return; + } + else if (iq_balancer->no_of_avg == 0) + { + iq_balancer->integrated_image_power = 0; + iq_balancer->integrated_total_power = 0; + memset(iq_balancer->boost, 0, FFTBins * sizeof(float)); + memset(iq_balancer->corr, 0, FFTBins * sizeof(complex_t)); + memset(iq_balancer->corr_plus, 0, FFTBins * sizeof(complex_t)); + } + + iq_balancer->maximum_image_power *= MaxPowerDecay; + + i = compute_corr(iq_balancer, iq, iq_balancer->corr, length, 0); + if (i == 0) + return; + + iq_balancer->no_of_avg += i; + compute_corr(iq_balancer, iq, iq_balancer->corr_plus, length, 1); + + if (iq_balancer->no_of_avg <= iq_balancer->correlation_integration * iq_balancer->fft_integration) + return; + + iq_balancer->no_of_avg = 0; + + if (iq_balancer->optimal_bin == FFTBins / 2) + { + if (iq_balancer->integrated_total_power < iq_balancer->maximum_image_power) + return; + iq_balancer->maximum_image_power = iq_balancer->integrated_total_power; + } + else + { + if (iq_balancer->integrated_image_power - iq_balancer->integrated_total_power * BoostWindowNorm < iq_balancer->maximum_image_power * PowerThreshold) + return; + iq_balancer->maximum_image_power = iq_balancer->integrated_image_power - iq_balancer->integrated_total_power * BoostWindowNorm; + } + + a = utility(iq_balancer, iq_balancer->corr); + b = utility(iq_balancer, iq_balancer->corr_plus); + + mu = a.im - b.im; + if (fabs(mu) > MinDeltaMu) + { + mu = a.im / mu; + if (mu < -MaxMu) + mu = -MaxMu; + else if (mu > MaxMu) + mu = MaxMu; + } + else + { + mu = 0; + } + + phase = iq_balancer->phase + PhaseStep * mu; + + mu = a.re - b.re; + if (fabs(mu) > MinDeltaMu) + { + mu = a.re / mu; + if (mu < -MaxMu) + mu = -MaxMu; + else if (mu > MaxMu) + mu = MaxMu; + } + else + { + mu = 0; + } + + amplitude = iq_balancer->amplitude + AmplitudeStep * mu; + + if (iq_balancer->no_of_raw < MaxLookback) + iq_balancer->no_of_raw++; + iq_balancer->raw_amplitudes[iq_balancer->raw_ptr] = amplitude; + iq_balancer->raw_phases[iq_balancer->raw_ptr] = phase; + i = iq_balancer->raw_ptr; + for (j = 0; j < iq_balancer->no_of_raw - 1; j++) + { + i = (i + MaxLookback - 1) & (MaxLookback - 1); + phase += iq_balancer->raw_phases[i]; + amplitude += iq_balancer->raw_amplitudes[i]; + } + phase /= iq_balancer->no_of_raw; + amplitude /= iq_balancer->no_of_raw; + iq_balancer->raw_ptr = (iq_balancer->raw_ptr + 1) & (MaxLookback - 1); + + iq_balancer->phase = phase; + iq_balancer->amplitude = amplitude; +} + +static void adjust_phase_amplitude(struct iq_balancer_t *iq_balancer, complex_t* iq, int length) +{ + int i; + float scale = 1.0f / (length - 1); + + for (i = 0; i < length; i++) + { + float phase = (i * iq_balancer->last_phase + (length - 1 - i) * iq_balancer->phase) * scale; + float amplitude = (i * iq_balancer->last_amplitude + (length - 1 - i) * iq_balancer->amplitude) * scale; + + float re = iq[i].re; + float im = iq[i].im; + + iq[i].re += phase * im; + iq[i].im += phase * re; + + iq[i].re *= 1 + amplitude; + iq[i].im *= 1 - amplitude; + } + + iq_balancer->last_phase = iq_balancer->phase; + iq_balancer->last_amplitude = iq_balancer->amplitude; +} + +void ADDCALL iq_balancer_process(struct iq_balancer_t *iq_balancer, complex_t* iq, int length, uint8_t skip_eval) +{ + int count; + + cancel_dc(iq_balancer, iq, length, skip_eval); + + if (!skip_eval) + { + count = WorkingBufferLength - iq_balancer->working_buffer_pos; + if (count >= length) + { + count = length; + } + memcpy(iq_balancer->working_buffer + iq_balancer->working_buffer_pos, iq, count * sizeof(complex_t)); + iq_balancer->working_buffer_pos += count; + if (iq_balancer->working_buffer_pos >= WorkingBufferLength) + { + iq_balancer->working_buffer_pos = 0; + + if (++iq_balancer->skipped_buffers > iq_balancer->buffers_to_skip) + { + iq_balancer->skipped_buffers = 0; + estimate_imbalance(iq_balancer, iq_balancer->working_buffer, WorkingBufferLength); + } + } + } + + adjust_phase_amplitude(iq_balancer, iq, length); +} + +void ADDCALL iq_balancer_set_optimal_point(struct iq_balancer_t *iq_balancer, float w) +{ + if (w < -0.5f) + { + w = -0.5f; + } + else if (w > 0.5f) + { + w = 0.5f; + } + + iq_balancer->optimal_bin = (int)floor(FFTBins * (0.5 + w)); + iq_balancer->reset_flag = 1; +} + +void ADDCALL iq_balancer_configure(struct iq_balancer_t *iq_balancer, int buffers_to_skip, int fft_integration, int fft_overlap, int correlation_integration) +{ + iq_balancer->buffers_to_skip = buffers_to_skip; + iq_balancer->fft_integration = fft_integration; + iq_balancer->fft_overlap = fft_overlap; + iq_balancer->correlation_integration = correlation_integration; + + free(iq_balancer->power_flag); + iq_balancer->power_flag = (int *)malloc(iq_balancer->fft_integration * sizeof(int)); + memset(iq_balancer->power_flag, 0, iq_balancer->fft_integration * sizeof(int)); + + iq_balancer->reset_flag = 1; +} + +struct iq_balancer_t * ADDCALL iq_balancer_create(float initial_phase, float initial_amplitude) +{ + struct iq_balancer_t *instance = (struct iq_balancer_t *) malloc(sizeof(struct iq_balancer_t)); + memset(instance, 0, sizeof(struct iq_balancer_t)); + + instance->phase = initial_phase; + instance->amplitude = initial_amplitude; + + instance->optimal_bin = FFTBins / 2; + + instance->buffers_to_skip = BuffersToSkip; + instance->fft_integration = FFTIntegration; + instance->fft_overlap = FFTOverlap; + instance->correlation_integration = CorrelationIntegration; + + instance->corr = (complex_t *)malloc(FFTBins * sizeof(complex_t)); + instance->corr_plus = (complex_t *)malloc(FFTBins * sizeof(complex_t)); + instance->working_buffer = (complex_t *)malloc(WorkingBufferLength * sizeof(complex_t)); + instance->boost = (float *)malloc(FFTBins * sizeof(float)); + instance->power_flag = (int *)malloc(instance->fft_integration * sizeof(int)); + + __init_library(); + return instance; +} + +void ADDCALL iq_balancer_destroy(struct iq_balancer_t *iq_balancer) +{ + free(iq_balancer->corr); + free(iq_balancer->corr_plus); + free(iq_balancer->working_buffer); + free(iq_balancer->boost); + free(iq_balancer->power_flag); + free(iq_balancer); +} diff --git a/Radio/HW/AirSpyHF/src/iqbalancer.h b/Radio/HW/AirSpyHF/src/iqbalancer.h new file mode 100644 index 0000000..baa1374 --- /dev/null +++ b/Radio/HW/AirSpyHF/src/iqbalancer.h @@ -0,0 +1,68 @@ +/* +Copyright (c) 2016-2023, Youssef Touil +Copyright (c) 2018, Leif Asbrink + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + Neither the name of Airspy HF+ nor the names of its contributors may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef __IQ_BALANCER_H__ +#define __IQ_BALANCER_H__ + +#include "airspyhf.h" + +#define FFTBins (4 * 1024) +#define BoostFactor 100000.0 +#define BinsToOptimize (FFTBins/25) +#define EdgeBinsToSkip (FFTBins/22) +#define CenterBinsToSkip 2 +#define MaxLookback 4 +#define PhaseStep 1e-2f +#define AmplitudeStep 1e-2f +#define MaxMu 50.0f +#define MinDeltaMu 0.1f +#define DcTimeConst 1e-4f +#define MinimumPower 0.01f +#define PowerThreshold 0.5f +#define BuffersToSkipOnReset 2 +#define MaxPowerDecay 0.98f +#define MaxPowerRatio 0.8f +#define BoostWindowNorm (MaxPowerRatio / 95) + +#if defined(__arm__) && !defined(__force_hiq__) + #define BuffersToSkip 4 + #define FFTIntegration 2 + #define FFTOverlap 1 + #define CorrelationIntegration 4 +#else + #define BuffersToSkip 2 + #define FFTIntegration 4 + #define FFTOverlap 2 + #define CorrelationIntegration 16 +#endif + +struct iq_balancer_t; + +typedef airspyhf_complex_float_t complex_t; + +ADDAPI struct iq_balancer_t * ADDCALL iq_balancer_create(float initial_phase, float initial_amplitude); +ADDAPI void ADDCALL iq_balancer_set_optimal_point(struct iq_balancer_t *iq_balancer, float w); +ADDAPI void ADDCALL iq_balancer_configure(struct iq_balancer_t *iq_balancer, int buffers_to_skip, int fft_integration, int fft_overlap, int correlation_integration); +ADDAPI void ADDCALL iq_balancer_process(struct iq_balancer_t *iq_balancer, complex_t* iq, int length, uint8_t skip_eval); +ADDAPI void ADDCALL iq_balancer_destroy(struct iq_balancer_t *iq_balancer); + +#endif -- cgit v1.2.3