parent
							
								
									53775a2ebf
								
							
						
					
					
						commit
						dbc49549a0
					
				| @ -0,0 +1,870 @@ | |||||||
|  | /*
 | ||||||
|  |  * rtl-sdr, turns your Realtek RTL2832 based DVB dongle into a SDR receiver | ||||||
|  |  * Copyright (C) 2012 by Steve Markgraf <steve@steve-m.de> | ||||||
|  |  * Copyright (C) 2012 by Hoernchen <la@tfc-server.de> | ||||||
|  |  * Copyright (C) 2012 by Kyle Keen <keenerd@gmail.com> | ||||||
|  |  * | ||||||
|  |  * 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/>.
 | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  |  * rtl_power: general purpose FFT integrator | ||||||
|  |  * -f low_freq:high_freq:max_bin_size | ||||||
|  |  * -i seconds | ||||||
|  |  * outputs CSV | ||||||
|  |  * time, low, high, step, db, db, db ... | ||||||
|  |  * db optional?  raw output might be better for noise correction | ||||||
|  |  * todo: | ||||||
|  |  *	threading | ||||||
|  |  *	randomized hopping | ||||||
|  |  *	noise correction | ||||||
|  |  *	continuous IIR | ||||||
|  |  *	general astronomy usefulness | ||||||
|  |  *	multiple dongles | ||||||
|  |  *	multiple FFT workers | ||||||
|  |  *	fft bins smaller than 61Hz | ||||||
|  |  *	bandwidths smaller than 1MHz | ||||||
|  |  *	check edge cropping for off-by-one and rounding errors | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #include <errno.h> | ||||||
|  | #include <signal.h> | ||||||
|  | #include <string.h> | ||||||
|  | #include <stdio.h> | ||||||
|  | #include <stdlib.h> | ||||||
|  | #include <math.h> | ||||||
|  | #include <time.h> | ||||||
|  | 
 | ||||||
|  | #ifndef _WIN32 | ||||||
|  | #include <unistd.h> | ||||||
|  | #else | ||||||
|  | #include <Windows.h> | ||||||
|  | #include <fcntl.h> | ||||||
|  | #include <io.h> | ||||||
|  | #include "getopt/getopt.h" | ||||||
|  | #define usleep(x) Sleep(x/1000) | ||||||
|  | #define round(x) (x > 0.0 ? floor(x + 0.5): ceil(x - 0.5)) | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | #include <pthread.h> | ||||||
|  | #include <libusb.h> | ||||||
|  | 
 | ||||||
|  | #include "rtl-sdr.h" | ||||||
|  | 
 | ||||||
|  | #define DEFAULT_SAMPLE_RATE		24000 | ||||||
|  | #define DEFAULT_ASYNC_BUF_NUMBER	32 | ||||||
|  | #define DEFAULT_BUF_LENGTH		(1 * 16384) | ||||||
|  | #define MAXIMUM_OVERSAMPLE		16 | ||||||
|  | #define MAXIMUM_BUF_LENGTH		(MAXIMUM_OVERSAMPLE * DEFAULT_BUF_LENGTH) | ||||||
|  | #define AUTO_GAIN			-100 | ||||||
|  | #define BUFFER_DUMP			(1<<12) | ||||||
|  | 
 | ||||||
|  | static volatile int do_exit = 0; | ||||||
|  | static rtlsdr_dev_t *dev = NULL; | ||||||
|  | FILE *file; | ||||||
|  | 
 | ||||||
|  | int16_t* Sinewave; | ||||||
|  | double* power_table; | ||||||
|  | int N_WAVE, LOG2_N_WAVE; | ||||||
|  | int next_power; | ||||||
|  | int16_t *fft_buf; | ||||||
|  | int *window_coefs; | ||||||
|  | 
 | ||||||
|  | struct tuning_state | ||||||
|  | /* one per tuning range */ | ||||||
|  | { | ||||||
|  | 	int freq; | ||||||
|  | 	int rate; | ||||||
|  | 	int bin_e; | ||||||
|  | 	long *avg;  /* length == 2^bin_e */ | ||||||
|  | 	int samples; | ||||||
|  | 	long mega_samples; | ||||||
|  | 	//pthread_rwlock_t avg_lock;
 | ||||||
|  | 	//pthread_mutex_t avg_mutex;
 | ||||||
|  | 	/* having the iq buffer here is wasteful, but will avoid contention */ | ||||||
|  | 	uint8_t *buf8; | ||||||
|  | 	int buf_len; | ||||||
|  | 	//pthread_rwlock_t buf_lock;
 | ||||||
|  | 	//pthread_mutex_t buf_mutex;
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /* 3000 is enough for 3GHz b/w worst case */ | ||||||
|  | #define MAX_TUNES	3000 | ||||||
|  | struct tuning_state tunes[MAX_TUNES]; | ||||||
|  | int tune_count = 0; | ||||||
|  | 
 | ||||||
|  | void usage(void) | ||||||
|  | { | ||||||
|  | 	fprintf(stderr, | ||||||
|  | 		"rtl_power, a simple FFT logger for RTL2832 based DVB-T receivers\n\n" | ||||||
|  | 		"Use:\trtl_power -f freq_range [-options] [filename]\n" | ||||||
|  | 		"\t-f lower:upper:bin_size [Hz]\n" | ||||||
|  | 		"\t (bin size is a maximum, smaller more convenient bins\n" | ||||||
|  | 		"\t  will be used.  valid range 61-2M)\n" | ||||||
|  | 		"\t[-i integration_interval (default: 10 seconds)]\n" | ||||||
|  | 		"\t (buggy if a full sweep takes longer than the interval)\n" | ||||||
|  | 		"\t[-1 enables single-shot mode (default: off)]\n" | ||||||
|  | 		"\t[-e exit_timer (default: off/0)]\n" | ||||||
|  | 		//"\t[-s avg/iir smoothing (default: avg)]\n"
 | ||||||
|  | 		//"\t[-t threads (default: 1)]\n"
 | ||||||
|  | 		"\t[-d device_index (default: 0)]\n" | ||||||
|  | 		"\t[-g tuner_gain (default: automatic)]\n" | ||||||
|  | 		"\t[-p ppm_error (default: 0)]\n" | ||||||
|  | 		"\tfilename (a '-' dumps samples to stdout)\n" | ||||||
|  | 		"\t (omitting the filename also uses stdout)\n" | ||||||
|  | 		"\n" | ||||||
|  | 		"Experimental options:\n" | ||||||
|  | 		"\t[-w window (default: rectangle)]\n" | ||||||
|  | 		"\t (hamming, blackman, blackman-harris, hann-poisson, bartlett, youssef)\n" | ||||||
|  | 		// kaiser
 | ||||||
|  | 		"\t[-c crop_percent (default: 0%, recommended: 20%%-50%%)]\n" | ||||||
|  | 		"\t (discards data at the edges, 100%% discards everything)\n" | ||||||
|  | 		"\t (has no effect in rms bin mode)\n" | ||||||
|  | 		"\n" | ||||||
|  | 		"CSV FFT output columns:\n" | ||||||
|  | 		"\tdate, time, Hz low, Hz high, Hz step, samples, dbm, dbm, ...\n\n" | ||||||
|  | 		"Examples:\n" | ||||||
|  | 		"\trtl_power -f 88M:108M:125k fm_stations.csv\n" | ||||||
|  | 		"\t (creates 160 bins across the FM band,\n" | ||||||
|  | 		"\t  individual stations should be visible)\n" | ||||||
|  | 		"\trtl_power -f 100M:1G:1M -i 5m -1 survey.csv\n" | ||||||
|  | 		"\t (a five minute low res scan of nearly everything)\n" | ||||||
|  | 		"\trtl_power -f ... -i 15m -1 log.csv\n" | ||||||
|  | 		"\t (integrate for 15 minutes and exit afterwards)\n" | ||||||
|  | 		"\trtl_power -f ... -e 1h | gzip > log.csv.gz\n" | ||||||
|  | 		"\t (collect data for one hour and compress it on the fly)\n" | ||||||
|  | 		"Convert CSV to a waterfall graphic with\n" | ||||||
|  | 		"\thttp://kmkeen.com/tmp/heatmap.py.txt\n" | ||||||
|  | 		""); | ||||||
|  | 	exit(1); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #ifdef _WIN32 | ||||||
|  | BOOL WINAPI | ||||||
|  | sighandler(int signum) | ||||||
|  | { | ||||||
|  | 	if (CTRL_C_EVENT == signum) { | ||||||
|  | 		fprintf(stderr, "Signal caught, exiting!\n"); | ||||||
|  | 		do_exit = 1; | ||||||
|  | 		return TRUE; | ||||||
|  | 	} | ||||||
|  | 	return FALSE; | ||||||
|  | } | ||||||
|  | #else | ||||||
|  | static void sighandler(int signum) | ||||||
|  | { | ||||||
|  | 	fprintf(stderr, "Signal caught, exiting!\n"); | ||||||
|  | 	do_exit = 1; | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | /* more cond dumbness */ | ||||||
|  | #define safe_cond_signal(n, m) pthread_mutex_lock(m); pthread_cond_signal(n); pthread_mutex_unlock(m) | ||||||
|  | #define safe_cond_wait(n, m) pthread_mutex_lock(m); pthread_cond_wait(n, m); pthread_mutex_unlock(m) | ||||||
|  | 
 | ||||||
|  | /* FFT based on fix_fft.c by Roberts, Slaney and Bouras
 | ||||||
|  |    http://www.jjj.de/fft/fftpage.html
 | ||||||
|  |    16 bit ints for everything | ||||||
|  |    -32768..+32768 maps to -1.0..+1.0 | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | void sine_table(int size) | ||||||
|  | { | ||||||
|  | 	int i; | ||||||
|  | 	double d; | ||||||
|  | 	LOG2_N_WAVE = size; | ||||||
|  | 	N_WAVE = 1 << LOG2_N_WAVE; | ||||||
|  | 	Sinewave = malloc(sizeof(int16_t) * N_WAVE*3/4); | ||||||
|  | 	power_table = malloc(sizeof(double) * N_WAVE); | ||||||
|  | 	for (i=0; i<N_WAVE*3/4; i++) | ||||||
|  | 	{ | ||||||
|  | 		d = (double)i * 2.0 * M_PI / N_WAVE; | ||||||
|  | 		Sinewave[i] = (int)round(32767*sin(d)); | ||||||
|  | 		//printf("%i\n", Sinewave[i]);
 | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | inline int16_t FIX_MPY(int16_t a, int16_t b) | ||||||
|  | /* fixed point multiply and scale */ | ||||||
|  | { | ||||||
|  | 	int c = ((int)a * (int)b) >> 14; | ||||||
|  | 	b = c & 0x01; | ||||||
|  | 	return (c >> 1) + b; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int fix_fft(int16_t iq[], int16_t m) | ||||||
|  | /* interleaved iq[], 0 <= n < 2**m, changes in place */ | ||||||
|  | { | ||||||
|  | 	int mr, nn, i, j, l, k, istep, n, shift; | ||||||
|  | 	int16_t qr, qi, tr, ti, wr, wi; | ||||||
|  | 	n = 1 << m; | ||||||
|  | 	if (n > N_WAVE) | ||||||
|  | 		{return -1;} | ||||||
|  | 	mr = 0; | ||||||
|  | 	nn = n - 1; | ||||||
|  | 	/* decimation in time - re-order data */ | ||||||
|  | 	for (m=1; m<=nn; ++m) { | ||||||
|  | 		l = n; | ||||||
|  | 		do | ||||||
|  | 			{l >>= 1;} | ||||||
|  | 		while (mr+l > nn); | ||||||
|  | 		mr = (mr & (l-1)) + l; | ||||||
|  | 		if (mr <= m) | ||||||
|  | 			{continue;} | ||||||
|  | 		// real = 2*m, imag = 2*m+1
 | ||||||
|  | 		tr = iq[2*m]; | ||||||
|  | 		iq[2*m] = iq[2*mr]; | ||||||
|  | 		iq[2*mr] = tr; | ||||||
|  | 		ti = iq[2*m+1]; | ||||||
|  | 		iq[2*m+1] = iq[2*mr+1]; | ||||||
|  | 		iq[2*mr+1] = ti; | ||||||
|  | 	} | ||||||
|  | 	l = 1; | ||||||
|  | 	k = LOG2_N_WAVE-1; | ||||||
|  | 	while (l < n) { | ||||||
|  | 		shift = 1; | ||||||
|  | 		istep = l << 1; | ||||||
|  | 		for (m=0; m<l; ++m) { | ||||||
|  | 			j = m << k; | ||||||
|  | 			wr =  Sinewave[j+N_WAVE/4]; | ||||||
|  | 			wi = -Sinewave[j]; | ||||||
|  | 			if (shift) { | ||||||
|  | 				wr >>= 1; wi >>= 1;} | ||||||
|  | 			for (i=m; i<n; i+=istep) { | ||||||
|  | 				j = i + l; | ||||||
|  | 				tr = FIX_MPY(wr,iq[2*j]) - FIX_MPY(wi,iq[2*j+1]); | ||||||
|  | 				ti = FIX_MPY(wr,iq[2*j+1]) + FIX_MPY(wi,iq[2*j]); | ||||||
|  | 				qr = iq[2*i]; | ||||||
|  | 				qi = iq[2*i+1]; | ||||||
|  | 				if (shift) { | ||||||
|  | 					qr >>= 1; qi >>= 1;} | ||||||
|  | 				iq[2*j] = qr - tr; | ||||||
|  | 				iq[2*j+1] = qi - ti; | ||||||
|  | 				iq[2*i] = qr + tr; | ||||||
|  | 				iq[2*i+1] = qi + ti; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		--k; | ||||||
|  | 		l = istep; | ||||||
|  | 	} | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | double rectangle(int i, int length) | ||||||
|  | { | ||||||
|  | 	return 1.0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | double hamming(int i, int length) | ||||||
|  | { | ||||||
|  | 	double a, b, w, N1; | ||||||
|  | 	a = 25.0/46.0; | ||||||
|  | 	b = 21.0/46.0; | ||||||
|  | 	N1 = (double)(length-1); | ||||||
|  | 	w = a - b*cos(2*i*M_PI/N1); | ||||||
|  | 	return w; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | double blackman(int i, int length) | ||||||
|  | { | ||||||
|  | 	double a0, a1, a2, w, N1; | ||||||
|  | 	a0 = 7938.0/18608.0; | ||||||
|  | 	a1 = 9240.0/18608.0; | ||||||
|  | 	a2 = 1430.0/18608.0; | ||||||
|  | 	N1 = (double)(length-1); | ||||||
|  | 	w = a0 - a1*cos(2*i*M_PI/N1) + a2*cos(4*i*M_PI/N1); | ||||||
|  | 	return w; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | double blackman_harris(int i, int length) | ||||||
|  | { | ||||||
|  | 	double a0, a1, a2, a3, w, N1; | ||||||
|  | 	a0 = 0.35875; | ||||||
|  | 	a1 = 0.48829; | ||||||
|  | 	a2 = 0.14128; | ||||||
|  | 	a3 = 0.01168; | ||||||
|  | 	N1 = (double)(length-1); | ||||||
|  | 	w = a0 - a1*cos(2*i*M_PI/N1) + a2*cos(4*i*M_PI/N1) - a3*cos(6*i*M_PI/N1); | ||||||
|  | 	return w; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | double hann_poisson(int i, int length) | ||||||
|  | { | ||||||
|  | 	double a, N1, w; | ||||||
|  | 	a = 2.0; | ||||||
|  | 	N1 = (double)(length-1); | ||||||
|  | 	w = 0.5 * (1 - cos(2*M_PI*i/N1)) * \
 | ||||||
|  | 	    pow(M_E, (-a*(double)abs((int)(N1-1-2*i)))/N1); | ||||||
|  | 	return w; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | double youssef(int i, int length) | ||||||
|  | /* really a blackman-harris-poisson window, but that is a mouthful */ | ||||||
|  | { | ||||||
|  | 	double a, a0, a1, a2, a3, w, N1; | ||||||
|  | 	a0 = 0.35875; | ||||||
|  | 	a1 = 0.48829; | ||||||
|  | 	a2 = 0.14128; | ||||||
|  | 	a3 = 0.01168; | ||||||
|  | 	N1 = (double)(length-1); | ||||||
|  | 	w = a0 - a1*cos(2*i*M_PI/N1) + a2*cos(4*i*M_PI/N1) - a3*cos(6*i*M_PI/N1); | ||||||
|  | 	a = 0.0025; | ||||||
|  | 	w *= pow(M_E, (-a*(double)abs((int)(N1-1-2*i)))/N1); | ||||||
|  | 	return w; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | double kaiser(int i, int length) | ||||||
|  | // todo, become more smart
 | ||||||
|  | { | ||||||
|  | 	return 1.0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | double bartlett(int i, int length) | ||||||
|  | { | ||||||
|  | 	double N1, L, w; | ||||||
|  | 	L = (double)length; | ||||||
|  | 	N1 = L - 1; | ||||||
|  | 	w = (i - N1/2) / (L/2); | ||||||
|  | 	if (w < 0) { | ||||||
|  | 		w = -w;} | ||||||
|  | 	w = 1 - w; | ||||||
|  | 	return w; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void rms_power(struct tuning_state *ts) | ||||||
|  | /* for bins between 1MHz and 2MHz */ | ||||||
|  | { | ||||||
|  | 	int i, s; | ||||||
|  | 	uint8_t *buf = ts->buf8; | ||||||
|  | 	int buf_len = ts->buf_len; | ||||||
|  | 	long p, t; | ||||||
|  | 	int ln, lp; | ||||||
|  | 	double dc, err; | ||||||
|  | 
 | ||||||
|  | 	p = t = 0L; | ||||||
|  | 	for (i=0; i<buf_len; i++) { | ||||||
|  | 		s = (int)buf[i] - 127; | ||||||
|  | 		t += (long)s; | ||||||
|  | 		p += (long)(s * s); | ||||||
|  | 	} | ||||||
|  | 	/* correct for dc offset in squares */ | ||||||
|  | 	dc = (double)t / (double)buf_len; | ||||||
|  | 	err = t * 2 * dc - dc * dc * buf_len; | ||||||
|  | 	p -= (long)round(err); | ||||||
|  | 
 | ||||||
|  | 	ts->avg[0] += p; | ||||||
|  | 	ts->samples += 1; | ||||||
|  | 	/* complex pairs, half length */ | ||||||
|  | 	ts->mega_samples += (long)(buf_len/2); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | double atofs(char *f) | ||||||
|  | /* standard suffixes */ | ||||||
|  | { | ||||||
|  | 	char last; | ||||||
|  | 	int len; | ||||||
|  | 	double suff = 1.0; | ||||||
|  | 	len = strlen(f); | ||||||
|  | 	last = f[len-1]; | ||||||
|  | 	f[len-1] = '\0'; | ||||||
|  | 	switch (last) { | ||||||
|  | 		case 'g': | ||||||
|  | 		case 'G': | ||||||
|  | 			suff *= 1e3; | ||||||
|  | 		case 'm': | ||||||
|  | 		case 'M': | ||||||
|  | 			suff *= 1e3; | ||||||
|  | 		case 'k': | ||||||
|  | 		case 'K': | ||||||
|  | 			suff *= 1e3; | ||||||
|  | 			suff *= atof(f); | ||||||
|  | 			f[len-1] = last; | ||||||
|  | 			return suff; | ||||||
|  | 	} | ||||||
|  | 	f[len-1] = last; | ||||||
|  | 	return atof(f); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | double atoft(char *f) | ||||||
|  | /* time suffixes */ | ||||||
|  | { | ||||||
|  | 	char last; | ||||||
|  | 	int len; | ||||||
|  | 	double suff = 1.0; | ||||||
|  | 	len = strlen(f); | ||||||
|  | 	last = f[len-1]; | ||||||
|  | 	f[len-1] = '\0'; | ||||||
|  | 	switch (last) { | ||||||
|  | 		case 'h': | ||||||
|  | 		case 'H': | ||||||
|  | 			suff *= 60; | ||||||
|  | 		case 'm': | ||||||
|  | 		case 'M': | ||||||
|  | 			suff *= 60; | ||||||
|  | 		case 's': | ||||||
|  | 		case 'S': | ||||||
|  | 			suff *= atof(f); | ||||||
|  | 			f[len-1] = last; | ||||||
|  | 			return suff; | ||||||
|  | 	} | ||||||
|  | 	f[len-1] = last; | ||||||
|  | 	return atof(f); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | double atofp(char *f) | ||||||
|  | /* percent suffixes */ | ||||||
|  | { | ||||||
|  | 	char last; | ||||||
|  | 	int len; | ||||||
|  | 	double suff = 1.0; | ||||||
|  | 	len = strlen(f); | ||||||
|  | 	last = f[len-1]; | ||||||
|  | 	f[len-1] = '\0'; | ||||||
|  | 	switch (last) { | ||||||
|  | 		case '%': | ||||||
|  | 			suff *= 0.01; | ||||||
|  | 			suff *= atof(f); | ||||||
|  | 			f[len-1] = last; | ||||||
|  | 			return suff; | ||||||
|  | 	} | ||||||
|  | 	f[len-1] = last; | ||||||
|  | 	return atof(f); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int nearest_gain(int target_gain) | ||||||
|  | { | ||||||
|  | 	int i, err1, err2, count, close_gain; | ||||||
|  | 	int* gains; | ||||||
|  | 	count = rtlsdr_get_tuner_gains(dev, NULL); | ||||||
|  | 	if (count <= 0) { | ||||||
|  | 		return 0; | ||||||
|  | 	} | ||||||
|  | 	gains = malloc(sizeof(int) * count); | ||||||
|  | 	count = rtlsdr_get_tuner_gains(dev, gains); | ||||||
|  | 	close_gain = gains[0]; | ||||||
|  | 	for (i=0; i<count; i++) { | ||||||
|  | 		err1 = abs(target_gain - close_gain); | ||||||
|  | 		err2 = abs(target_gain - gains[i]); | ||||||
|  | 		if (err2 < err1) { | ||||||
|  | 			close_gain = gains[i]; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	free(gains); | ||||||
|  | 	return close_gain; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void frequency_range(char *arg, double crop) | ||||||
|  | /* flesh out the tunes[] for scanning */ | ||||||
|  | // do we want the fewest ranges (easy) or the fewest bins (harder)?
 | ||||||
|  | { | ||||||
|  | 	char *start, *stop, *step; | ||||||
|  | 	int i, j, upper, lower, max_size, bw_seen, bw_used, bin_size, bin_e, buf_len; | ||||||
|  | 	struct tuning_state *ts; | ||||||
|  | 	/* hacky string parsing */ | ||||||
|  | 	start = arg; | ||||||
|  | 	stop = strchr(start, ':') + 1; | ||||||
|  | 	stop[-1] = '\0'; | ||||||
|  | 	step = strchr(stop, ':') + 1; | ||||||
|  | 	step[-1] = '\0'; | ||||||
|  | 	lower = (int)atofs(start); | ||||||
|  | 	upper = (int)atofs(stop); | ||||||
|  | 	max_size = (int)atofs(step); | ||||||
|  | 	stop[-1] = ':'; | ||||||
|  | 	step[-1] = ':'; | ||||||
|  | 	/* evenly sized ranges, as close to 2MHz as possible */ | ||||||
|  | 	for (i=1; i<1500; i++) { | ||||||
|  | 		bw_seen = (upper - lower) / i; | ||||||
|  | 		bw_used = (int)((double)(bw_seen) / (1.0 - crop)); | ||||||
|  | 		if (bw_used > 2000000) { | ||||||
|  | 			continue;} | ||||||
|  | 		tune_count = i; | ||||||
|  | 		break; | ||||||
|  | 	} | ||||||
|  | 	/* number of bins is power-of-two, bin size is under limit */ | ||||||
|  | 	for (i=1; i<=21; i++) { | ||||||
|  | 		bin_e = i; | ||||||
|  | 		bin_size = bw_used / (1<<i); | ||||||
|  | 		if (bin_size <= max_size) { | ||||||
|  | 			break;} | ||||||
|  | 	} | ||||||
|  | 	/* unless giant bins */ | ||||||
|  | 	if (max_size >= 1000000) { | ||||||
|  | 		bw_seen = max_size; | ||||||
|  | 		bw_used = max_size; | ||||||
|  | 		tune_count = (upper - lower) / bw_seen; | ||||||
|  | 		bin_e = 0; | ||||||
|  | 	} | ||||||
|  | 	if (tune_count > MAX_TUNES) { | ||||||
|  | 		fprintf(stderr, "Error: bandwidth too wide.\n"); | ||||||
|  | 		exit(1); | ||||||
|  | 	} | ||||||
|  | 	buf_len = DEFAULT_BUF_LENGTH; | ||||||
|  | 	if ((2<<bin_e) > buf_len) { | ||||||
|  | 		buf_len = (2<<bin_e); | ||||||
|  | 	} | ||||||
|  | 	/* build the array */ | ||||||
|  | 	for (i=0; i<tune_count; i++) { | ||||||
|  | 		ts = &tunes[i]; | ||||||
|  | 		ts->freq = lower + i*bw_seen + bw_seen/2; | ||||||
|  | 		ts->rate = bw_used; | ||||||
|  | 		ts->bin_e = bin_e; | ||||||
|  | 		ts->samples = 0; | ||||||
|  | 		ts->mega_samples = 0L; | ||||||
|  | 		ts->avg = (long*)malloc((1<<bin_e) * sizeof(long)); | ||||||
|  | 		if (!ts->avg) { | ||||||
|  | 			fprintf(stderr, "Error: malloc.\n"); | ||||||
|  | 			exit(1); | ||||||
|  | 		} | ||||||
|  | 		for (j=0; j<(1<<bin_e); j++) { | ||||||
|  | 			ts->avg[j] = 0L; | ||||||
|  | 		} | ||||||
|  | 		ts->buf8 = (uint8_t*)malloc(buf_len * sizeof(uint8_t)); | ||||||
|  | 		if (!ts->buf8) { | ||||||
|  | 			fprintf(stderr, "Error: malloc.\n"); | ||||||
|  | 			exit(1); | ||||||
|  | 		} | ||||||
|  | 		ts->buf_len = buf_len; | ||||||
|  | 	} | ||||||
|  | 	/* report */ | ||||||
|  | 	fprintf(stderr, "Number of frequency hops: %i\n", tune_count); | ||||||
|  | 	fprintf(stderr, "Dongle bandwidth: %iHz\n", bw_used); | ||||||
|  | 	fprintf(stderr, "Total FFT bins: %i\n", tune_count * (1<<bin_e)); | ||||||
|  | 	fprintf(stderr, "Logged FFT bins: %i\n", \
 | ||||||
|  | 	  (int)((double)(tune_count * (1<<bin_e)) * (1.0-crop))); | ||||||
|  | 	fprintf(stderr, "FFT bin size: %iHz\n", bin_size); | ||||||
|  | 	fprintf(stderr, "Buffer size: %0.2fms\n", 1000 * 0.5 * (float)buf_len / (float)bw_used); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void retune(rtlsdr_dev_t *d, int freq) | ||||||
|  | { | ||||||
|  | 	uint8_t dump[BUFFER_DUMP]; | ||||||
|  | 	int n_read; | ||||||
|  | 	rtlsdr_set_center_freq(d, (uint32_t)freq); | ||||||
|  | 	/* wait for settling and flush buffer */ | ||||||
|  | 	usleep(5000); | ||||||
|  | 	rtlsdr_read_sync(d, &dump, BUFFER_DUMP, &n_read); | ||||||
|  | 	if (n_read != BUFFER_DUMP) { | ||||||
|  | 		fprintf(stderr, "Error: bad retune.\n");} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void scanner(void) | ||||||
|  | { | ||||||
|  | 	int i, j, f, n_read, offset, bin_e, bin_len, buf_len; | ||||||
|  | 	struct tuning_state *ts; | ||||||
|  | 	bin_e = tunes[0].bin_e; | ||||||
|  | 	bin_len = 1 << bin_e; | ||||||
|  | 	buf_len = tunes[0].buf_len; | ||||||
|  | 	for (i=0; i<tune_count; i++) { | ||||||
|  | 		if (do_exit) { | ||||||
|  | 			break;} | ||||||
|  | 		ts = &tunes[i]; | ||||||
|  | 		f = (int)rtlsdr_get_center_freq(dev); | ||||||
|  | 		if (f != ts->freq) { | ||||||
|  | 			retune(dev, ts->freq);} | ||||||
|  | 		rtlsdr_read_sync(dev, ts->buf8, buf_len, &n_read); | ||||||
|  | 		if (n_read != buf_len) { | ||||||
|  | 			fprintf(stderr, "Error: dropped samples.\n");} | ||||||
|  | 		/* rms */ | ||||||
|  | 		if (bin_len == 1) { | ||||||
|  | 			rms_power(ts); | ||||||
|  | 			continue; | ||||||
|  | 		} | ||||||
|  | 		/* fft */ | ||||||
|  | 		for (j=0; j<buf_len; j++) { | ||||||
|  | 			fft_buf[j] = (int16_t)ts->buf8[j] - 127; | ||||||
|  | 		} | ||||||
|  | 		for (offset=0; offset<buf_len; offset+=(2*bin_len)) { | ||||||
|  | 			// todo, let rect skip this
 | ||||||
|  | 			for (j=0; j<bin_len; j++) { | ||||||
|  | 				fft_buf[offset+j*2]   *= window_coefs[j]; | ||||||
|  | 				fft_buf[offset+j*2+1] *= window_coefs[j]; | ||||||
|  | 			} | ||||||
|  | 			fix_fft(fft_buf+offset, bin_e); | ||||||
|  | 			for (j=0; j<bin_len; j++) { | ||||||
|  | 				ts->avg[j] += (long) abs(fft_buf[offset+j*2]); | ||||||
|  | 			} | ||||||
|  | 			ts->samples += 1; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void csv_dbm(struct tuning_state *ts, double crop) | ||||||
|  | { | ||||||
|  | 	int i, len, i1, i2, bw2; | ||||||
|  | 	long tmp; | ||||||
|  | 	double dbm; | ||||||
|  | 	len = 1 << ts->bin_e; | ||||||
|  | 	/* fix FFT stuff quirks */ | ||||||
|  | 	if (ts->bin_e > 0) { | ||||||
|  | 		/* nuke DC component (not effective for all windows) */ | ||||||
|  | 		ts->avg[0] = ts->avg[1]; | ||||||
|  | 		/* FFT is translated by 180 degrees */ | ||||||
|  | 		for (i=0; i<len/2; i++) { | ||||||
|  | 			tmp = ts->avg[i]; | ||||||
|  | 			ts->avg[i] = ts->avg[i+len/2]; | ||||||
|  | 			ts->avg[i+len/2] = tmp; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	/* Hz low, Hz high, Hz step, samples, dbm, dbm, ... */ | ||||||
|  | 	bw2 = (int)((double)ts->rate * (1.0-crop) * 0.5); | ||||||
|  | 	fprintf(file, "%i, %i, %.2f, %i, ", ts->freq - bw2, ts->freq + bw2, | ||||||
|  | 		(double)ts->rate / (double)len, ts->samples); | ||||||
|  | 	// something seems off with the dbm math
 | ||||||
|  | 	i1 = 0 + (int)((double)len * crop * 0.5); | ||||||
|  | 	i2 = (len-1) - (int)((double)len * crop * 0.5); | ||||||
|  | 	for (i=i1; i<i2; i++) { | ||||||
|  | 		dbm  = (double)ts->avg[i]; | ||||||
|  | 		dbm /= (double)ts->rate; | ||||||
|  | 		dbm /= (double)ts->samples; | ||||||
|  | 		dbm  = 10 * log10(dbm); | ||||||
|  | 		fprintf(file, "%.2f, ", dbm); | ||||||
|  | 	} | ||||||
|  | 	dbm = (double)ts->avg[i2] / ((double)ts->rate * (double)ts->samples); | ||||||
|  | 	if (ts->bin_e == 0) { | ||||||
|  | 		dbm = ((double)ts->avg[0] / \
 | ||||||
|  | 		((double)ts->rate * (double)ts->samples));} | ||||||
|  | 	dbm  = 10 * log10(dbm); | ||||||
|  | 	fprintf(file, "%.2f\n", dbm); | ||||||
|  | 	for (i=0; i<len; i++) { | ||||||
|  | 		ts->avg[i] = 0L; | ||||||
|  | 	} | ||||||
|  | 	ts->samples = 0; | ||||||
|  | 	ts->mega_samples = 0L; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int main(int argc, char **argv) | ||||||
|  | { | ||||||
|  | #ifndef _WIN32 | ||||||
|  | 	struct sigaction sigact; | ||||||
|  | #endif | ||||||
|  | 	char *filename = NULL; | ||||||
|  | 	int i, length, n_read, r, opt, wb_mode = 0; | ||||||
|  | 	int gain = AUTO_GAIN; // tenths of a dB
 | ||||||
|  | 	uint8_t *buffer; | ||||||
|  | 	uint32_t dev_index = 0; | ||||||
|  | 	int device_count; | ||||||
|  | 	int ppm_error = 0; | ||||||
|  | 	int interval = 10; | ||||||
|  | 	int fft_threads = 1; | ||||||
|  | 	int smoothing = 0; | ||||||
|  | 	int single = 0; | ||||||
|  | 	double crop = 0.1; | ||||||
|  | 	char vendor[256], product[256], serial[256]; | ||||||
|  | 	char *freq_optarg; | ||||||
|  | 	time_t next_tick; | ||||||
|  | 	time_t time_now; | ||||||
|  | 	time_t exit_time = 0; | ||||||
|  | 	char t_str[50]; | ||||||
|  | 	struct tm *cal_time; | ||||||
|  | 	double (*window_fn)(int, int) = rectangle; | ||||||
|  | 
 | ||||||
|  | 	while ((opt = getopt(argc, argv, "f:i:s:t:d:g:p:e:w:c:1h")) != -1) { | ||||||
|  | 		switch (opt) { | ||||||
|  | 		case 'f': // lower:upper:bin_size
 | ||||||
|  | 			freq_optarg = strdup(optarg); | ||||||
|  | 			break; | ||||||
|  | 		case 'd': | ||||||
|  | 			dev_index = atoi(optarg); | ||||||
|  | 			break; | ||||||
|  | 		case 'g': | ||||||
|  | 			gain = (int)(atof(optarg) * 10); | ||||||
|  | 			break; | ||||||
|  | 		case 'c': | ||||||
|  | 			crop = atofp(optarg); | ||||||
|  | 			break; | ||||||
|  | 		case 'i': | ||||||
|  | 			interval = (int)round(atoft(optarg)); | ||||||
|  | 			break; | ||||||
|  | 		case 'e': | ||||||
|  | 			exit_time = (time_t)((int)round(atoft(optarg))); | ||||||
|  | 			break; | ||||||
|  | 		case 's': | ||||||
|  | 			if (strcmp("avg",  optarg) == 0) { | ||||||
|  | 				smoothing = 0;} | ||||||
|  | 			if (strcmp("iir",  optarg) == 0) { | ||||||
|  | 				smoothing = 1;} | ||||||
|  | 			break; | ||||||
|  | 		case 'w': | ||||||
|  | 			if (strcmp("rectangle",  optarg) == 0) { | ||||||
|  | 				window_fn = rectangle;} | ||||||
|  | 			if (strcmp("hamming",  optarg) == 0) { | ||||||
|  | 				window_fn = hamming;} | ||||||
|  | 			if (strcmp("blackman",  optarg) == 0) { | ||||||
|  | 				window_fn = blackman;} | ||||||
|  | 			if (strcmp("blackman-harris",  optarg) == 0) { | ||||||
|  | 				window_fn = blackman_harris;} | ||||||
|  | 			if (strcmp("hann-poisson",  optarg) == 0) { | ||||||
|  | 				window_fn = hann_poisson;} | ||||||
|  | 			if (strcmp("youssef",  optarg) == 0) { | ||||||
|  | 				window_fn = youssef;} | ||||||
|  | 			if (strcmp("kaiser",  optarg) == 0) { | ||||||
|  | 				window_fn = kaiser;} | ||||||
|  | 			if (strcmp("bartlett",  optarg) == 0) { | ||||||
|  | 				window_fn = bartlett;} | ||||||
|  | 			break; | ||||||
|  | 		case 't': | ||||||
|  | 			fft_threads = atoi(optarg); | ||||||
|  | 			break; | ||||||
|  | 		case 'p': | ||||||
|  | 			ppm_error = atoi(optarg); | ||||||
|  | 			break; | ||||||
|  | 		case '1': | ||||||
|  | 			single = 1; | ||||||
|  | 			break; | ||||||
|  | 		case 'h': | ||||||
|  | 		default: | ||||||
|  | 			usage(); | ||||||
|  | 			break; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	frequency_range(freq_optarg, crop); | ||||||
|  | 
 | ||||||
|  | 	if (tune_count == 0) { | ||||||
|  | 		usage();} | ||||||
|  | 
 | ||||||
|  | 	if (argc <= optind) { | ||||||
|  | 		filename = "-"; | ||||||
|  | 	} else { | ||||||
|  | 		filename = argv[optind]; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (interval < 1) { | ||||||
|  | 		interval = 1;} | ||||||
|  | 
 | ||||||
|  | 	fprintf(stderr, "Reporting every %i seconds\n", interval); | ||||||
|  | 
 | ||||||
|  | 	device_count = rtlsdr_get_device_count(); | ||||||
|  | 	if (!device_count) { | ||||||
|  | 		fprintf(stderr, "No supported devices found.\n"); | ||||||
|  | 		exit(1); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	fprintf(stderr, "Found %d device(s):\n", device_count); | ||||||
|  | 	for (i = 0; i < device_count; i++) { | ||||||
|  | 		rtlsdr_get_device_usb_strings(i, vendor, product, serial); | ||||||
|  | 		fprintf(stderr, "  %d:  %s, %s, SN: %s\n", i, vendor, product, serial); | ||||||
|  | 	} | ||||||
|  | 	fprintf(stderr, "\n"); | ||||||
|  | 
 | ||||||
|  | 	fprintf(stderr, "Using device %d: %s\n", | ||||||
|  | 		dev_index, rtlsdr_get_device_name(dev_index)); | ||||||
|  | 
 | ||||||
|  | 	r = rtlsdr_open(&dev, dev_index); | ||||||
|  | 	if (r < 0) { | ||||||
|  | 		fprintf(stderr, "Failed to open rtlsdr device #%d.\n", dev_index); | ||||||
|  | 		exit(1); | ||||||
|  | 	} | ||||||
|  | #ifndef _WIN32 | ||||||
|  | 	sigact.sa_handler = sighandler; | ||||||
|  | 	sigemptyset(&sigact.sa_mask); | ||||||
|  | 	sigact.sa_flags = 0; | ||||||
|  | 	sigaction(SIGINT, &sigact, NULL); | ||||||
|  | 	sigaction(SIGTERM, &sigact, NULL); | ||||||
|  | 	sigaction(SIGQUIT, &sigact, NULL); | ||||||
|  | 	sigaction(SIGPIPE, &sigact, NULL); | ||||||
|  | #else | ||||||
|  | 	SetConsoleCtrlHandler( (PHANDLER_ROUTINE) sighandler, TRUE ); | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | 	/* Set the tuner gain */ | ||||||
|  | 	if (gain == AUTO_GAIN) { | ||||||
|  | 		r = rtlsdr_set_tuner_gain_mode(dev, 0); | ||||||
|  | 	} else { | ||||||
|  | 		r = rtlsdr_set_tuner_gain_mode(dev, 1); | ||||||
|  | 		gain = nearest_gain(gain); | ||||||
|  | 		r = rtlsdr_set_tuner_gain(dev, gain); | ||||||
|  | 	} | ||||||
|  | 	if (r != 0) { | ||||||
|  | 		fprintf(stderr, "WARNING: Failed to set tuner gain.\n"); | ||||||
|  | 	} else if (gain == AUTO_GAIN) { | ||||||
|  | 		fprintf(stderr, "Tuner gain set to automatic.\n"); | ||||||
|  | 	} else { | ||||||
|  | 		fprintf(stderr, "Tuner gain set to %0.2f dB.\n", gain/10.0); | ||||||
|  | 	} | ||||||
|  | 	r = rtlsdr_set_freq_correction(dev, ppm_error); | ||||||
|  | 
 | ||||||
|  | 	if (strcmp(filename, "-") == 0) { /* Write log to stdout */ | ||||||
|  | 		file = stdout; | ||||||
|  | #ifdef _WIN32 | ||||||
|  | 		_setmode(_fileno(file), _O_BINARY); | ||||||
|  | #endif | ||||||
|  | 	} else { | ||||||
|  | 		file = fopen(filename, "wb"); | ||||||
|  | 		if (!file) { | ||||||
|  | 			fprintf(stderr, "Failed to open %s\n", filename); | ||||||
|  | 			exit(1); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/* Reset endpoint before we start reading from it (mandatory) */ | ||||||
|  | 	r = rtlsdr_reset_buffer(dev); | ||||||
|  | 	if (r < 0) { | ||||||
|  | 		fprintf(stderr, "WARNING: Failed to reset buffers.\n");} | ||||||
|  | 
 | ||||||
|  | 	/* actually do stuff */ | ||||||
|  | 	rtlsdr_set_sample_rate(dev, (uint32_t)tunes[0].rate); | ||||||
|  | 	sine_table(tunes[0].bin_e); | ||||||
|  | 	next_tick = time(NULL) + interval; | ||||||
|  | 	if (exit_time) { | ||||||
|  | 		exit_time = time(NULL) + exit_time;} | ||||||
|  | 	fft_buf = malloc(tunes[0].buf_len * sizeof(int16_t)); | ||||||
|  | 	length = 1 << tunes[0].bin_e; | ||||||
|  | 	window_coefs = malloc(length * sizeof(int)); | ||||||
|  | 	for (i=0; i<length; i++) { | ||||||
|  | 		window_coefs[i] = (int)(256*window_fn(i, length)); | ||||||
|  | 	} | ||||||
|  | 	while (!do_exit) { | ||||||
|  | 		scanner(); | ||||||
|  | 		time_now = time(NULL); | ||||||
|  | 		if (time_now <= next_tick) { | ||||||
|  | 			continue;} | ||||||
|  | 		// time, Hz low, Hz high, Hz step, samples, dbm, dbm, ...
 | ||||||
|  | 		cal_time = localtime(&time_now); | ||||||
|  | 		strftime(t_str, 50, "%Y-%m-%d, %H:%M:%S", cal_time); | ||||||
|  | 		for (i=0; i<tune_count; i++) { | ||||||
|  | 			fprintf(file, "%s, ", t_str); | ||||||
|  | 			csv_dbm(&tunes[i], crop); | ||||||
|  | 		} | ||||||
|  | 		fflush(file); | ||||||
|  | 		while (time(NULL) >= next_tick) { | ||||||
|  | 			next_tick += interval;} | ||||||
|  | 		if (single) { | ||||||
|  | 			do_exit = 1;} | ||||||
|  | 		if (exit_time && time(NULL) >= exit_time) { | ||||||
|  | 			do_exit = 1;} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/* clean up */ | ||||||
|  | 
 | ||||||
|  | 	if (do_exit) { | ||||||
|  | 		fprintf(stderr, "\nUser cancel, exiting...\n");} | ||||||
|  | 	else { | ||||||
|  | 		fprintf(stderr, "\nLibrary error %d, exiting...\n", r);} | ||||||
|  | 
 | ||||||
|  | 	if (file != stdout) { | ||||||
|  | 		fclose(file);} | ||||||
|  | 
 | ||||||
|  | 	rtlsdr_close(dev); | ||||||
|  | 	free(fft_buf); | ||||||
|  | 	free(window_coefs); | ||||||
|  | 	//for (i=0; i<tune_count; i++) {
 | ||||||
|  | 	//	free(tunes[i].avg);
 | ||||||
|  | 	//	free(tunes[i].buf8);
 | ||||||
|  | 	//}
 | ||||||
|  | 	return r >= 0 ? r : -r; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // vim: tabstop=8:softtabstop=8:shiftwidth=8:noexpandtab
 | ||||||
					Loading…
					
					
				
		Reference in new issue