From b30b61282c9677fda612db2c45f9e8e42c2257a4 Mon Sep 17 00:00:00 2001 From: Andreas Eversberg Date: Wed, 4 Jan 2017 14:21:49 +0100 Subject: [PATCH] Generic SDR support --- configure.ac | 3 + src/common/Makefile.am | 7 + src/common/debug.c | 1 + src/common/debug.h | 1 + src/common/main_common.c | 40 +++++- src/common/sdr.c | 293 +++++++++++++++++++++++++++++++++++++++ src/common/sdr.h | 8 ++ src/common/sender.c | 17 +++ src/common/sender.h | 3 + 9 files changed, 371 insertions(+), 2 deletions(-) create mode 100644 src/common/sdr.c create mode 100644 src/common/sdr.h diff --git a/configure.ac b/configure.ac index 591c548..cb4dbdb 100644 --- a/configure.ac +++ b/configure.ac @@ -26,6 +26,9 @@ AC_CANONICAL_HOST PKG_CHECK_MODULES(ALSA, alsa >= 1.0) +have_sdr=no +AM_CONDITIONAL(HAVE_SDR, test "x$have_sdr" == "xyes" ) + AC_OUTPUT( src/common/Makefile src/anetz/Makefile diff --git a/src/common/Makefile.am b/src/common/Makefile.am index bd569d3..eece6fe 100644 --- a/src/common/Makefile.am +++ b/src/common/Makefile.am @@ -26,3 +26,10 @@ libcommon_a_SOURCES = \ ../common/display_wave.c \ ../common/main_common.c +if HAVE_SDR +AM_CPPFLAGS += -DHAVE_SDR + +libcommon_a_SOURCES += \ + ../common/sdr.c +endif + diff --git a/src/common/debug.c b/src/common/debug.c index 5a83ed2..acef16b 100644 --- a/src/common/debug.c +++ b/src/common/debug.c @@ -54,6 +54,7 @@ struct debug_cat { { "transaction", "\033[0;32m" }, { "dms", "\033[0;33m" }, { "sms", "\033[1;37m" }, + { "sdr", "\033[1;31m" }, { NULL, NULL } }; diff --git a/src/common/debug.h b/src/common/debug.h index ee62eb5..d86af09 100644 --- a/src/common/debug.h +++ b/src/common/debug.h @@ -19,6 +19,7 @@ #define DTRANS 12 #define DDMS 13 #define DSMS 14 +#define DSDR 15 #define PDEBUG(cat, level, fmt, arg...) _printdebug(__FILE__, __FUNCTION__, __LINE__, cat, level, -1, fmt, ## arg) #define PDEBUG_CHAN(cat, level, fmt, arg...) _printdebug(__FILE__, __FUNCTION__, __LINE__, cat, level, CHAN, fmt, ## arg) diff --git a/src/common/main_common.c b/src/common/main_common.c index 11fa358..4359546 100644 --- a/src/common/main_common.c +++ b/src/common/main_common.c @@ -32,6 +32,9 @@ #include "sender.h" #include "timer.h" #include "call.h" +#ifdef HAVE_SDR +#include "sdr.h" +#endif /* common settings */ int num_kanal = 0; @@ -54,6 +57,8 @@ int rt_prio = 0; const char *write_rx_wave = NULL; const char *write_tx_wave = NULL; const char *read_rx_wave = NULL; +static const char *sdr_args = ""; +double sdr_rx_gain = 0, sdr_tx_gain = 0; void print_help_common(const char *arg0, const char *ext_usage) { @@ -70,8 +75,9 @@ void print_help_common(const char *arg0, const char *ext_usage) printf(" -k --kanal \n"); printf(" -k --channel \n"); printf(" Channel (German = Kanal) number of \"Sender\" (German = Transceiver)\n"); - printf(" -a --audio-device hw:,\n"); - printf(" Sound card and device number (default = '%s')\n", sounddev[0]); + printf(" -a --audio-device hw:, | sdr\n"); + printf(" Sound card and device number (default = '%s')\n", audiodev[0]); + printf(" SDR device, if supported\n"); printf(" -s --samplerate \n"); printf(" Sample rate of sound device (default = '%d')\n", samplerate); printf(" -i --interval 1..25\n"); @@ -109,6 +115,14 @@ void print_help_common(const char *arg0, const char *ext_usage) printf(" Write transmitted audio to given wav audio file.\n"); printf(" --read-rx-wave \n"); printf(" Replace received audio by given wav audio file.\n"); +#ifdef HAVE_SDR + printf(" --sdr-args \n"); + printf(" Optional SDR device arguments\n"); + printf(" --sdr-rx-gain \n"); + printf(" SDR device's RX gain in dB (default = %.1f)\n", sdr_rx_gain); + printf(" --sdr-tx-gain \n"); + printf(" SDR device's TX gain in dB (default = %.1f)\n", sdr_tx_gain); +#endif } void print_hotkeys_common(void) @@ -123,6 +137,9 @@ void print_hotkeys_common(void) #define OPT_WRITE_RX_WAVE 1001 #define OPT_WRITE_TX_WAVE 1002 #define OPT_READ_RX_WAVE 1003 +#define OPT_SDR_ARGS 1004 +#define OPT_SDR_RX_GAIN 1005 +#define OPT_SDR_TX_GAIN 1006 static struct option long_options_common[] = { {"help", 0, 0, 'h'}, @@ -144,6 +161,9 @@ static struct option long_options_common[] = { {"write-rx-wave", 1, 0, OPT_WRITE_RX_WAVE}, {"write-tx-wave", 1, 0, OPT_WRITE_TX_WAVE}, {"read-rx-wave", 1, 0, OPT_READ_RX_WAVE}, + {"sdr-args", 1, 0, OPT_SDR_ARGS}, + {"sdr-rx-gain", 1, 0, OPT_SDR_RX_GAIN}, + {"sdr-tx-gain", 1, 0, OPT_SDR_TX_GAIN}, {0, 0, 0, 0} }; @@ -283,6 +303,18 @@ void opt_switch_common(int c, char *arg0, int *skip_args) read_rx_wave = strdup(optarg); *skip_args += 2; break; + case OPT_SDR_ARGS: + sdr_args = strdup(optarg); + *skip_args += 2; + break; + case OPT_SDR_RX_GAIN: + sdr_rx_gain = atof(optarg); + *skip_args += 2; + break; + case OPT_SDR_TX_GAIN: + sdr_tx_gain = atof(optarg); + *skip_args += 2; + break; default: exit (0); } @@ -330,6 +362,10 @@ void main_common(int *quit, int latency, int interval, void (*myhandler)(void)) struct termios term, term_orig; int c; +#ifdef HAVE_SDR + if (sdr_init(sdr_args, sdr_rx_gain, sdr_tx_gain)) + return; +#endif /* open audio */ if (sender_open_audio()) diff --git a/src/common/sdr.c b/src/common/sdr.c new file mode 100644 index 0000000..c2b38dc --- /dev/null +++ b/src/common/sdr.c @@ -0,0 +1,293 @@ +/* SDR processing + * + * (C) 2017 by Andreas Eversberg + * All Rights Reserved + * + * 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include "filter.h" +#include "sdr.h" +#ifdef HAVE_UHD +#include "uhd.h" +#endif +#include "debug.h" + +//#define FAST_SINE + +typedef struct sdr_chan { + double tx_frequency; /* frequency used */ + double rx_frequency; /* frequency used */ + double offset; /* offset to calculated center frequency */ + double tx_phase; /* current phase of FM (used to shift and modulate ) */ + double rx_rot; /* rotation step per sample to shift rx frequency (used to shift) */ + double rx_phase; /* current rotation phase (used to shift) */ + double rx_last_phase; /* last phase of FM (used to demodulate) */ + filter_lowpass_t rx_lp[2]; /* filters received IQ signal */ +} sdr_chan_t; + +typedef struct sdr { + sdr_chan_t *chan; + double spl_deviation; /* how to convert a sample step into deviation (Hz) */ + int channels; /* number of frequencies */ + double samplerate; /* IQ rate */ + double amplitude; /* amplitude of each carrier */ +} sdr_t; + +static const char *sdr_device_args; +static double sdr_rx_gain, sdr_tx_gain; + +#ifdef FAST_SINE +static float sdr_sine[256]; +#endif + +int sdr_init(const char *device_args, double rx_gain, double tx_gain) +{ +#ifdef FAST_SINE + int i; + + for (i = 0; i < 256; i++) { + sdr_sine[i] = sin(2.0*M_PI*i/256); + } +#endif + + sdr_device_args = strdup(device_args); + sdr_rx_gain = rx_gain; + sdr_tx_gain = tx_gain; + + return 0; +} + +void *sdr_open(const char __attribute__((__unused__)) *audiodev, double *tx_frequency, double *rx_frequency, int channels, int samplerate, double bandwidth, double sample_deviation) +{ + sdr_t *sdr; + double center_frequency; + int rc; + int c; + + if (channels < 1) { + PDEBUG(DSDR, DEBUG_ERROR, "No channel given, please fix!\n"); + abort(); + } + + sdr = calloc(sizeof(*sdr), 1); + if (!sdr) { + PDEBUG(DSDR, DEBUG_ERROR, "NO MEM!\n"); + goto error; + } + sdr->channels = channels; + sdr->samplerate = samplerate; + sdr->spl_deviation = sample_deviation; + sdr->amplitude = 0.4 / (double)channels; // FIXME: actual amplitude 0.1? + + /* create list of channel states */ + sdr->chan = calloc(sizeof(*sdr->chan), channels); + if (!sdr->chan) { + PDEBUG(DSDR, DEBUG_ERROR, "NO MEM!\n"); + goto error; + } + for (c = 0; c < channels; c++) { + PDEBUG(DSDR, DEBUG_INFO, "Frequency #%d: TX = %.6f MHz, RX = %.6f MHz\n", c, tx_frequency[c] / 1e6, rx_frequency[c] / 1e6); + sdr->chan[c].tx_frequency = tx_frequency[c]; + sdr->chan[c].rx_frequency = rx_frequency[c]; +#warning check rx frequency is in range + filter_lowpass_init(&sdr->chan[c].rx_lp[0], bandwidth, samplerate); + filter_lowpass_init(&sdr->chan[c].rx_lp[1], bandwidth, samplerate); + } + + /* calculate required bandwidth (IQ rate) */ + if (channels == 1) { + PDEBUG(DSDR, DEBUG_INFO, "Single frequency, so we use sample rate as IQ bandwidth: %.6f MHz\n", sdr->samplerate / 1e6); + center_frequency = sdr->chan[0].tx_frequency; + } else { + double low_frequency = sdr->chan[0].tx_frequency, high_frequency = sdr->chan[0].tx_frequency, range; + for (c = 1; c < channels; c++) { + if (sdr->chan[c].tx_frequency < low_frequency) + low_frequency = sdr->chan[c].tx_frequency; + if (sdr->chan[c].tx_frequency > high_frequency) + high_frequency = sdr->chan[c].tx_frequency; + } + range = high_frequency - low_frequency; + PDEBUG(DSDR, DEBUG_INFO, "Range between frequencies: %.6f MHz\n", range / 1e6); + if (range * 2 > sdr->samplerate) { + // why that? actually i don't know. i just want to be safe.... + PDEBUG(DSDR, DEBUG_NOTICE, "The sample rate must be at least twice the range between frequencies. Please increment samplerate!\n"); + goto error; + } + center_frequency = (high_frequency + low_frequency) / 2.0; + } + PDEBUG(DSDR, DEBUG_INFO, "Using center frequency: %.6f MHz\n", center_frequency / 1e6); + for (c = 0; c < channels; c++) { + sdr->chan[c].offset = sdr->chan[c].tx_frequency - center_frequency; + sdr->chan[c].rx_rot = 2 * M_PI * -sdr->chan[c].offset / sdr->samplerate; + PDEBUG(DSDR, DEBUG_INFO, "Frequency #%d offset: %.6f MHz\n", c, sdr->chan[c].offset / 1e6); + } + PDEBUG(DSDR, DEBUG_INFO, "Using gain: TX %.1f dB, RX %.1f dB\n", sdr_tx_gain, sdr_rx_gain); + +#ifdef HAVE_UHD +#warning hack + rc = uhd_open(sdr_device_args, center_frequency, center_frequency - sdr->chan[0].tx_frequency + sdr->chan[0].rx_frequency, sdr->samplerate, sdr_rx_gain, sdr_tx_gain); + if (rc) + goto error; +#endif + + return sdr; + +error: + sdr_close(sdr); + return NULL; +} + +void sdr_close(void *inst) +{ + sdr_t *sdr = (sdr_t *)inst; + +#ifdef HAVE_UHD + uhd_close(); +#endif + + if (sdr) { + free(sdr->chan); + free(sdr); + sdr = NULL; + } +} + +int sdr_write(void *inst, int16_t **samples, int num, int channels) +{ + sdr_t *sdr = (sdr_t *)inst; + float buff[num * 2]; + int c, s, ss; + double rate, phase, amplitude, dev; + int sent; + + if (channels != sdr->channels) { + PDEBUG(DSDR, DEBUG_ERROR, "Invalid number of channels, please fix!\n"); + abort(); + } + + /* process all channels */ + rate = sdr->samplerate; + amplitude = sdr->amplitude; + memset(buff, 0, sizeof(buff)); + for (c = 0; c < channels; c++) { + /* modulate */ + phase = sdr->chan[c].tx_phase; + for (s = 0, ss = 0; s < num; s++) { + /* deviation is defined by the sample value and the offset */ + dev = sdr->chan[c].offset + (double)samples[c][s] * sdr->spl_deviation; +#ifdef FAST_SINE + phase += 256.0 * dev / rate; + if (phase < 0.0) + phase += 256.0; + if (phase >= 256.0) + phase -= 256.0; + buff[ss++] += sdr_sine[((int)phase + 64) & 0xff] * amplitude; + buff[ss++] += sdr_sine[(int)phase & 0xff] * amplitude; +#else + phase += 2.0 * M_PI * dev / rate; + if (phase < 0.0) + phase += 2.0 * M_PI; + if (phase >= 2.0 * M_PI) + phase -= 2.0 * M_PI; + buff[ss++] += cos(phase) * amplitude; + buff[ss++] += sin(phase) * amplitude; +#endif + } + sdr->chan[c].tx_phase = phase; + } + +#ifdef HAVE_UHD + sent = uhd_send(buff, num); +#endif + if (sent < 0) + return sent; + + return sent; +} + +int sdr_read(void *inst, int16_t **samples, int num, int channels) +{ + sdr_t *sdr = (sdr_t *)inst; + float buff[num * 2]; + double I[num], Q[num], i, q; + int count; + int c, s, ss; + double phase, rot, last_phase, spl, dev, rate; + + rate = sdr->samplerate; + +#ifdef HAVE_UHD + count = uhd_receive(buff, num); +#endif + if (count <= 0) + return count; + + for (c = 0; c < channels; c++) { + rot = sdr->chan[c].rx_rot; + phase = sdr->chan[c].rx_phase; + for (s = 0, ss = 0; s < count; s++) { + phase += rot; + i = buff[ss++]; + q = buff[ss++]; + I[s] = i * cos(phase) - q * sin(phase); + Q[s] = i * sin(phase) + q * cos(phase); + } + sdr->chan[c].rx_phase = phase; +#warning eine interation von 2 führt zu müll (2. kanal gespiegeltes audio), muss man genauer mal analysieren + filter_lowpass_process(&sdr->chan[c].rx_lp[0], I, count, 1); + filter_lowpass_process(&sdr->chan[c].rx_lp[1], Q, count, 1); + last_phase = sdr->chan[c].rx_last_phase; + for (s = 0; s < count; s++) { + phase = atan2(I[s], Q[s]); + dev = (phase - last_phase) / 2 / M_PI; + last_phase = phase; + if (dev < -0.49) + dev += 1.0; + else if (dev > 0.49) + dev -= 1.0; + dev *= rate; + spl = dev / sdr->spl_deviation; + if (spl > 32766.0) + spl = 32766.0; + else if (spl < -32766.0) + spl = -32766.0; + samples[c][s] = spl; + } + sdr->chan[c].rx_last_phase = last_phase; + } + + return count; +} + +/* how many delay (in audio sample duration) do we have in the buffer */ +int sdr_get_inbuffer(void __attribute__((__unused__)) *inst) +{ +// sdr_t *sdr = (sdr_t *)inst; + int count; + +#ifdef HAVE_UHD + count = uhd_get_inbuffer(); +#endif + if (count < 0) + return count; + + return count; +} + + diff --git a/src/common/sdr.h b/src/common/sdr.h new file mode 100644 index 0000000..da3ccb0 --- /dev/null +++ b/src/common/sdr.h @@ -0,0 +1,8 @@ + +int sdr_init(const char *device_args, double rx_gain, double tx_gain); +void *sdr_open(const char *audiodev, double *tx_frequency, double *rx_frequency, int channels, int samplerate, double bandwidth, double sample_deviation); +void sdr_close(void *inst); +int sdr_write(void *inst, int16_t **samples, int num, int channels); +int sdr_read(void *inst, int16_t **samples, int num, int channels); +int sdr_get_inbuffer(void *inst); + diff --git a/src/common/sender.c b/src/common/sender.c index 3438554..a579388 100644 --- a/src/common/sender.c +++ b/src/common/sender.c @@ -49,6 +49,10 @@ int sender_create(sender_t *sender, int kanal, double sendefrequenz, double empf sender->de_emphasis = de_emphasis; sender->loopback = loopback; sender->loss_volume = loss_volume; +#ifdef HAVE_SDR + if (!strcmp(audiodev, "sdr")) + pilot_signal = PILOT_SIGNAL_NONE; +#endif sender->pilot_signal = pilot_signal; sender->pilotton_phaseshift = 1.0 / ((double)samplerate / 1000.0); @@ -89,6 +93,19 @@ int sender_create(sender_t *sender, int kanal, double sendefrequenz, double empf slave->slave = sender; } else { /* link audio device */ +#ifdef HAVE_SDR + if (!strcmp(audiodev, "sdr")) { + if (pilot_signal != PILOT_SIGNAL_NONE) { + PDEBUG(DSENDER, DEBUG_ERROR, "No pilot signal allowed with SDR, please fix!\n"); + abort(); + } + sender->audio_open = sdr_open; + sender->audio_close = sdr_close; + sender->audio_read = sdr_read; + sender->audio_write = sdr_write; + sender->audio_get_inbuffer = sdr_get_inbuffer; + } else +#endif { sender->audio_open = sound_open; sender->audio_close = sound_close; diff --git a/src/common/sender.h b/src/common/sender.h index 94bdee8..c655ca6 100644 --- a/src/common/sender.h +++ b/src/common/sender.h @@ -1,4 +1,7 @@ #include "sound.h" +#ifdef HAVE_SDR +#include "sdr.h" +#endif #include "wave.h" #include "samplerate.h" #include "jitter.h"