From fb206158f9ba8b426ca7c8fb180f4fc324ff208a Mon Sep 17 00:00:00 2001 From: Oliver Smith Date: Wed, 24 Jul 2019 10:57:39 +0200 Subject: contrib/jenkins.sh: osmo-fl2k build verification Related: OS#3203 Signed-off-by: Oliver Smith Signed-off-by: Steve Markgraf --- contrib/jenkins.sh | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100755 contrib/jenkins.sh 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 -- cgit v1.2.3 From d56968352c626dda51552bb469ac23a8964d6d29 Mon Sep 17 00:00:00 2001 From: Steve Markgraf Date: Sun, 25 Aug 2019 17:29:34 +0200 Subject: fl2k_fm: make inline functions static Otherwise the linker will complain when building with -DCMAKE_BUILD_TYPE=Debug --- src/fl2k_fm.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/fl2k_fm.c b/src/fl2k_fm.c index 4aae95b..5d14fba 100644 --- a/src/fl2k_fm.c +++ b/src/fl2k_fm.c @@ -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,7 +283,7 @@ 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; -- cgit v1.2.3 From 6c21e9d01f8169643de76a0342a1f49a52212cfc Mon Sep 17 00:00:00 2001 From: Steve Markgraf Date: Sun, 25 Aug 2019 17:31:09 +0200 Subject: fl2k_file: check read return value before repeating In case of a FIFO that has been closed or a 0 byte file this will otherwise lead to an endless loop. --- src/fl2k_file.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fl2k_file.c b/src/fl2k_file.c index cff5178..80ddcad 100644 --- a/src/fl2k_file.c +++ b/src/fl2k_file.c @@ -95,7 +95,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); -- cgit v1.2.3 From b8d33bfd820953982093b7a7db154a58d7f7bd9e Mon Sep 17 00:00:00 2001 From: Steve Markgraf Date: Sun, 25 Aug 2019 17:34:20 +0200 Subject: lib: wait for sample worker thread before freeing buffers This fixes a segfault when exiting, as sometimes it occured that the USB worker thread completed before the sample worker thread, and the buffers the latter was still accessing had already been freed. --- src/libosmo-fl2k.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libosmo-fl2k.c b/src/libosmo-fl2k.c index 7748238..0b03254 100644 --- a/src/libosmo-fl2k.c +++ b/src/libosmo-fl2k.c @@ -777,6 +777,8 @@ static void *fl2k_usb_worker(void *arg) } } + /* 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; -- cgit v1.2.3 From 077613efc501195f4ad0018e9d94f848654f6c3e Mon Sep 17 00:00:00 2001 From: Steve Markgraf Date: Mon, 26 Aug 2019 19:26:30 +0200 Subject: improve exit handling on device removal --- src/fl2k_file.c | 6 ++++++ src/fl2k_fm.c | 1 + src/fl2k_tcp.c | 12 ++++++++++++ src/fl2k_test.c | 10 ++++++---- src/libosmo-fl2k.c | 17 ++++++++++------- 5 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/fl2k_file.c b/src/fl2k_file.c index 80ddcad..5ae199b 100644 --- a/src/fl2k_file.c +++ b/src/fl2k_file.c @@ -85,6 +85,12 @@ void fl2k_callback(fl2k_data_info_t *data_info) int r, left = FL2K_BUF_LEN; 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; data_info->r_buf = txbuf; diff --git a/src/fl2k_fm.c b/src/fl2k_fm.c index 5d14fba..f947fda 100644 --- a/src/fl2k_fm.c +++ b/src/fl2k_fm.c @@ -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); } diff --git a/src/fl2k_tcp.c b/src/fl2k_tcp.c index bd01758..1fa9e5d 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; } } 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 0b03254..afc4d4f 100644 --- a/src/libosmo-fl2k.c +++ b/src/libosmo-fl2k.c @@ -529,6 +529,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 +542,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 +551,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); } } @@ -919,7 +923,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); -- cgit v1.2.3 From c196faa0095530c126de8f585a944be18f79b104 Mon Sep 17 00:00:00 2001 From: Steve Markgraf Date: Mon, 26 Aug 2019 19:27:11 +0200 Subject: fl2k_tcp: don't free buffer while lib still uses it This sometimes caused a segfault when terminating fl2k_tcp. --- src/fl2k_tcp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fl2k_tcp.c b/src/fl2k_tcp.c index 1fa9e5d..871ac26 100644 --- a/src/fl2k_tcp.c +++ b/src/fl2k_tcp.c @@ -245,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(); -- cgit v1.2.3 From 22ca7ecab52ac0f657568e99a662c21849fc593d Mon Sep 17 00:00:00 2001 From: Felix Erckenbrecht Date: Fri, 1 Nov 2019 14:41:44 +0100 Subject: Rename delta_freq to deviation --- src/fl2k_fm.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/fl2k_fm.c b/src/fl2k_fm.c index f947fda..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; @@ -289,7 +289,7 @@ static inline double modulate_sample(int lastwritepos, double lastfreq, double s /* 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 @@ -468,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); -- cgit v1.2.3 From eba517239291033433a5f6f16be53cc9b89a3ae3 Mon Sep 17 00:00:00 2001 From: Felix Erckenbrecht Date: Sat, 2 Nov 2019 04:02:59 +0100 Subject: Include garage door opener (12 Bit code) --- src/CMakeLists.txt | 11 +- src/fl2k_garage.c | 526 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 536 insertions(+), 1 deletion(-) create mode 100644 src/fl2k_garage.c diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f63780a..5e9dcb1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -79,7 +79,8 @@ 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) +set(INSTALL_TARGETS libosmo-fl2k_shared libosmo-fl2k_static fl2k_file fl2k_tcp fl2k_test fl2k_fm fl2k_garage) target_link_libraries(fl2k_file libosmo-fl2k_shared ${LIBUSB_LIBRARIES} @@ -101,10 +102,16 @@ target_link_libraries(fl2k_fm libosmo-fl2k_shared ${CMAKE_THREAD_LIBS_INIT} ) +target_link_libraries(fl2k_garage libosmo-fl2k_shared + ${LIBUSB_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} +) + if(UNIX) target_link_libraries(fl2k_test m) target_link_libraries(fl2k_fm m) +target_link_libraries(fl2k_garage m) endif() if(WIN32 AND NOT MINGW) @@ -112,10 +119,12 @@ 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) 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" ) endif() if(MINGW) diff --git a/src/fl2k_garage.c b/src/fl2k_garage.c new file mode 100644 index 0000000..6eb0057 --- /dev/null +++ b/src/fl2k_garage.c @@ -0,0 +1,526 @@ +/* + * osmo-fl2k, turns FL2000-based USB 3.0 to VGA adapters into + * low cost DACs + * + * Copyright (C) 2019 by Felix Erckenbrecht + * + * based on fl2k_fm code by: + * Copyright (C) 2016-2018 by Steve Markgraf + * + * based on FM modulator code from VGASIG: + * Copyright (C) 2009 by Bartek Kania + * + * 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 . + */ + +#include +#include +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#include +#else +#include +#include +#include +#include "getopt/getopt.h" +#endif + +#include +#include + +#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 1024 + +#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) + +fl2k_dev_t *dev = NULL; +int do_exit = 0; + +pthread_t fm_thread; +pthread_t am_thread; +pthread_mutex_t cb_mutex; +pthread_mutex_t fm_mutex; +pthread_mutex_t am_mutex; +pthread_cond_t cb_cond; +pthread_cond_t fm_cond; +pthread_cond_t am_cond; + +FILE *file; +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(&fm_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(&fm_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; + + /* + * 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++) { + if( ++b >= BASEBAND_SAMPLES_PER_CHIP ){ + b = 0; + if(counter < (BASEBAND_SPACE_LOW_CHIPS)){ + sample = 0; + } + else if(counter < (BASEBAND_CHIPS_PER_SPACE)){ + // synch symbol + sample = 1; + // reload code + code = code_input; + } + else{ + int m; + m = counter % BASEBAND_CHIPS_PER_BIT; + if(m == 0){ + sample = 0; + } + else if(m == 1){ + sample = (code & 1) ^ 1; + } + else{ + sample = 1; + code >>= 1; + } + } + if(++counter >= (BASEBAND_CHIPS_PER_SPACE + BASEBAND_CHIPS_PER_WORD)){ + counter = 0; + } + } + /* Modulate and buffer the sample */ + lastamp = modulate_sample_am(lastwritepos, lastamp, sample); + lastwritepos = writepos++; + writepos %= BUFFER_SAMPLES; + } + } else { + pthread_cond_wait(&fm_cond, &fm_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_cond_signal(&fm_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)); + 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(&fm_mutex, NULL); + pthread_mutex_init(&am_mutex, NULL); + pthread_cond_init(&cb_cond, NULL); + pthread_cond_init(&fm_cond, NULL); + pthread_cond_init(&am_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(&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 : %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); + + if (file != stdin) + fclose(file); + + free(ampbuf); + free(slopebuf); + free(buf1); + free(buf2); + + return 0; +} -- cgit v1.2.3 From 0a7da5e72a2cecfc63f45731b3cf46e89d00656d Mon Sep 17 00:00:00 2001 From: Felix Erckenbrecht Date: Sat, 2 Nov 2019 20:38:41 +0100 Subject: Precalculate baseband samples --- src/fl2k_garage.c | 92 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 57 insertions(+), 35 deletions(-) diff --git a/src/fl2k_garage.c b/src/fl2k_garage.c index 6eb0057..575c3ad 100644 --- a/src/fl2k_garage.c +++ b/src/fl2k_garage.c @@ -54,7 +54,7 @@ #define BUFFER_SAMPLES (1 << BUFFER_SAMPLES_SHIFT) #define BUFFER_SAMPLES_MASK ((1 << BUFFER_SAMPLES_SHIFT)-1) -#define AUDIO_BUF_SIZE 1024 +#define AUDIO_BUF_SIZE 4096 #define BASEBAND_SAMPLES_PER_CHIP 3 #define BASEBAND_WORD_BITS 12 @@ -63,6 +63,7 @@ #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; @@ -76,7 +77,9 @@ pthread_cond_t cb_cond; pthread_cond_t fm_cond; pthread_cond_t am_cond; -FILE *file; +int16_t *sample_buf; +int sample_buf_size; + int8_t *txbuf = NULL; int8_t *ambuf = NULL; int8_t *buf1 = NULL; @@ -313,6 +316,7 @@ void am_modulator(const int code_input) int32_t lastamp = 0; uint32_t lastwritepos = writepos; int16_t sample = 0; + int samplebuf_pos = 0; /* * 3*640 us = 1,92 ms pro Symbol @@ -329,36 +333,11 @@ void am_modulator(const int code_input) do_exit = 1; for (i = 0; i < len; i++) { - if( ++b >= BASEBAND_SAMPLES_PER_CHIP ){ - b = 0; - if(counter < (BASEBAND_SPACE_LOW_CHIPS)){ - sample = 0; - } - else if(counter < (BASEBAND_CHIPS_PER_SPACE)){ - // synch symbol - sample = 1; - // reload code - code = code_input; - } - else{ - int m; - m = counter % BASEBAND_CHIPS_PER_BIT; - if(m == 0){ - sample = 0; - } - else if(m == 1){ - sample = (code & 1) ^ 1; - } - else{ - sample = 1; - code >>= 1; - } - } - if(++counter >= (BASEBAND_CHIPS_PER_SPACE + BASEBAND_CHIPS_PER_WORD)){ - counter = 0; - } - } /* 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; @@ -369,6 +348,49 @@ void am_modulator(const int code_input) } } +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 |= code_input & (1<>= 1; + } + } + sbuf[counter * BASEBAND_SAMPLES_PER_CHIP + b] = sample; + } + } +} + void fl2k_callback(fl2k_data_info_t *data_info) { if (data_info->device_error) { @@ -453,6 +475,7 @@ int main(int argc, char **argv) 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); readpos = 0; writepos = 1; @@ -470,6 +493,8 @@ int main(int argc, char **argv) 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); @@ -495,7 +520,7 @@ int main(int argc, char **argv) /* Calculate needed constants */ carrier_per_signal = (int)((double) samp_rate * chiptime_us/(1000000*BASEBAND_SAMPLES_PER_CHIP) + 0.5); - printf("Cps : %d\n", carrier_per_signal); + printf("Cps :\t%d\n", carrier_per_signal); #ifndef _WIN32 sigact.sa_handler = sighandler; sigemptyset(&sigact.sa_mask); @@ -514,9 +539,6 @@ int main(int argc, char **argv) out: fl2k_close(dev); - if (file != stdin) - fclose(file); - free(ampbuf); free(slopebuf); free(buf1); -- cgit v1.2.3 From bb6d47bb0d3a77b889174fe2e9df223b540505c0 Mon Sep 17 00:00:00 2001 From: Felix Erckenbrecht Date: Sat, 2 Nov 2019 20:40:32 +0100 Subject: Precalculate baseband samples --- src/fl2k_garage.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fl2k_garage.c b/src/fl2k_garage.c index 575c3ad..f1c41a4 100644 --- a/src/fl2k_garage.c +++ b/src/fl2k_garage.c @@ -475,7 +475,7 @@ int main(int argc, char **argv) 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); + sample_buf = malloc((BASEBAND_SAMPLES_PER_CHIP * BASEBAND_CHIPS_TOTAL) * sizeof(int16_t)); readpos = 0; writepos = 1; -- cgit v1.2.3 From 1574d2db000356d761316e1f0043051f511512bb Mon Sep 17 00:00:00 2001 From: Felix Erckenbrecht Date: Sat, 2 Nov 2019 21:02:06 +0100 Subject: Rename mutexes --- src/fl2k_garage.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/fl2k_garage.c b/src/fl2k_garage.c index f1c41a4..e3d462b 100644 --- a/src/fl2k_garage.c +++ b/src/fl2k_garage.c @@ -68,13 +68,10 @@ fl2k_dev_t *dev = NULL; int do_exit = 0; -pthread_t fm_thread; pthread_t am_thread; pthread_mutex_t cb_mutex; -pthread_mutex_t fm_mutex; pthread_mutex_t am_mutex; pthread_cond_t cb_cond; -pthread_cond_t fm_cond; pthread_cond_t am_cond; int16_t *sample_buf; @@ -117,7 +114,7 @@ sighandler(int signum) fprintf(stderr, "Signal caught, exiting!\n"); fl2k_stop_tx(dev); do_exit = 1; - pthread_cond_signal(&fm_cond); + pthread_cond_signal(&am_cond); return TRUE; } return FALSE; @@ -128,7 +125,7 @@ static void sighandler(int signum) fprintf(stderr, "Signal caught, exiting!\n"); fl2k_stop_tx(dev); do_exit = 1; - pthread_cond_signal(&fm_cond); + pthread_cond_signal(&am_cond); } #endif @@ -343,7 +340,7 @@ void am_modulator(const int code_input) writepos %= BUFFER_SAMPLES; } } else { - pthread_cond_wait(&fm_cond, &fm_mutex); + pthread_cond_wait(&am_cond, &am_mutex); } } } @@ -396,7 +393,7 @@ 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); + pthread_cond_signal(&am_cond); } pthread_cond_signal(&cb_cond); @@ -486,10 +483,8 @@ int main(int argc, char **argv) fprintf(stderr, "Chip period:\t%d us\n", chiptime_us); pthread_mutex_init(&cb_mutex, NULL); - pthread_mutex_init(&fm_mutex, NULL); pthread_mutex_init(&am_mutex, NULL); pthread_cond_init(&cb_cond, NULL); - pthread_cond_init(&fm_cond, NULL); pthread_cond_init(&am_cond, NULL); pthread_attr_init(&attr); -- cgit v1.2.3 From 6f8e7dc277ae1f7827d16a1da8c5d3817556ec42 Mon Sep 17 00:00:00 2001 From: Felix Erckenbrecht Date: Sun, 24 Nov 2019 01:19:11 +0100 Subject: Bugfix: Generate proper baseband - code was shifted by one bit --- src/fl2k_garage.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/fl2k_garage.c b/src/fl2k_garage.c index e3d462b..d50922e 100644 --- a/src/fl2k_garage.c +++ b/src/fl2k_garage.c @@ -355,8 +355,8 @@ void prepare_baseband(const int code_input, int16_t * sbuf){ msb_first_code = 0; // change to msb first and invert for(b = 0;b<12;b++){ - msb_first_code |= code_input & (1<>= 1; + if(b == BASEBAND_SAMPLES_PER_CHIP-1){ + msb_first_code >>= 1; + } } } sbuf[counter * BASEBAND_SAMPLES_PER_CHIP + b] = sample; -- cgit v1.2.3 From d534cb69ade872bdf6acd6a9447573807c568977 Mon Sep 17 00:00:00 2001 From: Steve Markgraf Date: Sun, 19 Jan 2020 16:34:54 +0100 Subject: fix compiler warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit warning: absolute value function ‘fabsf’ given an argument of type ‘double’ but has parameter of type ‘float’ which may cause truncation of value warning: ‘__builtin_strncpy’ specified bound 64 equals destination size --- src/libosmo-fl2k.c | 6 +++--- src/rds_mod.c | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libosmo-fl2k.c b/src/libosmo-fl2k.c index afc4d4f..7e1a008 100644 --- a/src/libosmo-fl2k.c +++ b/src/libosmo-fl2k.c @@ -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); 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 -- cgit v1.2.3 From 4b72aab349ac55073208864036188ccc70e88b0a Mon Sep 17 00:00:00 2001 From: Steve Markgraf Date: Sun, 19 Jan 2020 16:36:37 +0100 Subject: set CMake policy CMP0075 if it exists Otherwise newer versions of CMake are throwing a warning. --- CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) 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") -- cgit v1.2.3 From a9f3771eb6ad4f9afb98d346dc6c54a1812d6e73 Mon Sep 17 00:00:00 2001 From: Steve Markgraf Date: Sun, 19 Jan 2020 16:45:20 +0100 Subject: lib: use interface 0 altsetting 1 instead of interface 1 This makes osmo-fl2k work again with Linux 5.5.0-rc6 or later, as the FL2000 shares the endpoints for interface 0 and 1, which is forbidden by the USB spec and the Kernel will not ignore this anymore. See: https://marc.info/?l=linux-usb&m=157944230213296&w=2 --- src/libosmo-fl2k.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libosmo-fl2k.c b/src/libosmo-fl2k.c index 7e1a008..7478893 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 + * Copyright (C) 2016-2020 by Steve Markgraf * * SPDX-License-Identifier: GPL-2.0+ * @@ -444,10 +444,10 @@ 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); + fprintf(stderr, "Error enabling IF 0 altsetting 1: %d\n", r); goto err; } -- cgit v1.2.3 From f05c961455f2c9aabce71ec3d9592199b07c47da Mon Sep 17 00:00:00 2001 From: Steve Markgraf Date: Sun, 26 Jan 2020 20:52:24 +0100 Subject: lib: fall back to iface 1 in case iface 0 altsetting 1 fails Some people on the mailing list reported that with older kernels setting interface 0 altsetting 1 does not work, fall back to the old behavior in this case for now, while still investigating the root cause of this problem. --- src/libosmo-fl2k.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/libosmo-fl2k.c b/src/libosmo-fl2k.c index 7478893..9ebe130 100644 --- a/src/libosmo-fl2k.c +++ b/src/libosmo-fl2k.c @@ -447,8 +447,13 @@ int fl2k_open(fl2k_dev_t **out_dev, uint32_t index) r = libusb_set_interface_alt_setting(dev->devh, 0, 1); if (r < 0) { - fprintf(stderr, "Error enabling IF 0 altsetting 1: %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); -- cgit v1.2.3 From f3e83976d10d6c2a54e3450dabdc278660d9ba7c Mon Sep 17 00:00:00 2001 From: Felix Erckenbrecht Date: Sat, 18 Apr 2020 17:59:45 +0200 Subject: fl2k_am working --- src/CMakeLists.txt | 11 +- src/fl2k_am.c | 499 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 509 insertions(+), 1 deletion(-) create mode 100644 src/fl2k_am.c diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5e9dcb1..72aaba1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -80,7 +80,8 @@ 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) add_executable(fl2k_garage fl2k_garage.c) -set(INSTALL_TARGETS libosmo-fl2k_shared libosmo-fl2k_static fl2k_file fl2k_tcp fl2k_test fl2k_fm fl2k_garage) +add_executable(fl2k_am fl2k_am.c) +set(INSTALL_TARGETS libosmo-fl2k_shared libosmo-fl2k_static fl2k_file fl2k_tcp fl2k_test fl2k_fm fl2k_garage fl2k_am) target_link_libraries(fl2k_file libosmo-fl2k_shared ${LIBUSB_LIBRARIES} @@ -107,11 +108,17 @@ target_link_libraries(fl2k_garage libosmo-fl2k_shared ${CMAKE_THREAD_LIBS_INIT} ) +target_link_libraries(fl2k_am libosmo-fl2k_shared + ${LIBUSB_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} +) + if(UNIX) target_link_libraries(fl2k_test m) target_link_libraries(fl2k_fm m) target_link_libraries(fl2k_garage m) +target_link_libraries(fl2k_am m) endif() if(WIN32 AND NOT MINGW) @@ -120,11 +127,13 @@ 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_am 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_am APPEND PROPERTY COMPILE_DEFINITIONS "libosmo-fl2k_STATIC" ) endif() if(MINGW) diff --git a/src/fl2k_am.c b/src/fl2k_am.c new file mode 100644 index 0000000..de9b01e --- /dev/null +++ b/src/fl2k_am.c @@ -0,0 +1,499 @@ +/* + * osmo-fl2k, turns FL2000-based USB 3.0 to VGA adapters into + * low cost DACs + * + * Copyright (C) 2016-2018 by Steve Markgraf + * + * based on FM modulator code from VGASIG: + * Copyright (C) 2009 by Bartek Kania + * + * 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 . + */ + +#include +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#include +#else +#include +#include +#include +#include "getopt/getopt.h" +#endif + +#include +#include + +#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 2048 + +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; + +FILE *file; +int8_t *txbuf = NULL; +int8_t *ambuf = NULL; +int8_t *buf1 = NULL; +int8_t *buf2 = NULL; + +uint32_t samp_rate = 100000000; + +int carrier_freq = 1440000; +int carrier_per_signal; +int input_freq = 44100; + +double mod_index = 0.9; +double *ampbuf; +double *slopebuf; +int writepos, readpos; + +void usage(void) +{ + fprintf(stderr, + "fl2k_fm, an FM modulator for FL2K VGA dongles\n\n" + "Usage:" + "\t[-d device index (default: 0)]\n" + "\t[-c carrier frequency (default: 1440 kHz)]\n" + "\t[-m Modulation index (default: 0.9)]\n" + "\t[-i input audio sample rate (default: 44100 Hz for mono FM)]\n" + "\t[-s samplerate in Hz (default: 100 MS/s)]\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(&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; + int32_t 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 = (int32_t) (amplitude * 32768.0); + 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; + int8_t amp8; + + // advance dds generator + tmp = dds->phase >> SIN_TABLE_SHIFT; + dds->phase += dds->phase_step; + dds->phase &= 0xffffffff; + + //amp = 255; + amp = dds->amplitude; // 0..15 + amp = amp * sine_table[tmp]; // 0..31 + amp8 = (int8_t) (amp >> 24); + //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 *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, 0, 1); + + 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 double modulate_sample_am(int lastwritepos, double lastamp, double sample) +{ + double amp, slope; + + /* Calculate modulator amplitude at this point to lessen + * the calculations needed in the signal generator */ + amp = 1 - ((1+sample) * mod_index)/2; + + /* 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() +{ + unsigned int i; + size_t len; + double freq; + double lastamp = 0; + int16_t audio_buf[AUDIO_BUF_SIZE]; + uint32_t lastwritepos = writepos; + double sample; + + while (!do_exit) { + len = writelen(AUDIO_BUF_SIZE); + if (len > 1) { + len = fread(audio_buf, 2, len, file); + + if (len == 0){ + if(ferror(file)){ + do_exit = 1; + } + } + + for (i = 0; i < len; i++) { + sample = (double) audio_buf[i] / 32768.0; + + /* Modulate and buffer the sample */ + lastamp = modulate_sample_am(lastwritepos, lastamp, sample); + lastwritepos = writepos++; + writepos %= BUFFER_SAMPLES; + } + } else { + pthread_cond_wait(&am_cond, &am_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_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 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, "d:c:m: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': + carrier_freq = (uint32_t)atof(optarg); + break; + case 'i': + input_freq = (uint32_t)atof(optarg); + input_freq_specified = 1; + break; + case 'm': + mod_index = atof(optarg); + break; + case 's': + samp_rate = (uint32_t)atof(optarg); + 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(double)); + ampbuf = malloc(BUFFER_SAMPLES * sizeof(double)); + readpos = 0; + writepos = 1; + + fprintf(stderr, "Samplerate:\t%3.2f MHz\n", (double)samp_rate/1000000); + fprintf(stderr, "Carrier:\t%5.0f kHz\n", (double)carrier_freq/1000); + fprintf(stderr, "Mod Index:\t%2.1f %%\n", (double)mod_index*100); + + 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); + + 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 = 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 + + am_modulator(); + +out: + fl2k_close(dev); + + if (file != stdin) + fclose(file); + + free(ampbuf); + free(slopebuf); + free(buf1); + free(buf2); + + return 0; +} -- cgit v1.2.3 From b82303f44fc8388817306235a673e0bfa671dd3f Mon Sep 17 00:00:00 2001 From: Sergey Alirzaev Date: Sat, 16 May 2020 03:28:28 +0300 Subject: lib: implement enabling RGB332 mode I've decided to provide a separate pointer for a raw buffer in case the library user comes up with a non-RGB332 use case that avoids byte rearrangement (and copying should be replaced with pulling from the user's pointer in this case) --- include/osmo-fl2k.h | 9 +++++++++ src/libosmo-fl2k.c | 26 +++++++++++++++++++------- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/include/osmo-fl2k.h b/include/osmo-fl2k.h index 02ad4ad..7aafc24 100644 --- a/include/osmo-fl2k.h +++ b/include/osmo-fl2k.h @@ -54,6 +54,7 @@ typedef struct fl2k_data_info { char *r_buf; /* pointer to red buffer */ char *g_buf; /* pointer to green buffer */ char *b_buf; /* pointer to blue buffer */ + char *raw_buf; /* pointer to pre-arranged buffer */ } fl2k_data_info_t; typedef struct fl2k_dev fl2k_dev_t; @@ -89,6 +90,14 @@ FL2K_API int fl2k_close(fl2k_dev_t *dev); */ FL2K_API int fl2k_set_sample_rate(fl2k_dev_t *dev, uint32_t target_freq); +/*! + * Set RGB332 sample format + * + * \param dev the device handle given by fl2k_open() + * \return 0 on success + */ +FL2K_API int fl2k_set_rgb332(fl2k_dev_t *dev); + /*! * Get actual sample rate the device is configured to. * diff --git a/src/libosmo-fl2k.c b/src/libosmo-fl2k.c index 9ebe130..2ac371e 100644 --- a/src/libosmo-fl2k.c +++ b/src/libosmo-fl2k.c @@ -204,6 +204,13 @@ int fl2k_deinit_device(fl2k_dev_t *dev) return r; } +int fl2k_set_rgb332(fl2k_dev_t *dev) +{ + uint32_t reg; + fl2k_read_reg(dev, 0x8004, ®); + return fl2k_write_reg(dev, 0x8004, reg | (1 << 25)); +} + static double fl2k_reg_to_freq(uint32_t reg) { double sample_clock, offset, offs_div; @@ -910,15 +917,20 @@ static void *fl2k_sample_worker(void *arg) xfer_info = (fl2k_xfer_info_t *)xfer->user_data; out_buf = (char *)xfer->buffer; - /* Re-arrange and copy bytes in buffer for DACs */ - fl2k_convert_r(out_buf, data_info.r_buf, dev->xfer_buf_len, - data_info.sampletype_signed ? 128 : 0); + if (data_info.raw_buf) { + /* Shove a pre-arranged buffer into the DACs */ + memcpy(out_buf, data_info.raw_buf, dev->xfer_buf_len); + } else { + /* Re-arrange and copy bytes in buffer for DACs */ + fl2k_convert_r(out_buf, data_info.r_buf, dev->xfer_buf_len, + data_info.sampletype_signed ? 128 : 0); - fl2k_convert_g(out_buf, data_info.g_buf, dev->xfer_buf_len, - data_info.sampletype_signed ? 128 : 0); + fl2k_convert_g(out_buf, data_info.g_buf, dev->xfer_buf_len, + data_info.sampletype_signed ? 128 : 0); - fl2k_convert_b(out_buf, data_info.b_buf, dev->xfer_buf_len, - data_info.sampletype_signed ? 128 : 0); + fl2k_convert_b(out_buf, data_info.b_buf, dev->xfer_buf_len, + data_info.sampletype_signed ? 128 : 0); + } xfer_info->seq = buf_cnt++; xfer_info->state = BUF_FILLED; -- cgit v1.2.3 From 3f44f8fc224c6b0b45a824109619f993e0b87552 Mon Sep 17 00:00:00 2001 From: Sergey Alirzaev Date: Sat, 16 May 2020 03:28:29 +0300 Subject: lib: added utility macros for rgb332 mode --- include/osmo-fl2k.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/osmo-fl2k.h b/include/osmo-fl2k.h index 7aafc24..f88ecdf 100644 --- a/include/osmo-fl2k.h +++ b/include/osmo-fl2k.h @@ -70,6 +70,13 @@ typedef struct fl2k_dev fl2k_dev_t; #define FL2K_BUF_LEN (1280 * 1024) #define FL2K_XFER_LEN (FL2K_BUF_LEN * 3) +/** Utility macros for 8 bit per sample mode */ +#define RGB332_TO_R(x) (((x) & 3) << 6) +#define RGB332_TO_G(x) (((x) & 7) << 3) +#define RGB332_TO_B(x) (((x) & 7) << 0) +#define RGB332_TO_RGB(r, g, b) (TO_R(r) | TO_G(g) | TO_B(b)) + + FL2K_API uint32_t fl2k_get_device_count(void); FL2K_API const char* fl2k_get_device_name(uint32_t index); -- cgit v1.2.3 From 58de0a5bccb6648ee3372643517ea51b3ab5b523 Mon Sep 17 00:00:00 2001 From: Sergey Alirzaev Date: Fri, 22 May 2020 20:02:07 +0300 Subject: lib: fixed rgb332 macros --- include/osmo-fl2k.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/osmo-fl2k.h b/include/osmo-fl2k.h index f88ecdf..17c7e1f 100644 --- a/include/osmo-fl2k.h +++ b/include/osmo-fl2k.h @@ -74,7 +74,7 @@ typedef struct fl2k_dev fl2k_dev_t; #define RGB332_TO_R(x) (((x) & 3) << 6) #define RGB332_TO_G(x) (((x) & 7) << 3) #define RGB332_TO_B(x) (((x) & 7) << 0) -#define RGB332_TO_RGB(r, g, b) (TO_R(r) | TO_G(g) | TO_B(b)) +#define RGB332_TO_RGB(r, g, b) (RGB332_TO_R(r) | RGB332_TO_G(g) | RGB332_TO_B(b)) FL2K_API uint32_t fl2k_get_device_count(void); -- cgit v1.2.3 From 44b6e3b9290991a27337b48106a5fe09c9d2df4e Mon Sep 17 00:00:00 2001 From: Steve Markgraf Date: Tue, 26 May 2020 22:02:09 +0200 Subject: lib: reuse hint message when zero-copy buffer alloc fails --- src/libosmo-fl2k.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/libosmo-fl2k.c b/src/libosmo-fl2k.c index 2ac371e..8d15581 100644 --- a/src/libosmo-fl2k.c +++ b/src/libosmo-fl2k.c @@ -585,6 +585,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; @@ -625,8 +629,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; } @@ -680,12 +685,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; } } -- cgit v1.2.3 From b11fc430f3d033f345f22340e62857cb544e1a2a Mon Sep 17 00:00:00 2001 From: Steve Markgraf Date: Tue, 26 May 2020 23:10:37 +0200 Subject: lib: fix hang on exit As the sample worker thread might still be waiting for a buffer, we need to wake it up first before trying to join. --- src/libosmo-fl2k.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libosmo-fl2k.c b/src/libosmo-fl2k.c index 8d15581..9b42bcd 100644 --- a/src/libosmo-fl2k.c +++ b/src/libosmo-fl2k.c @@ -794,6 +794,9 @@ 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); -- cgit v1.2.3 From 197f421c98f929d4266a09bc9d667b7869e09c27 Mon Sep 17 00:00:00 2001 From: Steve Markgraf Date: Tue, 2 Jun 2020 00:50:53 +0200 Subject: lib: revert implementing RGB233 mode We will soon have support for a palette mode, which can replicate the RGB233 mode if required, or any custom arrangement of bits per color. Furthermore, the RGB233 mode did not work correctly in this state, as we did not implement swapping of the 32 bit words. This reverts the following commits: 58de0a5bccb6648ee3372643517ea51b3ab5b523 3f44f8fc224c6b0b45a824109619f993e0b87552 b82303f44fc8388817306235a673e0bfa671dd3f --- include/osmo-fl2k.h | 16 ---------------- src/libosmo-fl2k.c | 26 +++++++------------------- 2 files changed, 7 insertions(+), 35 deletions(-) diff --git a/include/osmo-fl2k.h b/include/osmo-fl2k.h index 17c7e1f..02ad4ad 100644 --- a/include/osmo-fl2k.h +++ b/include/osmo-fl2k.h @@ -54,7 +54,6 @@ typedef struct fl2k_data_info { char *r_buf; /* pointer to red buffer */ char *g_buf; /* pointer to green buffer */ char *b_buf; /* pointer to blue buffer */ - char *raw_buf; /* pointer to pre-arranged buffer */ } fl2k_data_info_t; typedef struct fl2k_dev fl2k_dev_t; @@ -70,13 +69,6 @@ typedef struct fl2k_dev fl2k_dev_t; #define FL2K_BUF_LEN (1280 * 1024) #define FL2K_XFER_LEN (FL2K_BUF_LEN * 3) -/** Utility macros for 8 bit per sample mode */ -#define RGB332_TO_R(x) (((x) & 3) << 6) -#define RGB332_TO_G(x) (((x) & 7) << 3) -#define RGB332_TO_B(x) (((x) & 7) << 0) -#define RGB332_TO_RGB(r, g, b) (RGB332_TO_R(r) | RGB332_TO_G(g) | RGB332_TO_B(b)) - - FL2K_API uint32_t fl2k_get_device_count(void); FL2K_API const char* fl2k_get_device_name(uint32_t index); @@ -97,14 +89,6 @@ FL2K_API int fl2k_close(fl2k_dev_t *dev); */ FL2K_API int fl2k_set_sample_rate(fl2k_dev_t *dev, uint32_t target_freq); -/*! - * Set RGB332 sample format - * - * \param dev the device handle given by fl2k_open() - * \return 0 on success - */ -FL2K_API int fl2k_set_rgb332(fl2k_dev_t *dev); - /*! * Get actual sample rate the device is configured to. * diff --git a/src/libosmo-fl2k.c b/src/libosmo-fl2k.c index 9b42bcd..99dcd33 100644 --- a/src/libosmo-fl2k.c +++ b/src/libosmo-fl2k.c @@ -204,13 +204,6 @@ int fl2k_deinit_device(fl2k_dev_t *dev) return r; } -int fl2k_set_rgb332(fl2k_dev_t *dev) -{ - uint32_t reg; - fl2k_read_reg(dev, 0x8004, ®); - return fl2k_write_reg(dev, 0x8004, reg | (1 << 25)); -} - static double fl2k_reg_to_freq(uint32_t reg) { double sample_clock, offset, offs_div; @@ -921,20 +914,15 @@ static void *fl2k_sample_worker(void *arg) xfer_info = (fl2k_xfer_info_t *)xfer->user_data; out_buf = (char *)xfer->buffer; - if (data_info.raw_buf) { - /* Shove a pre-arranged buffer into the DACs */ - memcpy(out_buf, data_info.raw_buf, dev->xfer_buf_len); - } else { - /* Re-arrange and copy bytes in buffer for DACs */ - fl2k_convert_r(out_buf, data_info.r_buf, dev->xfer_buf_len, - data_info.sampletype_signed ? 128 : 0); + /* Re-arrange and copy bytes in buffer for DACs */ + fl2k_convert_r(out_buf, data_info.r_buf, dev->xfer_buf_len, + data_info.sampletype_signed ? 128 : 0); - fl2k_convert_g(out_buf, data_info.g_buf, dev->xfer_buf_len, - data_info.sampletype_signed ? 128 : 0); + fl2k_convert_g(out_buf, data_info.g_buf, dev->xfer_buf_len, + data_info.sampletype_signed ? 128 : 0); - fl2k_convert_b(out_buf, data_info.b_buf, dev->xfer_buf_len, - data_info.sampletype_signed ? 128 : 0); - } + fl2k_convert_b(out_buf, data_info.b_buf, dev->xfer_buf_len, + data_info.sampletype_signed ? 128 : 0); xfer_info->seq = buf_cnt++; xfer_info->state = BUF_FILLED; -- cgit v1.2.3 From b28432638b2b80e5540f9d114fc791a1751215b5 Mon Sep 17 00:00:00 2001 From: Felix Erckenbrecht Date: Sun, 26 Jul 2020 13:48:15 +0200 Subject: Refactor AM modulator into IQ modulator (work in progress) --- src/CMakeLists.txt | 22 +-- src/fl2k_am.c | 499 ---------------------------------------------------- src/fl2k_iq.c | 507 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 518 insertions(+), 510 deletions(-) delete mode 100644 src/fl2k_am.c create mode 100644 src/fl2k_iq.c diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 72aaba1..543226e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -80,35 +80,35 @@ 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) add_executable(fl2k_garage fl2k_garage.c) -add_executable(fl2k_am fl2k_am.c) -set(INSTALL_TARGETS libosmo-fl2k_shared libosmo-fl2k_static fl2k_file fl2k_tcp fl2k_test fl2k_fm fl2k_garage fl2k_am) +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 +target_link_libraries(fl2k_garage libosmo-fl2k_shared ${LIBUSB_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ) -target_link_libraries(fl2k_am libosmo-fl2k_shared +target_link_libraries(fl2k_iq libosmo-fl2k_shared ${LIBUSB_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ) @@ -118,7 +118,7 @@ if(UNIX) target_link_libraries(fl2k_test m) target_link_libraries(fl2k_fm m) target_link_libraries(fl2k_garage m) -target_link_libraries(fl2k_am m) +target_link_libraries(fl2k_iq m) endif() if(WIN32 AND NOT MINGW) @@ -127,13 +127,13 @@ 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_am 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_am 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_am.c b/src/fl2k_am.c deleted file mode 100644 index de9b01e..0000000 --- a/src/fl2k_am.c +++ /dev/null @@ -1,499 +0,0 @@ -/* - * osmo-fl2k, turns FL2000-based USB 3.0 to VGA adapters into - * low cost DACs - * - * Copyright (C) 2016-2018 by Steve Markgraf - * - * based on FM modulator code from VGASIG: - * Copyright (C) 2009 by Bartek Kania - * - * 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 . - */ - -#include -#include -#include -#include -#include - -#ifndef _WIN32 -#include -#include -#include -#else -#include -#include -#include -#include "getopt/getopt.h" -#endif - -#include -#include - -#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 2048 - -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; - -FILE *file; -int8_t *txbuf = NULL; -int8_t *ambuf = NULL; -int8_t *buf1 = NULL; -int8_t *buf2 = NULL; - -uint32_t samp_rate = 100000000; - -int carrier_freq = 1440000; -int carrier_per_signal; -int input_freq = 44100; - -double mod_index = 0.9; -double *ampbuf; -double *slopebuf; -int writepos, readpos; - -void usage(void) -{ - fprintf(stderr, - "fl2k_fm, an FM modulator for FL2K VGA dongles\n\n" - "Usage:" - "\t[-d device index (default: 0)]\n" - "\t[-c carrier frequency (default: 1440 kHz)]\n" - "\t[-m Modulation index (default: 0.9)]\n" - "\t[-i input audio sample rate (default: 44100 Hz for mono FM)]\n" - "\t[-s samplerate in Hz (default: 100 MS/s)]\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(&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; - int32_t 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 = (int32_t) (amplitude * 32768.0); - 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; - int8_t amp8; - - // advance dds generator - tmp = dds->phase >> SIN_TABLE_SHIFT; - dds->phase += dds->phase_step; - dds->phase &= 0xffffffff; - - //amp = 255; - amp = dds->amplitude; // 0..15 - amp = amp * sine_table[tmp]; // 0..31 - amp8 = (int8_t) (amp >> 24); - //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 *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, 0, 1); - - 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 double modulate_sample_am(int lastwritepos, double lastamp, double sample) -{ - double amp, slope; - - /* Calculate modulator amplitude at this point to lessen - * the calculations needed in the signal generator */ - amp = 1 - ((1+sample) * mod_index)/2; - - /* 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() -{ - unsigned int i; - size_t len; - double freq; - double lastamp = 0; - int16_t audio_buf[AUDIO_BUF_SIZE]; - uint32_t lastwritepos = writepos; - double sample; - - while (!do_exit) { - len = writelen(AUDIO_BUF_SIZE); - if (len > 1) { - len = fread(audio_buf, 2, len, file); - - if (len == 0){ - if(ferror(file)){ - do_exit = 1; - } - } - - for (i = 0; i < len; i++) { - sample = (double) audio_buf[i] / 32768.0; - - /* Modulate and buffer the sample */ - lastamp = modulate_sample_am(lastwritepos, lastamp, sample); - lastwritepos = writepos++; - writepos %= BUFFER_SAMPLES; - } - } else { - pthread_cond_wait(&am_cond, &am_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_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 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, "d:c:m: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': - carrier_freq = (uint32_t)atof(optarg); - break; - case 'i': - input_freq = (uint32_t)atof(optarg); - input_freq_specified = 1; - break; - case 'm': - mod_index = atof(optarg); - break; - case 's': - samp_rate = (uint32_t)atof(optarg); - 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(double)); - ampbuf = malloc(BUFFER_SAMPLES * sizeof(double)); - readpos = 0; - writepos = 1; - - fprintf(stderr, "Samplerate:\t%3.2f MHz\n", (double)samp_rate/1000000); - fprintf(stderr, "Carrier:\t%5.0f kHz\n", (double)carrier_freq/1000); - fprintf(stderr, "Mod Index:\t%2.1f %%\n", (double)mod_index*100); - - 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); - - 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 = 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 - - am_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_iq.c b/src/fl2k_iq.c new file mode 100644 index 0000000..9821d4f --- /dev/null +++ b/src/fl2k_iq.c @@ -0,0 +1,507 @@ +/* + * osmo-fl2k, turns FL2000-based USB 3.0 to VGA adapters into + * low cost DACs + * + * fl2k-iq + * Copyright (C) 2020 by Felix Erckenbrecht + * + * based on fl2k-fm code: + * Copyright (C) 2016-2018 by Steve Markgraf + * + * based on FM modulator code from VGASIG: + * Copyright (C) 2009 by Bartek Kania + * + * 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 . + */ + +#include +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#include +#else +#include +#include +#include +#include "getopt/getopt.h" +#endif + +#include +#include +#include + +#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; +int do_exit = 0; + +pthread_t iq_thread; +pthread_mutex_t cb_mutex; +pthread_mutex_t am_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 = 100000000; + +int base_freq = 1440000; +int rf_to_baseband_sample_ratio; +int input_freq = 48000; + +complex double *ampbuf; +complex double *slopebuf; +int writepos, readpos; + +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: 100 MS/s)]\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 { + double sample_freq; + double freq; + unsigned long int phase; + unsigned long int phase_step; + complex double amplitude; + complex 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, complex double amplitude, complex 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 (!trig_table.initialized) { + double incr = 1.0 / (double)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) * 32768.0; // 0..15 + amp_q = cimag(dds->amplitude) * 32768.0; + amp_i = amp_i * trig_table.sine[tmp]; // 0..31 + amp_q = amp_q * trig_table.cosine[tmp]; // 0..31 + amp8 = (int8_t) ((amp_i + amp_q) >> 25); // 0..32 >> 25 => 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 double freq; + register double 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_cond_wait(&cb_cond, &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_cond_signal(&iq_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 complex double modulate_sample_iq(const int lastwritepos, const complex double lastamp, const complex double sample) +{ + complex double amp, 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/ (double) rf_to_baseband_sample_ratio; + slopebuf[lastwritepos] = slope; + ampbuf[writepos] = amp; + + return amp; +} + +void iq_modulator() +{ + unsigned int i; + size_t len; + double freq; + complex double lastamp = 0; + int16_t baseband_buf[BASEBAND_BUF_SIZE][2]; + uint32_t lastwritepos = writepos; + complex double sample; + + while (!do_exit) { + 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 = (double) baseband_buf[i][0] / 32768.0 + I * (double) baseband_buf[i][0] / 32768.0; + + /* Modulate and buffer the sample */ + lastamp = modulate_sample_iq(lastwritepos, lastamp, sample); + lastwritepos = writepos++; + writepos %= BUFFER_SAMPLES; + } + } else { + pthread_cond_wait(&iq_cond, &am_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_cond_signal(&iq_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 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, "d:c:m: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; + 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(double)); + ampbuf = malloc(BUFFER_SAMPLES * sizeof(double)); + readpos = 0; + writepos = 1; + + fprintf(stderr, "Samplerate:\t%3.2f MHz\n", (double)samp_rate/1000000); + fprintf(stderr, "Center frequency:\t%5.0f kHz\n", (double)base_freq/1000); + + pthread_mutex_init(&cb_mutex, NULL); + pthread_mutex_init(&am_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; +} -- cgit v1.2.3 From 9ad84bf8a87f531a2dd9b61b450ba69e060e28be Mon Sep 17 00:00:00 2001 From: Felix Erckenbrecht Date: Sun, 26 Jul 2020 14:08:47 +0200 Subject: Implement proper amplitude scaling by 1/sqrt(2) to achieve correct amplitude --- src/fl2k_iq.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/fl2k_iq.c b/src/fl2k_iq.c index 9821d4f..0ea1c77 100644 --- a/src/fl2k_iq.c +++ b/src/fl2k_iq.c @@ -200,11 +200,11 @@ static inline int8_t dds_real(dds_t *dds) dds->phase &= 0xffffffff; //amp = 255; - amp_i = creal(dds->amplitude) * 32768.0; // 0..15 - amp_q = cimag(dds->amplitude) * 32768.0; - amp_i = amp_i * trig_table.sine[tmp]; // 0..31 - amp_q = amp_q * trig_table.cosine[tmp]; // 0..31 - amp8 = (int8_t) ((amp_i + amp_q) >> 25); // 0..32 >> 25 => 0..8 + 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; } -- cgit v1.2.3 From 7d556168e7e9318a88c73ccd9e6797a8256fad5a Mon Sep 17 00:00:00 2001 From: Felix Erckenbrecht Date: Sun, 26 Jul 2020 14:15:49 +0200 Subject: Bugfix: Fixed typo that lead to I-baseband sample being used twice and Q not at all --- src/fl2k_iq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fl2k_iq.c b/src/fl2k_iq.c index 0ea1c77..b6c028e 100644 --- a/src/fl2k_iq.c +++ b/src/fl2k_iq.c @@ -326,7 +326,7 @@ void iq_modulator() } for (i = 0; i < len; i++) { - sample = (double) baseband_buf[i][0] / 32768.0 + I * (double) baseband_buf[i][0] / 32768.0; + sample = (double) baseband_buf[i][0] / 32768.0 + I * (double) baseband_buf[i][1] / 32768.0; /* Modulate and buffer the sample */ lastamp = modulate_sample_iq(lastwritepos, lastamp, sample); -- cgit v1.2.3 From b5708eb5ebbdaf2d80bc1f499ee9ae5f4ec5d7a2 Mon Sep 17 00:00:00 2001 From: Felix Erckenbrecht Date: Fri, 31 Jul 2020 09:03:46 +0200 Subject: Refactor rename mutex --- src/fl2k_iq.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/fl2k_iq.c b/src/fl2k_iq.c index b6c028e..7a604be 100644 --- a/src/fl2k_iq.c +++ b/src/fl2k_iq.c @@ -62,7 +62,7 @@ int do_exit = 0; pthread_t iq_thread; pthread_mutex_t cb_mutex; -pthread_mutex_t am_mutex; +pthread_mutex_t iq_mutex; pthread_cond_t cb_cond; pthread_cond_t iq_cond; @@ -334,7 +334,7 @@ void iq_modulator() writepos %= BUFFER_SAMPLES; } } else { - pthread_cond_wait(&iq_cond, &am_mutex); + pthread_cond_wait(&iq_cond, &iq_mutex); } } } @@ -446,7 +446,7 @@ int main(int argc, char **argv) fprintf(stderr, "Center frequency:\t%5.0f kHz\n", (double)base_freq/1000); pthread_mutex_init(&cb_mutex, NULL); - pthread_mutex_init(&am_mutex, NULL); + pthread_mutex_init(&iq_mutex, NULL); pthread_cond_init(&cb_cond, NULL); pthread_cond_init(&iq_cond, NULL); pthread_attr_init(&attr); -- cgit v1.2.3 From 86813ec92f00b34d7275204e5cc215ad6e923de6 Mon Sep 17 00:00:00 2001 From: Felix Erckenbrecht Date: Fri, 31 Jul 2020 09:05:14 +0200 Subject: Lock mutex before calling cond_wait or cond_signal --- src/fl2k_iq.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/fl2k_iq.c b/src/fl2k_iq.c index 7a604be..40cc28a 100644 --- a/src/fl2k_iq.c +++ b/src/fl2k_iq.c @@ -249,7 +249,9 @@ static void *iq_worker(void *arg) 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); @@ -260,7 +262,9 @@ static void *iq_worker(void *arg) 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); @@ -334,7 +338,9 @@ void iq_modulator() writepos %= BUFFER_SAMPLES; } } else { + pthread_mutex_lock(&iq_mutex); pthread_cond_wait(&iq_cond, &iq_mutex); + pthread_mutex_unlock(&iq_mutex); } } } @@ -345,7 +351,9 @@ 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); -- cgit v1.2.3 From 5a69084b90a8582ed33e06967441885903d67e08 Mon Sep 17 00:00:00 2001 From: Felix Erckenbrecht Date: Fri, 31 Jul 2020 09:06:20 +0200 Subject: Remove unused option --- src/fl2k_iq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fl2k_iq.c b/src/fl2k_iq.c index 40cc28a..9ca0fd7 100644 --- a/src/fl2k_iq.c +++ b/src/fl2k_iq.c @@ -382,7 +382,7 @@ int main(int argc, char **argv) }; while (1) { - opt = getopt_long(argc, argv, "d:c:m:i:s:", long_options, &option_index); + opt = getopt_long(argc, argv, "d:c:i:s:", long_options, &option_index); /* end of options reached */ if (opt == -1) -- cgit v1.2.3 From 4518a583dd2716aca9c64d0541df2946c09cf50c Mon Sep 17 00:00:00 2001 From: Felix Erckenbrecht Date: Fri, 31 Jul 2020 09:09:19 +0200 Subject: Make shared variable volatile --- src/fl2k_iq.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/fl2k_iq.c b/src/fl2k_iq.c index 9ca0fd7..64a8416 100644 --- a/src/fl2k_iq.c +++ b/src/fl2k_iq.c @@ -58,7 +58,7 @@ #define BASEBAND_BUF_SIZE 2048 fl2k_dev_t *dev = NULL; -int do_exit = 0; +volatile int do_exit = 0; pthread_t iq_thread; pthread_mutex_t cb_mutex; @@ -308,6 +308,7 @@ static inline complex double modulate_sample_iq(const int lastwritepos, const co return amp; } + void iq_modulator() { unsigned int i; -- cgit v1.2.3 From a9a67e036a8fe19fc3a2a31695c0e8227afa47b6 Mon Sep 17 00:00:00 2001 From: Felix Erckenbrecht Date: Fri, 31 Jul 2020 10:07:09 +0200 Subject: Set default samplerate to 96 MS/s --- src/fl2k_iq.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/fl2k_iq.c b/src/fl2k_iq.c index 64a8416..889ec4e 100644 --- a/src/fl2k_iq.c +++ b/src/fl2k_iq.c @@ -72,7 +72,7 @@ int8_t *ambuf = NULL; int8_t *buf1 = NULL; int8_t *buf2 = NULL; -uint32_t samp_rate = 100000000; +uint32_t samp_rate = 96000000; int base_freq = 1440000; int rf_to_baseband_sample_ratio; @@ -90,7 +90,7 @@ void usage(void) "\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: 100 MS/s)]\n" + "\t[-s samplerate in Hz (default: 96 MS/s)]\n" "\tfilename (use '-' to read from stdin)\n\n" ); exit(1); -- cgit v1.2.3 From a801b9401cde031ab90865d04089b7183568ab7a Mon Sep 17 00:00:00 2001 From: Felix Erckenbrecht Date: Fri, 31 Jul 2020 12:44:29 +0200 Subject: Bugfix: allocate correct amount of memory --- src/fl2k_iq.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/fl2k_iq.c b/src/fl2k_iq.c index 889ec4e..0cb9a2f 100644 --- a/src/fl2k_iq.c +++ b/src/fl2k_iq.c @@ -446,8 +446,8 @@ int main(int argc, char **argv) txbuf = buf2; /* Decoded audio */ - slopebuf = malloc(BUFFER_SAMPLES * sizeof(double)); - ampbuf = malloc(BUFFER_SAMPLES * sizeof(double)); + slopebuf = malloc(BUFFER_SAMPLES * sizeof(double complex)); + ampbuf = malloc(BUFFER_SAMPLES * sizeof(double complex)); readpos = 0; writepos = 1; -- cgit v1.2.3 From 107cbb6fab042c7960fb384b00329334c77ff1e9 Mon Sep 17 00:00:00 2001 From: Felix Erckenbrecht Date: Fri, 31 Jul 2020 12:45:03 +0200 Subject: Use previous amplitude as start and slope towards actual amplitude --- src/fl2k_iq.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/fl2k_iq.c b/src/fl2k_iq.c index 0cb9a2f..26fcbfe 100644 --- a/src/fl2k_iq.c +++ b/src/fl2k_iq.c @@ -302,8 +302,8 @@ static inline complex double modulate_sample_iq(const int lastwritepos, const co efficient and pretty good interpolation filter. */ slope = amp - lastamp; slope = slope * 1.0/ (double) rf_to_baseband_sample_ratio; - slopebuf[lastwritepos] = slope; - ampbuf[writepos] = amp; + slopebuf[writepos] = slope; + ampbuf[writepos] = lastamp; return amp; } -- cgit v1.2.3 From 8ff76630be0bc62f33342d6a4d880c27f0d4eebc Mon Sep 17 00:00:00 2001 From: Felix Erckenbrecht Date: Fri, 31 Jul 2020 13:36:57 +0200 Subject: Turn double to float --- src/fl2k_iq.c | 47 ++++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/src/fl2k_iq.c b/src/fl2k_iq.c index 26fcbfe..6d253e0 100644 --- a/src/fl2k_iq.c +++ b/src/fl2k_iq.c @@ -78,8 +78,8 @@ int base_freq = 1440000; int rf_to_baseband_sample_ratio; int input_freq = 48000; -complex double *ampbuf; -complex double *slopebuf; +complex float *ampbuf; +complex float *slopebuf; int writepos, readpos; void usage(void) @@ -145,27 +145,27 @@ struct trigonometric_table_S { static struct trigonometric_table_S trig_table = { .initialized = 0 }; typedef struct { - double sample_freq; - double freq; + float sample_freq; + float freq; unsigned long int phase; unsigned long int phase_step; - complex double amplitude; - complex double ampslope; + complex float amplitude; + complex float ampslope; } dds_t; -static inline void dds_set_freq(dds_t *dds, double freq) +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 double amplitude, complex double ampslope) +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(double sample_freq, double freq, double phase, double amp) +dds_t dds_init(float sample_freq, float freq, float phase, float amp) { dds_t dds; int i; @@ -176,7 +176,7 @@ dds_t dds_init(double sample_freq, double freq, double phase, double amp) dds_set_amp(&dds, amp, 0); /* Initialize sine table, prescaled for 16 bit signed integer */ if (!trig_table.initialized) { - double incr = 1.0 / (double)TRIG_TABLE_LEN; + 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; @@ -222,8 +222,8 @@ static inline void dds_real_buf(dds_t *dds, int8_t *buf, int count) * in the amp buffer */ static void *iq_worker(void *arg) { - register double freq; - register double tmp; + register float freq; + register float tmp; dds_t base_signal; int8_t *tmp_ptr; uint32_t len = 0; @@ -286,9 +286,10 @@ static inline int writelen(int maxlen) return r; } -static inline complex double modulate_sample_iq(const int lastwritepos, const complex double lastamp, const complex double sample) +static inline float complex modulate_sample_iq(const int lastwritepos, const float complex lastamp, const float complex sample) { - complex double amp, slope; + float complex amp; + float complex slope; /* Calculate modulator amplitudes at this point to lessen * the calculations needed in the signal generator */ @@ -301,7 +302,7 @@ static inline complex double modulate_sample_iq(const int lastwritepos, const co the dds parameters. In fact this gives us a very efficient and pretty good interpolation filter. */ slope = amp - lastamp; - slope = slope * 1.0/ (double) rf_to_baseband_sample_ratio; + slope = slope * 1.0/ (float) rf_to_baseband_sample_ratio; slopebuf[writepos] = slope; ampbuf[writepos] = lastamp; @@ -313,11 +314,11 @@ void iq_modulator() { unsigned int i; size_t len; - double freq; - complex double lastamp = 0; + float freq; + float complex lastamp = 0; int16_t baseband_buf[BASEBAND_BUF_SIZE][2]; uint32_t lastwritepos = writepos; - complex double sample; + float complex sample; while (!do_exit) { len = writelen(BASEBAND_BUF_SIZE); @@ -331,7 +332,7 @@ void iq_modulator() } for (i = 0; i < len; i++) { - sample = (double) baseband_buf[i][0] / 32768.0 + I * (double) baseband_buf[i][1] / 32768.0; + sample = (float) baseband_buf[i][0] / 32768.0 + I * (float) baseband_buf[i][1] / 32768.0; /* Modulate and buffer the sample */ lastamp = modulate_sample_iq(lastwritepos, lastamp, sample); @@ -446,13 +447,13 @@ int main(int argc, char **argv) txbuf = buf2; /* Decoded audio */ - slopebuf = malloc(BUFFER_SAMPLES * sizeof(double complex)); - ampbuf = malloc(BUFFER_SAMPLES * sizeof(double complex)); + slopebuf = malloc(BUFFER_SAMPLES * sizeof(float complex)); + ampbuf = malloc(BUFFER_SAMPLES * sizeof(float complex)); readpos = 0; writepos = 1; - fprintf(stderr, "Samplerate:\t%3.2f MHz\n", (double)samp_rate/1000000); - fprintf(stderr, "Center frequency:\t%5.0f kHz\n", (double)base_freq/1000); + fprintf(stderr, "Samplerate:\t%3.2f MHz\n", (float)samp_rate/1000000); + fprintf(stderr, "Center frequency:\t%5.0f kHz\n", (float)base_freq/1000); pthread_mutex_init(&cb_mutex, NULL); pthread_mutex_init(&iq_mutex, NULL); -- cgit v1.2.3 From 3387a704bea2cb33ec389b7276ef7c293701fe5e Mon Sep 17 00:00:00 2001 From: Felix Erckenbrecht Date: Fri, 31 Jul 2020 14:45:52 +0200 Subject: Implement swap I/Q option --- src/fl2k_iq.c | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/fl2k_iq.c b/src/fl2k_iq.c index 6d253e0..2dce59b 100644 --- a/src/fl2k_iq.c +++ b/src/fl2k_iq.c @@ -81,17 +81,19 @@ 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" - "\tfilename (use '-' to read from stdin)\n\n" + "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); } @@ -321,6 +323,7 @@ void iq_modulator() 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); @@ -332,7 +335,7 @@ void iq_modulator() } for (i = 0; i < len; i++) { - sample = (float) baseband_buf[i][0] / 32768.0 + I * (float) baseband_buf[i][1] / 32768.0; + 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); @@ -384,7 +387,7 @@ int main(int argc, char **argv) }; while (1) { - opt = getopt_long(argc, argv, "d:c:i:s:", long_options, &option_index); + opt = getopt_long(argc, argv, "wd:c:i:s:", long_options, &option_index); /* end of options reached */ if (opt == -1) @@ -406,6 +409,9 @@ int main(int argc, char **argv) case 's': samp_rate = (uint32_t)atof(optarg); break; + case 'w': + swap_iq = 1; + break; default: usage(); break; @@ -452,8 +458,10 @@ int main(int argc, char **argv) readpos = 0; writepos = 1; - fprintf(stderr, "Samplerate:\t%3.2f MHz\n", (float)samp_rate/1000000); - fprintf(stderr, "Center frequency:\t%5.0f kHz\n", (float)base_freq/1000); + 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); -- cgit v1.2.3