/* * 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 * check edge cropping for off-by-one and rounding errors * 1.8MS/s for hiding xtal harmonics */ #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_BUF_LENGTH (1 * 16384) #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; int downsample; int downsample_passes; /* for the recursive filter */ double crop; //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; int boxcar = 1; 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 1Hz - 2MHz)\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 for bins larger than 1MHz)\n" "\t[-F enables low-leakage downsample filter (default: off)]\n" "\t (has bad roll off, try with '-c 50%%')\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\n" "Convert CSV to a waterfall graphic with:\n" "\t http://kmkeen.com/tmp/heatmap.py.txt \n"); exit(1); } void multi_bail(void) { if (do_exit == 1) { fprintf(stderr, "Signal caught, finishing scan pass.\n"); } if (do_exit >= 2) { fprintf(stderr, "Signal caught, aborting immediately.\n"); } } #ifdef _WIN32 BOOL WINAPI sighandler(int signum) { if (CTRL_C_EVENT == signum) { do_exit++; multi_bail(); return TRUE; } return FALSE; } #else static void sighandler(int signum) { do_exit++; multi_bail(); } #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[], int 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; } 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; } /* unless small bandwidth */ if (bw_used < 1000000) { tune_count = 1;} if (boxcar && bw_used < 1000000) { downsample = 2000000 / bw_used; bw_used = bw_used * downsample; } while (bw_used < 1000000) { /* not boxcar */ downsample_passes++; downsample = 1 << downsample_passes; bw_used = (int)((double)(bw_seen * downsample) / (1.0 - crop)); } /* number of bins is power-of-two, bin size is under limit */ for (i=1; i<=21; i++) { bin_e = i; bin_size = (double)bw_used / (double)((1<= 1000000) { bw_seen = max_size; bw_used = max_size; tune_count = (upper - lower) / bw_seen; bin_e = 0; crop = 0; } if (tune_count > MAX_TUNES) { fprintf(stderr, "Error: bandwidth too wide.\n"); exit(1); } buf_len = 2 * (1<freq = lower + i*bw_seen + bw_seen/2; ts->rate = bw_used; ts->bin_e = bin_e; ts->samples = 0; ts->crop = crop; ts->downsample = downsample; ts->downsample_passes = downsample_passes; 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, "Downsampling by: %ix\n", downsample); fprintf(stderr, "Cropping by: %0.2f%%\n", crop*100); fprintf(stderr, "Total FFT bins: %i\n", tune_count * (1<> 4; data[2] = ((b+c)*10 + (a+d)*5 + e + f) >> 4; data[4] = (a + (b+e)*5 + (c+d)*10 + f) >> 4; for (i=12; i> 4; } } void remove_dc(int16_t *data, int length) /* works on interleaved data */ { int i; int16_t ave; long sum = 0L; for (i=0; i < length; i+=2) { sum += data[i]; } ave = (int16_t)(sum / (long)(length)); if (ave == 0) { return;} for (i=0; i < length; i+=2) { data[i] -= ave; } } void downsample_iq(int16_t *data, int length) { fifth_order(data, length); //remove_dc(data, length); fifth_order(data+1, length-1); //remove_dc(data+1, length-1); } void scanner(void) { int i, j, j2, f, n_read, offset, bin_e, bin_len, buf_len, ds; int32_t w; 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= 2) {return;} 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; } /* prep for fft */ for (j=0; jbuf8[j] - 127; } ds = ts->downsample; if (boxcar) { j=2, j2=0; while (j < buf_len) { fft_buf[j2] += fft_buf[j]; fft_buf[j2+1] += fft_buf[j+1]; j += 2; if (j % (ds*2) == 0) { j2 += 2;} } } else { /* recursive */ for (j=0; j < ts->downsample_passes; j++) { downsample_iq(fft_buf, buf_len >> j); } } remove_dc(fft_buf, buf_len >> j); remove_dc(fft_buf+1, (buf_len >> j) - 1); /* window function and fft */ for (offset=0; offset<(buf_len/ds); offset+=(2*bin_len)) { // todo, let rect skip this for (j=0; javg[j] += (long) abs(fft_buf[offset+j*2]); } ts->samples += ds; } } } void csv_dbm(struct tuning_state *ts) { int i, len, ds, i1, i2, bw2, bin_count; long tmp; double dbm; len = 1 << ts->bin_e; ds = ts->downsample; /* 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, ... */ bin_count = (int)((double)len * (1.0 - ts->crop)); bw2 = (int)(((double)ts->rate * (double)bin_count) / (len * 2 * ds)); fprintf(file, "%i, %i, %.2f, %i, ", ts->freq - bw2, ts->freq + bw2, (double)ts->rate / (double)(len*ds), ts->samples); // something seems off with the dbm math i1 = 0 + (int)((double)len * ts->crop * 0.5); i2 = (len-1) - (int)((double)len * ts->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; iavg[i] = 0L; } ts->samples = 0; } 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 f_set = 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.0; 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; freq_optarg = ""; while ((opt = getopt(argc, argv, "f:i:s:t:d:g:p:e:w:c:1Fh")) != -1) { switch (opt) { case 'f': // lower:upper:bin_size freq_optarg = strdup(optarg); f_set = 1; 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 'F': boxcar = 0; break; case 'h': default: usage(); break; } } if (!f_set) { fprintf(stderr, "No frequency range provided.\n"); exit(1); } if ((crop < 0.0) || (crop > 1.0)) { fprintf(stderr, "Crop value outside of 0 to 1.\n"); exit(1); } 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 // Is this necessary? Output is ascii. _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