diff options
author | Kyle Keen <keenerd@gmail.com> | 2013-08-19 21:25:56 -0400 |
---|---|---|
committer | Steve Markgraf <steve@steve-m.de> | 2013-10-24 23:46:16 +0200 |
commit | dbc49549a0f21cbc47e0ab667b698507b647867f (patch) | |
tree | afab53334d3942dd8c2da3880ebc7167dd4264f0 | |
parent | 53775a2ebf3851ed387c016654e4c0d620a008ef (diff) |
add new tool: rtl_power
Signed-off-by: Steve Markgraf <steve@steve-m.de>
-rw-r--r-- | configure.ac | 3 | ||||
-rw-r--r-- | src/CMakeLists.txt | 10 | ||||
-rw-r--r-- | src/Makefile.am | 5 | ||||
-rw-r--r-- | src/rtl_power.c | 870 |
4 files changed, 886 insertions, 2 deletions
diff --git a/configure.ac b/configure.ac index ce47d89..d59d820 100644 --- a/configure.ac +++ b/configure.ac @@ -42,6 +42,9 @@ AC_CHECK_LIB(m, atan2, [LIBS="$LIBS -lm"]) dnl libmath (for rtl_adsb) AC_CHECK_LIB(m, sqrt, [LIBS="$LIBS -lm"]) +dnl libmath (for rtl_power) +AC_CHECK_LIB(m, atan2, [LIBS="$LIBS -lm"]) + dnl librealtime (for rtl_test) AC_CHECK_LIB(rt, clock_gettime, [LIBS="$LIBS -lrt"]) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7cd62c8..7bca10b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -73,7 +73,8 @@ add_executable(rtl_test rtl_test.c) add_executable(rtl_fm rtl_fm.c) add_executable(rtl_eeprom rtl_eeprom.c) add_executable(rtl_adsb rtl_adsb.c) -set(INSTALL_TARGETS rtlsdr_shared rtlsdr_static rtl_sdr rtl_tcp rtl_test rtl_fm rtl_eeprom rtl_adsb) +add_executable(rtl_power rtl_power.c) +set(INSTALL_TARGETS rtlsdr_shared rtlsdr_static rtl_sdr rtl_tcp rtl_test rtl_fm rtl_eeprom rtl_adsb rtl_power) target_link_libraries(rtl_sdr rtlsdr_shared ${LIBUSB_LIBRARIES} @@ -99,9 +100,14 @@ target_link_libraries(rtl_adsb rtlsdr_shared ${LIBUSB_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ) +target_link_libraries(rtl_power rtlsdr_shared + ${LIBUSB_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} +) if(UNIX) target_link_libraries(rtl_fm m) target_link_libraries(rtl_adsb m) +target_link_libraries(rtl_power m) if(APPLE) target_link_libraries(rtl_test m) else() @@ -116,12 +122,14 @@ target_link_libraries(rtl_test libgetopt_static) target_link_libraries(rtl_fm libgetopt_static) target_link_libraries(rtl_eeprom libgetopt_static) target_link_libraries(rtl_adsb libgetopt_static) +target_link_libraries(rtl_power libgetopt_static) set_property(TARGET rtl_sdr APPEND PROPERTY COMPILE_DEFINITIONS "rtlsdr_STATIC" ) set_property(TARGET rtl_tcp APPEND PROPERTY COMPILE_DEFINITIONS "rtlsdr_STATIC" ) set_property(TARGET rtl_test APPEND PROPERTY COMPILE_DEFINITIONS "rtlsdr_STATIC" ) set_property(TARGET rtl_fm APPEND PROPERTY COMPILE_DEFINITIONS "rtlsdr_STATIC" ) set_property(TARGET rtl_eeprom APPEND PROPERTY COMPILE_DEFINITIONS "rtlsdr_STATIC" ) set_property(TARGET rtl_adsb APPEND PROPERTY COMPILE_DEFINITIONS "rtlsdr_STATIC" ) +set_property(TARGET rtl_power APPEND PROPERTY COMPILE_DEFINITIONS "rtlsdr_STATIC" ) endif() ######################################################################## # Install built library files & utilities diff --git a/src/Makefile.am b/src/Makefile.am index 386d36b..91cb010 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -10,7 +10,7 @@ lib_LTLIBRARIES = librtlsdr.la librtlsdr_la_SOURCES = librtlsdr.c tuner_e4k.c tuner_fc0012.c tuner_fc0013.c tuner_fc2580.c tuner_r820t.c librtlsdr_la_LDFLAGS = -version-info $(LIBVERSION) -bin_PROGRAMS = rtl_sdr rtl_tcp rtl_test rtl_fm rtl_eeprom rtl_adsb +bin_PROGRAMS = rtl_sdr rtl_tcp rtl_test rtl_fm rtl_eeprom rtl_adsb rtl_power rtl_sdr_SOURCES = rtl_sdr.c rtl_sdr_LDADD = librtlsdr.la @@ -29,3 +29,6 @@ rtl_eeprom_LDADD = librtlsdr.la $(LIBM) rtl_adsb_SOURCES = rtl_adsb.c rtl_adsb_LDADD = librtlsdr.la $(LIBM) + +rtl_power_SOURCES = rtl_power.c +rtl_power_LDADD = librtlsdr.la $(LIBM) diff --git a/src/rtl_power.c b/src/rtl_power.c new file mode 100644 index 0000000..58da95a --- /dev/null +++ b/src/rtl_power.c @@ -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 |