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