rtl_power: exact size bins, refactoring

squashed
Kyle Keen 2014-08-20 11:51:40 -04:00
rodzic 25b3282eeb
commit 359e3c63b4
1 zmienionych plików z 209 dodań i 111 usunięć

Wyświetl plik

@ -72,7 +72,7 @@
#define AUTO_GAIN -100
#define BUFFER_DUMP (1<<12)
#define MAXIMUM_RATE 2800000
#define MAXIMUM_RATE 2400000
#define MINIMUM_RATE 1000000
static volatile int do_exit = 0;
@ -96,7 +96,10 @@ struct tuning_state
int samples;
int downsample;
int downsample_passes; /* for the recursive filter */
int bin_spec;
double crop;
int crop_i1, crop_i2;
int freq_low, freq_high;
//pthread_rwlock_t avg_lock;
//pthread_mutex_t avg_mutex;
/* having the iq buffer here is wasteful, but will avoid contention */
@ -107,12 +110,20 @@ struct tuning_state
//pthread_mutex_t buf_mutex;
};
struct channel_solve
/* details required to find optimal tuning */
{
int upper, lower, bin_spec;
int hops, bw_wanted, bw_needed;
int bin_e, downsample, downsample_passes;
double crop, crop_tmp;
};
/* 3000 is enough for 3GHz b/w worst case */
#define MAX_TUNES 3000
#define MAX_TUNES 4000
struct tuning_state tunes[MAX_TUNES];
int tune_count = 0;
int boxcar = 1;
int comp_fir_size = 0;
int peak_hold = 0;
@ -122,10 +133,9 @@ void usage(void)
"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 - 2.8MHz)\n"
"\t valid range for bin_size is 1Hz - 2.8MHz\n"
"\t[-i integration_interval (default: 10 seconds)]\n"
"\t (buggy if a full sweep takes longer than the interval)\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"
@ -134,19 +144,20 @@ void usage(void)
"\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"
"\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"
"\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[-c crop_percent (default: 0%% suggested: 20%%)]\n"
"\t discards data at the edges, 100%% discards everything\n"
"\t has no effect for bins larger than 1MHz\n"
"\t this value is a minimum crop size, more may be discarded\n"
"\t[-F fir_size (default: disabled)]\n"
"\t (enables low-leakage downsample filter,\n"
"\t enables low-leakage downsample filter,\n"
"\t fir_size can be 0 or 9. 0 has bad roll off,\n"
"\t try with '-c 50%%')\n"
"\t try -F 0 with '-c 50%%' to hide the roll off\n"
"\t[-P enables peak hold (default: off)]\n"
"\t[-D direct_sampling_mode, 0 (default/off), 1 (I), 2 (Q), 3 (no-mod)]\n"
"\t[-O enable offset tuning (default: off)]\n"
@ -155,16 +166,19 @@ void usage(void)
"\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"
"\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"
"\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"
"\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"
"\t collect data for one hour and compress it on the fly\n\n"
"\tIf you have issues writing +2GB logs on a 32bit platform\n"
"\tuse redirection (rtl_power ... > filename.csv) instead\n\n"
"Convert CSV to a waterfall graphic with:\n"
"\t http://kmkeen.com/tmp/heatmap.py.txt \n");
" https://github.com/keenerd/rtl-sdr-misc/blob/master/heatmap/heatmap.py \n"
"More examples at http://kmkeen.com/rtl-power/\n");
exit(1);
}
@ -424,89 +438,154 @@ void rms_power(struct tuning_state *ts)
ts->samples += 1;
}
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)?
/* todo, add errors to parse_freq, solve_foo */
int parse_frequency(char *arg, struct channel_solve *c)
{
char *start, *stop, *step;
int i, j, upper, lower, max_size, bw_seen, bw_used, bin_e, buf_len;
int downsample, downsample_passes;
double bin_size;
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);
c->lower = (int)atofs(start);
c->upper = (int)atofs(stop);
c->bin_spec = (int)atofs(step);
stop[-1] = ':';
step[-1] = ':';
downsample = 1;
downsample_passes = 0;
/* evenly sized ranges, as close to MAXIMUM_RATE as possible */
// todo, replace loop with algebra
for (i=1; i<1500; i++) {
bw_seen = (upper - lower) / i;
bw_used = (int)((double)(bw_seen) / (1.0 - crop));
if (bw_used > MAXIMUM_RATE) {
return 0;
}
int solve_giant_bins(struct channel_solve *c)
{
c->bw_wanted = c->bin_spec;
c->bw_needed = c->bin_spec;
c->hops = (c->upper - c->lower) / c->bin_spec;
c->bin_e = 0;
c->crop_tmp = 0;
return 0;
}
int solve_downsample(struct channel_solve *c, int boxcar)
{
int scan_size, bins_wanted, bins_needed, ds_next, bw;
scan_size = c->upper - c->lower;
c->hops = 1;
c->bw_wanted = scan_size;
bins_wanted = (int)ceil((double)scan_size / (double)c->bin_spec);
c->bin_e = (int)ceil(log2(bins_wanted));
while (1) {
bins_needed = 1 << c->bin_e;
c->crop_tmp = (double)(bins_needed - bins_wanted) / (double)bins_needed;
if (c->crop_tmp >= c->crop) {
break;}
c->bin_e++;
}
c->downsample = 1;
c->downsample_passes = 0;
while (1) {
bw = (int)((double)scan_size / (1.0 - c->crop_tmp));
c->bw_needed = bw * c->downsample;
if (boxcar) {
ds_next = c->downsample + 1;
} else {
ds_next = c->downsample * 2;
}
if ((bw * ds_next) > MAXIMUM_RATE) {
break;}
c->downsample = ds_next;
if (!boxcar) {
c->downsample_passes++;}
}
return 0;
}
int solve_hopping(struct channel_solve *c)
{
int i, scan_size, bins_all, bins_crop, bins_2, min_hops;
scan_size = c->upper - c->lower;
min_hops = scan_size / MAXIMUM_RATE - 1;
if (min_hops < 1) {
min_hops = 1;}
/* evenly sized ranges, as close to target_rate as possible */
for (i=min_hops; i<MAX_TUNES; i++) {
c->bw_wanted = scan_size / i;
bins_all = scan_size / c->bin_spec;
bins_crop = (int)ceil((double)bins_all / (double)i);
c->bin_e = (int)ceil(log2(bins_crop));
bins_2 = 1 << c->bin_e;
c->bw_needed = bins_2 * c->bin_spec;
c->crop_tmp = (double)(bins_2 - bins_crop) / (double)bins_2;
if (c->bw_needed > MAXIMUM_RATE) {
continue;}
tune_count = i;
if (c->crop_tmp < c->crop) {
continue;}
c->hops = i;
break;
}
/* unless small bandwidth */
if (bw_used < MINIMUM_RATE) {
tune_count = 1;
downsample = MAXIMUM_RATE / bw_used;
bw_used = bw_used * downsample;
return 0;
}
void frequency_range(char *arg, double crop, int boxcar)
/* flesh out the tunes[] for scanning */
{
struct channel_solve c;
struct tuning_state *ts;
int i, j, buf_len, length, hop_bins, logged_bins, planned_bins;
int lower_edge, actual_bw, upper_perfect, remainder;
parse_frequency(arg, &c);
c.downsample = 1;
c.downsample_passes = 0;
c.crop = crop;
if (c.bin_spec >= MINIMUM_RATE) {
fprintf(stderr, "Mode: rms power\n");
solve_giant_bins(&c);
} else if ((c.upper - c.lower) < MINIMUM_RATE) {
fprintf(stderr, "Mode: downsampling\n");
solve_downsample(&c, boxcar);
} else {
fprintf(stderr, "Mode: normal\n");
solve_hopping(&c);
}
if (!boxcar && downsample > 1) {
downsample_passes = (int)log2(downsample);
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 */
// todo, replace loop with log2
for (i=1; i<=21; i++) {
bin_e = i;
bin_size = (double)bw_used / (double)((1<<i) * downsample);
if (bin_size <= (double)max_size) {
break;}
}
/* unless giant bins */
if (max_size >= MINIMUM_RATE) {
bw_seen = max_size;
bw_used = max_size;
tune_count = (upper - lower) / bw_seen;
bin_e = 0;
crop = 0;
}
if (tune_count > MAX_TUNES) {
c.crop = c.crop_tmp;
if ((tune_count+c.hops) > MAX_TUNES) {
fprintf(stderr, "Error: bandwidth too wide.\n");
exit(1);
}
buf_len = 2 * (1<<bin_e) * downsample;
buf_len = 2 * (1<<c.bin_e) * c.downsample;
if (buf_len < DEFAULT_BUF_LENGTH) {
buf_len = DEFAULT_BUF_LENGTH;
}
/* 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;
logged_bins = 0;
lower_edge = c.lower;
planned_bins = (c.upper - c.lower) / c.bin_spec;
for (i=0; i < c.hops; i++) {
ts = &tunes[tune_count + i];
/* copy common values */
ts->rate = c.bw_needed;
ts->bin_e = c.bin_e;
ts->samples = 0;
ts->crop = crop;
ts->downsample = downsample;
ts->downsample_passes = downsample_passes;
ts->avg = (long*)malloc((1<<bin_e) * sizeof(long));
ts->bin_spec = c.bin_spec;
ts->crop = c.crop;
ts->downsample = c.downsample;
ts->downsample_passes = c.downsample_passes;
ts->avg = (long*)malloc((1<<c.bin_e) * sizeof(long));
if (!ts->avg) {
fprintf(stderr, "Error: malloc.\n");
exit(1);
}
for (j=0; j<(1<<bin_e); j++) {
for (j=0; j<(1<<c.bin_e); j++) {
ts->avg[j] = 0L;
}
ts->buf8 = (uint8_t*)malloc(buf_len * sizeof(uint8_t));
@ -515,17 +594,41 @@ void frequency_range(char *arg, double crop)
exit(1);
}
ts->buf_len = buf_len;
length = 1 << c.bin_e;
/* calculate unique values */
ts->freq_low = lower_edge;
hop_bins = c.bw_wanted / c.bin_spec;
actual_bw = hop_bins * c.bin_spec;
ts->freq_high = lower_edge + actual_bw;
upper_perfect = c.lower + (i+1) * c.bw_wanted;
if (ts->freq_high + c.bin_spec <= upper_perfect) {
hop_bins += 1;
actual_bw = hop_bins * c.bin_spec;
ts->freq_high = lower_edge + actual_bw;
}
remainder = planned_bins - logged_bins - hop_bins;
if (i == c.hops-1 && remainder > 0) {
hop_bins += remainder;
actual_bw = hop_bins * c.bin_spec;
ts->freq_high = lower_edge + actual_bw;
}
logged_bins += hop_bins;
ts->crop_i1 = (length - hop_bins) / 2;
ts->crop_i2 = ts->crop_i1 + hop_bins - 1;
ts->freq = (lower_edge - ts->crop_i1 * c.bin_spec) + c.bw_needed/(2*c.downsample);
/* prep for next hop */
lower_edge = ts->freq_high;
}
tune_count += c.hops;
/* 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<<bin_e));
fprintf(stderr, "Logged FFT bins: %i\n", \
(int)((double)(tune_count * (1<<bin_e)) * (1.0-crop)));
fprintf(stderr, "FFT bin size: %0.2fHz\n", bin_size);
fprintf(stderr, "Buffer size: %i bytes (%0.2fms)\n", buf_len, 1000 * 0.5 * (float)buf_len / (float)bw_used);
fprintf(stderr, "Number of frequency hops: %i\n", c.hops);
fprintf(stderr, "Dongle bandwidth: %iHz\n", c.bw_needed);
fprintf(stderr, "Downsampling by: %ix\n", c.downsample);
fprintf(stderr, "Cropping by: %0.2f%%\n", c.crop*100);
fprintf(stderr, "Total FFT bins: %i\n", c.hops * (1<<c.bin_e));
fprintf(stderr, "Logged FFT bins: %i\n", logged_bins);
fprintf(stderr, "FFT bin size: %iHz\n", c.bin_spec);
fprintf(stderr, "Buffer size: %i bytes (%0.2fms)\n", buf_len, 1000 * 0.5 * (float)buf_len / (float)c.bw_needed);
}
void retune(rtlsdr_dev_t *d, int freq)
@ -657,7 +760,16 @@ void scanner(void)
}
ds = ts->downsample;
ds_p = ts->downsample_passes;
if (boxcar && ds > 1) {
if (ds_p) { /* recursive */
for (j=0; j < ds_p; j++) {
downsample_iq(fft_buf, buf_len >> j);
}
/* droop compensation */
if (comp_fir_size == 9 && ds_p <= CIC_TABLE_MAX) {
generic_fir(fft_buf, buf_len >> j, cic_9_tables[ds_p]);
generic_fir(fft_buf+1, (buf_len >> j)-1, cic_9_tables[ds_p]);
}
} else if (ds > 1) { /* boxcar */
j=2, j2=0;
while (j < buf_len) {
fft_buf[j2] += fft_buf[j];
@ -668,15 +780,6 @@ void scanner(void)
if (j % (ds*2) == 0) {
j2 += 2;}
}
} else if (ds_p) { /* recursive */
for (j=0; j < ds_p; j++) {
downsample_iq(fft_buf, buf_len >> j);
}
/* droop compensation */
if (comp_fir_size == 9 && ds_p <= CIC_TABLE_MAX) {
generic_fir(fft_buf, buf_len >> j, cic_9_tables[ds_p]);
generic_fir(fft_buf+1, (buf_len >> j)-1, cic_9_tables[ds_p]);
}
}
remove_dc(fft_buf, buf_len / ds);
remove_dc(fft_buf+1, (buf_len / ds) - 1);
@ -710,9 +813,10 @@ void scanner(void)
void csv_dbm(struct tuning_state *ts)
{
int i, len, ds, i1, i2, bw2, bin_count;
int i, len, ds;
long tmp;
double dbm;
char *sep = ", ";
len = 1 << ts->bin_e;
ds = ts->downsample;
/* fix FFT stuff quirks */
@ -727,26 +831,19 @@ void csv_dbm(struct tuning_state *ts)
}
}
/* 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);
fprintf(file, "%i, %i, %i, %i, ", ts->freq_low, ts->freq_high,
ts->bin_spec, 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++) {
for (i=ts->crop_i1; i<=ts->crop_i2; i++) {
if (i == ts->crop_i2) {
sep = "\n";
}
dbm = (double)ts->avg[i];
dbm /= (double)ts->rate;
dbm /= (double)ts->samples;
dbm = 10 * log10(dbm);
fprintf(file, "%.2f, ", dbm);
fprintf(file, "%.2f%s", dbm, sep);
}
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;
}
@ -769,6 +866,7 @@ int main(int argc, char **argv)
int interval = 10;
int fft_threads = 1;
int smoothing = 0;
int boxcar = 1;
int single = 0;
int direct_sampling = 0;
int offset_tuning = 0;
@ -868,7 +966,7 @@ int main(int argc, char **argv)
exit(1);
}
frequency_range(freq_optarg, crop);
frequency_range(freq_optarg, crop, boxcar);
if (tune_count == 0) {
usage();}