aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthias P. Braendli <matthias.braendli@mpb.li>2022-01-09 20:02:59 +0100
committerMatthias P. Braendli <matthias.braendli@mpb.li>2022-01-09 20:02:59 +0100
commit36276e45cc3dbca2e954515bd7b6b3b4498ddbb2 (patch)
tree4ec57160a6b6ec14974e44b5beabf8103907aa1e
parent2cf0eaffddab2457fc81d672599c0f58eb6950f9 (diff)
parenta0e7a58447006cdb1e10f781323c88bd08e66eea (diff)
downloadosmo-fl2k-36276e45cc3dbca2e954515bd7b6b3b4498ddbb2.tar.gz
osmo-fl2k-36276e45cc3dbca2e954515bd7b6b3b4498ddbb2.tar.bz2
osmo-fl2k-36276e45cc3dbca2e954515bd7b6b3b4498ddbb2.zip
Merge branch 'iq' into interleaved_rg
-rw-r--r--CMakeLists.txt5
-rwxr-xr-xcontrib/jenkins.sh18
-rw-r--r--src/CMakeLists.txt28
-rw-r--r--src/fl2k_file.c8
-rw-r--r--src/fl2k_fm.c21
-rw-r--r--src/fl2k_garage.c545
-rw-r--r--src/fl2k_iq.c525
-rw-r--r--src/fl2k_tcp.c14
-rw-r--r--src/fl2k_test.c10
-rw-r--r--src/libosmo-fl2k.c58
-rw-r--r--src/rds_mod.c4
11 files changed, 1191 insertions, 45 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5fb110f..84bc55d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -24,6 +24,11 @@ project(libosmo-fl2k C)
include(GNUInstallDirs)
+# CMP0075 Include file check macros honor CMAKE_REQUIRED_LIBRARIES
+if(POLICY CMP0075)
+ cmake_policy(SET CMP0075 NEW)
+endif()
+
#select the release build type by default to get optimization flags
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Release")
diff --git a/contrib/jenkins.sh b/contrib/jenkins.sh
new file mode 100755
index 0000000..8c3b1cf
--- /dev/null
+++ b/contrib/jenkins.sh
@@ -0,0 +1,18 @@
+#!/bin/sh -ex
+# This is how we build on jenkins.osmocom.org.
+
+CFLAGS="-Werror"
+
+if ! [ -x "$(command -v osmo-clean-workspace.sh)" ]; then
+ echo "Error: We need to have scripts/osmo-clean-workspace.sh from osmo-ci.git in PATH!"
+ exit 2
+fi
+
+osmo-clean-workspace.sh
+cmake \
+ -DINSTALL_UDEV_RULES=ON \
+ -DCMAKE_C_FLAGS="$CFLAGS" \
+ .
+make $PARALLEL_MAKE
+make DESTDIR="_install" install
+osmo-clean-workspace.sh
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index f63780a..543226e 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -79,24 +79,36 @@ add_executable(fl2k_file fl2k_file.c)
add_executable(fl2k_tcp fl2k_tcp.c)
add_executable(fl2k_test fl2k_test.c)
add_executable(fl2k_fm fl2k_fm.c rds_waveforms.c rds_mod.c)
-set(INSTALL_TARGETS libosmo-fl2k_shared libosmo-fl2k_static fl2k_file fl2k_tcp fl2k_test fl2k_fm)
+add_executable(fl2k_garage fl2k_garage.c)
+add_executable(fl2k_iq fl2k_iq.c)
+set(INSTALL_TARGETS libosmo-fl2k_shared libosmo-fl2k_static fl2k_file fl2k_tcp fl2k_test fl2k_fm fl2k_garage fl2k_iq)
-target_link_libraries(fl2k_file libosmo-fl2k_shared
+target_link_libraries(fl2k_file libosmo-fl2k_shared
${LIBUSB_LIBRARIES}
${CMAKE_THREAD_LIBS_INIT}
)
-target_link_libraries(fl2k_tcp libosmo-fl2k_shared
+target_link_libraries(fl2k_tcp libosmo-fl2k_shared
${LIBUSB_LIBRARIES}
${CMAKE_THREAD_LIBS_INIT}
)
-target_link_libraries(fl2k_test libosmo-fl2k_shared
+target_link_libraries(fl2k_test libosmo-fl2k_shared
${LIBUSB_LIBRARIES}
${CMAKE_THREAD_LIBS_INIT}
)
-target_link_libraries(fl2k_fm libosmo-fl2k_shared
+target_link_libraries(fl2k_fm libosmo-fl2k_shared
+ ${LIBUSB_LIBRARIES}
+ ${CMAKE_THREAD_LIBS_INIT}
+)
+
+target_link_libraries(fl2k_garage libosmo-fl2k_shared
+ ${LIBUSB_LIBRARIES}
+ ${CMAKE_THREAD_LIBS_INIT}
+)
+
+target_link_libraries(fl2k_iq libosmo-fl2k_shared
${LIBUSB_LIBRARIES}
${CMAKE_THREAD_LIBS_INIT}
)
@@ -105,6 +117,8 @@ target_link_libraries(fl2k_fm libosmo-fl2k_shared
if(UNIX)
target_link_libraries(fl2k_test m)
target_link_libraries(fl2k_fm m)
+target_link_libraries(fl2k_garage m)
+target_link_libraries(fl2k_iq m)
endif()
if(WIN32 AND NOT MINGW)
@@ -112,10 +126,14 @@ target_link_libraries(fl2k_file libgetopt_static)
target_link_libraries(fl2k_tcp ws2_32 libgetopt_static)
target_link_libraries(fl2k_test libgetopt_static)
target_link_libraries(fl2k_fm libgetopt_static)
+target_link_libraries(fl2k_garage libgetopt_static)
+target_link_libraries(fl2k_iq libgetopt_static)
set_property(TARGET fl2k_file APPEND PROPERTY COMPILE_DEFINITIONS "libosmo-fl2k_STATIC" )
set_property(TARGET fl2k_tcp APPEND PROPERTY COMPILE_DEFINITIONS "libosmo-fl2k_STATIC" )
set_property(TARGET fl2k_test APPEND PROPERTY COMPILE_DEFINITIONS "libosmo-fl2k_STATIC" )
set_property(TARGET fl2k_fm APPEND PROPERTY COMPILE_DEFINITIONS "libosmo-fl2k_STATIC" )
+set_property(TARGET fl2k_garage APPEND PROPERTY COMPILE_DEFINITIONS "libosmo-fl2k_STATIC" )
+set_property(TARGET fl2k_iq APPEND PROPERTY COMPILE_DEFINITIONS "libosmo-fl2k_STATIC" )
endif()
if(MINGW)
diff --git a/src/fl2k_file.c b/src/fl2k_file.c
index 2cea6ff..6829deb 100644
--- a/src/fl2k_file.c
+++ b/src/fl2k_file.c
@@ -89,6 +89,12 @@ void fl2k_callback(fl2k_data_info_t *data_info)
int left = required_samples;
static uint32_t repeat_cnt = 0;
+ if (data_info->device_error) {
+ fprintf(stderr, "Device error, exiting.\n");
+ do_exit = 1;
+ return;
+ }
+
data_info->sampletype_signed = 1;
if (interleaved) {
data_info->iq_buf = txbuf;
@@ -103,7 +109,7 @@ void fl2k_callback(fl2k_data_info_t *data_info)
fprintf(stderr, "File Error\n");
if (feof(file)) {
- if (repeat) {
+ if (repeat && (r > 0)) {
repeat_cnt++;
fprintf(stderr, "repeat %d\n", repeat_cnt);
rewind(file);
diff --git a/src/fl2k_fm.c b/src/fl2k_fm.c
index 4aae95b..b22efcd 100644
--- a/src/fl2k_fm.c
+++ b/src/fl2k_fm.c
@@ -73,7 +73,7 @@ uint32_t samp_rate = 100000000;
#define PILOT_FREQ 19000 /* In Hz */
#define STEREO_CARRIER 38000 /* In Hz */
-int delta_freq = 75000;
+int deviation = 75000;
int carrier_freq = 97000000;
int carrier_per_signal;
int input_freq = 44100;
@@ -153,17 +153,17 @@ typedef struct {
unsigned long int phase_slope;
} dds_t;
-inline void dds_setphase(dds_t *dds, double phase)
+static inline void dds_setphase(dds_t *dds, double phase)
{
dds->phase = phase * ANG_INCR;
}
-inline double dds_getphase(dds_t *dds)
+static inline double dds_getphase(dds_t *dds)
{
return dds->phase / ANG_INCR;
}
-inline void dds_set_freq(dds_t *dds, double freq, double fslope)
+static inline void dds_set_freq(dds_t *dds, double freq, double fslope)
{
dds->fslope = fslope;
dds->phase_step = (freq / dds->sample_freq) * 2 * M_PI * ANG_INCR;
@@ -196,7 +196,7 @@ dds_t dds_init(double sample_freq, double freq, double phase)
return dds;
}
-inline int8_t dds_real(dds_t *dds)
+static inline int8_t dds_real(dds_t *dds)
{
int tmp;
@@ -209,7 +209,7 @@ inline int8_t dds_real(dds_t *dds)
return sine_table[tmp];
}
-inline void dds_real_buf(dds_t *dds, int8_t *buf, int count)
+static inline void dds_real_buf(dds_t *dds, int8_t *buf, int count)
{
int i;
for (i = 0; i < count; i++)
@@ -267,7 +267,7 @@ static void *fm_worker(void *arg)
pthread_exit(NULL);
}
-inline int writelen(int maxlen)
+static inline int writelen(int maxlen)
{
int rp = readpos;
int len;
@@ -283,13 +283,13 @@ inline int writelen(int maxlen)
return r;
}
-inline double modulate_sample(int lastwritepos, double lastfreq, double sample)
+static inline double modulate_sample(int lastwritepos, double lastfreq, double sample)
{
double freq, slope;
/* Calculate modulator frequency at this point to lessen
* the calculations needed in the signal generator */
- freq = sample * delta_freq;
+ freq = sample * deviation;
freq += carrier_freq;
/* What we do here is calculate a linear "slope" from
@@ -419,6 +419,7 @@ void fm_modulator_stereo(int use_rds)
void fl2k_callback(fl2k_data_info_t *data_info)
{
if (data_info->device_error) {
+ fprintf(stderr, "Device error, exiting.\n");
do_exit = 1;
pthread_cond_signal(&fm_cond);
}
@@ -467,7 +468,7 @@ int main(int argc, char **argv)
carrier_freq = (uint32_t)atof(optarg);
break;
case 'f':
- delta_freq = (uint32_t)atof(optarg);
+ deviation = (uint32_t)atof(optarg);
break;
case 'i':
input_freq = (uint32_t)atof(optarg);
diff --git a/src/fl2k_garage.c b/src/fl2k_garage.c
new file mode 100644
index 0000000..d50922e
--- /dev/null
+++ b/src/fl2k_garage.c
@@ -0,0 +1,545 @@
+/*
+ * osmo-fl2k, turns FL2000-based USB 3.0 to VGA adapters into
+ * low cost DACs
+ *
+ * Copyright (C) 2019 by Felix Erckenbrecht <eligs@eligs.de>
+ *
+ * based on fl2k_fm code by:
+ * Copyright (C) 2016-2018 by Steve Markgraf <steve@steve-m.de>
+ *
+ * based on FM modulator code from VGASIG:
+ * Copyright (C) 2009 by Bartek Kania <mbk@gnarf.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * 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 2 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <string.h>
+#include <errno.h>
+#include <stdint.h>
+
+#ifndef _WIN32
+#include <unistd.h>
+#include <fcntl.h>
+#include <getopt.h>
+#else
+#include <windows.h>
+#include <io.h>
+#include <fcntl.h>
+#include "getopt/getopt.h"
+#endif
+
+#include <math.h>
+#include <pthread.h>
+
+#include "osmo-fl2k.h"
+#include "rds_mod.h"
+
+#define BUFFER_SAMPLES_SHIFT 16
+#define BUFFER_SAMPLES (1 << BUFFER_SAMPLES_SHIFT)
+#define BUFFER_SAMPLES_MASK ((1 << BUFFER_SAMPLES_SHIFT)-1)
+
+#define AUDIO_BUF_SIZE 4096
+
+#define BASEBAND_SAMPLES_PER_CHIP 3
+#define BASEBAND_WORD_BITS 12
+#define BASEBAND_CHIPS_PER_BIT 3
+#define BASEBAND_CHIPS_PER_WORD (BASEBAND_WORD_BITS * BASEBAND_CHIPS_PER_BIT)
+#define BASEBAND_CHIPS_PER_SPACE BASEBAND_CHIPS_PER_WORD
+#define BASEBAND_SPACE_HIGH_CHIPS 1
+#define BASEBAND_SPACE_LOW_CHIPS (BASEBAND_CHIPS_PER_SPACE - BASEBAND_SPACE_HIGH_CHIPS)
+#define BASEBAND_CHIPS_TOTAL (BASEBAND_CHIPS_PER_SPACE + BASEBAND_CHIPS_PER_WORD)
+
+fl2k_dev_t *dev = NULL;
+int do_exit = 0;
+
+pthread_t am_thread;
+pthread_mutex_t cb_mutex;
+pthread_mutex_t am_mutex;
+pthread_cond_t cb_cond;
+pthread_cond_t am_cond;
+
+int16_t *sample_buf;
+int sample_buf_size;
+
+int8_t *txbuf = NULL;
+int8_t *ambuf = NULL;
+int8_t *buf1 = NULL;
+int8_t *buf2 = NULL;
+
+uint32_t samp_rate = 100000000;
+
+double mod_index = 0.9;
+int carrier_freq = 40685000;
+int carrier_per_signal;
+
+double *ampbuf;
+double *slopebuf;
+int writepos, readpos;
+
+void usage(void)
+{
+ fprintf(stderr,
+ "fl2k_garage, a garage door opener for FL2K VGA dongles\n\n"
+ "Usage:"
+ "\t[-d device index (default: 0)]\n"
+ "\t[-f carrier frequency (default: 40.685 MHz)]\n"
+ "\t[-c garage door code (12 Bit)]\n"
+ "\t[-b chip period in us (default 320 us)]\n"
+ "\t[-s samplerate in Hz (default: 100 MS/s)]\n"
+ );
+ exit(1);
+}
+
+#ifdef _WIN32
+BOOL WINAPI
+sighandler(int signum)
+{
+ if (CTRL_C_EVENT == signum) {
+ fprintf(stderr, "Signal caught, exiting!\n");
+ fl2k_stop_tx(dev);
+ do_exit = 1;
+ pthread_cond_signal(&am_cond);
+ return TRUE;
+ }
+ return FALSE;
+}
+#else
+static void sighandler(int signum)
+{
+ fprintf(stderr, "Signal caught, exiting!\n");
+ fl2k_stop_tx(dev);
+ do_exit = 1;
+ pthread_cond_signal(&am_cond);
+}
+#endif
+
+/* DDS Functions */
+
+#ifndef M_PI
+# define M_PI 3.14159265358979323846 /* pi */
+# define M_PI_2 1.57079632679489661923 /* pi/2 */
+# define M_PI_4 0.78539816339744830962 /* pi/4 */
+# define M_1_PI 0.31830988618379067154 /* 1/pi */
+# define M_2_PI 0.63661977236758134308 /* 2/pi */
+#endif
+#define DDS_2PI (M_PI * 2) /* 2 * Pi */
+#define DDS_3PI2 (M_PI_2 * 3) /* 3/2 * pi */
+
+#define SIN_TABLE_ORDER 8
+#define SIN_TABLE_SHIFT (32 - SIN_TABLE_ORDER)
+#define SIN_TABLE_LEN (1 << SIN_TABLE_ORDER)
+#define ANG_INCR (0xffffffff / DDS_2PI)
+
+int16_t sine_table[SIN_TABLE_LEN];
+int sine_table_init = 0;
+
+typedef struct {
+ double sample_freq;
+ double freq;
+ unsigned long int phase;
+ unsigned long int phase_step;
+ double amplitude;
+ double ampslope;
+} dds_t;
+
+static inline void dds_set_freq(dds_t *dds, double freq)
+{
+ dds->freq = freq;
+ dds->phase_step = (freq / dds->sample_freq) * 2 * M_PI * ANG_INCR;
+}
+
+static inline void dds_set_amp(dds_t *dds, double amplitude, double ampslope)
+{
+ dds->amplitude = amplitude;
+ dds->ampslope = ampslope;
+}
+
+dds_t dds_init(double sample_freq, double freq, double phase, double amp)
+{
+ dds_t dds;
+ int i;
+
+ dds.sample_freq = sample_freq;
+ dds.phase = phase * ANG_INCR;
+ dds_set_freq(&dds, freq);
+ dds_set_amp(&dds, amp, 0);
+ /* Initialize sine table, prescaled for 16 bit signed integer */
+ if (!sine_table_init) {
+ double incr = 1.0 / (double)SIN_TABLE_LEN;
+ for (i = 0; i < SIN_TABLE_LEN; i++)
+ sine_table[i] = sin(incr * i * DDS_2PI) * 32767;
+
+ sine_table_init = 1;
+ }
+
+ return dds;
+}
+
+static inline int8_t dds_real(dds_t *dds)
+{
+ int tmp;
+ int32_t amp;
+
+ // advance dds generator
+ tmp = dds->phase >> SIN_TABLE_SHIFT;
+ dds->phase += dds->phase_step;
+ dds->phase &= 0xffffffff;
+
+ amp = (int32_t)(dds->amplitude * 255) * sine_table[tmp];
+ dds->amplitude += dds->ampslope;
+
+ return (int8_t)(amp >> 16) ;
+}
+
+static inline void dds_real_buf(dds_t *dds, int8_t *buf, int count)
+{
+ int i;
+ for (i = 0; i < count; i++)
+ buf[i] = dds_real(dds);
+}
+
+/* Signal generation and some helpers */
+
+/* Generate the radio signal using the pre-calculated amplitude information
+ * in the amp buffer */
+static void *am_worker(void *arg)
+{
+ register double freq;
+ register double tmp;
+ dds_t carrier;
+ int8_t *tmp_ptr;
+ uint32_t len = 0;
+ uint32_t readlen, remaining;
+ int buf_prefilled = 0;
+
+ /* Prepare the oscillators */
+ carrier = dds_init(samp_rate, carrier_freq, 1, 0);
+
+ while (!do_exit) {
+ dds_set_amp(&carrier, ampbuf[readpos], slopebuf[readpos]);
+ readpos++;
+ readpos &= BUFFER_SAMPLES_MASK;
+
+ /* check if we reach the end of the buffer */
+ if ((len + carrier_per_signal) > FL2K_BUF_LEN) {
+ readlen = FL2K_BUF_LEN - len;
+ remaining = carrier_per_signal - readlen;
+ dds_real_buf(&carrier, &ambuf[len], readlen);
+
+ if (buf_prefilled) {
+ /* swap buffers */
+ tmp_ptr = ambuf;
+ ambuf = txbuf;
+ txbuf = tmp_ptr;
+ pthread_cond_wait(&cb_cond, &cb_mutex);
+ }
+
+ dds_real_buf(&carrier, ambuf, remaining);
+ len = remaining;
+
+ buf_prefilled = 1;
+ } else {
+ dds_real_buf(&carrier, &ambuf[len], carrier_per_signal);
+ len += carrier_per_signal;
+ }
+
+ pthread_cond_signal(&am_cond);
+ }
+
+ pthread_exit(NULL);
+}
+
+static inline int writelen(int maxlen)
+{
+ int rp = readpos;
+ int len;
+ int r;
+
+ if (rp < writepos)
+ rp += BUFFER_SAMPLES;
+
+ len = rp - writepos;
+
+ r = len > maxlen ? maxlen : len;
+
+ return r;
+}
+
+static inline int32_t modulate_sample_am(int lastwritepos, double lastamp, int16_t sample)
+{
+ double amp, slope;
+
+ /* Calculate modulator amplitude at this point to lessen
+ * the calculations needed in the signal generator */
+ amp = 1 - ((double)sample * mod_index);
+
+ /* What we do here is calculate a linear "slope" from
+ the previous sample to this one. This is then used by
+ the modulator to gently increase/decrease the amplitude
+ with each sample without the need to recalculate
+ the dds parameters. In fact this gives us a very
+ efficient and pretty good interpolation filter. */
+ slope = amp - lastamp;
+ slope /= carrier_per_signal;
+ slopebuf[lastwritepos] = slope;
+ ampbuf[writepos] = amp;
+
+ return amp;
+}
+
+void am_modulator(const int code_input)
+{
+ int counter = 0;
+ int code;
+ unsigned int i;
+ unsigned int b = 0;
+ size_t len;
+ int32_t lastamp = 0;
+ uint32_t lastwritepos = writepos;
+ int16_t sample = 0;
+ int samplebuf_pos = 0;
+
+ /*
+ * 3*640 us = 1,92 ms pro Symbol
+ * 12 Symbole Daten (12 Bit)
+ * 11 Symbole Pause, 1 Symbol 1 (synch)
+ *
+ * 1 = __-
+ * 0 = _--
+ */
+ while (!do_exit) {
+ len = writelen(AUDIO_BUF_SIZE);
+ if (len > 1) {
+ if (len == 0)
+ do_exit = 1;
+
+ for (i = 0; i < len; i++) {
+ /* Modulate and buffer the sample */
+ sample = sample_buf[samplebuf_pos++];
+ if(samplebuf_pos >= BASEBAND_SAMPLES_PER_CHIP * BASEBAND_CHIPS_TOTAL){
+ samplebuf_pos = 0;
+ }
+ lastamp = modulate_sample_am(lastwritepos, lastamp, sample);
+ lastwritepos = writepos++;
+ writepos %= BUFFER_SAMPLES;
+ }
+ } else {
+ pthread_cond_wait(&am_cond, &am_mutex);
+ }
+ }
+}
+
+void prepare_baseband(const int code_input, int16_t * sbuf){
+ int counter;
+ int b;
+ int sample_no;
+ int16_t sample;
+ int msb_first_code;
+
+ msb_first_code = 0;
+ // change to msb first and invert
+ for(b = 0;b<12;b++){
+ msb_first_code <<= 1;
+ msb_first_code |= code_input & (1<<b) ? 0 : 1;
+ }
+
+ sample_no = 0;
+ for(counter=0;counter < (BASEBAND_CHIPS_PER_SPACE + BASEBAND_CHIPS_PER_WORD) ; counter++){
+ for(b=0 ; b<BASEBAND_SAMPLES_PER_CHIP ; b++){
+ if(counter < (BASEBAND_SPACE_LOW_CHIPS)){
+ sample = 0;
+ }
+ else if(counter < (BASEBAND_CHIPS_PER_SPACE)){
+ // synch symbol
+ sample = 1;
+ }
+ else{
+ int m;
+ m = counter % BASEBAND_CHIPS_PER_BIT;
+ if(m == 0){
+ sample = 0;
+ }
+ else if(m == 1){
+ sample = (msb_first_code & 1);
+ }
+ else{
+ sample = 1;
+ if(b == BASEBAND_SAMPLES_PER_CHIP-1){
+ msb_first_code >>= 1;
+ }
+ }
+ }
+ sbuf[counter * BASEBAND_SAMPLES_PER_CHIP + b] = sample;
+ }
+ }
+}
+
+void fl2k_callback(fl2k_data_info_t *data_info)
+{
+ if (data_info->device_error) {
+ fprintf(stderr, "Device error, exiting.\n");
+ do_exit = 1;
+ pthread_cond_signal(&am_cond);
+ }
+
+ pthread_cond_signal(&cb_cond);
+
+ data_info->sampletype_signed = 1;
+ data_info->r_buf = (char *)txbuf;
+}
+
+int main(int argc, char **argv)
+{
+ int r, opt;
+ uint32_t buf_num = 0;
+ int dev_index = 0;
+ pthread_attr_t attr;
+ char *filename = NULL;
+ int option_index = 0;
+ int code = 0;
+ int chiptime_us = 320;
+
+#ifndef _WIN32
+ struct sigaction sigact, sigign;
+#endif
+
+ static struct option long_options[] =
+ {
+ {0, 0, 0, 0}
+ };
+
+ while (1) {
+ opt = getopt_long(argc, argv, "b:c:f:m:s:", long_options, &option_index);
+ /* end of options reached */
+ if (opt == -1)
+ break;
+
+ switch (opt) {
+ case 0:
+ break;
+ case 'b':
+ chiptime_us = atoi(optarg);
+ break;
+ case 'c':
+ code = atoi(optarg);
+ code &= 4095;
+ break;
+ case 'f':
+ carrier_freq = (uint32_t)atof(optarg);
+ break;
+ case 'm':
+ mod_index = atof(optarg);
+ break;
+ case 's':
+ samp_rate = (uint32_t)atof(optarg);
+ break;
+ default:
+ usage();
+ break;
+ }
+ }
+
+ if (argc < optind) {
+ usage();
+ }
+
+ /* allocate buffer */
+ buf1 = malloc(FL2K_BUF_LEN);
+ buf2 = malloc(FL2K_BUF_LEN);
+ if (!buf1 || !buf2) {
+ fprintf(stderr, "malloc error!\n");
+ exit(1);
+ }
+
+ ambuf = buf1;
+ txbuf = buf2;
+
+ /* Decoded audio */
+ slopebuf = malloc(BUFFER_SAMPLES * sizeof(double));
+ ampbuf = malloc(BUFFER_SAMPLES * sizeof(double));
+ slopebuf = malloc(BUFFER_SAMPLES * sizeof(double));
+ sample_buf = malloc((BASEBAND_SAMPLES_PER_CHIP * BASEBAND_CHIPS_TOTAL) * sizeof(int16_t));
+ readpos = 0;
+ writepos = 1;
+
+ fprintf(stderr, "Samplerate:\t%3.2f MHz\n", (double)samp_rate/1000000);
+ fprintf(stderr, "Carrier:\t%3.3f MHz\n", (double)carrier_freq/1000000);
+ fprintf(stderr, "Mod Index:\t%3.1f %%\n",
+ (double)(mod_index * 100));
+ fprintf(stderr, "Chip period:\t%d us\n", chiptime_us);
+
+ pthread_mutex_init(&cb_mutex, NULL);
+ pthread_mutex_init(&am_mutex, NULL);
+ pthread_cond_init(&cb_cond, NULL);
+ pthread_cond_init(&am_cond, NULL);
+ pthread_attr_init(&attr);
+
+ prepare_baseband(code, sample_buf);
+
+ fl2k_open(&dev, (uint32_t)dev_index);
+ if (NULL == dev) {
+ fprintf(stderr, "Failed to open fl2k device #%d.\n", dev_index);
+ goto out;
+ }
+
+ r = pthread_create(&am_thread, &attr, am_worker, NULL);
+ if (r < 0) {
+ fprintf(stderr, "Error spawning AM worker thread!\n");
+ goto out;
+ }
+
+ pthread_attr_destroy(&attr);
+ r = fl2k_start_tx(dev, fl2k_callback, NULL, 0);
+
+ /* Set the sample rate */
+ r = fl2k_set_sample_rate(dev, samp_rate);
+ if (r < 0)
+ fprintf(stderr, "WARNING: Failed to set sample rate. %d\n", r);
+
+ /* read back actual frequency */
+ samp_rate = fl2k_get_sample_rate(dev);
+
+ /* Calculate needed constants */
+ carrier_per_signal = (int)((double) samp_rate * chiptime_us/(1000000*BASEBAND_SAMPLES_PER_CHIP) + 0.5);
+ printf("Cps :\t%d\n", carrier_per_signal);
+#ifndef _WIN32
+ sigact.sa_handler = sighandler;
+ sigemptyset(&sigact.sa_mask);
+ sigact.sa_flags = 0;
+ sigign.sa_handler = SIG_IGN;
+ sigaction(SIGINT, &sigact, NULL);
+ sigaction(SIGTERM, &sigact, NULL);
+ sigaction(SIGQUIT, &sigact, NULL);
+ sigaction(SIGPIPE, &sigign, NULL);
+#else
+ SetConsoleCtrlHandler( (PHANDLER_ROUTINE) sighandler, TRUE );
+#endif
+
+ am_modulator(code);
+
+out:
+ fl2k_close(dev);
+
+ free(ampbuf);
+ free(slopebuf);
+ free(buf1);
+ free(buf2);
+
+ return 0;
+}
diff --git a/src/fl2k_iq.c b/src/fl2k_iq.c
new file mode 100644
index 0000000..2dce59b
--- /dev/null
+++ b/src/fl2k_iq.c
@@ -0,0 +1,525 @@
+/*
+ * osmo-fl2k, turns FL2000-based USB 3.0 to VGA adapters into
+ * low cost DACs
+ *
+ * fl2k-iq
+ * Copyright (C) 2020 by Felix Erckenbrecht <eligs@eligs.de>
+ *
+ * based on fl2k-fm code:
+ * Copyright (C) 2016-2018 by Steve Markgraf <steve@steve-m.de>
+ *
+ * based on FM modulator code from VGASIG:
+ * Copyright (C) 2009 by Bartek Kania <mbk@gnarf.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * 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 2 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <string.h>
+#include <errno.h>
+
+#ifndef _WIN32
+#include <unistd.h>
+#include <fcntl.h>
+#include <getopt.h>
+#else
+#include <windows.h>
+#include <io.h>
+#include <fcntl.h>
+#include "getopt/getopt.h"
+#endif
+
+#include <math.h>
+#include <complex.h>
+#include <pthread.h>
+
+#include "osmo-fl2k.h"
+#include "rds_mod.h"
+
+#define BUFFER_SAMPLES_SHIFT 16
+#define BUFFER_SAMPLES (1 << BUFFER_SAMPLES_SHIFT)
+#define BUFFER_SAMPLES_MASK ((1 << BUFFER_SAMPLES_SHIFT)-1)
+
+#define BASEBAND_BUF_SIZE 2048
+
+fl2k_dev_t *dev = NULL;
+volatile int do_exit = 0;
+
+pthread_t iq_thread;
+pthread_mutex_t cb_mutex;
+pthread_mutex_t iq_mutex;
+pthread_cond_t cb_cond;
+pthread_cond_t iq_cond;
+
+FILE *file;
+int8_t *txbuf = NULL;
+int8_t *ambuf = NULL;
+int8_t *buf1 = NULL;
+int8_t *buf2 = NULL;
+
+uint32_t samp_rate = 96000000;
+
+int base_freq = 1440000;
+int rf_to_baseband_sample_ratio;
+int input_freq = 48000;
+
+complex float *ampbuf;
+complex float *slopebuf;
+int writepos, readpos;
+int swap_iq = 0;
+
+void usage(void)
+{
+ fprintf(stderr,
+ "fl2k_iq, an IQ modulator for FL2K VGA dongles\n\n"
+ "Usage:"
+ "\t[-d device index (default: 0)]\n"
+ "\t[-c center frequency (default: 1440 kHz)]\n"
+ "\t[-i input baseband sample rate (default: 48000 Hz)]\n"
+ "\t[-s samplerate in Hz (default: 96 MS/s)]\n"
+ "\t[-w swap I & Q (invert spectrum)\n"
+ "\tfilename (use '-' to read from stdin)\n\n"
+ );
+ exit(1);
+}
+
+#ifdef _WIN32
+BOOL WINAPI
+sighandler(int signum)
+{
+ if (CTRL_C_EVENT == signum) {
+ fprintf(stderr, "Signal caught, exiting!\n");
+ fl2k_stop_tx(dev);
+ do_exit = 1;
+ pthread_cond_signal(&iq_cond);
+ return TRUE;
+ }
+ return FALSE;
+}
+#else
+static void sighandler(int signum)
+{
+ fprintf(stderr, "Signal caught, exiting!\n");
+ fl2k_stop_tx(dev);
+ do_exit = 1;
+ pthread_cond_signal(&iq_cond);
+}
+#endif
+
+/* DDS Functions */
+
+#ifndef M_PI
+# define M_PI 3.14159265358979323846 /* pi */
+# define M_PI_2 1.57079632679489661923 /* pi/2 */
+# define M_PI_4 0.78539816339744830962 /* pi/4 */
+# define M_1_PI 0.31830988618379067154 /* 1/pi */
+# define M_2_PI 0.63661977236758134308 /* 2/pi */
+#endif
+#define DDS_2PI (M_PI * 2) /* 2 * Pi */
+#define DDS_3PI2 (M_PI_2 * 3) /* 3/2 * pi */
+
+#define TRIG_TABLE_ORDER 8
+#define TRIG_TABLE_SHIFT (32 - TRIG_TABLE_ORDER)
+#define TRIG_TABLE_LEN (1 << TRIG_TABLE_ORDER)
+#define ANG_INCR (0xffffffff / DDS_2PI)
+
+struct trigonometric_table_S {
+ int initialized;
+ int16_t sine[TRIG_TABLE_LEN];
+ int16_t cosine[TRIG_TABLE_LEN];
+};
+
+static struct trigonometric_table_S trig_table = { .initialized = 0 };
+
+typedef struct {
+ float sample_freq;
+ float freq;
+ unsigned long int phase;
+ unsigned long int phase_step;
+ complex float amplitude;
+ complex float ampslope;
+} dds_t;
+
+static inline void dds_set_freq(dds_t *dds, float freq)
+{
+ dds->freq = freq;
+ dds->phase_step = (freq / dds->sample_freq) * 2 * M_PI * ANG_INCR;
+}
+
+static inline void dds_set_amp(dds_t *dds, complex float amplitude, complex float ampslope)
+{
+ dds->amplitude = amplitude;
+ dds->ampslope = ampslope;
+}
+
+dds_t dds_init(float sample_freq, float freq, float phase, float amp)
+{
+ dds_t dds;
+ int i;
+
+ dds.sample_freq = sample_freq;
+ dds.phase = phase * ANG_INCR;
+ dds_set_freq(&dds, freq);
+ dds_set_amp(&dds, amp, 0);
+ /* Initialize sine table, prescaled for 16 bit signed integer */
+ if (!trig_table.initialized) {
+ float incr = 1.0 / (float)TRIG_TABLE_LEN;
+ for (i = 0; i < TRIG_TABLE_LEN; i++){
+ trig_table.sine[i] = sin(incr * i * DDS_2PI) * 32767;
+ trig_table.cosine[i] = cos(incr * i * DDS_2PI) * 32767;
+ }
+
+ trig_table.initialized = 1;
+ }
+
+ return dds;
+}
+
+static inline int8_t dds_real(dds_t *dds)
+{
+ int tmp;
+ int32_t amp_i, amp_q;
+ int8_t amp8;
+
+ // advance dds generator
+ tmp = dds->phase >> TRIG_TABLE_SHIFT;
+ dds->phase += dds->phase_step;
+ dds->phase &= 0xffffffff;
+
+ //amp = 255;
+ amp_i = creal(dds->amplitude) * 23170.0; // 0..15, * 1/SQRT(2)
+ amp_q = cimag(dds->amplitude) * 23170.0;
+ amp_i = amp_i * trig_table.sine[tmp]; // 0..31, * 1/SQRT(2)
+ amp_q = amp_q * trig_table.cosine[tmp]; // 0..31, * 1/SQRT(2)
+ amp8 = (int8_t) ((amp_i + amp_q) >> 24); // 0..31 >> 24 => 0..8
+ dds->amplitude += dds->ampslope;
+ return amp8;
+}
+
+static inline void dds_real_buf(dds_t *dds, int8_t *buf, int count)
+{
+ int i;
+ for (i = 0; i < count; i++)
+ buf[i] = dds_real(dds);
+}
+
+/* Signal generation and some helpers */
+
+/* Generate the radio signal using the pre-calculated amplitude information
+ * in the amp buffer */
+static void *iq_worker(void *arg)
+{
+ register float freq;
+ register float tmp;
+ dds_t base_signal;
+ int8_t *tmp_ptr;
+ uint32_t len = 0;
+ uint32_t readlen, remaining;
+ int buf_prefilled = 0;
+
+ /* Prepare the oscillators */
+ base_signal = dds_init(samp_rate, base_freq, 0, 1);
+
+ while (!do_exit) {
+ dds_set_amp(&base_signal, ampbuf[readpos], slopebuf[readpos]);
+ readpos++;
+ readpos &= BUFFER_SAMPLES_MASK;
+
+ /* check if we reach the end of the buffer */
+ if ((len + rf_to_baseband_sample_ratio) > FL2K_BUF_LEN) {
+ readlen = FL2K_BUF_LEN - len;
+ remaining = rf_to_baseband_sample_ratio - readlen;
+ dds_real_buf(&base_signal, &ambuf[len], readlen);
+
+ if (buf_prefilled) {
+ /* swap buffers */
+ tmp_ptr = ambuf;
+ ambuf = txbuf;
+ txbuf = tmp_ptr;
+ pthread_mutex_lock(&cb_mutex);
+ pthread_cond_wait(&cb_cond, &cb_mutex);
+ pthread_mutex_unlock(&cb_mutex);
+ }
+
+ dds_real_buf(&base_signal, ambuf, remaining);
+ len = remaining;
+
+ buf_prefilled = 1;
+ } else {
+ dds_real_buf(&base_signal, &ambuf[len], rf_to_baseband_sample_ratio);
+ len += rf_to_baseband_sample_ratio;
+ }
+ pthread_mutex_lock(&iq_mutex);
+ pthread_cond_signal(&iq_cond);
+ pthread_mutex_unlock(&iq_mutex);
+ }
+
+ pthread_exit(NULL);
+}
+
+static inline int writelen(int maxlen)
+{
+ int rp = readpos;
+ int len;
+ int r;
+
+ if (rp < writepos)
+ rp += BUFFER_SAMPLES;
+
+ len = rp - writepos;
+
+ r = len > maxlen ? maxlen : len;
+
+ return r;
+}
+
+static inline float complex modulate_sample_iq(const int lastwritepos, const float complex lastamp, const float complex sample)
+{
+ float complex amp;
+ float complex slope;
+
+ /* Calculate modulator amplitudes at this point to lessen
+ * the calculations needed in the signal generator */
+ amp = sample;
+
+ /* What we do here is calculate a linear "slope" from
+ the previous sample to this one. This is then used by
+ the modulator to gently increase/decrease the amplitude
+ with each sample without the need to recalculate
+ the dds parameters. In fact this gives us a very
+ efficient and pretty good interpolation filter. */
+ slope = amp - lastamp;
+ slope = slope * 1.0/ (float) rf_to_baseband_sample_ratio;
+ slopebuf[writepos] = slope;
+ ampbuf[writepos] = lastamp;
+
+ return amp;
+}
+
+
+void iq_modulator()
+{
+ unsigned int i;
+ size_t len;
+ float freq;
+ float complex lastamp = 0;
+ int16_t baseband_buf[BASEBAND_BUF_SIZE][2];
+ uint32_t lastwritepos = writepos;
+ float complex sample;
+
+ while (!do_exit) {
+ int swap = swap_iq;
+ len = writelen(BASEBAND_BUF_SIZE);
+ if (len > 1) {
+ len = fread(baseband_buf, 4, len, file);
+
+ if (len == 0){
+ if(ferror(file)){
+ do_exit = 1;
+ }
+ }
+
+ for (i = 0; i < len; i++) {
+ sample = (float) baseband_buf[i][0+swap] / 32768.0 + I * (float) baseband_buf[i][1-swap] / 32768.0;
+
+ /* Modulate and buffer the sample */
+ lastamp = modulate_sample_iq(lastwritepos, lastamp, sample);
+ lastwritepos = writepos++;
+ writepos %= BUFFER_SAMPLES;
+ }
+ } else {
+ pthread_mutex_lock(&iq_mutex);
+ pthread_cond_wait(&iq_cond, &iq_mutex);
+ pthread_mutex_unlock(&iq_mutex);
+ }
+ }
+}
+
+
+void fl2k_callback(fl2k_data_info_t *data_info)
+{
+ if (data_info->device_error) {
+ fprintf(stderr, "Device error, exiting.\n");
+ do_exit = 1;
+ pthread_mutex_lock(&iq_mutex);
+ pthread_cond_signal(&iq_cond);
+ pthread_mutex_unlock(&iq_mutex);
+ }
+
+ pthread_cond_signal(&cb_cond);
+
+ data_info->sampletype_signed = 1;
+ data_info->r_buf = (char *)txbuf;
+}
+
+int main(int argc, char **argv)
+{
+ int r, opt;
+ uint32_t buf_num = 0;
+ int dev_index = 0;
+ pthread_attr_t attr;
+ char *filename = NULL;
+ int option_index = 0;
+ int input_freq_specified = 0;
+
+#ifndef _WIN32
+ struct sigaction sigact, sigign;
+#endif
+
+ static struct option long_options[] =
+ {
+ {0, 0, 0, 0}
+ };
+
+ while (1) {
+ opt = getopt_long(argc, argv, "wd:c:i:s:", long_options, &option_index);
+
+ /* end of options reached */
+ if (opt == -1)
+ break;
+
+ switch (opt) {
+ case 0:
+ break;
+ case 'd':
+ dev_index = (uint32_t)atoi(optarg);
+ break;
+ case 'c':
+ base_freq = (uint32_t)atof(optarg);
+ break;
+ case 'i':
+ input_freq = (uint32_t)atof(optarg);
+ input_freq_specified = 1;
+ break;
+ case 's':
+ samp_rate = (uint32_t)atof(optarg);
+ break;
+ case 'w':
+ swap_iq = 1;
+ break;
+ default:
+ usage();
+ break;
+ }
+ }
+
+ if (argc <= optind) {
+ usage();
+ } else {
+ filename = argv[optind];
+ }
+
+ if (dev_index < 0) {
+ exit(1);
+ }
+
+ if (strcmp(filename, "-") == 0) { /* Read samples from stdin */
+ file = stdin;
+#ifdef _WIN32
+ _setmode(_fileno(stdin), _O_BINARY);
+#endif
+ } else {
+ file = fopen(filename, "rb");
+ if (!file) {
+ fprintf(stderr, "Failed to open %s\n", filename);
+ return -ENOENT;
+ }
+ }
+
+ /* allocate buffer */
+ buf1 = malloc(FL2K_BUF_LEN);
+ buf2 = malloc(FL2K_BUF_LEN);
+ if (!buf1 || !buf2) {
+ fprintf(stderr, "malloc error!\n");
+ exit(1);
+ }
+
+ ambuf = buf1;
+ txbuf = buf2;
+
+ /* Decoded audio */
+ slopebuf = malloc(BUFFER_SAMPLES * sizeof(float complex));
+ ampbuf = malloc(BUFFER_SAMPLES * sizeof(float complex));
+ readpos = 0;
+ writepos = 1;
+
+ fprintf(stderr, "Samplerate: %3.2f MHz\n", (float)samp_rate/1000000);
+ fprintf(stderr, "Center frequency: %5.0f kHz\n", (float)base_freq/1000);
+ if(swap_iq)
+ fprintf(stderr, "Spectral inversion active.\n");
+
+ pthread_mutex_init(&cb_mutex, NULL);
+ pthread_mutex_init(&iq_mutex, NULL);
+ pthread_cond_init(&cb_cond, NULL);
+ pthread_cond_init(&iq_cond, NULL);
+ pthread_attr_init(&attr);
+
+ fl2k_open(&dev, (uint32_t)dev_index);
+ if (NULL == dev) {
+ fprintf(stderr, "Failed to open fl2k device #%d.\n", dev_index);
+ goto out;
+ }
+
+ r = pthread_create(&iq_thread, &attr, iq_worker, NULL);
+ if (r < 0) {
+ fprintf(stderr, "Error spawning IQ worker thread!\n");
+ goto out;
+ }
+
+ pthread_attr_destroy(&attr);
+ r = fl2k_start_tx(dev, fl2k_callback, NULL, 0);
+
+ /* Set the sample rate */
+ r = fl2k_set_sample_rate(dev, samp_rate);
+ if (r < 0)
+ fprintf(stderr, "WARNING: Failed to set sample rate. %d\n", r);
+
+ /* read back actual frequency */
+ samp_rate = fl2k_get_sample_rate(dev);
+
+ /* Calculate needed constants */
+ rf_to_baseband_sample_ratio = samp_rate / input_freq;
+
+#ifndef _WIN32
+ sigact.sa_handler = sighandler;
+ sigemptyset(&sigact.sa_mask);
+ sigact.sa_flags = 0;
+ sigign.sa_handler = SIG_IGN;
+ sigaction(SIGINT, &sigact, NULL);
+ sigaction(SIGTERM, &sigact, NULL);
+ sigaction(SIGQUIT, &sigact, NULL);
+ sigaction(SIGPIPE, &sigign, NULL);
+#else
+ SetConsoleCtrlHandler( (PHANDLER_ROUTINE) sighandler, TRUE );
+#endif
+
+ iq_modulator();
+
+out:
+ fl2k_close(dev);
+
+ if (file != stdin)
+ fclose(file);
+
+ free(ampbuf);
+ free(slopebuf);
+ free(buf1);
+ free(buf2);
+
+ return 0;
+}
diff --git a/src/fl2k_tcp.c b/src/fl2k_tcp.c
index bd01758..871ac26 100644
--- a/src/fl2k_tcp.c
+++ b/src/fl2k_tcp.c
@@ -107,6 +107,12 @@ void fl2k_callback(fl2k_data_info_t *data_info)
int r;
struct timeval tv = { 1, 0 };
+ if (data_info->device_error) {
+ fprintf(stderr, "Device error, exiting.\n");
+ do_exit = 1;
+ return;
+ }
+
if (!connected)
return;
@@ -122,6 +128,12 @@ void fl2k_callback(fl2k_data_info_t *data_info)
if (r) {
received = recv(sock, txbuf + (FL2K_BUF_LEN - left), left, 0);
+ if (!received) {
+ fprintf(stderr, "Connection was closed!\n");
+ fl2k_stop_tx(dev);
+ do_exit = 1;
+ }
+
left -= received;
}
}
@@ -233,8 +245,8 @@ int main(int argc, char **argv)
sleep_ms(500);
out:
- free(txbuf);
fl2k_close(dev);
+ free(txbuf);
closesocket(s);
#ifdef _WIN32
WSACleanup();
diff --git a/src/fl2k_test.c b/src/fl2k_test.c
index 6d82922..b166dda 100644
--- a/src/fl2k_test.c
+++ b/src/fl2k_test.c
@@ -213,6 +213,12 @@ static void ppm_test(uint32_t len)
void fl2k_callback(fl2k_data_info_t *data_info)
{
+ if (data_info->device_error) {
+ fprintf(stderr, "Device error, exiting.\n");
+ do_exit = 1;
+ return;
+ }
+
/* drop first couple of callbacks until everything is settled */
if (cb_cnt > 20) {
ppm_test(FL2K_BUF_LEN);
@@ -222,7 +228,6 @@ void fl2k_callback(fl2k_data_info_t *data_info)
data_info->r_buf = buffer;
cb_cnt++;
}
-
}
int main(int argc, char **argv)
@@ -295,9 +300,6 @@ int main(int argc, char **argv)
while (!do_exit)
sleep_ms(500);
- if (do_exit)
- fprintf(stderr, "\nUser cancel, exiting...\n");
-
exit:
fl2k_close(dev);
free(buffer);
diff --git a/src/libosmo-fl2k.c b/src/libosmo-fl2k.c
index c5959c3..3e166e0 100644
--- a/src/libosmo-fl2k.c
+++ b/src/libosmo-fl2k.c
@@ -2,7 +2,7 @@
* osmo-fl2k, turns FL2000-based USB 3.0 to VGA adapters into
* low cost DACs
*
- * Copyright (C) 2016-2018 by Steve Markgraf <steve@steve-m.de>
+ * Copyright (C) 2016-2020 by Steve Markgraf <steve@steve-m.de>
*
* SPDX-License-Identifier: GPL-2.0+
*
@@ -250,9 +250,9 @@ int fl2k_set_sample_rate(fl2k_dev_t *dev, uint32_t target_freq)
error = sample_clock - (double)target_freq;
/* Keep closest match */
- if (fabsf(error) < last_error) {
+ if (fabs(error) < last_error) {
result_reg = reg;
- last_error = fabsf(error);
+ last_error = fabs(error);
}
}
}
@@ -262,7 +262,7 @@ int fl2k_set_sample_rate(fl2k_dev_t *dev, uint32_t target_freq)
error = sample_clock - (double)target_freq;
dev->rate = sample_clock;
- if (fabsf(error) > 1)
+ if (fabs(error) > 1)
fprintf(stderr, "Requested sample rate %d not possible, using"
" %f, error is %f\n", target_freq, sample_clock, error);
@@ -444,11 +444,16 @@ int fl2k_open(fl2k_dev_t **out_dev, uint32_t index)
fprintf(stderr, "usb_claim_interface 0 error %d\n", r);
goto err;
}
- r = libusb_claim_interface(dev->devh, 1);
+ r = libusb_set_interface_alt_setting(dev->devh, 0, 1);
if (r < 0) {
- fprintf(stderr, "usb_claim_interface 1 error %d\n", r);
- goto err;
+ fprintf(stderr, "Failed to switch interface 0 to "
+ "altsetting 1, trying to use interface 1\n");
+
+ r = libusb_claim_interface(dev->devh, 1);
+ if (r < 0) {
+ fprintf(stderr, "Could not claim interface 1: %d\n", r);
+ }
}
r = fl2k_init_device(dev);
@@ -529,6 +534,7 @@ static void LIBUSB_CALL _libusb_callback(struct libusb_transfer *xfer)
fl2k_xfer_info_t *next_xfer_info;
fl2k_dev_t *dev = (fl2k_dev_t *)xfer_info->dev;
struct libusb_transfer *next_xfer = NULL;
+ int r = 0;
if (LIBUSB_TRANSFER_COMPLETED == xfer->status) {
/* resubmit transfer */
@@ -541,8 +547,7 @@ static void LIBUSB_CALL _libusb_callback(struct libusb_transfer *xfer)
/* Submit next filled transfer */
next_xfer_info->state = BUF_SUBMITTED;
- libusb_submit_transfer(next_xfer);
-
+ r = libusb_submit_transfer(next_xfer);
xfer_info->state = BUF_EMPTY;
pthread_cond_signal(&dev->buf_cond);
} else {
@@ -551,17 +556,21 @@ static void LIBUSB_CALL _libusb_callback(struct libusb_transfer *xfer)
* stops to output data and hangs
* (happens only in the hacked 'gapless'
* mode without HSYNC and VSYNC) */
- libusb_submit_transfer(xfer);
+ r = libusb_submit_transfer(xfer);
pthread_cond_signal(&dev->buf_cond);
dev->underflow_cnt++;
}
}
- } else if (LIBUSB_TRANSFER_CANCELLED != xfer->status) {
+ }
+
+ if (((LIBUSB_TRANSFER_CANCELLED != xfer->status) &&
+ (LIBUSB_TRANSFER_COMPLETED != xfer->status)) ||
+ (r == LIBUSB_ERROR_NO_DEVICE)) {
dev->dev_lost = 1;
fl2k_stop_tx(dev);
pthread_cond_signal(&dev->buf_cond);
- fprintf(stderr, "cb transfer status: %d, "
- "canceling...\n", xfer->status);
+ fprintf(stderr, "cb transfer status: %d, submit "
+ "transfer %d, canceling...\n", xfer->status, r);
}
}
@@ -569,6 +578,10 @@ static int fl2k_alloc_submit_transfers(fl2k_dev_t *dev)
{
unsigned int i;
int r = 0;
+ const char *incr_usbfs = "Please increase your allowed usbfs buffer"
+ " size with the following command:\n"
+ "echo 0 > /sys/module/usbcore/parameters/"
+ "usbfs_memory_mb\n";
if (!dev)
return FL2K_ERROR_INVALID_PARAM;
@@ -609,8 +622,9 @@ static int fl2k_alloc_submit_transfers(fl2k_dev_t *dev)
}
} else {
fprintf(stderr, "Failed to allocate zero-copy "
- "buffer for transfer %d\nFalling "
- "back to buffers in userspace\n", i);
+ "buffer for transfer %d\n%sFalling "
+ "back to buffers in userspace\n",
+ i, incr_usbfs);
dev->use_zerocopy = 0;
break;
}
@@ -664,12 +678,8 @@ static int fl2k_alloc_submit_transfers(fl2k_dev_t *dev)
dev->xfer_info[i].state = BUF_SUBMITTED;
if (r < 0) {
- fprintf(stderr, "Failed to submit transfer %i\n"
- "Please increase your allowed "
- "usbfs buffer size with the "
- "following command:\n"
- "echo 0 > /sys/module/usbcore"
- "/parameters/usbfs_memory_mb\n", i);
+ fprintf(stderr, "Failed to submit transfer %i\n%s",
+ i, incr_usbfs);
break;
}
}
@@ -777,6 +787,11 @@ static void *fl2k_usb_worker(void *arg)
}
}
+ /* wake up sample worker */
+ pthread_cond_signal(&dev->buf_cond);
+
+ /* wait for sample worker thread to finish before freeing buffers */
+ pthread_join(dev->sample_worker_thread, NULL);
_fl2k_free_async_buffers(dev);
dev->async_status = next_status;
@@ -954,7 +969,6 @@ static void *fl2k_sample_worker(void *arg)
if (dev->dev_lost && dev->cb) {
data_info.device_error = 1;
dev->cb(&data_info);
- fl2k_stop_tx(dev);
}
pthread_exit(NULL);
diff --git a/src/rds_mod.c b/src/rds_mod.c
index 8e31b0d..687aa65 100644
--- a/src/rds_mod.c
+++ b/src/rds_mod.c
@@ -38,8 +38,8 @@ extern double waveform_biphase[576];
struct {
uint16_t pi;
int ta;
- char ps[PS_LENGTH];
- char rt[RT_LENGTH];
+ char ps[PS_LENGTH+1];
+ char rt[RT_LENGTH+1];
} rds_params = { 0 };
/* The RDS error-detection code generator polynomial is