/* * rtl-sdr, turns your Realtek RTL2832 based DVB dongle into a SDR receiver * Copyright (C) 2012 by Steve Markgraf * Copyright (C) 2012 by Hoernchen * Copyright (C) 2012 by Kyle Keen * * 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 . */ /* * 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 #include #include #include #include #include #ifndef _WIN32 #include #else #include #include #include #include "getopt/getopt.h" #define usleep(x) Sleep(x/1000) #define round(x) (x > 0.0 ? floor(x + 0.5): ceil(x - 0.5)) #define _USE_MATH_DEFINES #endif #include #include #include #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> 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>= 1; wi >>= 1;} for (i=m; i>= 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; iavg[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 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<= 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< buf_len) { buf_len = (2<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<avg) { fprintf(stderr, "Error: malloc.\n"); exit(1); } for (j=0; j<(1<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<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; jbuf8[j] - 127; } for (offset=0; offsetavg[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; iavg[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; iavg[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; iavg[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= 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= 0 ? r : -r; } // vim: tabstop=8:softtabstop=8:shiftwidth=8:noexpandtab