Add SoapySDR support (#842)
This commit is contained in:
parent
2ceb535357
commit
d596f6aa8e
6 changed files with 715 additions and 38 deletions
|
@ -80,7 +80,22 @@ endif()
|
|||
########################################################################
|
||||
find_package(PkgConfig)
|
||||
find_package(LibRTLSDR)
|
||||
list(APPEND SDR_LIBRARIES ${LIBRTLSDR_LIBRARIES})
|
||||
if(LIBRTLSDR_FOUND)
|
||||
include_directories(${LIBRTLSDR_INCLUDE_DIRS})
|
||||
list(APPEND SDR_LIBRARIES ${LIBRTLSDR_LIBRARIES})
|
||||
ADD_DEFINITIONS(-DRTLSDR)
|
||||
else()
|
||||
message(STATUS "RTL-SDR development files not found, RTL-SDR device input won't be possible.")
|
||||
endif()
|
||||
|
||||
find_package(SoapySDR "0.6" NO_MODULE)
|
||||
if(SoapySDR_FOUND)
|
||||
include_directories(${SoapySDR_INCLUDE_DIRS})
|
||||
list(APPEND SDR_LIBRARIES ${SoapySDR_LIBRARIES})
|
||||
ADD_DEFINITIONS(-DSOAPYSDR)
|
||||
else()
|
||||
message(STATUS "Soapy SDR development files not found, Soapy SDR device input won't be possible.")
|
||||
endif()
|
||||
|
||||
# cmake -DCMAKE_BUILD_TYPE=Profile ..
|
||||
# CPUPROFILE=prof.out ./src/rtl_433 ...
|
||||
|
@ -106,7 +121,6 @@ endif()
|
|||
########################################################################
|
||||
include_directories(
|
||||
${CMAKE_SOURCE_DIR}/include
|
||||
${LIBRTLSDR_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
########################################################################
|
||||
|
|
|
@ -57,7 +57,7 @@ Usage: = Tuner options =
|
|||
[-E] Stop after outputting successful event(s)
|
||||
[-V] Output the version string and exit
|
||||
[-h] Output this usage help and exit
|
||||
Use -R, -X, -F, -r, or -w without argument for more help
|
||||
Use -d, -R, -X, -F, -r, or -w without argument for more help
|
||||
|
||||
Supported device protocols:
|
||||
[01] Silvercrest Remote Control
|
||||
|
|
15
configure.ac
15
configure.ac
|
@ -16,9 +16,18 @@ LT_INIT
|
|||
AC_PROG_LIBTOOL
|
||||
|
||||
LT_LIB_M
|
||||
PKG_CHECK_MODULES(LIBRTLSDR, librtlsdr)
|
||||
LIBS="$LIBS $LIBRTLSDR_LIBS"
|
||||
CFLAGS="$CFLAGS $LIBRTLSDR_CFLAGS"
|
||||
|
||||
PKG_CHECK_MODULES([LIBRTLSDR], [librtlsdr], [HAVE_LIBRTLSDR=1], [HAVE_LIBRTLSDR=0])
|
||||
AM_CONDITIONAL([USE_LIBRTLSDR], [test "$HAVE_LIBRTLSDR" -eq 1])
|
||||
AM_COND_IF([USE_LIBRTLSDR],
|
||||
[ LIBS="$LIBS $LIBRTLSDR_LIBS"
|
||||
CFLAGS="$CFLAGS $LIBRTLSDR_CFLAGS -DRTLSDR" ])
|
||||
|
||||
PKG_CHECK_MODULES([SOAPYSDR], [SoapySDR], [HAVE_SOAPYSDR=1], [HAVE_SOAPYSDR=0])
|
||||
AM_CONDITIONAL([USE_SOAPYSDR], [test "$HAVE_SOAPYSDR" -eq 1])
|
||||
AM_COND_IF([USE_SOAPYSDR],
|
||||
[ LIBS="$LIBS $SOAPYSDR_LIBS"
|
||||
CFLAGS="$CFLAGS $SOAPYSDR_CFLAGS -DSOAPYSDR" ])
|
||||
|
||||
AC_PATH_PROG(DOXYGEN,doxygen,false)
|
||||
AM_CONDITIONAL(HAVE_DOXYGEN, test $DOXYGEN != false)
|
||||
|
|
101
include/sdr.h
101
include/sdr.h
|
@ -17,21 +17,116 @@
|
|||
typedef struct sdr_dev sdr_dev_t;
|
||||
typedef void (*sdr_read_cb_t)(unsigned char *buf, uint32_t len, void *ctx);
|
||||
|
||||
int sdr_open(sdr_dev_t **out_dev, int *sample_size, char const *dev_query, int verbose);
|
||||
/*!
|
||||
* Find the closest matching device, optionally report status
|
||||
*
|
||||
* \param out_dev device output returned
|
||||
* \param sample_size stream output sample width returned
|
||||
* \param dev_query a string to be parsed as device spec
|
||||
* \param verbose the verbosity level for reports to stderr
|
||||
* \return dev 0 if successful
|
||||
*/
|
||||
int sdr_open(sdr_dev_t **out_dev, int *sample_size, char *dev_query, int verbose);
|
||||
|
||||
/*!
|
||||
* Close the device, optionally report status
|
||||
*
|
||||
* \param dev the device handle
|
||||
* \param verbose the verbosity level for reports to stderr
|
||||
* \return 0 on success
|
||||
*/
|
||||
int sdr_close(sdr_dev_t *dev);
|
||||
|
||||
/*!
|
||||
* Set device frequency, optionally report status
|
||||
*
|
||||
* \param dev the device handle
|
||||
* \param frequency in Hz
|
||||
* \param verbose the verbosity level for reports to stderr
|
||||
* \return 0 on success
|
||||
*/
|
||||
int sdr_set_center_freq(sdr_dev_t *dev, uint32_t freq, int verbose);
|
||||
|
||||
/*!
|
||||
* Get device frequency
|
||||
*
|
||||
* \param dev the device handle
|
||||
* \return frequency in Hz on success, 0 otherwise
|
||||
*/
|
||||
uint32_t sdr_get_center_freq(sdr_dev_t *dev);
|
||||
|
||||
/*!
|
||||
* Set the frequency correction value for the device, optionally report status
|
||||
*
|
||||
* \param dev the device handle
|
||||
* \param ppm_error correction value in parts per million (ppm)
|
||||
* \param verbose the verbosity level for reports to stderr
|
||||
* \return 0 on success
|
||||
*/
|
||||
int sdr_set_freq_correction(sdr_dev_t *dev, int ppm, int verbose);
|
||||
|
||||
/*!
|
||||
* Enable auto gain, optionally report status
|
||||
*
|
||||
* \param dev the device handle
|
||||
* \param verbose the verbosity level for reports to stderr
|
||||
* \return 0 on success
|
||||
*/
|
||||
int sdr_set_auto_gain(sdr_dev_t *dev, int verbose);
|
||||
int sdr_set_tuner_gain(sdr_dev_t *dev, int gain, int verbose);
|
||||
|
||||
/*!
|
||||
* Set tuner gain or gain elements, optionally report status
|
||||
*
|
||||
* \param dev the device handle
|
||||
* \param gain_str in tenths of a dB for RTL-SDR, string of gain element pairs (example LNA=40,VGA=20,AMP=0), or string of overall gain, in dB
|
||||
* \param verbose the verbosity level for reports to stderr
|
||||
* \return 0 on success
|
||||
*/
|
||||
int sdr_set_tuner_gain(sdr_dev_t *dev, char *gain_str, int verbose);
|
||||
|
||||
/*!
|
||||
* Set device sample rate, optionally report status
|
||||
*
|
||||
* \param dev the device handle
|
||||
* \param samp_rate in samples/second
|
||||
* \param verbose the verbosity level for reports to stderr
|
||||
* \return 0 on success
|
||||
*/
|
||||
int sdr_set_sample_rate(sdr_dev_t *dev, uint32_t rate, int verbose);
|
||||
|
||||
/*!
|
||||
* Get device sample rate
|
||||
*
|
||||
* \param dev the device handle
|
||||
* \return sample rate in samples/second on success, 0 otherwise
|
||||
*/
|
||||
uint32_t sdr_get_sample_rate(sdr_dev_t *dev);
|
||||
|
||||
int sdr_reset(sdr_dev_t *dev);
|
||||
/*!
|
||||
* Activate stream (only needed for SoapySDR)
|
||||
*
|
||||
* \param dev the device handle
|
||||
* \return 0 on success
|
||||
*/
|
||||
int sdr_activate(sdr_dev_t *dev);
|
||||
|
||||
/*!
|
||||
* Deactivate stream (only needed for SoapySDR)
|
||||
*
|
||||
* \param dev the device handle
|
||||
* \return 0 on success
|
||||
*/
|
||||
int sdr_deactivate(sdr_dev_t *dev);
|
||||
|
||||
/*!
|
||||
* Reset buffer (only needed for RTL-SDR), optionally report status
|
||||
*
|
||||
* \param dev the device handle
|
||||
* \param verbose the verbosity level for reports to stderr
|
||||
* \return 0 on success
|
||||
*/
|
||||
int sdr_reset(sdr_dev_t *dev, int verbose);
|
||||
|
||||
int sdr_start(sdr_dev_t *dev, sdr_read_cb_t cb, void *ctx, uint32_t buf_num, uint32_t buf_len);
|
||||
int sdr_stop(sdr_dev_t *dev);
|
||||
|
||||
|
|
|
@ -161,7 +161,7 @@ void usage(r_device *devices, int exit_code)
|
|||
"\t[-E] Stop after outputting successful event(s)\n"
|
||||
"\t[-V] Output the version string and exit\n"
|
||||
"\t[-h] Output this usage help and exit\n"
|
||||
"\t\t Use -R, -X, -F, -r, or -w without argument for more help\n\n",
|
||||
"\t\t Use -d, -R, -X, -F, -r, or -w without argument for more help\n\n",
|
||||
DEFAULT_FREQUENCY, DEFAULT_HOP_TIME, DEFAULT_SAMPLE_RATE, DEFAULT_LEVEL_LIMIT);
|
||||
|
||||
if (devices) {
|
||||
|
@ -176,6 +176,28 @@ void usage(r_device *devices, int exit_code)
|
|||
exit(exit_code);
|
||||
}
|
||||
|
||||
void help_device(void)
|
||||
{
|
||||
fprintf(stderr,
|
||||
#ifdef RTLSDR
|
||||
"\tRTL-SDR device driver is available.\n"
|
||||
#else
|
||||
"\tRTL-SDR device driver is not available.\n"
|
||||
#endif
|
||||
"[-d <RTL-SDR USB device index>] (default: 0)\n"
|
||||
"[-d :<RTL-SDR USB device serial (can be set with rtl_eeprom -s)>]\n"
|
||||
"\tTo set gain for RTL-SDR use -g X to set an overall gain in tenths of dB.\n"
|
||||
#ifdef SOAPYSDR
|
||||
"\tSoapySDR device driver is available.\n"
|
||||
#else
|
||||
"\tSoapySDR device driver is not available.\n"
|
||||
#endif
|
||||
"[-d \"\" Open default SoapySDR device\n"
|
||||
"[-d driver=rtlsdr Open e.g. specific SoapySDR device\n"
|
||||
"\tTo set gain for SoapySDR use -g ELEM=val,ELEM=val,... e.g. -g LNA=20,TIA=8,PGA=2 (for LimeSDR).\n");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
void help_output(void)
|
||||
{
|
||||
fprintf(stderr,
|
||||
|
@ -1177,7 +1199,7 @@ int main(int argc, char **argv) {
|
|||
FILE *in_file;
|
||||
int n_read;
|
||||
int r = 0, opt;
|
||||
int gain = 0;
|
||||
char *gain_str = NULL;
|
||||
int i;
|
||||
int ppm_error = 0;
|
||||
struct dm_state *demod;
|
||||
|
@ -1225,7 +1247,7 @@ int main(int argc, char **argv) {
|
|||
demod->hop_time = atoi_time(optarg, "-H: ");
|
||||
break;
|
||||
case 'g':
|
||||
gain = (int) (atof(optarg) * 10); /* tenths of a dB */
|
||||
gain_str = optarg;
|
||||
break;
|
||||
case 'G':
|
||||
register_all = 1;
|
||||
|
@ -1368,6 +1390,7 @@ int main(int argc, char **argv) {
|
|||
// handle missing arguments as help request
|
||||
if (optopt == 'R') usage(devices, 0);
|
||||
else if (optopt == 'X') flex_create_device(NULL);
|
||||
else if (optopt == 'd') help_device();
|
||||
else if (optopt == 'F') help_output();
|
||||
else if (optopt == 'r') help_read();
|
||||
else if (optopt == 'w') help_write();
|
||||
|
@ -1445,13 +1468,8 @@ int main(int argc, char **argv) {
|
|||
|
||||
fprintf(stderr, "Bit detection level set to %d%s.\n", demod->level_limit, (demod->level_limit ? "" : " (Auto)"));
|
||||
|
||||
if (0 == gain) {
|
||||
/* Enable automatic gain */
|
||||
r = sdr_set_auto_gain(dev, !quiet_mode);
|
||||
} else {
|
||||
/* Set manual gain */
|
||||
r = sdr_set_tuner_gain(dev, gain, !quiet_mode);
|
||||
}
|
||||
/* Enable automatic gain if gain_str empty (or 0 for RTL-SDR), set manual gain otherwise */
|
||||
r = sdr_set_tuner_gain(dev, gain_str, !quiet_mode);
|
||||
|
||||
if (ppm_error)
|
||||
r = sdr_set_freq_correction(dev, ppm_error, !quiet_mode);
|
||||
|
@ -1534,9 +1552,10 @@ int main(int argc, char **argv) {
|
|||
}
|
||||
|
||||
/* Reset endpoint before we start reading from it (mandatory) */
|
||||
r = sdr_reset(dev);
|
||||
r = sdr_reset(dev, !quiet_mode);
|
||||
if (r < 0)
|
||||
fprintf(stderr, "WARNING: Failed to reset buffers.\n");
|
||||
r = sdr_activate(dev);
|
||||
|
||||
if (frequencies == 0) {
|
||||
frequency[0] = DEFAULT_FREQUENCY;
|
||||
|
@ -1555,6 +1574,7 @@ int main(int argc, char **argv) {
|
|||
/* Set the frequency */
|
||||
center_frequency = frequency[frequency_current];
|
||||
r = sdr_set_center_freq(dev, center_frequency, !quiet_mode);
|
||||
|
||||
#ifndef _WIN32
|
||||
signal(SIGALRM, sighandler);
|
||||
alarm(3); // require callback to run every 3 second, abort otherwise
|
||||
|
@ -1579,6 +1599,8 @@ int main(int argc, char **argv) {
|
|||
if (dumper->file && (dumper->file != stdout))
|
||||
fclose(dumper->file);
|
||||
|
||||
r = sdr_deactivate(dev);
|
||||
|
||||
for (i = 0; i < demod->r_dev_num; i++)
|
||||
free(demod->r_devs[i]);
|
||||
|
||||
|
|
573
src/sdr.c
573
src/sdr.c
|
@ -2,6 +2,10 @@
|
|||
* SDR input from RTLSDR or SoapySDR
|
||||
*
|
||||
* Copyright (C) 2018 Christian Zuckschwerdt
|
||||
* based on code
|
||||
* Copyright (C) 2012 by Steve Markgraf <steve@steve-m.de>
|
||||
* Copyright (C) 2014 by Kyle Keen <keenerd@gmail.com>
|
||||
* Copyright (C) 2016 by Robert X. Seger
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -11,17 +15,37 @@
|
|||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "sdr.h"
|
||||
#include "rtl-sdr.h"
|
||||
#include "util.h"
|
||||
#ifdef RTLSDR
|
||||
#include "rtl-sdr.h"
|
||||
#endif
|
||||
#ifdef SOAPYSDR
|
||||
#include "optparse.h"
|
||||
#include <SoapySDR/Device.h>
|
||||
#include <SoapySDR/Formats.h>
|
||||
#include <SoapySDR/Logger.h>
|
||||
#endif
|
||||
|
||||
struct sdr_dev {
|
||||
rtlsdr_dev_t *rtlsdr_dev;
|
||||
int sample_size;
|
||||
#ifdef SOAPYSDR
|
||||
SoapySDRDevice *soapy_dev;
|
||||
SoapySDRStream *soapy_stream;
|
||||
double fullScale;
|
||||
int running;
|
||||
#endif
|
||||
#ifdef RTLSDR
|
||||
rtlsdr_dev_t *rtlsdr_dev;
|
||||
#endif
|
||||
int sample_size;
|
||||
};
|
||||
|
||||
int sdr_open(sdr_dev_t **out_dev, int *sample_size, char const *dev_query, int verbose)
|
||||
/* RTL-SDR helpers */
|
||||
|
||||
#ifdef RTLSDR
|
||||
|
||||
static int sdr_open_rtl(sdr_dev_t **out_dev, int *sample_size, char *dev_query, int verbose)
|
||||
{
|
||||
uint32_t device_count = rtlsdr_get_device_count();
|
||||
if (!device_count) {
|
||||
|
@ -89,18 +113,421 @@ int sdr_open(sdr_dev_t **out_dev, int *sample_size, char const *dev_query, int v
|
|||
} else {
|
||||
*out_dev = dev;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/* SoapySDR helpers */
|
||||
|
||||
#ifdef SOAPYSDR
|
||||
|
||||
static int soapysdr_set_bandwidth(SoapySDRDevice *dev, uint32_t bandwidth)
|
||||
{
|
||||
int r;
|
||||
r = (int)SoapySDRDevice_setBandwidth(dev, SOAPY_SDR_RX, 0, (double)bandwidth);
|
||||
uint32_t applied_bw = 0;
|
||||
if (r != 0) {
|
||||
fprintf(stderr, "WARNING: Failed to set bandwidth.\n");
|
||||
} else if (bandwidth > 0) {
|
||||
applied_bw = (uint32_t)SoapySDRDevice_getBandwidth(dev, SOAPY_SDR_RX, 0);
|
||||
if (applied_bw)
|
||||
fprintf(stderr, "Bandwidth parameter %u Hz resulted in %u Hz.\n", bandwidth, applied_bw);
|
||||
else
|
||||
fprintf(stderr, "Set bandwidth parameter %u Hz.\n", bandwidth);
|
||||
} else {
|
||||
fprintf(stderr, "Bandwidth set to automatic resulted in %u Hz.\n", applied_bw);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
static int soapysdr_direct_sampling(SoapySDRDevice *dev, int on)
|
||||
{
|
||||
int r = 0;
|
||||
char *value, *set_value;
|
||||
if (on == 0)
|
||||
value = "0";
|
||||
else if (on == 1)
|
||||
value = "1";
|
||||
else if (on == 2)
|
||||
value = "2";
|
||||
else
|
||||
return -1;
|
||||
SoapySDRDevice_writeSetting(dev, "direct_samp", value);
|
||||
set_value = SoapySDRDevice_readSetting(dev, "direct_samp");
|
||||
|
||||
if (set_value == NULL) {
|
||||
fprintf(stderr, "WARNING: Failed to set direct sampling mode.\n");
|
||||
return r;
|
||||
}
|
||||
if (atoi(set_value) == 0) {
|
||||
fprintf(stderr, "Direct sampling mode disabled.\n");}
|
||||
if (atoi(set_value) == 1) {
|
||||
fprintf(stderr, "Enabled direct sampling mode, input 1/I.\n");}
|
||||
if (atoi(set_value) == 2) {
|
||||
fprintf(stderr, "Enabled direct sampling mode, input 2/Q.\n");}
|
||||
if (on == 3) {
|
||||
fprintf(stderr, "Enabled no-mod direct sampling mode.\n");}
|
||||
return r;
|
||||
}
|
||||
|
||||
static int soapysdr_offset_tuning(SoapySDRDevice *dev)
|
||||
{
|
||||
int r = 0;
|
||||
SoapySDRDevice_writeSetting(dev, "offset_tune", "true");
|
||||
char *set_value = SoapySDRDevice_readSetting(dev, "offset_tune");
|
||||
|
||||
if (strcmp(set_value, "true") != 0) {
|
||||
/* TODO: detection of failure modes
|
||||
if ( r == -2 )
|
||||
fprintf(stderr, "WARNING: Failed to set offset tuning: tuner doesn't support offset tuning!\n");
|
||||
else if ( r == -3 )
|
||||
fprintf(stderr, "WARNING: Failed to set offset tuning: direct sampling not combinable with offset tuning!\n");
|
||||
else
|
||||
*/
|
||||
fprintf(stderr, "WARNING: Failed to set offset tuning.\n");
|
||||
} else {
|
||||
fprintf(stderr, "Offset tuning mode enabled.\n");
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
static int soapysdr_auto_gain(SoapySDRDevice *dev, int verbose)
|
||||
{
|
||||
int r = 0;
|
||||
|
||||
r = SoapySDRDevice_hasGainMode(dev, SOAPY_SDR_RX, 0);
|
||||
if (r) {
|
||||
r = SoapySDRDevice_setGainMode(dev, SOAPY_SDR_RX, 0, 1);
|
||||
if (r != 0) {
|
||||
fprintf(stderr, "WARNING: Failed to enable automatic gain.\n");
|
||||
} else {
|
||||
if (verbose)
|
||||
fprintf(stderr, "Tuner set to automatic gain.\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Per-driver hacks TODO: clean this up
|
||||
char *driver = SoapySDRDevice_getDriverKey(dev);
|
||||
if (strcmp(driver, "HackRF") == 0) {
|
||||
// HackRF has three gains LNA, VGA, and AMP, setting total distributes amongst, 116.0 dB seems to work well,
|
||||
// even though it logs HACKRF_ERROR_INVALID_PARAM? https://github.com/rxseger/rx_tools/issues/9
|
||||
// Total gain is distributed amongst all gains, 116 = 37,65,1; the LNA is OK (<40) but VGA is out of range (65 > 62)
|
||||
// TODO: generic means to set all gains, of any SDR? string parsing LNA=#,VGA=#,AMP=#?
|
||||
r = SoapySDRDevice_setGainElement(dev, SOAPY_SDR_RX, 0, "LNA", 40.); // max 40
|
||||
if (r != 0) {
|
||||
fprintf(stderr, "WARNING: Failed to set LNA tuner gain.\n");
|
||||
}
|
||||
r = SoapySDRDevice_setGainElement(dev, SOAPY_SDR_RX, 0, "VGA", 20.); // max 65
|
||||
if (r != 0) {
|
||||
fprintf(stderr, "WARNING: Failed to set VGA tuner gain.\n");
|
||||
}
|
||||
r = SoapySDRDevice_setGainElement(dev, SOAPY_SDR_RX, 0, "AMP", 0.); // on or off
|
||||
if (r != 0) {
|
||||
fprintf(stderr, "WARNING: Failed to set AMP tuner gain.\n");
|
||||
}
|
||||
|
||||
}
|
||||
// otherwise leave unset, hopefully the driver has good defaults
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static int soapysdr_gain_str_set(SoapySDRDevice *dev, char *gain_str, int verbose)
|
||||
{
|
||||
SoapySDRKwargs args = {0};
|
||||
int r = 0;
|
||||
|
||||
// Disable automatic gain
|
||||
r = SoapySDRDevice_hasGainMode(dev, SOAPY_SDR_RX, 0);
|
||||
if (r) {
|
||||
r = SoapySDRDevice_setGainMode(dev, SOAPY_SDR_RX, 0, 0);
|
||||
if (r != 0) {
|
||||
fprintf(stderr, "WARNING: Failed to disable automatic gain.\n");
|
||||
} else {
|
||||
if (verbose)
|
||||
fprintf(stderr, "Tuner set to manual gain.\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (strchr(gain_str, '=')) {
|
||||
// Set each gain individually (more control)
|
||||
char *name;
|
||||
char *value;
|
||||
while (getkwargs(&gain_str, &name, &value)) {
|
||||
double num = atof(value);
|
||||
if (verbose)
|
||||
fprintf(stderr, "Setting gain element %s: %f dB\n", name, num);
|
||||
r = SoapySDRDevice_setGainElement(dev, SOAPY_SDR_RX, 0, name, num);
|
||||
if (r != 0) {
|
||||
fprintf(stderr, "WARNING: setGainElement(%s, %f) failed: %d\n", name, num, r);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Set overall gain and let SoapySDR distribute amongst components
|
||||
double value = atof(gain_str);
|
||||
r = SoapySDRDevice_setGain(dev, SOAPY_SDR_RX, 0, value);
|
||||
if (r != 0) {
|
||||
fprintf(stderr, "WARNING: Failed to set tuner gain.\n");
|
||||
} else {
|
||||
if (verbose)
|
||||
fprintf(stderr, "Tuner gain set to %0.2f dB.\n", value);
|
||||
}
|
||||
// read back and print each individual gain element
|
||||
if (verbose) {
|
||||
size_t len = 0;
|
||||
char **gains = SoapySDRDevice_listGains(dev, SOAPY_SDR_RX, 0, &len);
|
||||
fprintf(stderr, "Gain elements: ");
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
double gain = SoapySDRDevice_getGain(dev, SOAPY_SDR_RX, 0);
|
||||
fprintf(stderr, "%s=%g ", gains[i], gain);
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static void soapysdr_show_device_info(SoapySDRDevice *dev)
|
||||
{
|
||||
size_t len = 0, i = 0;
|
||||
char **antennas = NULL;
|
||||
char **gains = NULL;
|
||||
char **frequencies = NULL;
|
||||
SoapySDRRange *frequencyRanges = NULL;
|
||||
SoapySDRRange *rates = NULL;
|
||||
SoapySDRRange *bandwidths = NULL;
|
||||
double fullScale;
|
||||
char **stream_formats = NULL;
|
||||
char *native_stream_format = NULL;
|
||||
SoapySDRKwargs args;
|
||||
char *hwkey = NULL;
|
||||
|
||||
int direction = SOAPY_SDR_RX;
|
||||
size_t channel = 0;
|
||||
|
||||
hwkey = SoapySDRDevice_getHardwareKey(dev);
|
||||
fprintf(stderr, "Using device %s: ", hwkey);
|
||||
|
||||
args = SoapySDRDevice_getHardwareInfo(dev);
|
||||
for (i = 0; i < args.size; ++i) {
|
||||
fprintf(stderr, "%s=%s ", args.keys[i], args.vals[i]);
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
|
||||
antennas = SoapySDRDevice_listAntennas(dev, direction, channel, &len);
|
||||
fprintf(stderr, "Found %zu antenna(s): ", len);
|
||||
for (i = 0; i < len; ++i) {
|
||||
fprintf(stderr, "%s ", antennas[i]);
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
|
||||
gains = SoapySDRDevice_listGains(dev, direction, channel, &len);
|
||||
fprintf(stderr, "Found %zu gain(s): ", len);
|
||||
for (i = 0; i < len; ++i) {
|
||||
SoapySDRRange gainRange = SoapySDRDevice_getGainRange(dev, direction, channel);
|
||||
fprintf(stderr, "%s %.0f - %.0f (step %.0f) ", gains[i], gainRange.minimum, gainRange.maximum, gainRange.step);
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
|
||||
frequencies = SoapySDRDevice_listFrequencies(dev, direction, channel, &len);
|
||||
fprintf(stderr, "Found %zu frequencies: ", len);
|
||||
for (i = 0; i < len; ++i) {
|
||||
fprintf(stderr, "%s ", frequencies[i]);
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
|
||||
frequencyRanges = SoapySDRDevice_getFrequencyRange(dev, direction, channel, &len);
|
||||
fprintf(stderr, "Found %zu frequency range(s): ", len);
|
||||
for (i = 0; i < len; ++i) {
|
||||
fprintf(stderr, "%.0f - %.0f (step %.0f) ", frequencyRanges[i].minimum, frequencyRanges[i].maximum, frequencyRanges[i].step);
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
|
||||
rates = SoapySDRDevice_getSampleRateRange(dev, direction, channel, &len);
|
||||
fprintf(stderr, "Found %zu sample rate range(s): ", len);
|
||||
for (i = 0; i < len; ++i) {
|
||||
if (rates[i].minimum == rates[i].maximum)
|
||||
fprintf(stderr, "%.0f ", rates[i].minimum);
|
||||
else
|
||||
fprintf(stderr, "%.0f - %.0f (step %.0f) ", rates[i].minimum, rates[i].maximum, rates[i].step);
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
|
||||
bandwidths = SoapySDRDevice_getBandwidthRange(dev, direction, channel, &len);
|
||||
fprintf(stderr, "Found %zu bandwidth range(s): ", len);
|
||||
for (i = 0; i < len; ++i) {
|
||||
fprintf(stderr, "%.0f - %.0f (step %.0f) ", bandwidths[i].minimum, bandwidths[i].maximum, bandwidths[i].step);
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
|
||||
double bandwidth = SoapySDRDevice_getBandwidth(dev, direction, channel);
|
||||
fprintf(stderr, "Found current bandwidth %.0f\n", bandwidth);
|
||||
|
||||
stream_formats = SoapySDRDevice_getStreamFormats(dev, direction, channel, &len);
|
||||
fprintf(stderr, "Found %zu stream format(s): ", len);
|
||||
for (i = 0; i < len; ++i) {
|
||||
fprintf(stderr, "%s ", stream_formats[i]);
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
|
||||
native_stream_format = SoapySDRDevice_getNativeStreamFormat(dev, direction, channel, &fullScale);
|
||||
fprintf(stderr, "Found native stream format: %s (full scale: %.1f)\n", native_stream_format, fullScale);
|
||||
}
|
||||
|
||||
static int sdr_open_soapy(sdr_dev_t **out_dev, int *sample_size, char *dev_query, int verbose)
|
||||
{
|
||||
if (verbose)
|
||||
SoapySDR_setLogLevel(SOAPY_SDR_DEBUG);
|
||||
|
||||
sdr_dev_t *dev = calloc(1, sizeof(sdr_dev_t));
|
||||
|
||||
dev->soapy_dev = SoapySDRDevice_makeStrArgs(dev_query);
|
||||
if (!dev->soapy_dev) {
|
||||
if (verbose)
|
||||
fprintf(stderr, "Failed to open sdr device matching '%s'.\n", dev_query);
|
||||
free(dev);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (verbose)
|
||||
soapysdr_show_device_info(dev->soapy_dev);
|
||||
|
||||
// select a stream format, in preference order: native CU8, CS8, CS16, forced CS16
|
||||
// stream_formats = SoapySDRDevice_getStreamFormats(dev->soapy_dev, SOAPY_SDR_RX, 0, &len);
|
||||
char *format = SoapySDRDevice_getNativeStreamFormat(dev->soapy_dev, SOAPY_SDR_RX, 0, &dev->fullScale);
|
||||
if (!strcmp(SOAPY_SDR_CU8, format)) {
|
||||
// actually not supported by SoapySDR
|
||||
*sample_size = sizeof(uint8_t); // CU8
|
||||
// } else if (!strcmp(SOAPY_SDR_CS8, format)) {
|
||||
// // TODO: CS8 needs conversion to CU8
|
||||
// // e.g. RTL-SDR (8 bit), scale is 128.0
|
||||
// *sample_size = sizeof(int8_t); // CS8
|
||||
} else if (!strcmp(SOAPY_SDR_CS16, format)) {
|
||||
// e.g. LimeSDR-mini (12 bit), scale is 2048.0
|
||||
*sample_size = sizeof(int16_t); // CS16
|
||||
} else {
|
||||
// force CS16
|
||||
format = SOAPY_SDR_CS16;
|
||||
*sample_size = sizeof(int16_t); // CS16
|
||||
dev->fullScale = 32768.0; // assume max for SOAPY_SDR_CS16
|
||||
}
|
||||
|
||||
SoapySDRKwargs stream_args = {0};
|
||||
if (SoapySDRDevice_setupStream(dev->soapy_dev, &dev->soapy_stream, SOAPY_SDR_RX, format, NULL, 0, &stream_args) != 0) {
|
||||
if (verbose)
|
||||
fprintf(stderr, "Failed to setup sdr device\n");
|
||||
free(dev);
|
||||
return -3;
|
||||
}
|
||||
|
||||
*out_dev = dev;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int soapysdr_read_loop(sdr_dev_t *dev, sdr_read_cb_t cb, void *ctx, uint32_t buf_num, uint32_t buf_len)
|
||||
{
|
||||
size_t buf_elems = buf_len / 2; // div sizeof int16_t, or demod->sample_size, maybe?
|
||||
int16_t *buffer = malloc(buf_elems * SoapySDR_formatToSize(SOAPY_SDR_CS16));
|
||||
|
||||
dev->running = 1;
|
||||
do {
|
||||
void *buffs[] = {buffer};
|
||||
int flags = 0;
|
||||
long long timeNs = 0;
|
||||
long timeoutUs = 1000000; // 1 second
|
||||
unsigned n_read = 0, i;
|
||||
int r;
|
||||
|
||||
do {
|
||||
buffs[0] = &buffer[n_read * 2];
|
||||
r = SoapySDRDevice_readStream(dev->soapy_dev, dev->soapy_stream, buffs, buf_elems - n_read, &flags, &timeNs, timeoutUs);
|
||||
if (r < 0)
|
||||
break;
|
||||
n_read += r; // r is number of elements read, elements=complex pairs, so buffer length is twice
|
||||
//fprintf(stderr, "readStream ret=%d, flags=%d, timeNs=%lld (%zu - %u)\n", r, flags, timeNs, buf_elems, n_read);
|
||||
} while (n_read < buf_elems);
|
||||
//fprintf(stderr, "readStream ret=%d (%d), flags=%d, timeNs=%lld\n", n_read, buf_len, flags, timeNs);
|
||||
if (r < 0) {
|
||||
if (r == SOAPY_SDR_OVERFLOW) {
|
||||
fprintf(stderr, "O");
|
||||
fflush(stderr);
|
||||
continue;
|
||||
}
|
||||
fprintf(stderr, "WARNING: sync read failed. %d\n", r);
|
||||
}
|
||||
|
||||
// convert to CS16 or CU8 if needed
|
||||
// if converting CS8 to CU8 -- vectorized with -O3
|
||||
//for (i = 0; i < n_read * 2; ++i)
|
||||
// cu8buf[i] = (int8_t)cu8buf[i] + 128;
|
||||
|
||||
// rescale cs16 buffer
|
||||
if (dev->fullScale == 2048.0) {
|
||||
for (i = 0; i < n_read * 2; ++i)
|
||||
buffer[i] <<= 4;
|
||||
} else if (dev->fullScale != 32768.0) {
|
||||
int upscale = 32768 / dev->fullScale;
|
||||
for (i = 0; i < n_read * 2; ++i)
|
||||
buffer[i] *= upscale;
|
||||
}
|
||||
|
||||
if (n_read > 0) // prevent a crash in callback
|
||||
cb((unsigned char *)buffer, n_read * 2 * 2, ctx);
|
||||
|
||||
} while (dev->running);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/* Public API */
|
||||
|
||||
int sdr_open(sdr_dev_t **out_dev, int *sample_size, char *dev_query, int verbose)
|
||||
{
|
||||
#if !defined(RTLSDR) && !defined(SOAPYSDR)
|
||||
if (verbose)
|
||||
fprintf(stderr, "No input drivers (RTL-SDR or SoapySDR) compiled in.\n");
|
||||
return -1;
|
||||
#endif
|
||||
#ifdef RTLSDR
|
||||
/* Open RTLSDR by default or if index or serial given, if available */
|
||||
if (!dev_query || *dev_query == ':' || (*dev_query >= '0' && *dev_query <= '9'))
|
||||
return sdr_open_rtl(out_dev, sample_size, dev_query, verbose);
|
||||
#endif
|
||||
#ifdef SOAPYSDR
|
||||
/* Open SoapySDR otherwise, if available */
|
||||
return sdr_open_soapy(out_dev, sample_size, dev_query, verbose);
|
||||
#endif
|
||||
}
|
||||
|
||||
int sdr_close(sdr_dev_t *dev)
|
||||
{
|
||||
return rtlsdr_close(dev->rtlsdr_dev);
|
||||
#ifdef SOAPYSDR
|
||||
if (dev->soapy_dev)
|
||||
return SoapySDRDevice_unmake(dev->soapy_dev);
|
||||
#endif
|
||||
#ifdef RTLSDR
|
||||
if (dev->rtlsdr_dev)
|
||||
return rtlsdr_close(dev->rtlsdr_dev);
|
||||
#endif
|
||||
return -1;
|
||||
}
|
||||
|
||||
int sdr_set_center_freq(sdr_dev_t *dev, uint32_t freq, int verbose)
|
||||
{
|
||||
int r = rtlsdr_set_center_freq(dev->rtlsdr_dev, freq);
|
||||
int r = -1;
|
||||
#ifdef SOAPYSDR
|
||||
SoapySDRKwargs args = {0};
|
||||
if (dev->soapy_dev) {
|
||||
r = SoapySDRDevice_setFrequency(dev->soapy_dev, SOAPY_SDR_RX, 0, (double)freq, &args);
|
||||
}
|
||||
#endif
|
||||
#ifdef RTLSDR
|
||||
if (dev->rtlsdr_dev)
|
||||
r = rtlsdr_set_center_freq(dev->rtlsdr_dev, freq);
|
||||
#endif
|
||||
if (verbose) {
|
||||
if (r < 0)
|
||||
fprintf(stderr, "WARNING: Failed to set center freq.\n");
|
||||
|
@ -112,24 +539,48 @@ int sdr_set_center_freq(sdr_dev_t *dev, uint32_t freq, int verbose)
|
|||
|
||||
uint32_t sdr_get_center_freq(sdr_dev_t *dev)
|
||||
{
|
||||
return rtlsdr_get_center_freq(dev->rtlsdr_dev);
|
||||
#ifdef SOAPYSDR
|
||||
if (dev->soapy_dev)
|
||||
return (int)SoapySDRDevice_getFrequency(dev->soapy_dev, SOAPY_SDR_RX, 0);
|
||||
#endif
|
||||
#ifdef RTLSDR
|
||||
if (dev->rtlsdr_dev)
|
||||
return rtlsdr_get_center_freq(dev->rtlsdr_dev);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sdr_set_freq_correction(sdr_dev_t *dev, int ppm, int verbose)
|
||||
{
|
||||
int r = rtlsdr_set_freq_correction(dev->rtlsdr_dev, ppm);
|
||||
int r = -1;
|
||||
#ifdef SOAPYSDR
|
||||
if (dev->soapy_dev)
|
||||
r = SoapySDRDevice_setFrequencyComponent(dev->soapy_dev, SOAPY_SDR_RX, 0, "CORR", (double)ppm, NULL);
|
||||
#endif
|
||||
#ifdef RTLSDR
|
||||
if (dev->rtlsdr_dev)
|
||||
r = rtlsdr_set_freq_correction(dev->rtlsdr_dev, ppm);
|
||||
#endif
|
||||
if (verbose) {
|
||||
if (r < 0)
|
||||
fprintf(stderr, "WARNING: Failed to set frequency correction.\n");
|
||||
else
|
||||
fprintf(stderr, "Frequency correction set to %d.\n", ppm);
|
||||
fprintf(stderr, "Frequency correction set to %d ppm.\n", ppm);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
int sdr_set_auto_gain(sdr_dev_t *dev, int verbose)
|
||||
{
|
||||
int r = rtlsdr_set_tuner_gain_mode(dev->rtlsdr_dev, 0);
|
||||
int r = -1;
|
||||
#ifdef SOAPYSDR
|
||||
if (dev->soapy_dev)
|
||||
r = soapysdr_auto_gain(dev->soapy_dev, verbose);
|
||||
#endif
|
||||
#ifdef RTLSDR
|
||||
if (dev->rtlsdr_dev)
|
||||
r = rtlsdr_set_tuner_gain_mode(dev->rtlsdr_dev, 0);
|
||||
#endif
|
||||
if (verbose) {
|
||||
if (r < 0)
|
||||
fprintf(stderr, "WARNING: Failed to enable automatic gain.\n");
|
||||
|
@ -139,8 +590,24 @@ int sdr_set_auto_gain(sdr_dev_t *dev, int verbose)
|
|||
return r;
|
||||
}
|
||||
|
||||
int sdr_set_tuner_gain(sdr_dev_t *dev, int gain, int verbose)
|
||||
int sdr_set_tuner_gain(sdr_dev_t *dev, char *gain_str, int verbose)
|
||||
{
|
||||
if (!gain_str || !*gain_str) {
|
||||
/* Enable automatic gain */
|
||||
return sdr_set_auto_gain(dev, verbose);
|
||||
}
|
||||
#ifdef SOAPYSDR
|
||||
/* Enable manual gain */
|
||||
if (dev->soapy_dev)
|
||||
return soapysdr_gain_str_set(dev->soapy_dev, gain_str, verbose);
|
||||
#endif
|
||||
#ifdef RTLSDR
|
||||
int gain = (int)(atof(gain_str) * 10); /* tenths of a dB */
|
||||
if (gain == 0) {
|
||||
/* Enable automatic gain */
|
||||
return sdr_set_auto_gain(dev, verbose);
|
||||
}
|
||||
|
||||
/* Enable manual gain */
|
||||
int r = rtlsdr_set_tuner_gain_mode(dev->rtlsdr_dev, 1);
|
||||
if (verbose)
|
||||
|
@ -156,35 +623,105 @@ int sdr_set_tuner_gain(sdr_dev_t *dev, int gain, int verbose)
|
|||
fprintf(stderr, "Tuner gain set to %f dB.\n", gain / 10.0);
|
||||
}
|
||||
return r;
|
||||
#endif
|
||||
return -1;
|
||||
}
|
||||
|
||||
int sdr_set_sample_rate(sdr_dev_t *dev, uint32_t rate, int verbose)
|
||||
{
|
||||
int r = rtlsdr_set_sample_rate(dev->rtlsdr_dev, rate);
|
||||
int r = -1;
|
||||
#ifdef SOAPYSDR
|
||||
if (dev->soapy_dev)
|
||||
r = SoapySDRDevice_setSampleRate(dev->soapy_dev, SOAPY_SDR_RX, 0, (double)rate);
|
||||
#endif
|
||||
#ifdef RTLSDR
|
||||
if (dev->rtlsdr_dev)
|
||||
r = rtlsdr_set_sample_rate(dev->rtlsdr_dev, rate);
|
||||
#endif
|
||||
if (verbose) {
|
||||
if (r < 0)
|
||||
fprintf(stderr, "WARNING: Failed to set sample rate.\n");
|
||||
else
|
||||
fprintf(stderr, "Sample rate set to %d.\n", sdr_get_sample_rate(dev)); // Unfortunately, doesn't return real rate
|
||||
fprintf(stderr, "Sample rate set to %d S/s.\n", sdr_get_sample_rate(dev)); // Unfortunately, doesn't return real rate
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
uint32_t sdr_get_sample_rate(sdr_dev_t *dev)
|
||||
{
|
||||
return rtlsdr_get_sample_rate(dev->rtlsdr_dev);
|
||||
#ifdef SOAPYSDR
|
||||
if (dev->soapy_dev)
|
||||
return (int)SoapySDRDevice_getSampleRate(dev->soapy_dev, SOAPY_SDR_RX, 0);
|
||||
#endif
|
||||
#ifdef RTLSDR
|
||||
if (dev->rtlsdr_dev)
|
||||
return rtlsdr_get_sample_rate(dev->rtlsdr_dev);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sdr_reset(sdr_dev_t *dev)
|
||||
int sdr_activate(sdr_dev_t *dev)
|
||||
{
|
||||
return rtlsdr_reset_buffer(dev->rtlsdr_dev);
|
||||
#ifdef SOAPYSDR
|
||||
if (dev->soapy_dev) {
|
||||
if (SoapySDRDevice_activateStream(dev->soapy_dev, dev->soapy_stream, 0, 0, 0) != 0) {
|
||||
fprintf(stderr, "Failed to activate stream\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sdr_deactivate(sdr_dev_t *dev)
|
||||
{
|
||||
#ifdef SOAPYSDR
|
||||
if (dev->soapy_dev) {
|
||||
SoapySDRDevice_deactivateStream(dev->soapy_dev, dev->soapy_stream, 0, 0);
|
||||
SoapySDRDevice_closeStream(dev->soapy_dev, dev->soapy_stream);
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sdr_reset(sdr_dev_t *dev, int verbose)
|
||||
{
|
||||
int r = 0;
|
||||
#ifdef RTLSDR
|
||||
if (dev->rtlsdr_dev)
|
||||
r = rtlsdr_reset_buffer(dev->rtlsdr_dev);
|
||||
#endif
|
||||
if (verbose) {
|
||||
if (r < 0)
|
||||
fprintf(stderr, "WARNING: Failed to reset buffers.\n");
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
int sdr_start(sdr_dev_t *dev, sdr_read_cb_t cb, void *ctx, uint32_t buf_num, uint32_t buf_len)
|
||||
{
|
||||
return rtlsdr_read_async(dev->rtlsdr_dev, cb, ctx, buf_num, buf_len);
|
||||
#ifdef SOAPYSDR
|
||||
if (dev->soapy_dev)
|
||||
return soapysdr_read_loop(dev, cb, ctx, buf_num, buf_len);
|
||||
#endif
|
||||
#ifdef RTLSDR
|
||||
if (dev->rtlsdr_dev)
|
||||
return rtlsdr_read_async(dev->rtlsdr_dev, cb, ctx, buf_num, buf_len);
|
||||
#endif
|
||||
return -1;
|
||||
}
|
||||
|
||||
int sdr_stop(sdr_dev_t *dev)
|
||||
{
|
||||
return rtlsdr_cancel_async(dev->rtlsdr_dev);
|
||||
#ifdef SOAPYSDR
|
||||
if (dev->soapy_dev) {
|
||||
dev->running = 0;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
#ifdef RTLSDR
|
||||
if (dev->rtlsdr_dev)
|
||||
return rtlsdr_cancel_async(dev->rtlsdr_dev);
|
||||
#endif
|
||||
return -1;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue